Compare commits

...

20 Commits

Author SHA1 Message Date
sanchezvem 6b5f18d486 Implemented rustfs in app 2026-06-05 11:05:50 +01:00
sanchezvem 2116737987 Changer signature jwt jey 2026-05-30 12:17:07 +01:00
sanchezvem df59a97d4f Fixed error with user creation 2026-05-18 15:38:45 +01:00
sanchezvem e1c505e7a1 Added regex in password field 2026-05-18 15:26:24 +01:00
sanchezvem 76cbe90521 Cleaned code 2026-05-18 14:28:59 +01:00
sanchezvem bb1120f967 Fixed error with achievements and upgrade of spec to list challenges and proofs 2026-05-18 14:25:56 +01:00
sanchezvem 75876b3ab3 Cleaned code 2026-05-14 18:49:35 +01:00
sanchezvem e6d47a4005 Added Achievement Service in Program 2026-05-14 18:17:21 +01:00
sanchezvem f11e5e274b Added AchievementService.cs for gestion of achievements 2026-05-14 18:16:19 +01:00
sanchezvem 62fb8ec932 Fixed error 2026-05-14 15:20:12 +01:00
sanchezvem 8df7f06d60 Added series gestion 2026-05-14 15:11:45 +01:00
sanchezvem eb99eca363 Deleted unused endpoint 2026-05-14 14:37:17 +01:00
sanchezvem b3ee30a9cc fixed error with foreign key in post 2026-05-14 14:22:08 +01:00
sanchezvem 80db4dd531 Added missing relations on database 2026-05-14 14:16:50 +01:00
sanchezvem 8bce9df5a1 Added missing include 2026-05-14 13:43:53 +01:00
sanchezvem 1fff5f816f Cleaned code 2026-05-14 11:48:20 +01:00
sanchezvem f86fd80efc refactor code of challenges and proofs in profil vue 2026-05-14 11:38:41 +01:00
sanchezvem 01b7675703 Added proof on endpoint to see all posts 2026-05-14 11:17:19 +01:00
sanchezvem 747ad10090 deleted unused endpoint 2026-05-13 23:03:39 +01:00
sanchezvem 727494aab4 Fixed error in message DTO and hub 2026-05-13 22:49:41 +01:00
43 changed files with 970 additions and 297 deletions
+3
View File
@@ -1,6 +1,9 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEndpoint_002ESend_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fc2fa49f697e4d07c58aa3f35484a5103de685287155db8b628f2cdce16b69_003FEndpoint_002ESend_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEndpoint_002ESetup_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F656240c46e44c14018dc6190e77764a3404d76b559ed75474669c553eb6227dd_003FEndpoint_002ESetup_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIConfiguration_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F17a3a8d481919bdc3f1630b5a7deadb2cd394fa12fff3312e6abe29bfc4_003FIConfiguration_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIFormFile_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F4d6d6283215de958b1baadc74d6f99ce76f43090cfc86e26e51d845b779450_003FIFormFile_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIHubClients_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fd6ae5218e3a0462f8a94591a77a902637b750_003Faf_003F6ad738e4_003FIHubClients_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIHubContext_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F731dbe51d65849048244493644b992cd78910_003Fda_003Fd14ae6ee_003FIHubContext_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARepositoryBaseOfT_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F5023b5be698d783ffe9f42b2e944a85a7a66b61bc5e19c76c591036343fd16_003FRepositoryBaseOfT_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARepositoryBaseOfT_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F5023b5be698d783ffe9f42b2e944a85a7a66b61bc5e19c76c591036343fd16_003FRepositoryBaseOfT_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+1
View File
@@ -10,6 +10,7 @@
<PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="9.3.1" />
<PackageReference Include="AutoMapper" Version="16.0.0" />
<PackageReference Include="AutoMapper.Collection" Version="13.0.0" />
<PackageReference Include="AWSSDK.S3" Version="4.0.24" />
<PackageReference Include="BCrypt.Net-Next" Version="4.1.0" />
<PackageReference Include="FastEndpoints" Version="7.2.0" />
<PackageReference Include="FastEndpoints.Security" Version="7.2.0" />
@@ -4,5 +4,4 @@ public class CreateMessageDto
{
public int GroupId { get; set; }
public string? Libelle { get; set; }
public DateTime SendDate { get; set; }
}
+1 -1
View File
@@ -7,7 +7,7 @@ public class GetPostDto
public DateTime CreationDate { get; set; }
public int Likes { get; set; }
public bool IsLiked { get; set; }
public string? Proof { get; set; }
public int UserId { get; set; }
public string? Username { get; set; }
}
@@ -1,18 +0,0 @@
using BeReadyBackend.DTO.Achievements;
using BeReadyBackend.Repositories;
using FastEndpoints;
namespace BeReadyBackend.Endpoints.Achievements;
public class GetAllAchievementsEndpoint(AchievementsRepository achievementsRepository) : EndpointWithoutRequest<List<GetAchievementDto>>
{
public override void Configure()
{
Get("/Achievements/");
}
public override async Task HandleAsync(CancellationToken ct)
{
await Send.OkAsync(await achievementsRepository.ProjectToListAsync<GetAchievementDto>(ct), ct);
}
}
@@ -1,27 +0,0 @@
using BeReadyBackend.DTO.Achievements;
using BeReadyBackend.Repositories;
using BeReadyBackend.Services;
using BeReadyBackend.Specifications.Achievements;
using FastEndpoints;
namespace BeReadyBackend.Endpoints.Achievements;
public class GetLockedAchievementsEndpoint(
AchievementsRepository achievementsRepository,
UserService userService)
: EndpointWithoutRequest<List<GetAchievementDto>>
{
public override void Configure()
{
Get("/Achievements/Locked/Users/");
}
public override async Task HandleAsync(CancellationToken ct)
{
int userId = userService.GetUserIdFromToken();
List<GetAchievementDto> achievementsLocked = await achievementsRepository.ProjectToListAsync<GetAchievementDto>(new GetLockedAchievementsSpec(userId), ct);
await Send.OkAsync(achievementsLocked, ct);
}
}
@@ -1,50 +0,0 @@
using BeReadyBackend.DTO.Achievements;
using BeReadyBackend.Models;
using BeReadyBackend.Repositories;
using BeReadyBackend.Services;
using BeReadyBackend.Specifications.Achievements;
using BeReadyBackend.Specifications.UserAchievements;
using FastEndpoints;
namespace BeReadyBackend.Endpoints.Achievements;
public class UnlockAchievementEndpoint(
UserAchievementsRepository userAchievementsRepository,
AchievementsRepository achievementsRepository,
UserService userService) : Endpoint<UnlockAchievementDto>
{
public override void Configure()
{
Post("/Achievements/{@AchievementId}/Users/", x => new { x.AchievementId });
Description(x => x.Accepts<UnlockAchievementDto>());
}
public override async Task HandleAsync(UnlockAchievementDto req, CancellationToken ct)
{
int userId = userService.GetUserIdFromToken();
Achievement? achievement = await achievementsRepository.SingleOrDefaultAsync(new GetAchievementByIdSpec(req.AchievementId), ct);
if (achievement is null)
{
await Send.NotFoundAsync(ct);
return;
}
UserAchievement? userAchievement = await userAchievementsRepository.SingleOrDefaultAsync(new GetUserAchievementByIdSpec(userId, req.AchievementId), ct);
if (userAchievement is not null)
{
await Send.StringAsync("Le succès est déjà attribué à cet utilisateur", 500, cancellation: ct);
return;
}
userAchievement = new UserAchievement
{
UserId = userId,
AchievementId = req.AchievementId
};
await userAchievementsRepository.AddAsync(userAchievement, ct);
await Send.NoContentAsync(ct);
}
}
@@ -29,7 +29,7 @@ public class LoginEndpoint(UsersRepository usersRepository, AutoMapper.IMapper m
{
string jwtToken = JwtBearer.CreateToken(o =>
{
o.SigningKey = "ThisIsASuperSecretJwtKeyThatIsAtLeast32CharsLong";
o.SigningKey = "v9!Qx7#Lk2@pZ8$wR6!tN5%uF3&cD9^mH1*eY4";
o.ExpireAt = DateTime.UtcNow.AddDays(15);
o.User.Claims.Add(("Username", user.Username)!);
o.User.Claims.Add(("FullName", user.FirstName + user.Name));
@@ -40,7 +40,7 @@ public class RefreshTokenEndpoint(UsersRepository usersRepository, AutoMapper.IM
string jwtToken = JwtBearer.CreateToken(o =>
{
o.SigningKey = "ThisIsASuperSecretJwtKeyThatIsAtLeast32CharsLong";
o.SigningKey = "v9!Qx7#Lk2@pZ8$wR6!tN5%uF3&cD9^mH1*eY4";
o.ExpireAt = DateTime.UtcNow.AddDays(15);
o.User.Claims.Add(("Username", user.Username)!);
o.User.Claims.Add(("FullName", user.FirstName + user.Name));
@@ -6,8 +6,7 @@ using FastEndpoints;
namespace BeReadyBackend.Endpoints.Friends;
public class GetAllFriendRequestsEndpoint(UserFriendsRepository userFriendsRepository, UserService userService, AutoMapper.IMapper mapper)
: EndpointWithoutRequest<List<GetFriendRequestDto>>
public class GetAllFriendRequestsEndpoint(UserFriendsRepository userFriendsRepository, UserService userService) : EndpointWithoutRequest<List<GetFriendRequestDto>>
{
public override void Configure()
{
@@ -6,8 +6,7 @@ using FastEndpoints;
namespace BeReadyBackend.Endpoints.Friends;
public class GetAllFriendsEndpoint(UserFriendsRepository userFriendsRepository, UsersRepository usersRepository, UserService userService, AutoMapper.IMapper mapper)
: EndpointWithoutRequest<List<GetFriendDto>>
public class GetAllFriendsEndpoint(UserFriendsRepository userFriendsRepository, UserService userService) : EndpointWithoutRequest<List<GetFriendDto>>
{
public override void Configure()
{
@@ -17,7 +16,6 @@ public class GetAllFriendsEndpoint(UserFriendsRepository userFriendsRepository,
public override async Task HandleAsync(CancellationToken ct)
{
int userId = userService.GetUserIdFromToken();
await Send.OkAsync(await userFriendsRepository.ProjectToListAsync<GetFriendDto>(new GetFriendsByUserIdSpec(userId), ct), ct);
}
}
@@ -1,47 +0,0 @@
using BeReadyBackend.Models;
using BeReadyBackend.Repositories;
using BeReadyBackend.Services;
using BeReadyBackend.Specifications.Groups;
using FastEndpoints;
namespace BeReadyBackend.Endpoints.Groups;
public class AddUserToGroupRequest
{
public int UserId { get; set; }
public int GroupId { get; set; }
}
public class AddUserToGroupEndpoint(UserService userService, UserGroupsRepository userGroupsRepository, AutoMapper.IMapper mapper) : Endpoint<AddUserToGroupRequest>
{
public override void Configure()
{
Post("/Groups/{@GroupId}/Users/{@UserId}/", x => new { x.GroupId, x.UserId });
}
public override async Task HandleAsync(AddUserToGroupRequest req, CancellationToken ct)
{
int userId = userService.GetUserIdFromToken();
UserGroup? member = await userGroupsRepository.SingleOrDefaultAsync(new GetUserInGroupByIdsSpec(req.GroupId, userId), ct);
if (member is null)
{
await Send.StringAsync("Vous n'êtes pas dans ce groupe", 400, cancellation: ct);
return;
}
UserGroup? userGroup = await userGroupsRepository.SingleOrDefaultAsync(new GetUserInGroupByIdsSpec(req.GroupId, req.UserId), ct);
if (userGroup is not null)
{
await Send.StringAsync("L'utilisateur fait déjà partie du groupe", 400, cancellation: ct);
return;
}
userGroup = mapper.Map<UserGroup>(req);
userGroup.Grade = "Member";
await userGroupsRepository.AddAsync(userGroup, ct);
await Send.NoContentAsync(ct);
}
}
@@ -1,34 +0,0 @@
using BeReadyBackend.Models;
using BeReadyBackend.Repositories;
using BeReadyBackend.Specifications.Messages;
using FastEndpoints;
namespace BeReadyBackend.Endpoints.Messages;
public class DeleteMessageRequest
{
public int Id { get; set; }
public int GroupId { get; set; }
}
public class DeleteMessageEndpoint(MessagesRepository messagesRepository) : Endpoint<DeleteMessageRequest>
{
public override void Configure()
{
Delete("/Messages/{@Id}/Groups/{@GroupId}/", x => new { x.Id, x.GroupId });
}
public override async Task HandleAsync(DeleteMessageRequest req, CancellationToken ct)
{
Message? message = await messagesRepository.SingleOrDefaultAsync(new GetMessageByIdSpec(req.Id), ct);
if (message is null)
{
await Send.NotFoundAsync(ct);
return;
}
await messagesRepository.DeleteAsync(message, ct);
await Send.NoContentAsync(ct);
}
}
@@ -4,6 +4,7 @@ using BeReadyBackend.Models;
using BeReadyBackend.Repositories;
using BeReadyBackend.Services;
using BeReadyBackend.Specifications.Groups;
using BeReadyBackend.Specifications.Messages;
using FastEndpoints;
using Microsoft.AspNetCore.SignalR;
using Group = System.Text.RegularExpressions.Group;
@@ -34,10 +35,12 @@ public class SendMessageEndpoint(
Message message = new();
mapper.Map(req, message);
message.SendDate = DateTime.Now;
message.UserId = userService.GetUserIdFromToken();
await messagesRepository.AddAsync(message, ct);
await hubContext.Clients.Group($"group-{req.GroupId}").SendAsync("ReceiveMessage", message, cancellationToken: ct);
GetMessageDto messageDto = await messagesRepository.ProjectToSingleAsync<GetMessageDto>(new GetMessageByIdSpec(message.Id), ct);
await hubContext.Clients.Group($"group-{req.GroupId}").SendAsync("ReceiveMessage", messageDto, cancellationToken: ct);
await Send.NoContentAsync(ct);
}
}
@@ -7,7 +7,7 @@ using FastEndpoints;
namespace BeReadyBackend.Endpoints.Posts;
public class GetAllPostsEndpoint(PostsRepository postsRepository, UserService userService) : EndpointWithoutRequest<List<GetPostDto>>
public class GetAllPostsEndpoint(PostsRepository postsRepository, UserService userService, StorageService storageService) : EndpointWithoutRequest<List<GetPostDto>>
{
public override void Configure()
{
@@ -20,15 +20,25 @@ public class GetAllPostsEndpoint(PostsRepository postsRepository, UserService us
List<Post> posts = await postsRepository.ListAsync(new GetPostNotMeSpec(userId), ct);
List<GetPostDto> result = posts.Select(x => new GetPostDto
List<GetPostDto> result = posts.Select(x =>
{
Id = x.Id,
Libelle = x.Libelle,
CreationDate = x.CreationDate,
Likes = x.Likes,
UserId = x.UserId,
Username = x.User?.Username,
IsLiked = x.UserPosts?.Count(y => y.UserId == userId) > 0
string? proof = x.User?.UserRandomChallenges?
.SingleOrDefault(u =>
u.RandomChallenge?.GeneratedAt is not null
&& DateOnly.FromDateTime(u.RandomChallenge.GeneratedAt.Value) == DateOnly.FromDateTime(x.CreationDate))
?.Proof;
return new GetPostDto
{
Id = x.Id,
Libelle = x.Libelle,
CreationDate = x.CreationDate,
Likes = x.Likes,
Proof = proof is not null ? storageService.GetUrl(proof) : null,
UserId = x.UserId,
Username = x.User?.Username,
IsLiked = x.UserPosts?.Count(y => y.UserId == userId) > 0
};
}).ToList();
await Send.OkAsync(result, ct);
@@ -18,6 +18,7 @@ public class PatchLikeEndpoint(UserPostsRepository userPostsRepository, UsersRep
public override void Configure()
{
Patch("/Posts/{@PostId}/Like", x => new { x.PostId });
Description(x => x.Accepts<PatchLikeRequest>());
}
public override async Task HandleAsync(PatchLikeRequest req, CancellationToken ct)
@@ -10,16 +10,16 @@ namespace BeReadyBackend.Endpoints.RandomChallenges;
public class RandomChallengeProofRequest
{
public int RandomChallengeId { get; set; }
public string? Libelle { get; set; }
public IFormFile? Proof { get; set; }
}
public class PatchProofEndpoint(
UsersRepository usersRepository,
UserRandomChallengesRepository userRandomChallengesRepository,
UserService userService,
RandomChallengesRepository randomChallengesRepository,
PostsRepository postsRepository) : Endpoint<RandomChallengeProofRequest>
PostsRepository postsRepository,
UserService userService,
StorageService storageService) : Endpoint<RandomChallengeProofRequest>
{
public override void Configure()
{
@@ -71,23 +71,25 @@ public class PatchProofEndpoint(
return;
}
// Encodage en base64
using MemoryStream memoryStream = new();
await req.Proof.CopyToAsync(memoryStream, ct);
byte[] proofBytes = memoryStream.ToArray();
string key = await storageService.UploadFile(req.Proof, $"random-challenges/{req.RandomChallengeId}/{userId}", ct);
userRandomChallenge.Proof = Convert.ToBase64String(proofBytes);
userRandomChallenge.Proof = key;
user.TotalChallenge++;
user.TotalChallenge++; // +1 challenge de fait
UserRandomChallenge? lastChallenge = await userRandomChallengesRepository.SingleOrDefaultAsync(new GetLastRandomChallengeSpec(userId), ct);
if (lastChallenge is not null || user.Series == 0) user.Series++;
else user.Series = 1;
await usersRepository.SaveChangesAsync(ct);
await userRandomChallengesRepository.SaveChangesAsync(ct);
Post post = new()
{
Libelle = req.Libelle,
CreationDate = DateTime.Now,
UserId = userId
Libelle = randomChallenge.Label,
UserId = userId,
RandomChallengeId = req.RandomChallengeId,
};
await postsRepository.AddAsync(post, ct);
@@ -1,13 +1,18 @@
using BeReadyBackend.DTO.Users;
using BeReadyBackend.Models;
using BeReadyBackend.Repositories;
using BeReadyBackend.Specifications.RandomChallenges;
using BeReadyBackend.Specifications.Users;
using FastEndpoints;
using PasswordGenerator;
namespace BeReadyBackend.Endpoints.Users;
public class CreateUserEndpoint(UsersRepository usersRepository, AutoMapper.IMapper mapper) : Endpoint<CreateUserDto>
public class CreateUserEndpoint(
UsersRepository usersRepository,
RandomChallengesRepository randomChallengesRepository,
UserRandomChallengesRepository userRandomChallengesRepository,
AutoMapper.IMapper mapper) : Endpoint<CreateUserDto>
{
public override void Configure()
{
@@ -33,6 +38,18 @@ public class CreateUserEndpoint(UsersRepository usersRepository, AutoMapper.IMap
user.Password = BCrypt.Net.BCrypt.HashPassword(req.Password + salt);
await usersRepository.AddAsync(user, ct);
RandomChallenge? randomChallenge = await randomChallengesRepository.SingleOrDefaultAsync(new GetRandomChallengeByDateSpec(), ct);
if (randomChallenge is not null)
{
UserRandomChallenge userRandomChallenge = new()
{
UserId = user.Id,
RandomChallengeId = randomChallenge.Id
};
await userRandomChallengesRepository.AddAsync(userRandomChallenge, ct);
}
await Send.NoContentAsync(ct);
}
}
@@ -2,12 +2,13 @@
using BeReadyBackend.Models;
using BeReadyBackend.Repositories;
using BeReadyBackend.Services;
using BeReadyBackend.Specifications.Users;
using BeReadyBackend.Specifications.RandomChallenges;
using FastEndpoints;
namespace BeReadyBackend.Endpoints.Users;
public class GetAllUserChallengesEndpoint(UsersRepository usersRepository, UserService userService, AutoMapper.IMapper mapper) : EndpointWithoutRequest<List<GetUserChallengeDto>>
public class GetAllUserChallengesEndpoint(UserRandomChallengesRepository userRandomChallengesRepository, UserService userService, AutoMapper.IMapper mapper)
: EndpointWithoutRequest<List<GetUserChallengeDto>>
{
public override void Configure()
{
@@ -18,21 +19,11 @@ public class GetAllUserChallengesEndpoint(UsersRepository usersRepository, UserS
{
int userId = userService.GetUserIdFromToken();
User? user = await usersRepository.SingleOrDefaultAsync(new GetProofOrChallengeByUserIdSpec(userId), ct);
List<UserRandomChallenge>? userRandomChallenge = await userRandomChallengesRepository.ListAsync(new GetUserRandomChallengeByIdSpec(userId), ct);
if (user is null)
{
await Send.NotFoundAsync(ct);
return;
}
List<GetUserChallengeDto> challenges = [];
if (user.UserRandomChallenges is not null)
challenges.AddRange(
user.UserRandomChallenges
.Where(x => x.Proof is not null)
.Select(x => mapper.Map<GetUserChallengeDto>(x.RandomChallenge))
);
List<GetUserChallengeDto> challenges = userRandomChallenge
.Select(x => mapper.Map<GetUserChallengeDto>(x.RandomChallenge))
.ToList();
await Send.OkAsync(challenges.OrderByDescending(x => x.ChallengeStartDate).ToList(), ct);
}
@@ -7,7 +7,8 @@ using FastEndpoints;
namespace BeReadyBackend.Endpoints.Users;
public class GetAllUserProofsEndpoint(UsersRepository usersRepository, UserService userService) : EndpointWithoutRequest<List<GetUserProofDto>>
public class GetAllUserProofsEndpoint(UserRandomChallengesRepository userRandomChallengesRepository, UserService userService, StorageService storageService)
: EndpointWithoutRequest<List<GetUserProofDto>>
{
public override void Configure()
{
@@ -18,24 +19,14 @@ public class GetAllUserProofsEndpoint(UsersRepository usersRepository, UserServi
{
int userId = userService.GetUserIdFromToken();
User? user = await usersRepository.SingleOrDefaultAsync(new GetProofOrChallengeByUserIdSpec(userId), ct);
List<UserRandomChallenge> userProofs = await userRandomChallengesRepository.ListAsync(new GetUserProofByUserIdSpec(userId), ct);
if (user is null)
{
await Send.NotFoundAsync(ct);
return;
}
List<GetUserProofDto> proofs = [];
if (user.UserRandomChallenges is not null)
proofs.AddRange(
user.UserRandomChallenges
.Where(x => x.Proof is not null)
.Select(x => new GetUserProofDto
{
Proof = x.Proof
})
);
List<GetUserProofDto> proofs = userProofs
.Select(x => new GetUserProofDto
{
Proof = storageService.GetUrl(x.Proof!)
})
.ToList();
await Send.OkAsync(proofs, ct);
}
@@ -7,7 +7,8 @@ using FastEndpoints;
namespace BeReadyBackend.Endpoints.Users;
public class GetUserDetailsEndpoint(UsersRepository usersRepository, UserService userService, AutoMapper.IMapper mapper) : EndpointWithoutRequest<GetUserDetailsDto>
public class GetUserDetailsEndpoint(UsersRepository usersRepository, UserService userService, AutoMapper.IMapper mapper, AchievementService achievementService)
: EndpointWithoutRequest<GetUserDetailsDto>
{
public override void Configure()
{
@@ -18,6 +19,7 @@ public class GetUserDetailsEndpoint(UsersRepository usersRepository, UserService
{
int userId = userService.GetUserIdFromToken();
User? user = await usersRepository.SingleOrDefaultAsync(new GetUserByIdSpec(userId), ct);
await achievementService.CheckAchievement(userId);
await Send.OkAsync(mapper.Map<GetUserDetailsDto>(user), ct);
}
+3 -2
View File
@@ -1,11 +1,12 @@
using Microsoft.AspNetCore.SignalR;
using BeReadyBackend.DTO.Messages;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Primitives;
namespace BeReadyBackend.Hubs;
public class GroupHub : Hub
{
public async Task SendMessageToGroup(int groupId, string message)
public async Task SendMessageToGroup(int groupId, CreateMessageDto message)
{
await Clients.Group($"group-{groupId}").SendAsync("ReceiveMessage", message);
}
@@ -31,7 +31,6 @@ public class EntityToDtoMappings : Profile
CreateMap<User, GetUserStatsDto>();
CreateMap<UserGroup, GetUserProofDto>();
CreateMap<UserRandomChallenge, GetUserProofDto>();
CreateMap<RandomChallenge, GetUserChallengeDto>()
@@ -0,0 +1,506 @@
// <auto-generated />
using System;
using BeReadyBackend;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace BeReadyBackend.Migrations
{
[DbContext(typeof(BeReadyDbContext))]
[Migration("20260514131334_AddedMissingRelations")]
partial class AddedMissingRelations
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.20")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("BeReadyBackend.Models.Achievement", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Description")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Label")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Achievements");
});
modelBuilder.Entity("BeReadyBackend.Models.Designation", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Label")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Designations");
});
modelBuilder.Entity("BeReadyBackend.Models.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreationDate")
.HasColumnType("datetime2");
b.Property<string>("Label")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Groups");
});
modelBuilder.Entity("BeReadyBackend.Models.Message", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("GroupId")
.HasColumnType("int");
b.Property<string>("Libelle")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("SendDate")
.HasColumnType("datetime2");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("GroupId");
b.HasIndex("UserId");
b.ToTable("Messages");
});
modelBuilder.Entity("BeReadyBackend.Models.Post", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreationDate")
.HasColumnType("datetime2");
b.Property<string>("Libelle")
.HasColumnType("nvarchar(max)");
b.Property<int>("Likes")
.HasColumnType("int");
b.Property<int>("RandomChallengeId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("RandomChallengeId");
b.HasIndex("UserId");
b.ToTable("Posts");
});
modelBuilder.Entity("BeReadyBackend.Models.RandomChallenge", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime?>("GeneratedAt")
.HasColumnType("datetime2");
b.Property<bool>("IsAlreadyPast")
.HasColumnType("bit");
b.Property<string>("Label")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Libelle")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("RandomChallenges");
});
modelBuilder.Entity("BeReadyBackend.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreationDate")
.HasColumnType("datetime2");
b.Property<int?>("DesignationId")
.HasColumnType("int");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Password")
.IsRequired()
.HasMaxLength(60)
.HasColumnType("nvarchar(60)");
b.Property<string>("Salt")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("Series")
.HasColumnType("int");
b.Property<int>("TotalChallenge")
.HasColumnType("int");
b.Property<int>("TotalLikes")
.HasColumnType("int");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("DesignationId");
b.ToTable("Users");
});
modelBuilder.Entity("BeReadyBackend.Models.UserAchievement", b =>
{
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<int>("AchievementId")
.HasColumnType("int");
b.HasKey("UserId", "AchievementId");
b.HasIndex("AchievementId");
b.ToTable("UserAchievements");
});
modelBuilder.Entity("BeReadyBackend.Models.UserFriend", b =>
{
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<int>("FriendId")
.HasColumnType("int");
b.Property<bool>("IsAccepted")
.HasColumnType("bit");
b.HasKey("UserId", "FriendId");
b.HasIndex("FriendId");
b.ToTable("UserFriends");
});
modelBuilder.Entity("BeReadyBackend.Models.UserGroup", b =>
{
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<int>("GroupId")
.HasColumnType("int");
b.Property<string>("Grade")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "GroupId");
b.HasIndex("GroupId");
b.ToTable("UserGroups");
});
modelBuilder.Entity("BeReadyBackend.Models.UserPost", b =>
{
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<int>("PostId")
.HasColumnType("int");
b.HasKey("UserId", "PostId");
b.HasIndex("PostId");
b.ToTable("UserPosts");
});
modelBuilder.Entity("BeReadyBackend.Models.UserRandomChallenge", b =>
{
b.Property<int>("UserId")
.HasColumnType("int");
b.Property<int>("RandomChallengeId")
.HasColumnType("int");
b.Property<string>("Proof")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "RandomChallengeId");
b.HasIndex("RandomChallengeId");
b.ToTable("UserRandomChallenges");
});
modelBuilder.Entity("BeReadyBackend.Models.Message", b =>
{
b.HasOne("BeReadyBackend.Models.Group", "Group")
.WithMany("Messages")
.HasForeignKey("GroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BeReadyBackend.Models.User", "User")
.WithMany("Messages")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Group");
b.Navigation("User");
});
modelBuilder.Entity("BeReadyBackend.Models.Post", b =>
{
b.HasOne("BeReadyBackend.Models.RandomChallenge", "RandomChallenge")
.WithMany("Posts")
.HasForeignKey("RandomChallengeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BeReadyBackend.Models.User", "User")
.WithMany("Posts")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("RandomChallenge");
b.Navigation("User");
});
modelBuilder.Entity("BeReadyBackend.Models.User", b =>
{
b.HasOne("BeReadyBackend.Models.Designation", "Designation")
.WithMany("Users")
.HasForeignKey("DesignationId");
b.Navigation("Designation");
});
modelBuilder.Entity("BeReadyBackend.Models.UserAchievement", b =>
{
b.HasOne("BeReadyBackend.Models.Achievement", "Achievement")
.WithMany("UserAchievements")
.HasForeignKey("AchievementId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BeReadyBackend.Models.User", "User")
.WithMany("UserAchievements")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Achievement");
b.Navigation("User");
});
modelBuilder.Entity("BeReadyBackend.Models.UserFriend", b =>
{
b.HasOne("BeReadyBackend.Models.User", "Friend")
.WithMany()
.HasForeignKey("FriendId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("BeReadyBackend.Models.User", "User")
.WithMany("UserFriends")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Friend");
b.Navigation("User");
});
modelBuilder.Entity("BeReadyBackend.Models.UserGroup", b =>
{
b.HasOne("BeReadyBackend.Models.Group", "Group")
.WithMany("UserGroups")
.HasForeignKey("GroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BeReadyBackend.Models.User", "User")
.WithMany("UserGroups")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Group");
b.Navigation("User");
});
modelBuilder.Entity("BeReadyBackend.Models.UserPost", b =>
{
b.HasOne("BeReadyBackend.Models.Post", "Post")
.WithMany("UserPosts")
.HasForeignKey("PostId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BeReadyBackend.Models.User", "User")
.WithMany("UserPosts")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.NoAction)
.IsRequired();
b.Navigation("Post");
b.Navigation("User");
});
modelBuilder.Entity("BeReadyBackend.Models.UserRandomChallenge", b =>
{
b.HasOne("BeReadyBackend.Models.RandomChallenge", "RandomChallenge")
.WithMany("UserRandomChallenges")
.HasForeignKey("RandomChallengeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BeReadyBackend.Models.User", "User")
.WithMany("UserRandomChallenges")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("RandomChallenge");
b.Navigation("User");
});
modelBuilder.Entity("BeReadyBackend.Models.Achievement", b =>
{
b.Navigation("UserAchievements");
});
modelBuilder.Entity("BeReadyBackend.Models.Designation", b =>
{
b.Navigation("Users");
});
modelBuilder.Entity("BeReadyBackend.Models.Group", b =>
{
b.Navigation("Messages");
b.Navigation("UserGroups");
});
modelBuilder.Entity("BeReadyBackend.Models.Post", b =>
{
b.Navigation("UserPosts");
});
modelBuilder.Entity("BeReadyBackend.Models.RandomChallenge", b =>
{
b.Navigation("Posts");
b.Navigation("UserRandomChallenges");
});
modelBuilder.Entity("BeReadyBackend.Models.User", b =>
{
b.Navigation("Messages");
b.Navigation("Posts");
b.Navigation("UserAchievements");
b.Navigation("UserFriends");
b.Navigation("UserGroups");
b.Navigation("UserPosts");
b.Navigation("UserRandomChallenges");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,50 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BeReadyBackend.Migrations
{
/// <inheritdoc />
public partial class AddedMissingRelations : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "RandomChallengeId",
table: "Posts",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateIndex(
name: "IX_Posts_RandomChallengeId",
table: "Posts",
column: "RandomChallengeId");
migrationBuilder.AddForeignKey(
name: "FK_Posts_RandomChallenges_RandomChallengeId",
table: "Posts",
column: "RandomChallengeId",
principalTable: "RandomChallenges",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Posts_RandomChallenges_RandomChallengeId",
table: "Posts");
migrationBuilder.DropIndex(
name: "IX_Posts_RandomChallengeId",
table: "Posts");
migrationBuilder.DropColumn(
name: "RandomChallengeId",
table: "Posts");
}
}
}
@@ -40,7 +40,7 @@ namespace BeReadyBackend.Migrations
b.HasKey("Id");
b.ToTable("Achievements", (string)null);
b.ToTable("Achievements");
});
modelBuilder.Entity("BeReadyBackend.Models.Designation", b =>
@@ -57,7 +57,7 @@ namespace BeReadyBackend.Migrations
b.HasKey("Id");
b.ToTable("Designations", (string)null);
b.ToTable("Designations");
});
modelBuilder.Entity("BeReadyBackend.Models.Group", b =>
@@ -77,7 +77,7 @@ namespace BeReadyBackend.Migrations
b.HasKey("Id");
b.ToTable("Groups", (string)null);
b.ToTable("Groups");
});
modelBuilder.Entity("BeReadyBackend.Models.Message", b =>
@@ -107,7 +107,7 @@ namespace BeReadyBackend.Migrations
b.HasIndex("UserId");
b.ToTable("Messages", (string)null);
b.ToTable("Messages");
});
modelBuilder.Entity("BeReadyBackend.Models.Post", b =>
@@ -127,14 +127,19 @@ namespace BeReadyBackend.Migrations
b.Property<int>("Likes")
.HasColumnType("int");
b.Property<int>("RandomChallengeId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("RandomChallengeId");
b.HasIndex("UserId");
b.ToTable("Posts", (string)null);
b.ToTable("Posts");
});
modelBuilder.Entity("BeReadyBackend.Models.RandomChallenge", b =>
@@ -161,7 +166,7 @@ namespace BeReadyBackend.Migrations
b.HasKey("Id");
b.ToTable("RandomChallenges", (string)null);
b.ToTable("RandomChallenges");
});
modelBuilder.Entity("BeReadyBackend.Models.User", b =>
@@ -217,7 +222,7 @@ namespace BeReadyBackend.Migrations
b.HasIndex("DesignationId");
b.ToTable("Users", (string)null);
b.ToTable("Users");
});
modelBuilder.Entity("BeReadyBackend.Models.UserAchievement", b =>
@@ -232,7 +237,7 @@ namespace BeReadyBackend.Migrations
b.HasIndex("AchievementId");
b.ToTable("UserAchievements", (string)null);
b.ToTable("UserAchievements");
});
modelBuilder.Entity("BeReadyBackend.Models.UserFriend", b =>
@@ -250,7 +255,7 @@ namespace BeReadyBackend.Migrations
b.HasIndex("FriendId");
b.ToTable("UserFriends", (string)null);
b.ToTable("UserFriends");
});
modelBuilder.Entity("BeReadyBackend.Models.UserGroup", b =>
@@ -269,7 +274,7 @@ namespace BeReadyBackend.Migrations
b.HasIndex("GroupId");
b.ToTable("UserGroups", (string)null);
b.ToTable("UserGroups");
});
modelBuilder.Entity("BeReadyBackend.Models.UserPost", b =>
@@ -284,7 +289,7 @@ namespace BeReadyBackend.Migrations
b.HasIndex("PostId");
b.ToTable("UserPosts", (string)null);
b.ToTable("UserPosts");
});
modelBuilder.Entity("BeReadyBackend.Models.UserRandomChallenge", b =>
@@ -302,7 +307,7 @@ namespace BeReadyBackend.Migrations
b.HasIndex("RandomChallengeId");
b.ToTable("UserRandomChallenges", (string)null);
b.ToTable("UserRandomChallenges");
});
modelBuilder.Entity("BeReadyBackend.Models.Message", b =>
@@ -326,12 +331,20 @@ namespace BeReadyBackend.Migrations
modelBuilder.Entity("BeReadyBackend.Models.Post", b =>
{
b.HasOne("BeReadyBackend.Models.RandomChallenge", "RandomChallenge")
.WithMany("Posts")
.HasForeignKey("RandomChallengeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BeReadyBackend.Models.User", "User")
.WithMany("Posts")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("RandomChallenge");
b.Navigation("User");
});
@@ -463,6 +476,8 @@ namespace BeReadyBackend.Migrations
modelBuilder.Entity("BeReadyBackend.Models.RandomChallenge", b =>
{
b.Navigation("Posts");
b.Navigation("UserRandomChallenges");
});
+3
View File
@@ -12,5 +12,8 @@ public class Post
public User? User { get; set; }
[Required] public int UserId { get; set; }
public RandomChallenge? RandomChallenge { get; set; }
[Required] public int RandomChallengeId { get; set; }
public List<UserPost>? UserPosts { get; set; }
}
+1
View File
@@ -12,4 +12,5 @@ public class RandomChallenge
public DateTime? GeneratedAt { get; set; }
public List<UserRandomChallenge>? UserRandomChallenges { get; set; }
public List<Post>? Posts { get; set; }
}
+30 -2
View File
@@ -1,9 +1,10 @@
using Amazon.S3;
using Amazon.S3.Model;
using AutoMapper;
using AutoMapper.EquivalencyExpression;
using BeReadyBackend;
using BeReadyBackend.Hubs;
using BeReadyBackend.MappingProfiles;
using BeReadyBackend.Models;
using FastEndpoints;
using FastEndpoints.Swagger;
using FastEndpoints.Security;
@@ -15,7 +16,7 @@ WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// On ajoute ici FastEndpoints, un framework REPR et Swagger aux services disponibles dans le projet
builder.Services
.AddAuthenticationJwtBearer(s => s.SigningKey = "ThisIsASuperSecretJwtKeyThatIsAtLeast32CharsLong")
.AddAuthenticationJwtBearer(s => s.SigningKey = "v9!Qx7#Lk2@pZ8$wR6!tN5%uF3&cD9^mH1*eY4")
.AddAuthorization()
.AddFastEndpoints()
.AddCors(options =>
@@ -52,6 +53,8 @@ builder.Services.AddSignalR();
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<UserService>();
builder.Services.AddScoped<AchievementService>();
builder.Services.AddScoped<StorageService>();
MapperConfiguration mappingConfig = new(mc =>
{
@@ -64,6 +67,31 @@ MapperConfiguration mappingConfig = new(mc =>
AutoMapper.IMapper mapper = mappingConfig.CreateMapper();
builder.Services.AddSingleton(mapper);
// RUSTFS
IConfigurationSection config = builder.Configuration.GetSection("RustFS");
AmazonS3Client s3Client = new(
config["AccessKey"],
config["SecretKey"],
new AmazonS3Config
{
ServiceURL = config["ServiceUrl"],
ForcePathStyle = true
}
);
ListBucketsResponse? buckets = await s3Client.ListBucketsAsync();
bool exist = buckets?.Buckets?.Any(x => x.BucketName == config["BucketName"]) == true;
if (!exist)
{
await s3Client.PutBucketAsync(new PutBucketRequest
{
BucketName = config["BucketName"]
});
}
builder.Services.AddSingleton<IAmazonS3>(s3Client);
WebApplication app = builder.Build();
app.UseAuthentication()
.UseAuthorization()
@@ -0,0 +1,146 @@
using BeReadyBackend.Models;
using BeReadyBackend.Repositories;
using BeReadyBackend.Specifications.Achievements;
using BeReadyBackend.Specifications.Friends;
using BeReadyBackend.Specifications.Posts;
using BeReadyBackend.Specifications.RandomChallenges;
using BeReadyBackend.Specifications.UserAchievements;
using BeReadyBackend.Specifications.Users;
namespace BeReadyBackend.Services;
public class AchievementService(
UsersRepository usersRepository,
AchievementsRepository achievementsRepository,
UserAchievementsRepository userAchievementsRepository,
UserPostsRepository userPostsRepository,
UserFriendsRepository userFriendsRepository,
UserRandomChallengesRepository userRandomChallengesRepository)
{
public async Task Unlock(int userId, int achievementId)
{
Achievement? achievement = await achievementsRepository.SingleOrDefaultAsync(new GetAchievementByIdSpec(achievementId));
if (achievement is null) return;
bool alreadyUnlocked = await userAchievementsRepository.CountAsync(new GetUserAchievementByIdSpec(userId, achievement.Id)) > 0;
if (alreadyUnlocked) return;
UserAchievement userAchievement = new()
{
UserId = userId,
AchievementId = achievement.Id
};
await userAchievementsRepository.AddAsync(userAchievement);
}
public async Task CheckAchievement(int userId)
{
User? user = await usersRepository.SingleOrDefaultAsync(new GetUserByIdSpec(userId));
if (user is null) return;
int challengesCounter = await userRandomChallengesRepository.CountAsync(new GetUserRandomChallengeByIdSpec(userId));
int likedCounter = await userPostsRepository.CountAsync(new GetPostsLikedByUserSpec(userId));
int friendsCounter = await userFriendsRepository.CountAsync(new GetFriendsByUserIdSpec(userId));
int friendsRequestsCounter = await userFriendsRepository.CountAsync(new GetFriendRequestSpec(userId));
int achievementsCounter = await userAchievementsRepository.CountAsync(new GetAchievementUnlockedSpec(userId));
switch (challengesCounter)
{
case 1:
await Unlock(userId, 1);
break;
case 10:
await Unlock(userId, 12);
break;
case 20:
await Unlock(userId, 3);
break;
case 50:
await Unlock(userId, 29);
break;
case 100:
await Unlock(userId, 32);
break;
}
switch (user.Series)
{
case 5:
await Unlock(userId, 2);
break;
case 7:
await Unlock(userId, 39);
break;
case 10:
await Unlock(userId, 4);
break;
case 30:
await Unlock(userId, 40);
break;
}
switch (likedCounter)
{
case 50:
await Unlock(userId, 37);
break;
case 100:
await Unlock(userId, 9);
break;
case 200:
await Unlock(userId, 38);
break;
}
switch (user.TotalLikes)
{
case 50:
await Unlock(userId, 13);
break;
case 500:
await Unlock(userId, 44);
break;
case 1000:
await Unlock(userId, 33);
break;
}
switch (friendsCounter)
{
case 5:
await Unlock(userId, 18);
break;
case 10:
await Unlock(userId, 19);
break;
case 20:
await Unlock(userId, 36);
break;
}
switch (friendsRequestsCounter)
{
case 10:
await Unlock(userId, 21);
break;
}
switch (achievementsCounter)
{
case 5:
await Unlock(userId, 22);
break;
case 20:
await Unlock(userId, 23);
break;
case 23:
await Unlock(userId, 46);
break;
}
if (DateTime.Now >= DateTime.Today.AddHours(20)) await Unlock(userId, 25);
}
}
+37
View File
@@ -0,0 +1,37 @@
using Amazon.S3;
using Amazon.S3.Model;
namespace BeReadyBackend.Services;
public class StorageService(IAmazonS3 amazonS3, IConfiguration config)
{
private readonly string _bucket = config["RustFs:BucketName"]!;
private readonly string _url = config["RustFs:ServiceUrl"]!;
public async Task<string> UploadFile(IFormFile file, string path, CancellationToken ct)
{
if (file.Length == 0) throw new Exception("Fichier vide");
string key = $"{path}/{Guid.NewGuid()}";
using MemoryStream memoryStream = new();
await file.CopyToAsync(memoryStream, ct);
memoryStream.Position = 0;
PutObjectRequest uploadRequest = new()
{
BucketName = _bucket,
ContentType = file.ContentType,
InputStream = memoryStream,
Key = key,
};
await amazonS3.PutObjectAsync(uploadRequest, ct);
return key;
}
public string? GetUrl(string key)
{
return string.IsNullOrEmpty(key) ? null : $"{_url}/{_bucket}/{key}";
}
}
@@ -0,0 +1,13 @@
using Ardalis.Specification;
using BeReadyBackend.Models;
namespace BeReadyBackend.Specifications.Achievements;
public class GetAchievementUnlockedSpec : Specification<UserAchievement>
{
public GetAchievementUnlockedSpec(int userId)
{
Query
.Where(x => x.UserId == userId);
}
}
@@ -1,13 +0,0 @@
using Ardalis.Specification;
using BeReadyBackend.Models;
namespace BeReadyBackend.Specifications.Achievements;
public class GetLockedAchievementsSpec : Specification<Achievement>
{
public GetLockedAchievementsSpec(int userId)
{
Query
.Where(x => x.UserAchievements != null && x.UserAchievements.All(y => y.UserId != userId));
}
}
@@ -8,6 +8,7 @@ public class GetPostByIdSpec : SingleResultSpecification<Post>
public GetPostByIdSpec(int postId)
{
Query
.Include(x => x.User)
.Where(x => x.Id == postId);
}
}
@@ -9,7 +9,9 @@ public class GetPostNotMeSpec : Specification<Post>
{
Query
.Include(x => x.User)
.Include(x => x.UserPosts!)
.ThenInclude(urc => urc.UserRandomChallenges)
.ThenInclude(r => r.RandomChallenge)
.Include(x => x.UserPosts)
.ThenInclude(up => up.User)
.Where(x => x.UserId != userId &&
x.CreationDate >= DateTime.Today && x.CreationDate < DateTime.Today.AddDays(1));
@@ -0,0 +1,13 @@
using Ardalis.Specification;
using BeReadyBackend.Models;
namespace BeReadyBackend.Specifications.Posts;
public class GetPostsLikedByUserSpec : Specification<UserPost>
{
public GetPostsLikedByUserSpec(int userId)
{
Query
.Where(x => x.UserId == userId);
}
}
@@ -0,0 +1,19 @@
using Ardalis.Specification;
using BeReadyBackend.Models;
namespace BeReadyBackend.Specifications.RandomChallenges;
public class GetLastRandomChallengeSpec : SingleResultSpecification<UserRandomChallenge>
{
public GetLastRandomChallengeSpec(int userId)
{
Query
.Include(x => x.RandomChallenge)
.Where(x => x.RandomChallenge != null
&& x.UserId == userId
&& x.Proof != null
&& x.RandomChallenge.IsAlreadyPast
&& x.RandomChallenge.GeneratedAt != null
&& DateOnly.FromDateTime(x.RandomChallenge.GeneratedAt.Value) == DateOnly.FromDateTime(DateTime.Now).AddDays(-1));
}
}
@@ -0,0 +1,14 @@
using Ardalis.Specification;
using BeReadyBackend.Models;
namespace BeReadyBackend.Specifications.RandomChallenges;
public class GetUserRandomChallengeByIdSpec : Specification<UserRandomChallenge>
{
public GetUserRandomChallengeByIdSpec(int userId)
{
Query
.Include(x => x.RandomChallenge)
.Where(x => x.UserId == userId && x.Proof != null);
}
}
@@ -1,17 +0,0 @@
using Ardalis.Specification;
using BeReadyBackend.Models;
namespace BeReadyBackend.Specifications.Users;
public class GetProofOrChallengeByUserIdSpec : SingleResultSpecification<User>
{
public GetProofOrChallengeByUserIdSpec(int userId)
{
Query
.Include(x => x.UserRandomChallenges!)
.ThenInclude(x => x.RandomChallenge)
.Include(x => x.UserGroups!)
.ThenInclude(x => x.Group)
.Where(x => x.Id == userId);
}
}
@@ -0,0 +1,13 @@
using Ardalis.Specification;
using BeReadyBackend.Models;
namespace BeReadyBackend.Specifications.Users;
public class GetUserProofByUserIdSpec : Specification<UserRandomChallenge>
{
public GetUserProofByUserIdSpec(int userId)
{
Query
.Where(x => x.UserId == userId && x.Proof != null);
}
}
@@ -16,10 +16,6 @@ public class CreateMessageDtoValidator : Validator<CreateMessageDto>
.MinimumLength(2)
.WithMessage("Libelle must exceed 2 characters");
RuleFor(x => x.SendDate)
.NotEmpty()
.WithMessage("SendDate is required");
RuleFor(x => x.GroupId)
.NotEmpty()
.WithMessage("GroupId is required")
@@ -43,9 +43,8 @@ public class CreateUserDtoValidator : Validator<CreateUserDto>
RuleFor(x => x.Password)
.NotEmpty()
.WithMessage("Password is required")
.MaximumLength(60)
.WithMessage("Password cannot exceed 60 characters")
.MinimumLength(12)
.WithMessage("Password must exceed 12 characters");
.WithMessage("Password must exceed 12 characters")
.Matches(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*?[#?_!@$%^&*-])");
}
}
@@ -4,5 +4,11 @@
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"RustFS": {
"ServiceUrl": "https://stockage.sanchezvende.fr",
"AccessKey": "Admin_Beready_Exam2026",
"SecretKey": "4dm1n-Pr0j_2026-B3r34d7",
"BucketName": "images"
}
}