Compare commits

..

48 Commits

Author SHA1 Message Date
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 c6914f401c commit de fichier config 2026-05-05 10:45:57 +02:00
gokhoal 1044e150f4 program update 2 2026-05-05 10:42:21 +02:00
gokhoal ba507463c7 program update 2026-05-05 10:17:04 +02:00
carteronm 1a3ca3fc2d Patch endpoint 2026-03-26 17:49:43 +01:00
carteronm 07bd241e0c Endpoint P2 2026-03-26 16:38:04 +01:00
carteronm 579f50a2de Début endpoints 2026-03-26 15:34:49 +01:00
oistig df6e559f00 Commit du reste des profils 2026-03-26 15:23:53 +01:00
oistig 1c1f9b2fcc Changements de models (à terminer) + GroupProfile 2026-03-19 17:53:02 +01:00
155 changed files with 5985 additions and 1375 deletions
+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; }
}
@@ -2,7 +2,11 @@ namespace Knots.DTO.Message;
public class GetMessageDetailsDto
{
public int Id { get; set; }
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; } = "";
}
-7
View File
@@ -1,7 +0,0 @@
namespace Knots.DTO.Message;
public class UpdateMessageDto
{
public string? Contenu { get; set; }
public DateTime Date { 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; }
}
@@ -2,5 +2,6 @@ namespace Knots.DTO.User;
public class UpdateUserDescriptionDto
{
public int Id { get; set; }
public string? Description {get; set;}
}
+1 -6
View File
@@ -2,10 +2,5 @@ namespace Knots.DTO.User;
public class UpdateUserDto
{
public string? Username { get; set; }
public string? Description {get; set;}
public string? Password { get; set; }
public string? Email { get; set; }
public string? Tel { get; set; }
public string? ProfilePicture { get; set; }
public int Id { get; set; }
}
-6
View File
@@ -1,6 +0,0 @@
namespace Knots.DTO.User;
public class UpdateUserTelDto
{
public string? Tel { get; set; }
}
-1
View File
@@ -3,6 +3,5 @@ namespace Knots.DTO.User;
public class UpdateUsernameDto
{
public int Id { get; set; }
public string? Username { get; set; }
}
@@ -1,32 +1,21 @@
using FastEndpoints;
using Knots.DTO.Discussion;
using Knots.DTO.User;
namespace Knots.Endpoints.Discussion;
public class CreateUserEndpoint(KnotsDbContext knotsDbContext) : Endpoint<CreateDiscussionDto, GetDiscussionDto>
public class CreateDiscussionEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<CreateDiscussionDto>
{
public override void Configure()
{
Post("/users");
Post("/discussions");
AllowAnonymous();
}
public override async Task HandleAsync(CreateDiscussionDto req, CancellationToken ct)
{
Models.Discussion discussion = new()
{
};
knotsDbContext.Add(discussion);
await knotsDbContext.SaveChangesAsync(ct);
GetDiscussionDto response = new()
{
Id = discussion.Id,
};
await Send.OkAsync(response, ct);
Models.Discussion? discussion = mapper.Map<Models.Discussion>(req);
db.Discussions.Add(discussion);
await db.SaveChangesAsync(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; } = "";
}
@@ -1,6 +1,21 @@
using FastEndpoints;
using Knots.DTO.Discussion;
namespace Knots.Endpoints.Discussion;
public class DeleteDiscussionEndpoint
public class DeleteDiscussionEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<DeleteDiscussionDto>
{
public override void Configure()
{
Delete("/discussions");
AllowAnonymous();
}
public override async Task HandleAsync(DeleteDiscussionDto req, CancellationToken ct)
{
Models.Discussion? discussion = mapper.Map<Models.Discussion>(req);
db.Discussions.Remove(discussion);
await db.SaveChangesAsync(ct);
await SendNoContentAsync(ct);
}
}
@@ -1,6 +1,29 @@
using Knots.DTO.Discussion;
using Knots.DTO.Key;
using Microsoft.EntityFrameworkCore;
using FastEndpoints;
namespace Knots.Endpoints.Discussion;
public class GetDiscussionEndpoint
public class GetDiscussionEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<GetDiscussionDto>
{
public override void Configure()
{
Get("/discussions");
AllowAnonymous();
}
public override async Task HandleAsync(GetDiscussionDto req, CancellationToken ct)
{
Models.Discussion? databaseDiscussion = await db.Discussions.SingleOrDefaultAsync(x => x.Id == req.Id, cancellationToken: ct);
if (databaseDiscussion == null)
{
await SendNotFoundAsync(ct);
return;
}
var keyDto = mapper.Map<GetKeyDetailsDto>(databaseDiscussion);
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);
}
}
+16 -1
View File
@@ -1,6 +1,21 @@
using Knots.DTO.Group;
using FastEndpoints;
namespace Knots.Endpoints.Group;
public class CreateGroupEndpoint
public class CreateGroupEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<CreateGroupDto>
{
public override void Configure()
{
Post("/groups");
AllowAnonymous();
}
public override async Task HandleAsync(CreateGroupDto req, CancellationToken ct)
{
Models.Group? group = mapper.Map<Models.Group>(req);
db.Groups.Add(group);
await db.SaveChangesAsync(ct);
await SendNoContentAsync(ct);
}
}
+16 -1
View File
@@ -1,6 +1,21 @@
using FastEndpoints;
using Knots.DTO.Group;
namespace Knots.Endpoints.Group;
public class DeleteGroupEndpoint
public class DeleteGroupEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<DeleteGroupDto>
{
public override void Configure()
{
Delete("/groups");
AllowAnonymous();
}
public override async Task HandleAsync(DeleteGroupDto req, CancellationToken ct)
{
Models.Group? group = mapper.Map<Models.Group>(req);
db.Groups.Remove(group);
await db.SaveChangesAsync(ct);
await SendNoContentAsync(ct);
}
}
+24 -1
View File
@@ -1,6 +1,29 @@
using FastEndpoints;
using Knots.DTO.Group;
using Knots.DTO.Key;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.Group;
public class GetGroupEndpoint
public class GetGroupEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<GetGroupDetailsDto>
{
public override void Configure()
{
Get("/groups");
AllowAnonymous();
}
public override async Task HandleAsync(GetGroupDetailsDto req, CancellationToken ct)
{
Models.Group? databaseGroup = await db.Groups.SingleOrDefaultAsync(x => x.Id == req.Id, cancellationToken: ct);
if (databaseGroup == null)
{
await SendNotFoundAsync(ct);
return;
}
var keyDto = mapper.Map<GetKeyDetailsDto>(databaseGroup);
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);
}
}
+16 -1
View File
@@ -1,6 +1,21 @@
using Knots.DTO.Key;
using FastEndpoints;
namespace Knots.Endpoints.Key;
public class CreateKeyEndpoint
public class CreateKeyEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<CreateKeyDto>
{
public override void Configure()
{
Post("/keys");
AllowAnonymous();
}
public override async Task HandleAsync(CreateKeyDto req, CancellationToken ct)
{
Models.Key? key = mapper.Map<Models.Key>(req);
db.Keys.Add(key);
await db.SaveChangesAsync(ct);
await SendNoContentAsync(ct);
}
}
+16 -1
View File
@@ -1,6 +1,21 @@
using FastEndpoints;
using Knots.DTO.Key;
namespace Knots.Endpoints.Key;
public class DeleteKeyEndpoint
public class DeleteKeyEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<DeleteKeyDto>
{
public override void Configure()
{
Delete("/keys");
AllowAnonymous();
}
public override async Task HandleAsync(DeleteKeyDto req, CancellationToken ct)
{
Models.Key? key = mapper.Map<Models.Key>(req);
db.Keys.Remove(key);
await db.SaveChangesAsync(ct);
await SendNoContentAsync(ct);
}
}
+25 -2
View File
@@ -1,6 +1,29 @@
using FastEndpoints;
using Knots.DTO.Key;
using Knots.DTO.User;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.Key;
public class GetKeyEndpoint
public class GetKeyEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint <GetKeyDetailsDto>
{
public override void Configure()
{
Get("/keys/{@Id}");
AllowAnonymous();
}
public override async Task HandleAsync(GetKeyDetailsDto req, CancellationToken ct)
{
Models.Key? databaseKey = await db.Keys.SingleOrDefaultAsync(x => x.Id == req.Id, cancellationToken: ct);
if (databaseKey == null)
{
await SendNotFoundAsync(ct);
return;
}
var keyDto = mapper.Map<GetKeyDetailsDto>(databaseKey);
await SendOkAsync(keyDto, ct);
}
}
@@ -1,6 +1,21 @@
using FastEndpoints;
using Knots.DTO.Message;
namespace Knots.Endpoints.Message;
public class CreateMessageEndpoint
public class CreateMessageEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<CreateMessageDto>
{
public override void Configure()
{
Post("/messages");
AllowAnonymous();
}
public override async Task HandleAsync(CreateMessageDto req, CancellationToken ct)
{
Models.Message? message = mapper.Map<Models.Message>(req);
db.Messages.Add(message);
await db.SaveChangesAsync(ct);
await SendNoContentAsync(ct);
}
}
@@ -1,6 +1,21 @@
using FastEndpoints;
using Knots.DTO.Message;
namespace Knots.Endpoints.Message;
public class DeleteMessageEndpoint
public class DeleteMessageEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<DeleteMessageDto>
{
public override void Configure()
{
Delete("/messages");
AllowAnonymous();
}
public override async Task HandleAsync(DeleteMessageDto req, CancellationToken ct)
{
Models.Message? message = mapper.Map<Models.Message>(req);
db.Messages.Remove(message);
await db.SaveChangesAsync(ct);
await SendNoContentAsync(ct);
}
}
+58 -2
View File
@@ -1,6 +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
public class GetMessageEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper, EncryptionService encryption) : Endpoint<GetDiscussionMessagesRequest, List<GetMessageDetailsDto>>
{
public override void Configure()
{
Get("/discussions/{DiscussionId}/messages");
AllowAnonymous();
}
public override async Task HandleAsync(GetDiscussionMessagesRequest req, CancellationToken ct)
{
int userId = int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
// 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 SendForbiddenAsync(ct);
return;
}
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; }
}
+19 -19
View File
@@ -1,32 +1,32 @@
using FastEndpoints;
using Knots.DTO.Role;
using Knots.DTO.User;
using Knots.Models;
namespace Knots.Endpoints.Role;
public class CreateRoleEndpoint(KnotsDbContext knotsDbContext) : Endpoint<CreateRoleDto, GetRoleDto>
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 = new()
{
Libelle = req.Libelle
};
knotsDbContext.Add(role);
await knotsDbContext.SaveChangesAsync(ct);
GetRoleDto response = new()
{
Libelle = role.Libelle
};
await Send.OkAsync(response, ct);
Models.Role role = new() { Libelle = req.Libelle };
db.Roles.Add(role);
await db.SaveChangesAsync(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; } = "";
}
@@ -0,0 +1,21 @@
using FastEndpoints;
using Knots.DTO.Role;
namespace Knots.Endpoints.Role;
public class DeleteRoleEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<DeleteRoleDto>
{
public override void Configure()
{
Delete("/roles");
AllowAnonymous();
}
public override async Task HandleAsync(DeleteRoleDto req, CancellationToken ct)
{
Models.Role? role = mapper.Map<Models.Role>(req);
db.Roles.Remove(role);
await db.SaveChangesAsync(ct);
await SendNoContentAsync(ct);
}
}
+5 -9
View File
@@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.Role;
public class GetRoleEndpoint(KnotsDbContext knotsDbContext) : Endpoint <GetRoleDto>
public class GetRoleEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint <GetRoleDto>
{
public override void Configure()
{
@@ -15,19 +15,15 @@ public class GetRoleEndpoint(KnotsDbContext knotsDbContext) : Endpoint <GetRoleD
public override async Task HandleAsync(GetRoleDto req, CancellationToken ct)
{
Models.Role? databaseRole = await knotsDbContext.Roles.SingleOrDefaultAsync(x => x.Libelle == req.Libelle, cancellationToken: ct);
Models.Role? databaseRole = await db.Roles.SingleOrDefaultAsync(x => x.Libelle == req.Libelle, cancellationToken: ct);
if (databaseRole == null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
GetRoleDto dto = new()
{
Libelle = databaseRole.Libelle,
};
await Send.OkAsync(dto, ct);
var roleDto = mapper.Map<GetRoleDto>(databaseRole);
await SendOkAsync(roleDto, ct);
}
}
+16 -19
View File
@@ -1,9 +1,10 @@
using FastEndpoints;
using Knots.DTO.User;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.User;
public class CreateUserEndpoint(KnotsDbContext knotsDbContext) : Endpoint<CreateUserDto, GetUserDto>
public class CreateUserEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<CreateUserDto, GetUserDto>
{
public override void Configure()
{
@@ -13,24 +14,20 @@ public class CreateUserEndpoint(KnotsDbContext knotsDbContext) : Endpoint<Create
public override async Task HandleAsync(CreateUserDto req, CancellationToken ct)
{
Models.User user = new()
bool usernameExists = await db.Users
.AnyAsync(x => x.Username == req.Username, cancellationToken: ct);
if (usernameExists)
{
Username = req.Username,
Description = req.Description,
Password = req.Password,
Email = req.Email,
Tel = req.Tel,
ProfilePicture = req.ProfilePicture
};
knotsDbContext.Add(user);
await knotsDbContext.SaveChangesAsync(ct);
GetUserDto response = new()
{
Username = user.Username
};
await Send.OkAsync(response, ct);
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 SendNoContentAsync(ct);
}
}
+7 -14
View File
@@ -4,25 +4,18 @@ using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.User;
public class DeleteUserEndpoint(KnotsDbContext knotsDbContext) : Endpoint <GetUserDto, GetUserDetailsDto>
public class DeleteUserEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint <DeleteUserDto>
{
public override void Configure()
{
Delete ("/users/{@Id}", x => new { x.Username });
Delete ("/users/{@Id}");
}
public override async Task HandleAsync(GetUserDto req, CancellationToken ct)
public override async Task HandleAsync(DeleteUserDto req, CancellationToken ct)
{
Models.User? databaseUser = await knotsDbContext.Users.SingleOrDefaultAsync(x => x.Username == req.Username, cancellationToken: ct);
if (databaseUser == null)
{
await Send.NotFoundAsync(ct);
return;
}
knotsDbContext.Users.Remove(databaseUser);
await knotsDbContext.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
Models.User? user = mapper.Map<Models.User>(req);
db.Users.Add(user);
await db.SaveChangesAsync(ct);
await SendNoContentAsync(ct);
}
}
+6 -6
View File
@@ -1,10 +1,11 @@
using AutoMapper.QueryableExtensions;
using FastEndpoints;
using Knots.DTO.User;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.User;
public class GetAllUsersEndpoint(KnotsDbContext knotsDbContext) : EndpointWithoutRequest<List<GetUserDto>>
public class GetAllUsersEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : EndpointWithoutRequest<List<GetUserDetailsDto>>
{
public override void Configure()
{
@@ -14,11 +15,10 @@ public class GetAllUsersEndpoint(KnotsDbContext knotsDbContext) : EndpointWithou
public override async Task HandleAsync(CancellationToken ct)
{
List<GetUserDto> users= await knotsDbContext.Users.Select(x => new GetUserDto()
{
Username = x.Username,
}).ToListAsync(ct);
var users = await db.Users
.ProjectTo<GetUserDetailsDto>(mapper.ConfigurationProvider)
.ToListAsync(ct);
await Send.OkAsync(users, ct);
await SendOkAsync(users, ct);
}
}
+7 -16
View File
@@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.User;
public class GetUserEndpoint(KnotsDbContext knotsDbContext) : Endpoint <GetUserDto, GetUserDetailsDto>
public class GetUserEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint <GetUserDetailsDto>
{
public override void Configure()
{
@@ -12,26 +12,17 @@ public class GetUserEndpoint(KnotsDbContext knotsDbContext) : Endpoint <GetUserD
AllowAnonymous();
}
public override async Task HandleAsync(GetUserDto req, CancellationToken ct)
public override async Task HandleAsync(GetUserDetailsDto req, CancellationToken ct)
{
Models.User? databaseLogin = await knotsDbContext.Users.SingleOrDefaultAsync(x => x.Username == req.Username, cancellationToken: ct);
Models.User? databaseUser = await db.Users.SingleOrDefaultAsync(x => x.Username == req.Username, cancellationToken: ct);
if (databaseLogin == null)
if (databaseUser == null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
GetUserDetailsDto dto = new()
{
Username = databaseLogin.Username,
Description = databaseLogin.Description,
Password = databaseLogin.Password,
Email = databaseLogin.Email,
Tel = databaseLogin.Tel,
ProfilePicture = databaseLogin.ProfilePicture
};
await Send.OkAsync(dto, ct);
var userDto = mapper.Map<GetUserDetailsDto>(databaseUser);
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);
}
}
@@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.User;
public class PatchUserContactEndpoint(KnotsDbContext knotsDbContext) : Endpoint<UpdateUserContactDto>
public class PatchUserContactEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<UpdateUserContactDto>
{
public override void Configure()
{
@@ -14,11 +14,11 @@ public class PatchUserContactEndpoint(KnotsDbContext knotsDbContext) : Endpoint<
public override async Task HandleAsync(UpdateUserContactDto req, CancellationToken ct)
{
Models.User? databaseUser = await knotsDbContext.Users.SingleOrDefaultAsync(x => x.Id == req.Id, cancellationToken: ct);
Models.User? databaseUser = await db.Users.SingleOrDefaultAsync(x => x.Id == req.Id, cancellationToken: ct);
if (databaseUser is null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
@@ -39,7 +39,9 @@ public class PatchUserContactEndpoint(KnotsDbContext knotsDbContext) : Endpoint<
databaseUser.Tel = databaseUser.Tel;
}
await knotsDbContext.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
mapper.Map(req, databaseUser);
await db.SaveChangesAsync(ct);
await SendNoContentAsync(ct);
}
}
@@ -0,0 +1,30 @@
using FastEndpoints;
using Knots.DTO.User;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.User;
public class PatchUserDescriptionEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<UpdateUserDescriptionDto>
{
public override void Configure()
{
Patch("/users/{@Id}/description/");
AllowAnonymous();
}
public override async Task HandleAsync(UpdateUserDescriptionDto req, CancellationToken ct)
{
Models.User? databaseUser = await db.Users.SingleOrDefaultAsync(x => x.Id == req.Id, cancellationToken: ct);
if (databaseUser is null)
{
await SendNotFoundAsync(ct);
return;
}
mapper.Map(req, databaseUser);
await db.SaveChangesAsync(ct);
await SendNoContentAsync(ct);
}
}
@@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.User;
public class PatchUserPasswordEndpoint(KnotsDbContext knotsDbContext) : Endpoint<UpdateUserPasswordDto>
public class PatchUserPasswordEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<UpdateUserPasswordDto>
{
public override void Configure()
{
@@ -14,16 +14,17 @@ public class PatchUserPasswordEndpoint(KnotsDbContext knotsDbContext) : Endpoint
public override async Task HandleAsync(UpdateUserPasswordDto req, CancellationToken ct)
{
Models.User? databaseUser = await knotsDbContext.Users.SingleOrDefaultAsync(x => x.Id == req.Id, cancellationToken: ct);
Models.User? databaseUser = await db.Users.SingleOrDefaultAsync(x => x.Id == req.Id, cancellationToken: ct);
if (databaseUser is null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
databaseUser.Password = req.Password;
await knotsDbContext.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
mapper.Map(req, databaseUser);
await db.SaveChangesAsync(ct);
await SendNoContentAsync(ct);
}
}
@@ -4,26 +4,27 @@ using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.User;
public class PatchUserProfilePictureEndpoint(KnotsDbContext knotsDbContext) : Endpoint<UpdateUserProfilePictureDto>
public class PatchUserProfilePictureEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<UpdateUserProfilePictureDto>
{
public override void Configure()
{
Patch("/users/{@Id}/profilePicture/", x => new {x.Id});
Patch("/users/{@Id}/profilepicture/");
AllowAnonymous();
}
public override async Task HandleAsync(UpdateUserProfilePictureDto req, CancellationToken ct)
{
Models.User? databaseUser = await knotsDbContext.Users.SingleOrDefaultAsync(x => x.Id == req.Id, cancellationToken: ct);
Models.User? databaseUser = await db.Users.SingleOrDefaultAsync(x => x.Id == req.Id, cancellationToken: ct);
if (databaseUser is null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
databaseUser.ProfilePicture = req.ProfilePicture;
await knotsDbContext.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
mapper.Map(req, databaseUser);
await db.SaveChangesAsync(ct);
await SendNoContentAsync(ct);
}
}
+18 -7
View File
@@ -5,26 +5,37 @@ using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.User;
public class PatchUsernameEndpoint(KnotsDbContext knotsDbContext) : Endpoint<UpdateUsernameDto>
public class PatchUsernameEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<UpdateUsernameDto>
{
public override void Configure()
{
Patch("/users/{@Id}/username/", x => new {x.Id});
Patch("/users/{@Id}/username/");
AllowAnonymous();
}
public override async Task HandleAsync(UpdateUsernameDto req, CancellationToken ct)
{
Models.User? databaseUser = await knotsDbContext.Users.SingleOrDefaultAsync(x => x.Id == req.Id, cancellationToken: ct);
Models.User? databaseUser = await db.Users.SingleOrDefaultAsync(x => x.Id == req.Id, cancellationToken: ct);
if (databaseUser is null)
{
await Send.NotFoundAsync(ct);
await SendNotFoundAsync(ct);
return;
}
databaseUser.Username = req.Username;
await knotsDbContext.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
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 SendNoContentAsync(ct);
}
}
@@ -1,46 +0,0 @@
using FastEndpoints;
using Knots.DTO.User;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.User;
public class UpdateUserEndpoint(KnotsDbContext knotsDbContext) : Endpoint <UpdateUserDto, GetUserDetailsDto>
{
public override void Configure()
{
Put ("/users/{@Id}", x => new { x.Username });
}
public override async Task HandleAsync(UpdateUserDto req, CancellationToken ct)
{
Models.User? databaseUser = await knotsDbContext.Users.SingleOrDefaultAsync(x => x.Username == req.Username, cancellationToken: ct);
if (databaseUser == null)
{
await Send.NotFoundAsync(ct);
return;
}
else
{
databaseUser.Username = req.Username;
databaseUser.Password = req.Password;
databaseUser.Description = req.Description;
databaseUser.Tel = req.Tel;
databaseUser.Email = req.Email;
databaseUser.ProfilePicture = req.ProfilePicture;
}
await knotsDbContext.SaveChangesAsync(ct);
GetUserDetailsDto dto = new()
{
Username = databaseUser.Username,
Password = databaseUser.Password,
Description = databaseUser.Description,
Tel = databaseUser.Tel,
Email = databaseUser.Email,
ProfilePicture = databaseUser.ProfilePicture
};
await Send.OkAsync(dto, 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);
}
}
+7 -2
View File
@@ -8,15 +8,20 @@
<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>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.25" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
</ItemGroup>
<ItemGroup>
+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
}
}
+11
View File
@@ -5,4 +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 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 -1
View File
@@ -1,5 +1,4 @@
using System.ComponentModel.DataAnnotations;
using System.Net.Mime;
namespace Knots.Models;
@@ -9,4 +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 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; }
}
+8
View File
@@ -8,4 +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 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 -1
View File
@@ -6,9 +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 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!;
}
+11 -2
View File
@@ -1,6 +1,15 @@
using AutoMapper;
using Knots.DTO.Group;
using Knots.Models;
namespace Knots.Profiles;
public class GroupProfile
public class GroupProfile : Profile
{
public GroupProfile()
{
CreateMap<Group, GetGroupDto>();
CreateMap<Group, GetGroupDetailsDto>();
CreateMap<CreateGroupDto, Group>();
}
}
+8 -2
View File
@@ -1,10 +1,16 @@
using AutoMapper;
using Knots.DTO.Discussion;
using Knots.DTO.Key;
using Knots.Models;
namespace Knots.Profiles;
public class KeyProfile
public class KeyProfile : Profile
{
public KeyProfile()
{
CreateMap<Key, GetKeyDetailsDto>();
CreateMap<Key, CreateKeyDto>();
CreateMap<CreateKeyDto, Key>();
}
}
+8 -2
View File
@@ -1,10 +1,16 @@
using AutoMapper;
using Knots.DTO.Discussion;
using Knots.DTO.Message;
using Knots.Models;
namespace Knots.Profiles;
public class MessageProfile
public class MessageProfile : Profile
{
public MessageProfile()
{
CreateMap<Message, GetMessageDetailsDto>();
CreateMap<Message, CreateMessageDto>();
CreateMap<CreateMessageDto, Message>();
}
}
+8 -2
View File
@@ -1,10 +1,16 @@
using AutoMapper;
using Knots.DTO.Discussion;
using Knots.DTO.Role;
using Knots.Models;
namespace Knots.Profiles;
public class RoleProfile
public class RoleProfile : Profile
{
public RoleProfile()
{
CreateMap<Role, GetRoleDto>();
CreateMap<Role, CreateRoleDto>();
CreateMap<CreateRoleDto, Role>();
}
}
+21 -2
View File
@@ -1,10 +1,29 @@
using AutoMapper;
using Knots.DTO.Discussion;
using Knots.DTO.User;
using Knots.Models;
namespace Knots.Profiles;
public class UserProfile
public class UserProfile : Profile
{
public UserProfile()
{
CreateMap<User, GetUserDetailsDto>();
CreateMap<User, GetUserDto>();
CreateMap<CreateUserDto, User>();
CreateMap<UpdateUserDto, User>();
CreateMap<UpdateUserContactDto, User>();
CreateMap<UpdateUserDescriptionDto, User>();
CreateMap<UpdateUsernameDto, User>();
CreateMap<UpdateUserProfilePictureDto, User>();
CreateMap<UpdateUserPasswordDto, User>();
CreateMap<User, UpdateUserContactDto>();
CreateMap<User, UpdateUserDto>();
CreateMap<User, UpdateUserDescriptionDto>();
CreateMap<User, UpdateUserProfilePictureDto>();
CreateMap<User, UpdateUserPasswordDto>();
CreateMap<User, CreateUserDto>();
}
}
+63 -5
View File
@@ -1,6 +1,13 @@
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);
@@ -12,16 +19,69 @@ builder.Services.AddCors(options =>
{ options.AddDefaultPolicy(policyBuilder =>
{
policyBuilder
.WithOrigins("http://localhost:4200")
.WithOrigins("http://localhost:5250", "http://localhost:4200")
.WithMethods("GET", "POST", "PUT", "PATCH", "DELETE")
.AllowAnyHeader();
.AllowAnyHeader()
.AllowCredentials();
});
});
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();
app.UseHttpsRedirection();
app.UseAuthentication()
.UseAuthorization()
.UseFastEndpoints(options =>
@@ -31,8 +91,6 @@ app.UseAuthentication()
}
).UseSwaggerGen();
app.UseHttpsRedirection();
app.UseCors();
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.

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