Compare commits

..

62 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
oistig 641552fba2 profiles encore parce que mathieu est un gros bebe 2026-03-19 17:06:15 +01:00
oistig 3699b28e03 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	Knots/obj/Debug/net8.0/Knots.assets.cache
#	Knots/obj/Debug/net8.0/Knots.csproj.AssemblyReference.cache
#	Knots/obj/project.nuget.cache
#	Knots/obj/project.packagespec.json
#	Knots/obj/rider.project.model.nuget.info
#	Knots/obj/rider.project.restore.info
2026-03-19 16:45:46 +01:00
oistig 26106c5db9 Ajout des profiles (pas fini) + Ajout du builder automapper dans Program.cs 2026-03-19 16:45:28 +01:00
carteronm b4595c173c Fin validator 2026-03-19 16:44:48 +01:00
oistig ecd038f020 Fusion des patchs Tel et Email en un Patch Contact 2026-03-19 16:25:34 +01:00
oistig 00b79a58d0 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	Knots/obj/Debug/net8.0/Knots.AssemblyInfo.cs
#	Knots/obj/Debug/net8.0/Knots.AssemblyInfoInputs.cache
#	Knots/obj/rider.project.model.nuget.info
2026-03-19 16:19:07 +01:00
oistig 7c2e77ed99 Changements dans le Group Model + endpoints Patch des Groupes et Users 2026-03-19 16:18:52 +01:00
oistig 24613de57c commit 2026-03-19 15:11:43 +01:00
carteronm 7acd4e7e11 Merge remote-tracking branch 'origin/develop' into develop 2026-03-19 15:10:00 +01:00
carteronm 50227abc26 Validator update 2026-03-19 15:09:54 +01:00
oistig dc9aa8c840 Merge remote-tracking branch 'origin/develop' into develop 2026-03-12 17:54:21 +01:00
oistig 3b178144ae Commit endpoints 2026-03-12 17:54:18 +01:00
carteronm 3a83372403 Merge remote-tracking branch 'origin/develop' into develop 2026-03-12 17:44:44 +01:00
carteronm da3def1eea Ajout d'update pour patch + début validator 2026-03-12 17:44:39 +01:00
187 changed files with 6698 additions and 1706 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; }
}
+1 -1
View File
@@ -2,5 +2,5 @@ namespace Knots.DTO.Group;
public class DeleteGroupDto
{
public string? Id { get; set; }
public int? Id { get; set; }
}
@@ -0,0 +1,7 @@
namespace Knots.DTO.Group;
public class UpdateGroupMembersAmountDto
{
public int Id { get; set; }
public int MembersAmount { get; set; }
}
+7
View File
@@ -0,0 +1,7 @@
namespace Knots.DTO.Group;
public class UpdateGroupNameDto
{
public int Id { get; set; }
public string? Name { get; set; }
}
@@ -0,0 +1,7 @@
namespace Knots.DTO.Group;
public class UpdateGroupProfilePictureDto
{
public int Id { get; set; }
public string? ProfilePicture { 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; }
}
+1 -1
View File
@@ -2,5 +2,5 @@ namespace Knots.DTO.Role;
public class GetRoleDto
{
public string? Id { get; set; }
public string? Libelle { 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; }
}
+8
View File
@@ -0,0 +1,8 @@
namespace Knots.DTO.User;
public class UpdateUserContactDto
{
public int Id { get; set; }
public string? Email { get; set; }
public string? Tel { get; set; }
}
@@ -0,0 +1,7 @@
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; }
}
+7
View File
@@ -0,0 +1,7 @@
namespace Knots.DTO.User;
public class UpdateUserPasswordDto
{
public int Id { get; set; }
public string? Password { get; set; }
}
@@ -0,0 +1,7 @@
namespace Knots.DTO.User;
public class UpdateUserProfilePictureDto
{
public int Id { get; set; }
public string? ProfilePicture { get; set; }
}
+7
View File
@@ -0,0 +1,7 @@
namespace Knots.DTO.User;
public class UpdateUsernameDto
{
public int Id { get; set; }
public string? Username { get; set; }
}
@@ -1,6 +1,21 @@
using FastEndpoints;
using Knots.DTO.Discussion;
namespace Knots.Endpoints.Discussion;
public class CreateDiscussionEndpoint
public class CreateDiscussionEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<CreateDiscussionDto>
{
public override void Configure()
{
Post("/discussions");
AllowAnonymous();
}
public override async Task HandleAsync(CreateDiscussionDto req, CancellationToken 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);
}
}
@@ -0,0 +1,29 @@
using FastEndpoints;
using Knots.DTO.Group;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.Group;
public class PatchGroupMembersAmountEndpoint(KnotsDbContext knotsDbContext) : Endpoint<UpdateGroupMembersAmountDto>
{
public override void Configure()
{
Patch("/groups/{@Id}/membersAmount/", x => new {x.Id});
AllowAnonymous();
}
public override async Task HandleAsync(UpdateGroupMembersAmountDto req, CancellationToken ct)
{
Models.Group? databaseGroup = await knotsDbContext.Groups.SingleOrDefaultAsync(x => x.Id == req.Id, cancellationToken: ct);
if (databaseGroup is null)
{
await SendNotFoundAsync(ct);
return;
}
databaseGroup.MembersAmount = req.MembersAmount;
await knotsDbContext.SaveChangesAsync(ct);
await SendNoContentAsync(ct);
}
}
@@ -0,0 +1,29 @@
using FastEndpoints;
using Knots.DTO.Group;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.Group;
public class PatchGroupNameEndpoint(KnotsDbContext knotsDbContext) : Endpoint<UpdateGroupNameDto>
{
public override void Configure()
{
Patch("/groups/{@Id}/name/", x => new {x.Id});
AllowAnonymous();
}
public override async Task HandleAsync(UpdateGroupNameDto req, CancellationToken ct)
{
Models.Group? databaseGroup = await knotsDbContext.Groups.SingleOrDefaultAsync(x => x.Id == req.Id, cancellationToken: ct);
if (databaseGroup is null)
{
await SendNotFoundAsync(ct);
return;
}
databaseGroup.Name = req.Name;
await knotsDbContext.SaveChangesAsync(ct);
await SendNoContentAsync(ct);
}
}
@@ -0,0 +1,29 @@
using FastEndpoints;
using Knots.DTO.Group;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.Group;
public class PatchGroupProfilePictureEndpoint(KnotsDbContext knotsDbContext) : Endpoint<UpdateGroupProfilePictureDto>
{
public override void Configure()
{
Patch("/groups/{@Id}/profilePicture/", x => new {x.Id});
AllowAnonymous();
}
public override async Task HandleAsync(UpdateGroupProfilePictureDto req, CancellationToken ct)
{
Models.Group? databaseGroup = await knotsDbContext.Groups.SingleOrDefaultAsync(x => x.Id == req.Id, cancellationToken: ct);
if (databaseGroup is null)
{
await SendNotFoundAsync(ct);
return;
}
databaseGroup.ProfilePicture = req.ProfilePicture;
await knotsDbContext.SaveChangesAsync(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; }
}
+28 -2
View File
@@ -1,6 +1,32 @@
using FastEndpoints;
using Knots.Models;
namespace Knots.Endpoints.Role;
public class CreateRoleEndpoint
public class CreateRoleEndpoint(KnotsDbContext db) : Endpoint<CreateRoleRequest, RoleDto>
{
public override void Configure()
{
Post("/roles");
}
public override async Task HandleAsync(CreateRoleRequest req, CancellationToken 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);
}
}
+25 -2
View File
@@ -1,6 +1,29 @@
using FastEndpoints;
using Knots.DTO.Role;
using Knots.DTO.User;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.Role;
public class GetRoleEndpoint
public class GetRoleEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint <GetRoleDto>
{
public override void Configure()
{
Get ("/roles/{@Id}", x => new { x.Libelle });
AllowAnonymous();
}
public override async Task HandleAsync(GetRoleDto req, CancellationToken ct)
{
Models.Role? databaseRole = await db.Roles.SingleOrDefaultAsync(x => x.Libelle == req.Libelle, cancellationToken: ct);
if (databaseRole == null)
{
await SendNotFoundAsync(ct);
return;
}
var roleDto = mapper.Map<GetRoleDto>(databaseRole);
await SendOkAsync(roleDto, ct);
}
}
@@ -1,6 +0,0 @@
namespace Knots.Endpoints.Role;
public class UpdateRoleEndpoint
{
}
+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);
}
}
@@ -0,0 +1,47 @@
using FastEndpoints;
using Knots.DTO.User;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.User;
public class PatchUserContactEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<UpdateUserContactDto>
{
public override void Configure()
{
Patch("/users/{@Id}/contact/", x => new {x.Id});
AllowAnonymous();
}
public override async Task HandleAsync(UpdateUserContactDto 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;
}
if (databaseUser.Email != req.Email)
{
databaseUser.Email = req.Email;
}
else
{
databaseUser.Email = databaseUser.Email;
}
if (databaseUser.Tel != req.Tel)
{
databaseUser.Tel = req.Tel;
}else
{
databaseUser.Tel = databaseUser.Tel;
}
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);
}
}
@@ -0,0 +1,30 @@
using FastEndpoints;
using Knots.DTO.User;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.User;
public class PatchUserPasswordEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<UpdateUserPasswordDto>
{
public override void Configure()
{
Patch("/users/{@Id}/password/", x => new {x.Id});
AllowAnonymous();
}
public override async Task HandleAsync(UpdateUserPasswordDto 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);
}
}
@@ -0,0 +1,30 @@
using FastEndpoints;
using Knots.DTO.User;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.User;
public class PatchUserProfilePictureEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<UpdateUserProfilePictureDto>
{
public override void Configure()
{
Patch("/users/{@Id}/profilepicture/");
AllowAnonymous();
}
public override async Task HandleAsync(UpdateUserProfilePictureDto 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);
}
}
@@ -0,0 +1,41 @@
using FastEndpoints;
using Knots.DTO.User;
using Microsoft.EntityFrameworkCore;
namespace Knots.Endpoints.User;
public class PatchUsernameEndpoint(KnotsDbContext db, AutoMapper.IMapper mapper) : Endpoint<UpdateUsernameDto>
{
public override void Configure()
{
Patch("/users/{@Id}/username/");
AllowAnonymous();
}
public override async Task HandleAsync(UpdateUsernameDto 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;
}
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);
}
}
+8 -2
View File
@@ -7,15 +7,21 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FastEndpoints" Version="8.0.1" />
<PackageReference Include="FastEndpoints.Swagger" Version="8.0.1" />
<PackageReference Include="AutoMapper" Version="16.1.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; } = [];
}
+7 -3
View File
@@ -1,12 +1,16 @@
using System.ComponentModel.DataAnnotations;
using System.Net.Mime;
namespace Knots.Models;
public class Group
{
[Key] public int Id { get; set; }
[Required, MaxLength(50)] public string? Nom { get; set; }
[Required] public int NombreMembres { get; set; }
[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; }
}
+3 -1
View File
@@ -5,5 +5,7 @@ namespace Knots.Models;
public class Role
{
public int Id { get; set; }
[Required, MaxLength(50)] public string Libelle { 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!;
}
+17
View File
@@ -0,0 +1,17 @@
using AutoMapper;
using Knots.DTO.Discussion;
using Knots.Models;
namespace Knots.Profiles;
public class DiscussionProfile : Profile
{
public DiscussionProfile()
{
CreateMap<Discussion, GetDiscussionDto>();
CreateMap<Discussion, CreateDiscussionDto>();
CreateMap<CreateDiscussionDto, Discussion>();
}
}
+15
View File
@@ -0,0 +1,15 @@
using AutoMapper;
using Knots.DTO.Group;
using Knots.Models;
namespace Knots.Profiles;
public class GroupProfile : Profile
{
public GroupProfile()
{
CreateMap<Group, GetGroupDto>();
CreateMap<Group, GetGroupDetailsDto>();
CreateMap<CreateGroupDto, Group>();
}
}
+16
View File
@@ -0,0 +1,16 @@
using AutoMapper;
using Knots.DTO.Discussion;
using Knots.DTO.Key;
using Knots.Models;
namespace Knots.Profiles;
public class KeyProfile : Profile
{
public KeyProfile()
{
CreateMap<Key, GetKeyDetailsDto>();
CreateMap<Key, CreateKeyDto>();
CreateMap<CreateKeyDto, Key>();
}
}
+16
View File
@@ -0,0 +1,16 @@
using AutoMapper;
using Knots.DTO.Discussion;
using Knots.DTO.Message;
using Knots.Models;
namespace Knots.Profiles;
public class MessageProfile : Profile
{
public MessageProfile()
{
CreateMap<Message, GetMessageDetailsDto>();
CreateMap<Message, CreateMessageDto>();
CreateMap<CreateMessageDto, Message>();
}
}
+16
View File
@@ -0,0 +1,16 @@
using AutoMapper;
using Knots.DTO.Discussion;
using Knots.DTO.Role;
using Knots.Models;
namespace Knots.Profiles;
public class RoleProfile : Profile
{
public RoleProfile()
{
CreateMap<Role, GetRoleDto>();
CreateMap<Role, CreateRoleDto>();
CreateMap<CreateRoleDto, Role>();
}
}
+29
View File
@@ -0,0 +1,29 @@
using AutoMapper;
using Knots.DTO.Discussion;
using Knots.DTO.User;
using Knots.Models;
namespace Knots.Profiles;
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>();
}
}
+65 -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,14 +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 =>
@@ -29,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);
}
}
@@ -1,6 +1,15 @@
using FastEndpoints;
using FluentValidation;
using Knots.DTO.Discussion;
namespace Knots.Validators.Discussion;
public class CreateDiscussionDtoValidator
public class CreateDiscussionDtoValidator : Validator<CreateDiscussionDto>
{
public CreateDiscussionDtoValidator()
{
RuleFor(x => x.Id)
.NotEmpty()
.WithMessage("Id is required");
}
}
@@ -1,6 +1,15 @@
using FastEndpoints;
using FluentValidation;
using Knots.DTO.Discussion;
namespace Knots.Validators.Discussion;
public class DeleteDiscussionDtoValidator
public class DeleteDiscussionDtoValidator : Validator<DeleteDiscussionDto>
{
public DeleteDiscussionDtoValidator()
{
RuleFor(x => x.Id)
.NotEmpty()
.WithMessage("Id is required");
}
}
@@ -1,6 +0,0 @@
namespace Knots.Validators.Discussion;
public class GetDiscussionDtoValidator
{
}
@@ -1,6 +1,21 @@
using FastEndpoints;
using FluentValidation;
using Knots.DTO.Group;
namespace Knots.Validators.Group;
public class CreateGroupDtoValidator
public class CreateGroupDtoValidator : Validator<CreateGroupDto>
{
public CreateGroupDtoValidator()
{
RuleFor(x => x.Nom)
.NotEmpty()
.WithMessage("You must enter a name for the group")
.MaximumLength(50)
.WithMessage("Maximum 50 character are required");
RuleFor(x => x.NombreMembres)
.NotEmpty()
.WithMessage("Members cannot be empty");
}
}
@@ -1,6 +1,17 @@
using FastEndpoints;
using FluentValidation;
using Knots.DTO.Group;
namespace Knots.Validators.Group;
public class DeleteGroupDtoValidator
public class DeleteGroupDtoValidator : Validator<DeleteGroupDto>
{
}
public DeleteGroupDtoValidator()
{
RuleFor(x => x.Id)
.NotEmpty()
.WithMessage("Id is required")
.GreaterThan(0)
.WithMessage("Id cannot be less than zero");
}
}
@@ -1,6 +0,0 @@
namespace Knots.Validators.Group;
public class GetGroupDetailsDtoValidator
{
}
+11 -2
View File
@@ -1,6 +1,15 @@
using FastEndpoints;
using FluentValidation;
using Knots.DTO.Group;
namespace Knots.Validators.Group;
public class GetGroupDtoValidator
public class GetGroupDtoValidator : Validator<GetGroupDto>
{
public GetGroupDtoValidator()
{
RuleFor(x => x.Id)
.NotEmpty()
.WithMessage("Id cannot be empty");
}
}
@@ -1,6 +1,9 @@
using FastEndpoints;
using Knots.DTO.Group;
namespace Knots.Validators.Group;
public class UpdateGroupDtoValidator
public class UpdateGroupDtoValidator : Validator<UpdateGroupDto>
{
}
@@ -0,0 +1,21 @@
using FastEndpoints;
using FluentValidation;
using Knots.DTO.Group;
namespace Knots.Validators.Group;
public class UpdateGroupMembersAmountDtoValidator : Validator<UpdateGroupMembersAmountDto>
{
public UpdateGroupMembersAmountDtoValidator()
{
RuleFor(x => x.Id)
.NotEmpty()
.WithMessage("L'id est requis")
.GreaterThan(0)
.WithMessage("L'id doit être supérieur à 0");
RuleFor(x => x.MembersAmount)
.NotEmpty()
.WithMessage("Le nombre de membres est requis");
}
}
@@ -0,0 +1,20 @@
using FastEndpoints;
using FluentValidation;
using Knots.DTO.Group;
namespace Knots.Validators.Group;
public class UpdateGroupNameDtoValidator : Validator<UpdateGroupNameDto>
{
public UpdateGroupNameDtoValidator()
{
RuleFor(x => x.Id)
.NotNull()
.WithMessage("L'id doit être renseigné");
RuleFor(x => x.Name)
.NotEmpty()
.WithMessage("Le nom de groupe est requis");
}
}
+11 -2
View File
@@ -1,6 +1,15 @@
using FastEndpoints;
using FluentValidation;
using Knots.DTO.Key;
namespace Knots.Validators.Key;
public class CreateKeyDtoValidator
public class CreateKeyDtoValidator : Validator<CreateKeyDto>
{
public CreateKeyDtoValidator()
{
RuleFor(x => x.EnKey)
.NotEmpty()
.WithMessage("La clé de chiffrement ne doit pas être nulle.");
}
}
+13 -2
View File
@@ -1,6 +1,17 @@
using FastEndpoints;
using FluentValidation;
using Knots.DTO.Key;
namespace Knots.Validators.Key;
public class DeleteKeyDtoValidator
public class DeleteKeyDtoValidator : Validator<DeleteKeyDto>
{
public DeleteKeyDtoValidator()
{
RuleFor(x => x.Id)
.NotEmpty()
.WithMessage("L'id doit être renseigné")
.GreaterThan(0)
.WithMessage("L'id renseigné doit être supérieur à 0");
}
}
@@ -1,6 +0,0 @@
namespace Knots.Validators.Key;
public class GetKeyDetailsDtoValidator
{
}
+13 -2
View File
@@ -1,6 +1,17 @@
using FastEndpoints;
using FluentValidation;
using Knots.DTO.Key;
namespace Knots.Validators.Key;
public class GetKeyDtoValidator
public class GetKeyDtoValidator : Validator<GetKeyDto>
{
public GetKeyDtoValidator()
{
RuleFor(x => x.Id)
.NotEmpty()
.WithMessage("L'id est requis")
.GreaterThan(0)
.WithMessage("L'id doit être supérieur à 0");
}
}
@@ -1,6 +1,26 @@
using FastEndpoints;
using FluentValidation;
using Knots.DTO.Message;
namespace Knots.Validators.Message;
public class CreateMessageDtoValidator
public class CreateMessageDtoValidator : Validator<CreateMessageDto>
{
public CreateMessageDtoValidator()
{
RuleFor(x => x.Contenu)
.NotEmpty()
.WithMessage("Le message ne peux pas être vide")
.MaximumLength(2000)
.WithMessage("Le message ne doit pas faire plus de 1000 caractères");
RuleFor(x => x.Date)
.NotEmpty()
.WithMessage("La date ne peut pas être vide");
RuleFor(x => x.Type)
.NotEmpty()
.WithMessage("Le type de message doit être renseigné");
}
}
@@ -1,6 +0,0 @@
namespace Knots.Validators.Message;
public class GetMessageDetailsDto
{
}
@@ -1,6 +1,17 @@
using FastEndpoints;
using FluentValidation;
using Knots.DTO.Role;
namespace Knots.Validators.Role;
public class CreateRoleDtoValidator
public class CreateRoleDtoValidator : Validator<CreateRoleDto>
{
public CreateRoleDtoValidator()
{
RuleFor(x => x.Libelle)
.NotEmpty()
.WithMessage("Libelle cannot be empty")
.MaximumLength(50)
.WithMessage("Libelle maximum length is 50");
}
}
@@ -1,6 +1,15 @@
using FastEndpoints;
using FluentValidation;
using Knots.DTO.Role;
namespace Knots.Validators.Role;
public class DeleteRoleDtoValidator
public class DeleteRoleDtoValidator : Validator<DeleteRoleDto>
{
public DeleteRoleDtoValidator()
{
RuleFor(x => x.Id)
.NotEmpty()
.WithMessage("Id cannot be empty");
}
}
+13 -2
View File
@@ -1,6 +1,17 @@
using FastEndpoints;
using FluentValidation;
using Knots.DTO.Role;
namespace Knots.Validators.Role;
public class GetRoleDtoValidator
public class GetRoleDtoValidator : Validator<GetRoleDto>
{
public GetRoleDtoValidator()
{
RuleFor(x => x.Libelle)
.NotEmpty()
.WithMessage("Libelle cannot be empty")
.MaximumLength(50)
.WithMessage("Libelle maximum length is 50");
}
}
@@ -1,6 +0,0 @@
namespace Knots.Validators.User;
public class GetUserDetailsDtoValidator
{
}
@@ -0,0 +1,27 @@
using FastEndpoints;
using FluentValidation;
using Knots.DTO.User;
namespace Knots.Validators.User;
public class UpdateUserContactDtoValidator : Validator<UpdateUserContactDto>
{
public UpdateUserContactDtoValidator()
{
RuleFor(x => x.Id)
.NotEmpty()
.WithMessage("L'id est requis")
.GreaterThan(0)
.WithMessage("L'id doit être supérieur à 0");
RuleFor(x => x.Email)
.NotEmpty()
.WithMessage("L'email est requis")
.EmailAddress()
.WithMessage("Ce n'est pas un email valide");
RuleFor(x => x.Tel)
.NotEmpty()
.WithMessage("Le numéro de téléphone est requis");
}
}

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