Compare commits

...

44 Commits

Author SHA1 Message Date
carteronm ff98045932 Merge vers main 2026-06-11 10:49:06 +02:00
carteronm e1e8a834b2 push fichiers inutiles 2026-06-11 10:48:28 +02:00
gokhoal 61f3d2889c chiffrement 2026-06-11 10:33:02 +02:00
carteronm 66a7e633a9 Rôles ajoutés 2026-06-11 01:49:21 +02:00
carteronm fff484d4ba Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	Knots/bin/Debug/net8.0/Knots.dll
#	Knots/bin/Debug/net8.0/Knots.exe
#	Knots/bin/Debug/net8.0/Knots.pdb
#	Knots/obj/Debug/net8.0/Knots.AssemblyInfo.cs
#	Knots/obj/Debug/net8.0/Knots.AssemblyInfoInputs.cache
#	Knots/obj/Debug/net8.0/Knots.dll
#	Knots/obj/Debug/net8.0/Knots.pdb
#	Knots/obj/Debug/net8.0/apphost.exe
#	Knots/obj/Debug/net8.0/ref/Knots.dll
#	Knots/obj/Debug/net8.0/refint/Knots.dll
2026-06-11 01:32:33 +02:00
carteronm e69550048b Préparation des rôles 2026-06-11 01:32:20 +02:00
gokhoal a29fb27b0d commit blabla3 2026-06-11 01:30:14 +02:00
gokhoal c23cc4a03a LES MESSAGES MARCHENT 2026-06-11 01:29:27 +02:00
carteronm 1d2f96b2f4 Fin création des groupes 2026-06-11 01:14:32 +02:00
carteronm f1cdc5de19 Modif BDD Discussion 2026-06-11 00:56:32 +02:00
carteronm 4e06dc5f23 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	Knots/bin/Debug/net8.0/Knots.dll
#	Knots/bin/Debug/net8.0/Knots.pdb
#	Knots/obj/Debug/net8.0/Knots.csproj.CoreCompileInputs.cache
#	Knots/obj/Debug/net8.0/Knots.csproj.FileListAbsolute.txt
#	Knots/obj/Debug/net8.0/Knots.dll
#	Knots/obj/Debug/net8.0/Knots.pdb
#	Knots/obj/Debug/net8.0/ref/Knots.dll
#	Knots/obj/Debug/net8.0/refint/Knots.dll
2026-06-11 00:46:04 +02:00
carteronm 779acbe531 Groupe rectification 2026-06-11 00:45:49 +02:00
gokhoal e6021dcc61 envoi et creation messages 2026-06-11 00:44:28 +02:00
gokhoal 4f994ba183 conmmit blabla2 2026-06-11 00:20:02 +02:00
gokhoal 2bb1f730c9 Creatediscussions private et group 2026-06-11 00:19:32 +02:00
gokhoal c92e134698 Revert "Endpoint group"
This reverts commit b7dad57c
2026-06-11 00:15:58 +02:00
gokhoal 0b9e01c925 Revert "endpoint de creation de discussion"
This reverts commit af1b14b0d2.
2026-06-11 00:13:30 +02:00
gokhoal c1bd8a12a9 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	Knots/bin/Debug/net8.0/Knots.dll
#	Knots/bin/Debug/net8.0/Knots.pdb
#	Knots/obj/Debug/net8.0/Knots.assets.cache
#	Knots/obj/Debug/net8.0/Knots.csproj.AssemblyReference.cache
#	Knots/obj/Debug/net8.0/Knots.csproj.CoreCompileInputs.cache
#	Knots/obj/Debug/net8.0/Knots.dll
#	Knots/obj/Debug/net8.0/Knots.pdb
#	Knots/obj/Debug/net8.0/ref/Knots.dll
#	Knots/obj/Debug/net8.0/refint/Knots.dll
#	Knots/obj/project.nuget.cache
#	Knots/obj/project.packagespec.json
#	Knots/obj/rider.project.model.nuget.info
#	Knots/obj/rider.project.restore.info
2026-06-11 00:13:10 +02:00
gokhoal c120d34e62 hub pour websocket 2026-06-11 00:12:29 +02:00
carteronm b7dad57cee Endpoint group 2026-06-10 23:33:04 +02:00
gokhoal af1b14b0d2 endpoint de creation de discussion 2026-06-10 22:46:40 +02:00
gokhoal 7cfb3909b6 changements de getmessageendpoint et du dto details 2026-06-10 21:33:37 +02:00
gokhoal ff317cc944 correction du modele User et du context 2026-06-10 20:15:05 +02:00
carteronm 79a926bc2d push de con 2026-06-10 17:29:50 +02:00
gokhoal 431e96d9e4 correction de l'endpoint 2026-06-10 17:19:16 +02:00
gokhoal eb70f4b3de changement structure bdd + gestion discussions 2026-06-10 17:17:01 +02:00
gokhoal 690d85009e Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	Knots/bin/Debug/net8.0/Knots.dll
#	Knots/bin/Debug/net8.0/Knots.exe
2026-06-10 17:02:43 +02:00
gokhoal cec4437f2f commit blabla 2026-06-10 17:02:21 +02:00
carteronm 33987f080e modif 2026-06-10 16:26:11 +02:00
gokhoal c5b5ac6b84 origin acceptee ajoutee 2026-06-10 16:09:44 +02:00
carteronm db72346683 Résolution du problème de merde 2026-06-10 15:57:09 +02:00
carteronm 70c4570b48 Merge remote-tracking branch 'origin/develop' into develop 2026-06-10 15:30:45 +02:00
carteronm 6b5084238c modif + Bcrypt 2026-06-10 15:30:39 +02:00
gokhoal 103010f59e Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	Knots/obj/Debug/net8.0/apphost.exe
2026-06-10 12:28:31 +02:00
gokhoal 719be662d2 .exe for some reason 2026-06-10 12:28:02 +02:00
carteronm 84d0376138 Arrivée du Login 2026-06-10 11:48:49 +02:00
gokhoal e8d5ac797b changements de modeles 2026-06-08 21:18:33 +02:00
carteronm d3aeb3abc8 localhost modif 2026-05-05 11:31:53 +02:00
carteronm f40a37bc07 swagger 2026-05-05 11:30:22 +02:00
carteronm 1ac93dae8d swagger 2026-05-05 11:29:47 +02:00
carteronm 4fe072d837 corrections d'erreurs 2026-05-05 11:28:30 +02:00
gokhoal 42187ba088 commit de fichiers info 2026-04-03 09:06:35 +02:00
oistig cc90904228 gitignore 2026-03-12 15:02:49 +01:00
oistig 47cf23d740 premier push de fichiers inutiles 2026-03-12 15:02:10 +01:00
140 changed files with 5144 additions and 1265 deletions
+2
View File
@@ -0,0 +1,2 @@
/.idea/.idea.Knots/.idea/.name
/Knots/obj/rider.project.model.nuget.info
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="@romaric-thibault.fr" uuid="2e0ff1eb-4394-46d9-aa2d-362392df37df">
<data-source source="LOCAL" name="@romaric-thibault.fr" uuid="821fc51f-62ea-4fa2-99b3-cb667f09e32a">
<driver-ref>sqlserver.jb</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.jetbrains.jdbc.sqlserver.SqlServerDriver</jdbc-driver>
+5
View File
@@ -3,4 +3,9 @@ namespace Knots.DTO.Discussion;
public class GetDiscussionDto
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public bool IsGroup { get; set; }
public int? MembersCount { get; set; }
public int? GroupId { get; set; }
}
@@ -6,4 +6,7 @@ public class GetMessageDetailsDto
public string? Contenu { get; set; }
public DateTime Date { get; set; }
public Boolean Type { get; set; }
public int UserId { get; set; }
public string AuthorName { get; set; } = "";
}
+12
View File
@@ -0,0 +1,12 @@
namespace Knots.DTO.User;
public class LoginResponseDto
{
public string? Token { get; set; }
public int Id { get; set; }
public string? Username { get; set; }
public string? Email { get; set; }
public string? Tel { get; set; }
public string? ProfilePicture { get; set; }
public string? Description { get; set; }
}
+7
View File
@@ -0,0 +1,7 @@
namespace Knots.DTO.User;
public class LoginUserDto
{
public string? Username { get; set; }
public string? Password { get; set; }
}
@@ -16,6 +16,6 @@ public class CreateDiscussionEndpoint(KnotsDbContext db, AutoMapper.IMapper mapp
Models.Discussion? discussion = mapper.Map<Models.Discussion>(req);
db.Discussions.Add(discussion);
await db.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
@@ -0,0 +1,91 @@
using FastEndpoints;
using Knots.DTO.Discussion;
using Knots.Models;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
using Knots.Services;
namespace Knots.Endpoints.Discussion;
public class CreateGroupDiscussionEndpoint(KnotsDbContext db, EncryptionService encryption) : Endpoint<CreateGroupDiscussionRequest, GetDiscussionDto>
{
public override void Configure()
{
Post("/discussions/group");
}
public override async Task HandleAsync(CreateGroupDiscussionRequest req, CancellationToken ct)
{
int currentUserId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
if (req.Usernames == null || req.Usernames.Count == 0)
{
await SendErrorsAsync(400, ct);
return;
}
List<Models.User> targets = await db.Users
.Where(u => req.Usernames.Contains(u.Username!))
.ToListAsync(ct);
if (targets.Count != req.Usernames.Count)
{
await SendNotFoundAsync(ct);
return;
}
if (targets.Any(t => t.Id == currentUserId))
{
await SendErrorsAsync(400, ct);
return;
}
int totalMembers = targets.Count + 1;
Models.Discussion discussion = new()
{
IsGroup = true,
Key = new Models.Key { EnKey = encryption.GenerateKey() },
UserDiscussions = targets
.Select(t => new UserDiscussion { UserId = t.Id })
.Append(new UserDiscussion { UserId = currentUserId })
.ToList()
};
db.Discussions.Add(discussion);
await db.SaveChangesAsync(ct); // discussion.Id disponible
Models.Group group = new()
{
Name = req.GroupName,
MembersAmount = totalMembers,
DiscussionId = discussion.Id,
GroupUsers = targets
.Select(t => new GroupUser { UserId = t.Id })
.Append(new GroupUser { UserId = currentUserId })
.ToList()
};
db.Groups.Add(group);
await db.SaveChangesAsync(ct); // group.Id disponible
discussion.GroupId = group.Id;
await db.SaveChangesAsync(ct);
await SendOkAsync(new GetDiscussionDto
{
Id = discussion.Id,
IsGroup = true,
Name = group.Name,
MembersCount = totalMembers
}, ct);
}
}
public class CreateGroupDiscussionRequest
{
public string GroupName { get; set; } = "";
public List<string> Usernames { get; set; } = [];
}
@@ -0,0 +1,85 @@
using FastEndpoints;
using Knots.DTO.Discussion;
using Knots.Models;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
using Knots.Services;
namespace Knots.Endpoints.Discussion;
public class CreatePrivateDiscussionEndpoint(KnotsDbContext db, EncryptionService encryption)
: Endpoint<CreatePrivateDiscussionRequest, GetDiscussionDto>
{
public override void Configure()
{
Post("/discussions/private");
}
public override async Task HandleAsync(CreatePrivateDiscussionRequest req, CancellationToken ct)
{
int currentUserId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
// 1. retrouver l'utilisateur cible par son nom
Models.User? target = await db.Users
.SingleOrDefaultAsync(u => u.Username == req.Username, ct);
if (target is null)
{
await SendNotFoundAsync(ct); // utilisateur introuvable
return;
}
if (target.Id == currentUserId)
{
await SendErrorsAsync(400, ct); // pas de discussion avec soi-même
return;
}
// 2. vérifier qu'une discussion privée entre les deux n'existe pas déjà
Models.Discussion? existing = await db.Discussions
.Where(d => d.GroupId == null
&& d.UserDiscussions.Any(ud => ud.UserId == currentUserId)
&& d.UserDiscussions.Any(ud => ud.UserId == target.Id))
.FirstOrDefaultAsync(ct);
if (existing is not null)
{
await SendOkAsync(new GetDiscussionDto
{
Id = existing.Id,
IsGroup = false,
Name = target.Username!,
MembersCount = null
}, ct);
return;
}
// 3. créer la discussion + les deux participants
Models.Discussion discussion = new()
{
IsGroup = false,
Key = new Models.Key { EnKey = encryption.GenerateKey() },
UserDiscussions =
[
new UserDiscussion { UserId = currentUserId },
new UserDiscussion { UserId = target.Id }
]
};
db.Discussions.Add(discussion);
await db.SaveChangesAsync(ct);
await SendOkAsync(new GetDiscussionDto
{
Id = discussion.Id,
IsGroup = false,
Name = target.Username!,
MembersCount = null
}, ct);
}
}
public class CreatePrivateDiscussionRequest
{
public string Username { get; set; } = "";
}
@@ -16,6 +16,6 @@ public class DeleteDiscussionEndpoint(KnotsDbContext db, AutoMapper.IMapper mapp
Models.Discussion? discussion = mapper.Map<Models.Discussion>(req);
db.Discussions.Remove(discussion);
await db.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
@@ -9,7 +9,7 @@ public class GetDiscussionEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper)
{
public override void Configure()
{
Get("/groups");
Get("/discussions");
AllowAnonymous();
}
@@ -19,11 +19,11 @@ public class GetDiscussionEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper)
if (databaseDiscussion == null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
var keyDto = mapper.Map<GetKeyDetailsDto>(databaseDiscussion);
await Send.OkAsync(keyDto, ct);
await SendOkAsync(keyDto, ct);
}
}
@@ -0,0 +1,35 @@
using System.Security.Claims;
using FastEndpoints;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.Discussion;
public class GetDiscussionMembersEndpoint(KnotsDbContext db) : EndpointWithoutRequest<List<string>>
{
public override void Configure()
{
Get("/discussions/{discussionId}/members");
}
public override async Task HandleAsync(CancellationToken ct)
{
int discussionId = Route<int>("discussionId");
Models.Discussion? discussion = await db.Discussions
.Include(d => d.UserDiscussions)
.ThenInclude(ud => ud.User)
.SingleOrDefaultAsync(d => d.Id == discussionId, ct);
if (discussion is null)
{
await SendNotFoundAsync(ct);
return;
}
List<string> members = discussion.UserDiscussions
.Select(ud => ud.User.Username!)
.ToList();
await SendOkAsync(members, ct);
}
}
@@ -0,0 +1,52 @@
using FastEndpoints;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.Discussion;
public class GetDiscussionMembersWithRolesEndpoint(KnotsDbContext db) : EndpointWithoutRequest<List<MemberWithRoleDto>>
{
public override void Configure()
{
Get("/discussions/{discussionId}/members/roles");
}
public override async Task HandleAsync(CancellationToken ct)
{
int discussionId = Route<int>("discussionId");
Models.Discussion? discussion = await db.Discussions
.Include(d => d.Group)
.ThenInclude(g => g!.GroupUsers)
.ThenInclude(gu => gu.User)
.Include(d => d.Group)
.ThenInclude(g => g!.GroupUsers)
.ThenInclude(gu => gu.Role)
.SingleOrDefaultAsync(d => d.Id == discussionId, ct);
if (discussion?.Group is null)
{
await SendNotFoundAsync(ct);
return;
}
List<MemberWithRoleDto> members = discussion.Group.GroupUsers
.Select(gu => new MemberWithRoleDto
{
UserId = gu.UserId,
Username = gu.User.Username!,
RoleId = gu.RoleId,
RoleLibelle = gu.Role?.Libelle
})
.ToList();
await SendOkAsync(members, ct);
}
}
public class MemberWithRoleDto
{
public int UserId { get; set; }
public string Username { get; set; } = "";
public int? RoleId { get; set; }
public string? RoleLibelle { get; set; }
}
@@ -0,0 +1,50 @@
using System.Security.Claims;
using FastEndpoints;
using Knots.DTO.Discussion;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.Discussion;
public class GetMyDiscussionEndpoint(KnotsDbContext db) : EndpointWithoutRequest<List<GetDiscussionDto>>
{
public override void Configure()
{
Get("/discussions/my");
}
public override async Task HandleAsync(CancellationToken ct)
{
int userId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
// Discussions privées : l'utilisateur est l'un des participants
IQueryable<GetDiscussionDto> privees = db.Discussions
.Where(d => d.GroupId == null && d.UserDiscussions.Any(ud => ud.UserId == userId))
.Select(d => new GetDiscussionDto
{
Id = d.Id,
IsGroup = false,
Name = d.UserDiscussions
.Where(ud => ud.UserId != userId)
.Select(ud => ud.User.Username)
.FirstOrDefault() ?? "",
MembersCount = null,
GroupId = null
});
// Discussions de groupe : l'utilisateur est membre du groupe
IQueryable<GetDiscussionDto> groupes = db.Discussions
.Where(d => d.Group != null && d.Group.GroupUsers.Any(gu => gu.UserId == userId))
.Select(d => new GetDiscussionDto
{
Id = d.Id,
IsGroup = true,
Name = d.Group!.Name!,
MembersCount = d.Group.MembersAmount,
GroupId = d.Group.Id
});
List<GetDiscussionDto> discussions = await privees.Concat(groupes).ToListAsync(ct);
await SendOkAsync(discussions, ct);
}
}
+1 -1
View File
@@ -16,6 +16,6 @@ public class CreateGroupEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) :
Models.Group? group = mapper.Map<Models.Group>(req);
db.Groups.Add(group);
await db.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
+1 -1
View File
@@ -16,6 +16,6 @@ public class DeleteGroupEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) :
Models.Group? group = mapper.Map<Models.Group>(req);
db.Groups.Remove(group);
await db.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
+2 -2
View File
@@ -19,11 +19,11 @@ public class GetGroupEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : En
if (databaseGroup == null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
var keyDto = mapper.Map<GetKeyDetailsDto>(databaseGroup);
await Send.OkAsync(keyDto, ct);
await SendOkAsync(keyDto, ct);
}
}
@@ -18,12 +18,12 @@ public class PatchGroupMembersAmountEndpoint(KnotsDbContext knotsDbContext) : En
if (databaseGroup is null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
databaseGroup.MembersAmount = req.MembersAmount;
await knotsDbContext.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
@@ -18,12 +18,12 @@ public class PatchGroupNameEndpoint(KnotsDbContext knotsDbContext) : Endpoint<Up
if (databaseGroup is null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
databaseGroup.Name = req.Name;
await knotsDbContext.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
@@ -18,12 +18,12 @@ public class PatchGroupProfilePictureEndpoint(KnotsDbContext knotsDbContext) : E
if (databaseGroup is null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
databaseGroup.ProfilePicture = req.ProfilePicture;
await knotsDbContext.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
+2 -2
View File
@@ -7,7 +7,7 @@ public class CreateKeyEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : E
{
public override void Configure()
{
Post("/groups");
Post("/keys");
AllowAnonymous();
}
@@ -16,6 +16,6 @@ public class CreateKeyEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : E
Models.Key? key = mapper.Map<Models.Key>(req);
db.Keys.Add(key);
await db.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
+2 -2
View File
@@ -7,7 +7,7 @@ public class DeleteKeyEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : E
{
public override void Configure()
{
Delete("/groups");
Delete("/keys");
AllowAnonymous();
}
@@ -16,6 +16,6 @@ public class DeleteKeyEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : E
Models.Key? key = mapper.Map<Models.Key>(req);
db.Keys.Remove(key);
await db.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
+2 -2
View File
@@ -19,11 +19,11 @@ public class GetKeyEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endp
if (databaseKey == null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
var keyDto = mapper.Map<GetKeyDetailsDto>(databaseKey);
await Send.OkAsync(keyDto, ct);
await SendOkAsync(keyDto, ct);
}
}
@@ -16,6 +16,6 @@ public class CreateMessageEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper)
Models.Message? message = mapper.Map<Models.Message>(req);
db.Messages.Add(message);
await db.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
@@ -16,6 +16,6 @@ public class DeleteMessageEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper)
Models.Message? message = mapper.Map<Models.Message>(req);
db.Messages.Remove(message);
await db.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
+41 -8
View File
@@ -1,29 +1,62 @@
using System.Security.Claims;
using FastEndpoints;
using Knots.DTO.Message;
using Knots.DTO.User;
using Knots.Services;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.Message;
public class GetMessageEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint <GetMessageDetailsDto>
public class GetMessageEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper, EncryptionService encryption) : Endpoint<GetDiscussionMessagesRequest, List<GetMessageDetailsDto>>
{
public override void Configure()
{
Get("/messages/{@Id}");
Get("/discussions/{DiscussionId}/messages");
AllowAnonymous();
}
public override async Task HandleAsync(GetMessageDetailsDto req, CancellationToken ct)
public override async Task HandleAsync(GetDiscussionMessagesRequest req, CancellationToken ct)
{
Models.Message? databaseMessage = await db.Messages.SingleOrDefaultAsync(x => x.Id == req.Id, cancellationToken: ct);
int userId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
if (databaseMessage == null)
// l'utilisateur participe-t-il à cette discussion (privée ou via le groupe) ?
bool autorise = await db.Discussions
.Where(d => d.Id == req.DiscussionId)
.AnyAsync(d =>
d.UserDiscussions.Any(ud => ud.UserId == userId) ||
(d.Group != null && d.Group.GroupUsers.Any(gu => gu.UserId == userId)), ct);
if (!autorise)
{
await Send.NotFoundAsync(ct);
await SendForbiddenAsync(ct);
return;
}
var messageDto = mapper.Map<GetMessageDetailsDto>(databaseMessage);
await Send.OkAsync(messageDto, ct);
string? key = await db.Discussions
.Where(d => d.Id == req.DiscussionId)
.Select(d => d.Key!.EnKey)
.SingleAsync(ct);
var rows = await db.Messages
.Where(m => m.DiscussionId == req.DiscussionId)
.OrderBy(m => m.Date)
.Select(m => new { m.Id, m.Contenu, m.Date, m.UserId, AuthorName = m.User.Username! })
.ToListAsync(ct);
List<GetMessageDetailsDto> messages = rows.Select(m => new GetMessageDetailsDto
{
Id = m.Id,
Contenu = encryption.Decrypt(m.Contenu!, key!),
Date = m.Date,
UserId = m.UserId,
AuthorName = m.AuthorName
}).ToList();
await SendOkAsync(messages, ct);
}
}
public class GetDiscussionMessagesRequest
{
public int DiscussionId { get; set; }
}
@@ -0,0 +1,45 @@
using FastEndpoints;
using Knots.Models;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.Role;
public class AssignRoleEndpoint(KnotsDbContext db) : Endpoint<AssignRoleRequest>
{
public override void Configure()
{
Post("/groups/{groupId}/members/{userId}/role");
}
public override async Task HandleAsync(AssignRoleRequest req, CancellationToken ct)
{
int groupId = Route<int>("groupId");
int userId = Route<int>("userId");
GroupUser? groupUser = await db.GroupUsers
.SingleOrDefaultAsync(gu => gu.GroupId == groupId && gu.UserId == userId, ct);
if (groupUser is null)
{
await SendNotFoundAsync(ct);
return;
}
bool roleExists = await db.Roles.AnyAsync(r => r.Id == req.RoleId, ct);
if (!roleExists)
{
await SendNotFoundAsync(ct);
return;
}
groupUser.RoleId = req.RoleId;
await db.SaveChangesAsync(ct);
await SendOkAsync(ct);
}
}
public class AssignRoleRequest
{
public int RoleId { get; set; }
}
+17 -7
View File
@@ -1,22 +1,32 @@
using FastEndpoints;
using Knots.DTO.Role;
using Knots.DTO.User;
using Knots.Models;
namespace Knots.Endpoints.Role;
public class CreateRoleEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<CreateRoleDto>
public class CreateRoleEndpoint(KnotsDbContext db) : Endpoint<CreateRoleRequest, RoleDto>
{
public override void Configure()
{
Post("/roles");
AllowAnonymous();
}
public override async Task HandleAsync(CreateRoleDto req, CancellationToken ct)
public override async Task HandleAsync(CreateRoleRequest req, CancellationToken ct)
{
Models.Role? role = mapper.Map<Models.Role>(req);
Models.Role role = new() { Libelle = req.Libelle };
db.Roles.Add(role);
await db.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendOkAsync(new RoleDto { Id = role.Id, Libelle = role.Libelle! }, ct);
}
}
public class CreateRoleRequest
{
public string Libelle { get; set; } = "";
}
public class RoleDto
{
public int Id { get; set; }
public string Libelle { get; set; } = "";
}
+1 -1
View File
@@ -16,6 +16,6 @@ public class DeleteRoleEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) :
Models.Role? role = mapper.Map<Models.Role>(req);
db.Roles.Remove(role);
await db.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
+2 -2
View File
@@ -19,11 +19,11 @@ public class GetRoleEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : End
if (databaseRole == null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
var roleDto = mapper.Map<GetRoleDto>(databaseRole);
await Send.OkAsync(roleDto, ct);
await SendOkAsync(roleDto, ct);
}
}
+13 -1
View File
@@ -1,5 +1,6 @@
using FastEndpoints;
using Knots.DTO.User;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.User;
@@ -13,9 +14,20 @@ public class CreateUserEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) :
public override async Task HandleAsync(CreateUserDto req, CancellationToken ct)
{
bool usernameExists = await db.Users
.AnyAsync(x => x.Username == req.Username, cancellationToken: ct);
if (usernameExists)
{
AddError(x => x.Username, "Ce nom d'utilisateur est déjà pris.");
await SendErrorsAsync(cancellation: ct);
return;
}
req.Password = BCrypt.Net.BCrypt.HashPassword(req.Password);
Models.User? user = mapper.Map<Models.User>(req);
db.Users.Add(user);
await db.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
+1 -1
View File
@@ -16,6 +16,6 @@ public class DeleteUserEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) :
Models.User? user = mapper.Map<Models.User>(req);
db.Users.Add(user);
await db.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
+1 -1
View File
@@ -19,6 +19,6 @@ public class GetAllUsersEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) :
.ProjectTo<GetUserDetailsDto>(mapper.ConfigurationProvider)
.ToListAsync(ct);
await Send.OkAsync(users, ct);
await SendOkAsync(users, ct);
}
}
+2 -2
View File
@@ -18,11 +18,11 @@ public class GetUserEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : End
if (databaseUser == null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
var userDto = mapper.Map<GetUserDetailsDto>(databaseUser);
await Send.OkAsync(userDto, ct);
await SendOkAsync(userDto, ct);
}
}
+40
View File
@@ -0,0 +1,40 @@
using FastEndpoints;
using Knots.DTO.User;
using Knots.Services;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.User;
public class LoginEndpoint(KnotsDbContext db, JwtService jwtService) : Endpoint<LoginUserDto, LoginResponseDto>
{
public override void Configure()
{
Post("/users/login");
AllowAnonymous();
}
public override async Task HandleAsync(LoginUserDto req, CancellationToken ct)
{
Models.User? user = await db.Users
.SingleOrDefaultAsync(x => x.Username == req.Username, cancellationToken: ct);
if (user is null || !BCrypt.Net.BCrypt.Verify(req.Password, user.Password)) // hash à ajouter plus tard
{
await SendUnauthorizedAsync(ct);
return;
}
string token = jwtService.GenerateToken(user);
await SendOkAsync(new LoginResponseDto
{
Token = token,
Id = user.Id,
Username = user.Username!,
Email = user.Email,
Tel = user.Tel,
ProfilePicture = user.ProfilePicture,
Description = user.Description
}, ct);
}
}
@@ -18,7 +18,7 @@ public class PatchUserContactEndpoint(KnotsDbContext db, AutoMapper.IMapper mapp
if (databaseUser is null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
@@ -42,6 +42,6 @@ public class PatchUserContactEndpoint(KnotsDbContext db, AutoMapper.IMapper mapp
mapper.Map(req, databaseUser);
await db.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
@@ -18,13 +18,13 @@ public class PatchUserDescriptionEndpoint(KnotsDbContext db, AutoMapper.IMapper
if (databaseUser is null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
mapper.Map(req, databaseUser);
await db.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
@@ -18,13 +18,13 @@ public class PatchUserPasswordEndpoint(KnotsDbContext db, AutoMapper.IMapper map
if (databaseUser is null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
mapper.Map(req, databaseUser);
await db.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
@@ -8,7 +8,7 @@ public class PatchUserProfilePictureEndpoint(KnotsDbContext db, AutoMapper.IMapp
{
public override void Configure()
{
Patch("/users/{@Id}/profilepicture/", x => new {x.Id});
Patch("/users/{@Id}/profilepicture/");
AllowAnonymous();
}
@@ -18,13 +18,13 @@ public class PatchUserProfilePictureEndpoint(KnotsDbContext db, AutoMapper.IMapp
if (databaseUser is null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
mapper.Map(req, databaseUser);
await db.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
+12 -2
View File
@@ -19,13 +19,23 @@ public class PatchUsernameEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper)
if (databaseUser is null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
bool usernameExists = await db.Users
.AnyAsync(x => x.Username == req.Username && x.Id != req.Id, cancellationToken: ct);
if (usernameExists)
{
AddError(x => x.Username, "Ce nom d'utilisateur est déjà pris.");
await SendErrorsAsync(cancellation: ct);
return;
}
mapper.Map(req, databaseUser);
await db.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
await SendNoContentAsync(ct);
}
}
+60
View File
@@ -0,0 +1,60 @@
using Knots.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
namespace Knots.Hubs;
[Authorize]
public class ChatHub(KnotsDbContext db, AutoMapper.IMapper mapper, EncryptionService encryption) : Hub
{
// Rejoindre une conversation (room)
public async Task JoinConversation(string discussionId)
{
await Groups.AddToGroupAsync(Context.ConnectionId, discussionId);
}
// Quitter une conversation
public async Task LeaveConversation(string discussionId)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, discussionId);
}
// Envoyer un message à une conversation
public async Task SendMessage(string discussionId, string content)
{
int id = int.Parse(discussionId);
Models.Discussion discussion = await db.Discussions
.Include(d => d.Key)
.SingleAsync(d => d.Id == id);
var message = new Models.Message
{
Contenu = encryption.Encrypt(content, discussion.Key!.EnKey!), // chiffré en base
Date = DateTime.UtcNow,
Type = false,
UserId = int.Parse(Context.UserIdentifier!),
DiscussionId = id
};
db.Messages.Add(message);
await db.SaveChangesAsync();
// diffusion en clair, avec les noms de champs attendus par le front
await Clients.Group(discussionId).SendAsync("ReceiveMessage", new
{
id = message.Id,
contenu = content,
date = message.Date,
userId = message.UserId
});
}
// Notifier que l'utilisateur est en train d'écrire
public async Task Typing(string discussionId)
{
await Clients.OthersInGroup(discussionId)
.SendAsync("UserTyping", Context.UserIdentifier);
}
}
+6 -2
View File
@@ -8,9 +8,13 @@
<ItemGroup>
<PackageReference Include="AutoMapper" Version="16.1.1" />
<PackageReference Include="FastEndpoints" Version="8.0.1" />
<PackageReference Include="FastEndpoints.Swagger" Version="8.0.1" />
<PackageReference Include="BCrypt.Net-Next" Version="4.2.0" />
<PackageReference Include="FastEndpoints" Version="5.33.0" />
<PackageReference Include="FastEndpoints.Swagger" Version="5.33.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.3.11" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.28" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.25" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.25" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.25">
<PrivateAssets>all</PrivateAssets>
+58 -12
View File
@@ -5,29 +5,75 @@ namespace Knots;
public class KnotsDbContext : DbContext
{
public DbSet<Discussion> Discussions { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<Key> Keys { get; set; }
public DbSet<Message> Messages { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<User> Users => Set<User>();
public DbSet<Discussion> Discussions => Set<Discussion>();
public DbSet<Group> Groups => Set<Group>();
public DbSet<Message> Messages => Set<Message>();
public DbSet<Role> Roles => Set<Role>();
public DbSet<Key> Keys => Set<Key>();
public DbSet<UserDiscussion> UserDiscussions => Set<UserDiscussion>();
public DbSet<GroupUser> GroupUsers => Set<GroupUser>();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
//Infos de connexion à la base de données
string connectionString =
"Server=romaric-thibault.fr;" +
"Database=Knots;" +
"User Id=mathieu;" +
"Password=Onto9-Cage-Afflicted;" +
"Database=knots;" +
"User Id=knots;" +
"Password=knots;" +
"TrustServerCertificate=true;";
optionsBuilder.UseSqlServer(connectionString);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//Données à insérer
modelBuilder.Entity<GroupUser>()
.HasKey(gu => new { gu.GroupId, gu.UserId });
modelBuilder.Entity<GroupUser>()
.HasOne(gu => gu.Group)
.WithMany(g => g.GroupUsers)
.HasForeignKey(gu => gu.GroupId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<GroupUser>()
.HasOne(gu => gu.User)
.WithMany()
.HasForeignKey(gu => gu.UserId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<GroupUser>()
.HasOne(gu => gu.Role)
.WithMany(r => r.GroupUsers)
.HasForeignKey(gu => gu.RoleId);
modelBuilder.Entity<Discussion>()
.HasOne(d => d.Group)
.WithMany()
.HasForeignKey(d => d.GroupId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Group>()
.HasOne(g => g.Discussion)
.WithMany()
.HasForeignKey(g => g.DiscussionId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<UserDiscussion>()
.HasKey(ud => new { ud.UserId, ud.DiscussionId });
modelBuilder.Entity<UserDiscussion>()
.HasOne(ud => ud.Discussion)
.WithMany(d => d.UserDiscussions)
.HasForeignKey(ud => ud.DiscussionId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<UserDiscussion>()
.HasOne(ud => ud.User)
.WithMany(u => u.UserDiscussions)
.HasForeignKey(ud => ud.UserId)
.OnDelete(DeleteBehavior.Restrict);
}
}
@@ -12,8 +12,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Knots.Migrations
{
[DbContext(typeof(KnotsDbContext))]
[Migration("20260312155557_Initial")]
partial class Initial
[Migration("20260505083044_InitialDatabase")]
partial class InitialDatabase
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -33,6 +33,9 @@ namespace Knots.Migrations
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("KeyId")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("Discussions");
@@ -46,14 +49,17 @@ namespace Knots.Migrations
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Nom")
b.Property<int>("KeyId")
.HasColumnType("int");
b.Property<int>("MembersAmount")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<int>("NombreMembres")
.HasColumnType("int");
b.Property<string>("ProfilePicture")
.HasColumnType("nvarchar(max)");
@@ -96,11 +102,31 @@ namespace Knots.Migrations
b.Property<DateTime>("Date")
.HasColumnType("datetime2");
b.Property<int?>("DiscussionId")
.HasColumnType("int");
b.Property<int>("GroupId")
.HasColumnType("int");
b.Property<int>("KeyId")
.HasColumnType("int");
b.Property<bool>("Type")
.HasColumnType("bit");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("DiscussionId");
b.HasIndex("GroupId");
b.HasIndex("KeyId");
b.HasIndex("UserId");
b.ToTable("Messages");
});
@@ -159,6 +185,47 @@ namespace Knots.Migrations
b.ToTable("Users");
});
modelBuilder.Entity("Knots.Models.Message", b =>
{
b.HasOne("Knots.Models.Discussion", null)
.WithMany("Messages")
.HasForeignKey("DiscussionId");
b.HasOne("Knots.Models.Group", "Group")
.WithMany()
.HasForeignKey("GroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Knots.Models.Key", "Key")
.WithMany()
.HasForeignKey("KeyId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Knots.Models.User", "User")
.WithMany("Messages")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Group");
b.Navigation("Key");
b.Navigation("User");
});
modelBuilder.Entity("Knots.Models.Discussion", b =>
{
b.Navigation("Messages");
});
modelBuilder.Entity("Knots.Models.User", b =>
{
b.Navigation("Messages");
});
#pragma warning restore 612, 618
}
}
@@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
namespace Knots.Migrations
{
/// <inheritdoc />
public partial class Initial : Migration
public partial class InitialDatabase : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@@ -16,7 +16,8 @@ namespace Knots.Migrations
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1")
.Annotation("SqlServer:Identity", "1, 1"),
KeyId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
@@ -29,9 +30,10 @@ namespace Knots.Migrations
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Nom = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
NombreMembres = table.Column<int>(type: "int", nullable: false),
ProfilePicture = table.Column<string>(type: "nvarchar(max)", nullable: true)
Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
MembersAmount = table.Column<int>(type: "int", nullable: false),
ProfilePicture = table.Column<string>(type: "nvarchar(max)", nullable: true),
KeyId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
@@ -51,21 +53,6 @@ namespace Knots.Migrations
table.PrimaryKey("PK_Keys", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Messages",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Contenu = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: false),
Date = table.Column<DateTime>(type: "datetime2", nullable: false),
Type = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Messages", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Roles",
columns: table => new
@@ -96,11 +83,79 @@ namespace Knots.Migrations
{
table.PrimaryKey("PK_Users", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Messages",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Contenu = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: false),
Date = table.Column<DateTime>(type: "datetime2", nullable: false),
Type = table.Column<bool>(type: "bit", nullable: false),
GroupId = table.Column<int>(type: "int", nullable: false),
KeyId = table.Column<int>(type: "int", nullable: false),
UserId = table.Column<int>(type: "int", nullable: false),
DiscussionId = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Messages", x => x.Id);
table.ForeignKey(
name: "FK_Messages_Discussions_DiscussionId",
column: x => x.DiscussionId,
principalTable: "Discussions",
principalColumn: "Id");
table.ForeignKey(
name: "FK_Messages_Groups_GroupId",
column: x => x.GroupId,
principalTable: "Groups",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Messages_Keys_KeyId",
column: x => x.KeyId,
principalTable: "Keys",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Messages_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Messages_DiscussionId",
table: "Messages",
column: "DiscussionId");
migrationBuilder.CreateIndex(
name: "IX_Messages_GroupId",
table: "Messages",
column: "GroupId");
migrationBuilder.CreateIndex(
name: "IX_Messages_KeyId",
table: "Messages",
column: "KeyId");
migrationBuilder.CreateIndex(
name: "IX_Messages_UserId",
table: "Messages",
column: "UserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Messages");
migrationBuilder.DropTable(
name: "Roles");
migrationBuilder.DropTable(
name: "Discussions");
@@ -110,12 +165,6 @@ namespace Knots.Migrations
migrationBuilder.DropTable(
name: "Keys");
migrationBuilder.DropTable(
name: "Messages");
migrationBuilder.DropTable(
name: "Roles");
migrationBuilder.DropTable(
name: "Users");
}
@@ -0,0 +1,316 @@
// <auto-generated />
using System;
using Knots;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Knots.Migrations
{
[DbContext(typeof(KnotsDbContext))]
[Migration("20260610135459_AddRoleIdToUser")]
partial class AddRoleIdToUser
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.25")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Knots.Models.Discussion", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<bool>("IsGroup")
.HasColumnType("bit");
b.HasKey("Id");
b.ToTable("Discussions");
});
modelBuilder.Entity("Knots.Models.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("DiscussionId")
.HasColumnType("int");
b.Property<int>("KeyId")
.HasColumnType("int");
b.Property<int>("MembersAmount")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("ProfilePicture")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("DiscussionId")
.IsUnique();
b.ToTable("Groups");
});
modelBuilder.Entity("Knots.Models.Key", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("EnKey")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.HasKey("Id");
b.ToTable("Keys");
});
modelBuilder.Entity("Knots.Models.Message", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Contenu")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<DateTime>("Date")
.HasColumnType("datetime2");
b.Property<int>("DiscussionId")
.HasColumnType("int");
b.Property<int?>("GroupId")
.HasColumnType("int");
b.Property<int?>("KeyId")
.HasColumnType("int");
b.Property<bool>("Type")
.HasColumnType("bit");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("DiscussionId");
b.HasIndex("GroupId");
b.HasIndex("KeyId");
b.HasIndex("UserId");
b.ToTable("Messages");
});
modelBuilder.Entity("Knots.Models.Role", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Libelle")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.HasKey("Id");
b.ToTable("Roles");
});
modelBuilder.Entity("Knots.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Description")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(70)
.HasColumnType("nvarchar(70)");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("ProfilePicture")
.HasColumnType("nvarchar(max)");
b.Property<int?>("RoleId")
.HasColumnType("int");
b.Property<string>("Tel")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Username")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("Users");
});
modelBuilder.Entity("Knots.Models.UserDiscussion", b =>
{
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<int>("DiscussionId")
.HasColumnType("int");
b.HasKey("UserId", "DiscussionId");
b.HasIndex("DiscussionId");
b.ToTable("UserDiscussions");
});
modelBuilder.Entity("Knots.Models.Group", b =>
{
b.HasOne("Knots.Models.Discussion", "Discussion")
.WithOne("Group")
.HasForeignKey("Knots.Models.Group", "DiscussionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Discussion");
});
modelBuilder.Entity("Knots.Models.Message", b =>
{
b.HasOne("Knots.Models.Discussion", "Discussion")
.WithMany("Messages")
.HasForeignKey("DiscussionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Knots.Models.Group", "Group")
.WithMany()
.HasForeignKey("GroupId");
b.HasOne("Knots.Models.Key", "Key")
.WithMany("Messages")
.HasForeignKey("KeyId");
b.HasOne("Knots.Models.User", "User")
.WithMany("Messages")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Discussion");
b.Navigation("Group");
b.Navigation("Key");
b.Navigation("User");
});
modelBuilder.Entity("Knots.Models.User", b =>
{
b.HasOne("Knots.Models.Role", "Role")
.WithMany("Users")
.HasForeignKey("RoleId");
b.Navigation("Role");
});
modelBuilder.Entity("Knots.Models.UserDiscussion", b =>
{
b.HasOne("Knots.Models.Discussion", "Discussion")
.WithMany("UserDiscussions")
.HasForeignKey("DiscussionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Knots.Models.User", "User")
.WithMany("UserDiscussions")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Discussion");
b.Navigation("User");
});
modelBuilder.Entity("Knots.Models.Discussion", b =>
{
b.Navigation("Group");
b.Navigation("Messages");
b.Navigation("UserDiscussions");
});
modelBuilder.Entity("Knots.Models.Key", b =>
{
b.Navigation("Messages");
});
modelBuilder.Entity("Knots.Models.Role", b =>
{
b.Navigation("Users");
});
modelBuilder.Entity("Knots.Models.User", b =>
{
b.Navigation("Messages");
b.Navigation("UserDiscussions");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,270 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Knots.Migrations
{
/// <inheritdoc />
public partial class AddRoleIdToUser : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Messages_Discussions_DiscussionId",
table: "Messages");
migrationBuilder.DropForeignKey(
name: "FK_Messages_Groups_GroupId",
table: "Messages");
migrationBuilder.DropForeignKey(
name: "FK_Messages_Keys_KeyId",
table: "Messages");
migrationBuilder.DropColumn(
name: "KeyId",
table: "Discussions");
migrationBuilder.AddColumn<int>(
name: "RoleId",
table: "Users",
type: "int",
nullable: true);
migrationBuilder.AlterColumn<int>(
name: "KeyId",
table: "Messages",
type: "int",
nullable: true,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.AlterColumn<int>(
name: "GroupId",
table: "Messages",
type: "int",
nullable: true,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.AlterColumn<int>(
name: "DiscussionId",
table: "Messages",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true);
migrationBuilder.AddColumn<int>(
name: "DiscussionId",
table: "Groups",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "Discussions",
type: "datetime2",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<bool>(
name: "IsGroup",
table: "Discussions",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.CreateTable(
name: "UserDiscussions",
columns: table => new
{
UserId = table.Column<int>(type: "int", nullable: false),
DiscussionId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_UserDiscussions", x => new { x.UserId, x.DiscussionId });
table.ForeignKey(
name: "FK_UserDiscussions_Discussions_DiscussionId",
column: x => x.DiscussionId,
principalTable: "Discussions",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_UserDiscussions_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Users_RoleId",
table: "Users",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "IX_Groups_DiscussionId",
table: "Groups",
column: "DiscussionId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_UserDiscussions_DiscussionId",
table: "UserDiscussions",
column: "DiscussionId");
migrationBuilder.AddForeignKey(
name: "FK_Groups_Discussions_DiscussionId",
table: "Groups",
column: "DiscussionId",
principalTable: "Discussions",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Messages_Discussions_DiscussionId",
table: "Messages",
column: "DiscussionId",
principalTable: "Discussions",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Messages_Groups_GroupId",
table: "Messages",
column: "GroupId",
principalTable: "Groups",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Messages_Keys_KeyId",
table: "Messages",
column: "KeyId",
principalTable: "Keys",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Users_Roles_RoleId",
table: "Users",
column: "RoleId",
principalTable: "Roles",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Groups_Discussions_DiscussionId",
table: "Groups");
migrationBuilder.DropForeignKey(
name: "FK_Messages_Discussions_DiscussionId",
table: "Messages");
migrationBuilder.DropForeignKey(
name: "FK_Messages_Groups_GroupId",
table: "Messages");
migrationBuilder.DropForeignKey(
name: "FK_Messages_Keys_KeyId",
table: "Messages");
migrationBuilder.DropForeignKey(
name: "FK_Users_Roles_RoleId",
table: "Users");
migrationBuilder.DropTable(
name: "UserDiscussions");
migrationBuilder.DropIndex(
name: "IX_Users_RoleId",
table: "Users");
migrationBuilder.DropIndex(
name: "IX_Groups_DiscussionId",
table: "Groups");
migrationBuilder.DropColumn(
name: "RoleId",
table: "Users");
migrationBuilder.DropColumn(
name: "DiscussionId",
table: "Groups");
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "Discussions");
migrationBuilder.DropColumn(
name: "IsGroup",
table: "Discussions");
migrationBuilder.AlterColumn<int>(
name: "KeyId",
table: "Messages",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true);
migrationBuilder.AlterColumn<int>(
name: "GroupId",
table: "Messages",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true);
migrationBuilder.AlterColumn<int>(
name: "DiscussionId",
table: "Messages",
type: "int",
nullable: true,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.AddColumn<int>(
name: "KeyId",
table: "Discussions",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddForeignKey(
name: "FK_Messages_Discussions_DiscussionId",
table: "Messages",
column: "DiscussionId",
principalTable: "Discussions",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Messages_Groups_GroupId",
table: "Messages",
column: "GroupId",
principalTable: "Groups",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Messages_Keys_KeyId",
table: "Messages",
column: "KeyId",
principalTable: "Keys",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}
@@ -0,0 +1,380 @@
// <auto-generated />
using System;
using Knots;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Knots.Migrations
{
[DbContext(typeof(KnotsDbContext))]
[Migration("20260610224937_FixGroupDiscussion")]
partial class FixGroupDiscussion
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.25")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Knots.Models.Discussion", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<int?>("GroupId")
.HasColumnType("int");
b.Property<bool>("IsGroup")
.HasColumnType("bit");
b.HasKey("Id");
b.HasIndex("GroupId");
b.ToTable("Discussions");
});
modelBuilder.Entity("Knots.Models.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("DiscussionId")
.HasColumnType("int");
b.Property<int>("MembersAmount")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("ProfilePicture")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("DiscussionId");
b.ToTable("Groups");
});
modelBuilder.Entity("Knots.Models.GroupUser", b =>
{
b.Property<int>("GroupId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<int?>("RoleId")
.HasColumnType("int");
b.HasKey("GroupId", "UserId");
b.HasIndex("RoleId");
b.HasIndex("UserId");
b.ToTable("GroupUsers");
});
modelBuilder.Entity("Knots.Models.Key", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("EnKey")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.HasKey("Id");
b.ToTable("Keys");
});
modelBuilder.Entity("Knots.Models.Message", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("AuthorId")
.HasColumnType("int");
b.Property<string>("Contenu")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<DateTime>("Date")
.HasColumnType("datetime2");
b.Property<int>("DiscussionId")
.HasColumnType("int");
b.Property<int?>("GroupId")
.HasColumnType("int");
b.Property<int?>("KeyId")
.HasColumnType("int");
b.Property<bool>("Type")
.HasColumnType("bit");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("DiscussionId");
b.HasIndex("GroupId");
b.HasIndex("KeyId");
b.HasIndex("UserId");
b.ToTable("Messages");
});
modelBuilder.Entity("Knots.Models.Role", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Libelle")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.HasKey("Id");
b.ToTable("Roles");
});
modelBuilder.Entity("Knots.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Description")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(70)
.HasColumnType("nvarchar(70)");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("ProfilePicture")
.HasColumnType("nvarchar(max)");
b.Property<int?>("RoleId")
.HasColumnType("int");
b.Property<string>("Tel")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Username")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("Users");
});
modelBuilder.Entity("Knots.Models.UserDiscussion", b =>
{
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<int>("DiscussionId")
.HasColumnType("int");
b.HasKey("UserId", "DiscussionId");
b.HasIndex("DiscussionId");
b.ToTable("UserDiscussions");
});
modelBuilder.Entity("Knots.Models.Discussion", b =>
{
b.HasOne("Knots.Models.Group", "Group")
.WithMany()
.HasForeignKey("GroupId")
.OnDelete(DeleteBehavior.Restrict);
b.Navigation("Group");
});
modelBuilder.Entity("Knots.Models.Group", b =>
{
b.HasOne("Knots.Models.Discussion", "Discussion")
.WithMany()
.HasForeignKey("DiscussionId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Discussion");
});
modelBuilder.Entity("Knots.Models.GroupUser", b =>
{
b.HasOne("Knots.Models.Group", "Group")
.WithMany("GroupUsers")
.HasForeignKey("GroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Knots.Models.Role", "Role")
.WithMany("GroupUsers")
.HasForeignKey("RoleId");
b.HasOne("Knots.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Group");
b.Navigation("Role");
b.Navigation("User");
});
modelBuilder.Entity("Knots.Models.Message", b =>
{
b.HasOne("Knots.Models.Discussion", "Discussion")
.WithMany("Messages")
.HasForeignKey("DiscussionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Knots.Models.Group", "Group")
.WithMany()
.HasForeignKey("GroupId");
b.HasOne("Knots.Models.Key", "Key")
.WithMany("Messages")
.HasForeignKey("KeyId");
b.HasOne("Knots.Models.User", "User")
.WithMany("Messages")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Discussion");
b.Navigation("Group");
b.Navigation("Key");
b.Navigation("User");
});
modelBuilder.Entity("Knots.Models.User", b =>
{
b.HasOne("Knots.Models.Role", "Role")
.WithMany("Users")
.HasForeignKey("RoleId");
b.Navigation("Role");
});
modelBuilder.Entity("Knots.Models.UserDiscussion", b =>
{
b.HasOne("Knots.Models.Discussion", "Discussion")
.WithMany("UserDiscussions")
.HasForeignKey("DiscussionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Knots.Models.User", "User")
.WithMany("UserDiscussions")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Discussion");
b.Navigation("User");
});
modelBuilder.Entity("Knots.Models.Discussion", b =>
{
b.Navigation("Messages");
b.Navigation("UserDiscussions");
});
modelBuilder.Entity("Knots.Models.Group", b =>
{
b.Navigation("GroupUsers");
});
modelBuilder.Entity("Knots.Models.Key", b =>
{
b.Navigation("Messages");
});
modelBuilder.Entity("Knots.Models.Role", b =>
{
b.Navigation("GroupUsers");
b.Navigation("Users");
});
modelBuilder.Entity("Knots.Models.User", b =>
{
b.Navigation("Messages");
b.Navigation("UserDiscussions");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,21 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Knots.Migrations
{
/// <inheritdoc />
public partial class FixGroupDiscussion : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "AuthorId",
table: "Messages",
type: "int",
nullable: false,
defaultValue: 0);
}
}
}
+219 -4
View File
@@ -30,8 +30,19 @@ namespace Knots.Migrations
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<int?>("GroupId")
.HasColumnType("int");
b.Property<bool>("IsGroup")
.HasColumnType("bit");
b.HasKey("Id");
b.HasIndex("GroupId");
b.ToTable("Discussions");
});
@@ -43,22 +54,47 @@ namespace Knots.Migrations
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Nom")
b.Property<int>("DiscussionId")
.HasColumnType("int");
b.Property<int>("MembersAmount")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<int>("NombreMembres")
.HasColumnType("int");
b.Property<string>("ProfilePicture")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("DiscussionId");
b.ToTable("Groups");
});
modelBuilder.Entity("Knots.Models.GroupUser", b =>
{
b.Property<int>("GroupId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<int?>("RoleId")
.HasColumnType("int");
b.HasKey("GroupId", "UserId");
b.HasIndex("RoleId");
b.HasIndex("UserId");
b.ToTable("GroupUsers");
});
modelBuilder.Entity("Knots.Models.Key", b =>
{
b.Property<int>("Id")
@@ -85,6 +121,9 @@ namespace Knots.Migrations
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("AuthorId")
.HasColumnType("int");
b.Property<string>("Contenu")
.IsRequired()
.HasMaxLength(1000)
@@ -93,11 +132,31 @@ namespace Knots.Migrations
b.Property<DateTime>("Date")
.HasColumnType("datetime2");
b.Property<int>("DiscussionId")
.HasColumnType("int");
b.Property<int?>("GroupId")
.HasColumnType("int");
b.Property<int?>("KeyId")
.HasColumnType("int");
b.Property<bool>("Type")
.HasColumnType("bit");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("DiscussionId");
b.HasIndex("GroupId");
b.HasIndex("KeyId");
b.HasIndex("UserId");
b.ToTable("Messages");
});
@@ -143,6 +202,9 @@ namespace Knots.Migrations
b.Property<string>("ProfilePicture")
.HasColumnType("nvarchar(max)");
b.Property<int?>("RoleId")
.HasColumnType("int");
b.Property<string>("Tel")
.IsRequired()
.HasColumnType("nvarchar(max)");
@@ -154,8 +216,161 @@ namespace Knots.Migrations
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("Users");
});
modelBuilder.Entity("Knots.Models.UserDiscussion", b =>
{
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<int>("DiscussionId")
.HasColumnType("int");
b.HasKey("UserId", "DiscussionId");
b.HasIndex("DiscussionId");
b.ToTable("UserDiscussions");
});
modelBuilder.Entity("Knots.Models.Discussion", b =>
{
b.HasOne("Knots.Models.Group", "Group")
.WithMany()
.HasForeignKey("GroupId")
.OnDelete(DeleteBehavior.Restrict);
b.Navigation("Group");
});
modelBuilder.Entity("Knots.Models.Group", b =>
{
b.HasOne("Knots.Models.Discussion", "Discussion")
.WithMany()
.HasForeignKey("DiscussionId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Discussion");
});
modelBuilder.Entity("Knots.Models.GroupUser", b =>
{
b.HasOne("Knots.Models.Group", "Group")
.WithMany("GroupUsers")
.HasForeignKey("GroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Knots.Models.Role", "Role")
.WithMany("GroupUsers")
.HasForeignKey("RoleId");
b.HasOne("Knots.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Group");
b.Navigation("Role");
b.Navigation("User");
});
modelBuilder.Entity("Knots.Models.Message", b =>
{
b.HasOne("Knots.Models.Discussion", "Discussion")
.WithMany("Messages")
.HasForeignKey("DiscussionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Knots.Models.Group", "Group")
.WithMany()
.HasForeignKey("GroupId");
b.HasOne("Knots.Models.Key", "Key")
.WithMany("Messages")
.HasForeignKey("KeyId");
b.HasOne("Knots.Models.User", "User")
.WithMany("Messages")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Discussion");
b.Navigation("Group");
b.Navigation("Key");
b.Navigation("User");
});
modelBuilder.Entity("Knots.Models.User", b =>
{
b.HasOne("Knots.Models.Role", "Role")
.WithMany("Users")
.HasForeignKey("RoleId");
b.Navigation("Role");
});
modelBuilder.Entity("Knots.Models.UserDiscussion", b =>
{
b.HasOne("Knots.Models.Discussion", "Discussion")
.WithMany("UserDiscussions")
.HasForeignKey("DiscussionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Knots.Models.User", "User")
.WithMany("UserDiscussions")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Discussion");
b.Navigation("User");
});
modelBuilder.Entity("Knots.Models.Discussion", b =>
{
b.Navigation("Messages");
b.Navigation("UserDiscussions");
});
modelBuilder.Entity("Knots.Models.Group", b =>
{
b.Navigation("GroupUsers");
});
modelBuilder.Entity("Knots.Models.Key", b =>
{
b.Navigation("Messages");
});
modelBuilder.Entity("Knots.Models.Role", b =>
{
b.Navigation("GroupUsers");
b.Navigation("Users");
});
modelBuilder.Entity("Knots.Models.User", b =>
{
b.Navigation("Messages");
b.Navigation("UserDiscussions");
});
#pragma warning restore 612, 618
}
}
+10 -2
View File
@@ -5,7 +5,15 @@ namespace Knots.Models;
public class Discussion
{
[Key] public int Id { get; set; }
public bool IsGroup { get; set; } = false;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public List<Message> Messages { get; set; }
public Key KeyId { get; set; }
public int? GroupId { get; set; }
public Group? Group { get; set; }
public int? KeyId { get; set; }
public Key? Key { get; set; }
public List<Message> Messages { get; set; } = [];
public List<UserDiscussion> UserDiscussions { get; set; } = [];
}
+5 -4
View File
@@ -1,5 +1,4 @@
using System.ComponentModel.DataAnnotations;
using System.Net.Mime;
namespace Knots.Models;
@@ -9,7 +8,9 @@ public class Group
[Required, MaxLength(50)] public string? Name { get; set; }
[Required] public int MembersAmount { get; set; }
public string? ProfilePicture { get; set; }
public Key KeyId { get; set; }
List<Message> Messages { get; set; }
List<User> Users { get; set; }
public List<GroupUser> GroupUsers { get; set; } = [];
public int DiscussionId { get; set; }
public Discussion Discussion { get; set; } = null!;
}
+13
View File
@@ -0,0 +1,13 @@
namespace Knots.Models;
public class GroupUser
{
public int GroupId { get; set; }
public Group Group { get; set; } = null!;
public int UserId { get; set; }
public User User { get; set; } = null!;
public int? RoleId { get; set; }
public Role? Role { get; set; }
}
-1
View File
@@ -6,5 +6,4 @@ public class Key
{
[Key] public int Id { get; set; }
[Required, MaxLength(50)] public string? EnKey { get; set; }
List<Message> Messages { get; set; }
}
+8 -3
View File
@@ -8,7 +8,12 @@ public class Message
[Required, MaxLength(1000)] public string? Contenu { get; set; }
[Required] public DateTime Date { get; set; }
[Required] public Boolean Type { get; set; }
public Group Group { get; set; }
public Key Key { get; set; }
public User User { get; set; }
public int UserId { get; set; }
public User User { get; set; } = null!;
public int DiscussionId { get; set; }
public Discussion Discussion { get; set; } = null!;
public Group? Group { get; set; }
}
+2
View File
@@ -6,4 +6,6 @@ public class Role
{
public int Id { get; set; }
[Required, MaxLength(50)] public string? Libelle { get; set; }
public List<User> Users { get; set; } = [];
public List<GroupUser> GroupUsers { get; set; } = [];
}
+5 -2
View File
@@ -6,10 +6,13 @@ public class User
{
public int Id { get; set; }
[Required, MaxLength(50)] public string? Username { get; set; }
[MaxLength(200)] public string? Description {get; set;}
[MaxLength(200)] public string? Description { get; set; }
[Required, Length(12, 50)] public string? Password { get; set; }
[Required, MaxLength(70)] public string? Email { get; set; }
[Required, Length(10, 10)] public string? Tel { get; set; }
public string? ProfilePicture { get; set; }
public List<Message> Messages { get; set; }
public List<Message> Messages { get; set; } = [];
public int? RoleId { get; set; }
public Role? Role { get; set; }
public List<UserDiscussion> UserDiscussions { get; set; } = [];
}
+10
View File
@@ -0,0 +1,10 @@
namespace Knots.Models;
public class UserDiscussion
{
public int UserId { get; set; }
public User User { get; set; } = null!;
public int DiscussionId { get; set; }
public Discussion Discussion { get; set; } = null!;
}
+1 -1
View File
@@ -7,7 +7,7 @@ namespace Knots.Profiles;
public class MessageProfile : Profile
{
MessageProfile()
public MessageProfile()
{
CreateMap<Message, GetMessageDetailsDto>();
CreateMap<Message, CreateMessageDto>();
+1 -1
View File
@@ -7,7 +7,7 @@ namespace Knots.Profiles;
public class UserProfile : Profile
{
UserProfile()
public UserProfile()
{
CreateMap<User, GetUserDetailsDto>();
CreateMap<User, GetUserDto>();
+57 -15
View File
@@ -1,16 +1,16 @@
using System.Text;
using Knots;
using FastEndpoints;
using FastEndpoints.Swagger;
using Knots.Hubs;
using Knots.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http.Json;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Knots", Version = "v1" });
});
// On ajoute ici la configuration de la base de données
builder.Services.AddDbContext<KnotsDbContext>();
@@ -19,26 +19,68 @@ builder.Services.AddCors(options =>
{ options.AddDefaultPolicy(policyBuilder =>
{
policyBuilder
.WithOrigins("http://localhost:5250")
.WithOrigins("http://localhost:5250", "http://localhost:4200")
.WithMethods("GET", "POST", "PUT", "PATCH", "DELETE")
.AllowAnyHeader();
.AllowAnyHeader()
.AllowCredentials();
});
});
builder.Services.AddAuthentication();
builder.Services.AddFastEndpoints(o =>
{
o.DisableAutoDiscovery = false;
});
builder.Services.SwaggerDocument();
builder.Services.AddScoped<JwtService>();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
path.StartsWithSegments("/hubs"))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
builder.Services.AddAuthorization();
builder.Services.AddSignalR();
builder.Services.AddAutoMapper(cfg => { }, typeof(Program).Assembly);
builder.Services.AddSingleton<EncryptionService>();
// On construit l'application en lui donnant vie
WebApplication app = builder.Build();
app.UseCors();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication()
.UseAuthorization()
@@ -49,6 +91,6 @@ app.UseAuthentication()
}
).UseSwaggerGen();
app.UseHttpsRedirection();
app.MapHub<ChatHub>("hubs/chat");
app.Run();
+56
View File
@@ -0,0 +1,56 @@
using System.Security.Cryptography;
using System.Text;
namespace Knots.Services;
public class EncryptionService
{
private const int NonceSize = 12; // AesGcm.NonceByteSizes.MaxSize
private const int TagSize = 16; // AesGcm.TagByteSizes.MaxSize
// Génère une clé AES-256 (32 octets) encodée en Base64
public string GenerateKey()
=> Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
// Chiffre → renvoie Base64(nonce + tag + ciphertext)
public string Encrypt(string plainText, string base64Key)
{
byte[] key = Convert.FromBase64String(base64Key);
byte[] plain = Encoding.UTF8.GetBytes(plainText);
byte[] nonce = RandomNumberGenerator.GetBytes(NonceSize);
byte[] cipher = new byte[plain.Length];
byte[] tag = new byte[TagSize];
using AesGcm aes = new(key, TagSize);
aes.Encrypt(nonce, plain, cipher, tag);
byte[] result = new byte[NonceSize + TagSize + cipher.Length];
Buffer.BlockCopy(nonce, 0, result, 0, NonceSize);
Buffer.BlockCopy(tag, 0, result, NonceSize, TagSize);
Buffer.BlockCopy(cipher, 0, result, NonceSize + TagSize, cipher.Length);
return Convert.ToBase64String(result);
}
// Déchiffre Base64(nonce + tag + ciphertext)
public string Decrypt(string base64Cipher, string base64Key)
{
byte[] key = Convert.FromBase64String(base64Key);
byte[] data = Convert.FromBase64String(base64Cipher);
byte[] nonce = new byte[NonceSize];
byte[] tag = new byte[TagSize];
byte[] cipher = new byte[data.Length - NonceSize - TagSize];
Buffer.BlockCopy(data, 0, nonce, 0, NonceSize);
Buffer.BlockCopy(data, NonceSize, tag, 0, TagSize);
Buffer.BlockCopy(data, NonceSize + TagSize, cipher, 0, cipher.Length);
byte[] plain = new byte[cipher.Length];
using AesGcm aes = new(key, TagSize);
aes.Decrypt(nonce, cipher, tag, plain);
return Encoding.UTF8.GetString(plain);
}
}
+31
View File
@@ -0,0 +1,31 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
namespace Knots.Services;
public class JwtService(IConfiguration configuration)
{
public string GenerateToken(Models.User user)
{
List<Claim> claims =
[
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username!)
];
SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(configuration["Jwt:Key"]!));
SigningCredentials creds = new(key, SecurityAlgorithms.HmacSha256);
JwtSecurityToken token = new(
issuer: configuration["Jwt:Issuer"],
audience: configuration["Jwt:Audience"],
claims: claims,
expires: DateTime.UtcNow.AddDays(7),
signingCredentials: creds
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
@@ -11,7 +11,7 @@ public class CreateMessageDtoValidator : Validator<CreateMessageDto>
RuleFor(x => x.Contenu)
.NotEmpty()
.WithMessage("Le message ne peux pas être vide")
.MaximumLength(1000)
.MaximumLength(2000)
.WithMessage("Le message ne doit pas faire plus de 1000 caractères");
RuleFor(x => x.Date)
+7 -1
View File
@@ -5,5 +5,11 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
"AllowedHosts": "*",
"Jwt": {
"Key": "QmwiaGBl2FG8LtECB9c5x9t6637Aknw3KQcggKkeuh0",
"Issuer": "knots",
"Audience": "knots"
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More