diff --git a/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada.yml b/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada.yml index 4f10cb4bd..46a179f44 100644 --- a/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada.yml +++ b/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada.yml @@ -590,7 +590,7 @@ modules: auth: true - upstream: /search - method: POST + method: GET use: downstream downstream: comments-service/comments/search @@ -615,11 +615,21 @@ modules: downstream: reactions-service/reactions auth: true + - upstream: /{reactionId} + method: PUT + use: downstream + downstream: reactions-service/reactions/{reactionId} + auth: true + bind: + - reactionId: {reactionId} + - upstream: /{reactionId} method: DELETE use: downstream downstream: reactions-service/reactions/{reactionId} auth: true + bind: + - reactionId: {reactionId} - upstream: / method: GET @@ -771,6 +781,14 @@ modules: downstream: posts-service/posts auth: true + - upstream: /users/{userId}/feed + method: GET + use: downstream + downstream: posts-service/posts/users/{userId}/feed + bind: + - userId:{userId} + auth: true + - upstream: /search method: GET use: downstream @@ -798,6 +816,8 @@ modules: method: GET use: downstream downstream: posts-service/posts/{postId} + bind: + - postId:{postId} - upstream: / method: GET diff --git a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Api/MiniSpace.Services.Comments.Api.sln b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Api/MiniSpace.Services.Comments.Api.sln new file mode 100644 index 000000000..62a580626 --- /dev/null +++ b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Api/MiniSpace.Services.Comments.Api.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniSpace.Services.Comments.Api", "MiniSpace.Services.Comments.Api.csproj", "{A198F9BC-F912-464E-BBCB-851CFBDD320F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A198F9BC-F912-464E-BBCB-851CFBDD320F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A198F9BC-F912-464E-BBCB-851CFBDD320F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A198F9BC-F912-464E-BBCB-851CFBDD320F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A198F9BC-F912-464E-BBCB-851CFBDD320F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {331542E3-F8A0-4711-AC84-18DD6B0D3896} + EndGlobalSection +EndGlobal diff --git a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Api/Program.cs b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Api/Program.cs index f50e68cd7..60fe637b4 100644 --- a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Api/Program.cs +++ b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Api/Program.cs @@ -17,6 +17,7 @@ using MiniSpace.Services.Comments.Application.Queries; using MiniSpace.Services.Comments.Application.Services; using MiniSpace.Services.Comments.Infrastructure; +using MiniSpace.Services.Comments.Core.Wrappers; namespace MiniSpace.Services.Identity.Api { @@ -34,14 +35,10 @@ public static async Task Main(string[] args) .Configure(app => app .UseInfrastructure() .UseEndpoints(endpoints => endpoints - .Get("", ctx => ctx.Response.WriteAsync(ctx.RequestServices.GetService().Name)) - .Post("comments/search", async (cmd, ctx) => - { - var pagedResult = await ctx.RequestServices.GetService().BrowseCommentsAsync(cmd); - await ctx.Response.WriteJsonAsync(pagedResult); - })) + .Get("", ctx => ctx.Response.WriteAsync(ctx.RequestServices.GetService().Name))) .UseDispatcherEndpoints(endpoints => endpoints .Get("comments/{commentID}") + .Get>("comments/search") .Post("comments") .Put("comments/{commentID}") .Delete("comments/{commentID}") diff --git a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Commands/AddLike.cs b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Commands/AddLike.cs index 440c96dc4..438a62314 100644 --- a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Commands/AddLike.cs +++ b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Commands/AddLike.cs @@ -5,11 +5,15 @@ namespace MiniSpace.Services.Comments.Application.Commands { public class AddLike : ICommand { - public Guid CommentId { get; set; } + public Guid CommentId { get; } + public Guid UserId { get; } + public string CommentContext { get; } - public AddLike(Guid commentId) + public AddLike(Guid commentId, Guid userId, string commentContext) { CommentId = commentId; + UserId = userId; + CommentContext = commentContext; } } } diff --git a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Commands/Handlers/AddLikeHandler.cs b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Commands/Handlers/AddLikeHandler.cs index abd04f183..75ceb747f 100644 --- a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Commands/Handlers/AddLikeHandler.cs +++ b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Commands/Handlers/AddLikeHandler.cs @@ -1,48 +1,111 @@ using System; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Convey.CQRS.Commands; using MiniSpace.Services.Comments.Application.Events; using MiniSpace.Services.Comments.Application.Exceptions; -using MiniSpace.Services.Comments.Application.Services; +using MiniSpace.Services.Comments.Application.Services.Clients; using MiniSpace.Services.Comments.Core.Entities; using MiniSpace.Services.Comments.Core.Repositories; +using MiniSpace.Services.Comments.Core.Exceptions; +using MiniSpace.Services.Comments.Application.Services; +using System.Text.Json; namespace MiniSpace.Services.Comments.Application.Commands.Handlers { public class AddLikeHandler : ICommandHandler { - private readonly ICommentRepository _commentRepository; - private readonly IAppContext _appContext; + private readonly IOrganizationEventsCommentRepository _organizationEventsCommentRepository; + private readonly IOrganizationPostsCommentRepository _organizationPostsCommentRepository; + private readonly IUserEventsCommentRepository _userEventsCommentRepository; + private readonly IUserPostsCommentRepository _userPostsCommentRepository; private readonly IMessageBroker _messageBroker; + private readonly IAppContext _appContext; + private readonly IStudentsServiceClient _userServiceClient; - public AddLikeHandler(ICommentRepository commentRepository, IAppContext appContext, - IMessageBroker messageBroker) + public AddLikeHandler( + IOrganizationEventsCommentRepository organizationEventsCommentRepository, + IOrganizationPostsCommentRepository organizationPostsCommentRepository, + IUserEventsCommentRepository userEventsCommentRepository, + IUserPostsCommentRepository userPostsCommentRepository, + IMessageBroker messageBroker, + IAppContext appContext, + IStudentsServiceClient userServiceClient) { - _commentRepository = commentRepository; - _appContext = appContext; + _organizationEventsCommentRepository = organizationEventsCommentRepository; + _organizationPostsCommentRepository = organizationPostsCommentRepository; + _userEventsCommentRepository = userEventsCommentRepository; + _userPostsCommentRepository = userPostsCommentRepository; _messageBroker = messageBroker; + _appContext = appContext; + _userServiceClient = userServiceClient; } public async Task HandleAsync(AddLike command, CancellationToken cancellationToken = default) { - var comment = await _commentRepository.GetAsync(command.CommentId); - if (comment is null) + var commandJson = JsonSerializer.Serialize(command, new JsonSerializerOptions { WriteIndented = true }); + Console.WriteLine($"Received AddLike command: {commandJson}"); + var identity = _appContext.Identity; + + if (!identity.IsAuthenticated || identity.Id != command.UserId) { - throw new CommentNotFoundException(command.CommentId); + throw new UnauthorizedCommentAccessException(command.CommentId, identity.Id); } - var identity = _appContext.Identity; - if (!identity.IsAuthenticated) + var user = await _userServiceClient.GetAsync(command.UserId); + if (user == null) { - throw new UnauthorizedCommentAccessException(command.CommentId, identity.Id); + throw new UserNotFoundException(command.UserId); + } + + if (!Enum.TryParse(command.CommentContext, true, out var commentContext)) + { + throw new InvalidCommentContextEnumException(command.CommentContext); } - comment.Like(identity.Id); - await _commentRepository.UpdateAsync(comment); + Comment comment = await GetCommentAsync(command.CommentId, commentContext); + if (comment == null) + { + throw new CommentNotFoundException(command.CommentId); + } + + comment.Like(command.UserId); + await UpdateCommentAsync(comment, commentContext); await _messageBroker.PublishAsync(new CommentUpdated(command.CommentId)); } + + private async Task GetCommentAsync(Guid commentId, CommentContext context) + { + return context switch + { + CommentContext.OrganizationEvent => await _organizationEventsCommentRepository.GetAsync(commentId), + CommentContext.OrganizationPost => await _organizationPostsCommentRepository.GetAsync(commentId), + CommentContext.UserEvent => await _userEventsCommentRepository.GetAsync(commentId), + CommentContext.UserPost => await _userPostsCommentRepository.GetAsync(commentId), + _ => null + }; + } + + private async Task UpdateCommentAsync(Comment comment, CommentContext context) + { + switch (context) + { + case CommentContext.OrganizationEvent: + await _organizationEventsCommentRepository.UpdateAsync(comment); + break; + case CommentContext.OrganizationPost: + await _organizationPostsCommentRepository.UpdateAsync(comment); + break; + case CommentContext.UserEvent: + await _userEventsCommentRepository.UpdateAsync(comment); + break; + case CommentContext.UserPost: + await _userPostsCommentRepository.UpdateAsync(comment); + break; + default: + throw new InvalidCommentContextEnumException(context.ToString()); + } + } } } diff --git a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Commands/Handlers/CreateCommentHandler.cs b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Commands/Handlers/CreateCommentHandler.cs index 4481b83a8..bacf66423 100644 --- a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Commands/Handlers/CreateCommentHandler.cs +++ b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Commands/Handlers/CreateCommentHandler.cs @@ -48,6 +48,16 @@ public CreateCommentHandler( public async Task HandleAsync(CreateComment command, CancellationToken cancellationToken = default) { var identity = _appContext.Identity; + + if (identity.IsAuthenticated) + { + Console.WriteLine($"User is authenticated: {identity.Id}"); + Console.WriteLine($"User command id is: {command.UserId}"); + } + else + { + Console.WriteLine("User is not authenticated."); + } if (identity.IsAuthenticated && identity.Id != command.UserId) { throw new UnauthorizedCommentAccessException(command.ContextId, identity.Id); diff --git a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Commands/SearchComments.cs b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Commands/SearchComments.cs deleted file mode 100644 index 34e9bf540..000000000 --- a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Commands/SearchComments.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using Convey.CQRS.Commands; -using MiniSpace.Services.Comments.Application.Dto; - -namespace MiniSpace.Services.Comments.Application.Commands -{ - public class SearchComments : ICommand - { - public Guid ContextId { get; set; } - public string CommentContext { get; set; } - public Guid ParentId { get; set; } - public PageableDto Pageable { get; set; } - } -} \ No newline at end of file diff --git a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Queries/SearchComments.cs b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Queries/SearchComments.cs new file mode 100644 index 000000000..6f49f714c --- /dev/null +++ b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Queries/SearchComments.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Convey.CQRS.Queries; +using MiniSpace.Services.Comments.Application.Dto; +using MiniSpace.Services.Comments.Core.Wrappers; + +namespace MiniSpace.Services.Comments.Application.Queries +{ + public class SearchComments : IQuery> + { + public Guid ContextId { get; set; } + public string CommentContext { get; set; } + public Guid? ParentId { get; set; } + public int Page { get; set; } + public int Size { get; set; } + public string SortBy { get; set; } // Changed to string to handle the incoming JSON properly + public string Direction { get; set; } + + // This property will split the SortBy string into an array + public IEnumerable SortByArray => + string.IsNullOrEmpty(SortBy) ? new List { "CreatedAt" } : SortBy.Split(','); + } +} diff --git a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Services/ICommentService.cs b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Services/ICommentService.cs deleted file mode 100644 index 0124f30e6..000000000 --- a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Application/Services/ICommentService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using MiniSpace.Services.Comments.Application.Commands; -using MiniSpace.Services.Comments.Application.Dto; -using MiniSpace.Services.Comments.Application.Wrappers; -using MiniSpace.Services.Comments.Core.Wrappers; - -namespace MiniSpace.Services.Comments.Application.Services -{ - public interface ICommentService - { - Task> BrowseCommentsAsync(SearchComments command); - } -} \ No newline at end of file diff --git a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Core/Entities/Comment.cs b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Core/Entities/Comment.cs index 328c55b9f..92384badd 100644 --- a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Core/Entities/Comment.cs +++ b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Core/Entities/Comment.cs @@ -8,8 +8,8 @@ namespace MiniSpace.Services.Comments.Core.Entities { public class Comment : AggregateRoot { - private ISet _likes = new HashSet(); - private ISet _replies = new HashSet(); + private readonly ISet _likes = new HashSet(); + private readonly ISet _replies = new HashSet(); public Guid ContextId { get; private set; } public CommentContext CommentContext { get; private set; } @@ -22,17 +22,10 @@ public class Comment : AggregateRoot public int RepliesCount => _replies.Count; public bool IsDeleted { get; private set; } - public IEnumerable Likes - { - get => _likes; - private set => _likes = new HashSet(value); - } + // Expose the _likes set as a read-only IEnumerable + public IEnumerable Likes => _likes; - public IEnumerable Replies - { - get => _replies; - private set => _replies = new HashSet(value); - } + public IEnumerable Replies => _replies; public Comment(Guid id, Guid contextId, CommentContext commentContext, Guid userId, IEnumerable likes, Guid parentId, string textContent, DateTime createdAt, DateTime lastUpdatedAt, @@ -42,19 +35,19 @@ public Comment(Guid id, Guid contextId, CommentContext commentContext, Guid user ContextId = contextId; CommentContext = commentContext; UserId = userId; - Likes = likes; + _likes = new HashSet(likes); ParentId = parentId; TextContent = textContent; CreatedAt = createdAt; LastUpdatedAt = lastUpdatedAt; LastReplyAt = lastReplyAt; - Replies = replies; + _replies = new HashSet(replies); IsDeleted = isDeleted; } public void Like(Guid userId) { - if (Likes.Any(id => id == userId)) + if (_likes.Contains(userId)) { throw new UserAlreadyLikesCommentException(userId); } @@ -65,7 +58,7 @@ public void Like(Guid userId) public void UnLike(Guid userId) { - if (Likes.All(id => id != userId)) + if (!_likes.Contains(userId)) { throw new UserNotLikeCommentException(userId, Id); } diff --git a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Extensions.cs b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Extensions.cs index 1d997e6d8..38ac1c4c9 100644 --- a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Extensions.cs +++ b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Extensions.cs @@ -39,6 +39,8 @@ using MiniSpace.Services.Comments.Infrastructure.Mongo.Repositories; using MiniSpace.Services.Comments.Infrastructure.Services; using System.Diagnostics.CodeAnalysis; +using MiniSpace.Services.Comments.Application.Services.Clients; +using MiniSpace.Services.Comments.Infrastructure.Services.Clients; namespace MiniSpace.Services.Comments.Infrastructure { @@ -53,9 +55,10 @@ public static IConveyBuilder AddInfrastructure(this IConveyBuilder builder) builder.Services.AddTransient(); builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(ctx => ctx.GetRequiredService().Create()); diff --git a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Documents/Extensions.cs b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Documents/Extensions.cs index 7f87c8a54..0c1c1e1f7 100644 --- a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Documents/Extensions.cs +++ b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Documents/Extensions.cs @@ -79,6 +79,25 @@ public static ReplyDocument ToDocument(this Reply entity) TextContent = entity.TextContent, CreatedAt = entity.CreatedAt }; + + public static CommentDto AsDto(this Comment comment) + { + return new CommentDto + { + Id = comment.Id, + ContextId = comment.ContextId, + CommentContext = comment.CommentContext.ToString().ToLowerInvariant(), + UserId = comment.UserId, + Likes = comment.Likes, + ParentId = comment.ParentId, + TextContent = comment.TextContent, + CreatedAt = comment.CreatedAt, + LastUpdatedAt = comment.LastUpdatedAt, + LastReplyAt = comment.LastReplyAt, + RepliesCount = comment.Replies.Count(), + IsDeleted = comment.IsDeleted + }; + } public static OrganizationEventCommentDocument ToOrganizationEventDocument(this IEnumerable comments, Guid organizationEventId, Guid organizationId) => new OrganizationEventCommentDocument diff --git a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Queries/Handlers/SearchCommentsHandler.cs b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Queries/Handlers/SearchCommentsHandler.cs new file mode 100644 index 000000000..388ff24a3 --- /dev/null +++ b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Queries/Handlers/SearchCommentsHandler.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Convey.CQRS.Queries; +using MiniSpace.Services.Comments.Application.Dto; +using MiniSpace.Services.Comments.Application.Queries; +using MiniSpace.Services.Comments.Core.Entities; +using MiniSpace.Services.Comments.Core.Repositories; +using MiniSpace.Services.Comments.Core.Wrappers; +using MiniSpace.Services.Comments.Infrastructure.Mongo.Documents; + +namespace MiniSpace.Services.Comments.Infrastructure.Mongo.Queries.Handlers +{ + public class SearchCommentsHandler : IQueryHandler> + { + private readonly IOrganizationEventsCommentRepository _organizationEventsRepository; + private readonly IOrganizationPostsCommentRepository _organizationPostsRepository; + private readonly IUserEventsCommentRepository _userEventsRepository; + private readonly IUserPostsCommentRepository _userPostsRepository; + + public SearchCommentsHandler( + IOrganizationEventsCommentRepository organizationEventsRepository, + IOrganizationPostsCommentRepository organizationPostsRepository, + IUserEventsCommentRepository userEventsRepository, + IUserPostsCommentRepository userPostsRepository) + { + _organizationEventsRepository = organizationEventsRepository; + _organizationPostsRepository = organizationPostsRepository; + _userEventsRepository = userEventsRepository; + _userPostsRepository = userPostsRepository; + } + + public async Task> HandleAsync(SearchComments query, CancellationToken cancellationToken) +{ + try + { + if (query == null) + { + throw new ArgumentNullException(nameof(query)); + } + + // Initialize the list to hold all comments from all repositories + var allComments = new List(); + + // Use the new SortByArray property + var sortByList = query.SortByArray.ToList(); + + CommentContext contextEnum; + if (!Enum.TryParse(query.CommentContext, true, out contextEnum)) + { + throw new ArgumentException($"Invalid CommentContext value: {query.CommentContext}"); + } + + var browseRequest = new BrowseCommentsRequest( + pageNumber: query.Page, + pageSize: query.Size, + contextId: query.ContextId, + context: contextEnum, + parentId: query.ParentId ?? Guid.Empty, + sortBy: sortByList, + sortDirection: query.Direction + ); + + // Logging the browseRequest for debug purposes + Console.WriteLine($"Searching with ContextId: {query.ContextId}, CommentContext: {query.CommentContext}"); + + // Search in OrganizationEventsCommentRepository + Console.WriteLine("Searching in OrganizationEventsCommentRepository..."); + var orgEventsComments = await _organizationEventsRepository.BrowseCommentsAsync(browseRequest); + if (orgEventsComments?.Items != null && orgEventsComments.Items.Any()) + { + Console.WriteLine($"Found {orgEventsComments.Items.Count()} comments in OrganizationEventsCommentRepository."); + allComments.AddRange(orgEventsComments.Items); + } + else + { + Console.WriteLine("No comments found in OrganizationEventsCommentRepository."); + } + + // Search in OrganizationPostsCommentRepository + Console.WriteLine("Searching in OrganizationPostsCommentRepository..."); + var orgPostsComments = await _organizationPostsRepository.BrowseCommentsAsync(browseRequest); + if (orgPostsComments?.Items != null && orgPostsComments.Items.Any()) + { + Console.WriteLine($"Found {orgPostsComments.Items.Count()} comments in OrganizationPostsCommentRepository."); + allComments.AddRange(orgPostsComments.Items); + } + else + { + Console.WriteLine("No comments found in OrganizationPostsCommentRepository."); + } + + // Search in UserEventsCommentRepository + Console.WriteLine("Searching in UserEventsCommentRepository..."); + var userEventsComments = await _userEventsRepository.BrowseCommentsAsync(browseRequest); + if (userEventsComments?.Items != null && userEventsComments.Items.Any()) + { + Console.WriteLine($"Found {userEventsComments.Items.Count()} comments in UserEventsCommentRepository."); + allComments.AddRange(userEventsComments.Items); + } + else + { + Console.WriteLine("No comments found in UserEventsCommentRepository."); + } + + // Search in UserPostsCommentRepository + Console.WriteLine("Searching in UserPostsCommentRepository..."); + var userPostsComments = await _userPostsRepository.BrowseCommentsAsync(browseRequest); + if (userPostsComments?.Items != null && userPostsComments.Items.Any()) + { + Console.WriteLine($"Found {userPostsComments.Items.Count()} comments in UserPostsCommentRepository."); + allComments.AddRange(userPostsComments.Items); + } + else + { + Console.WriteLine("No comments found in UserPostsCommentRepository."); + } + + if (!allComments.Any()) + { + Console.WriteLine("No comments found in any repository."); + } + + // Sort and paginate the aggregated comments + var sortedComments = sortByList.Contains("CreatedAt") + ? allComments.OrderByDescending(c => c.CreatedAt).ToList() + : allComments.OrderBy(c => c.Id).ToList(); // default sorting + + var pagedComments = sortedComments + .Skip((query.Page - 1) * query.Size) + .Take(query.Size) + .ToList(); + + var totalItems = allComments.Count; + var commentDtos = pagedComments.Select(c => c.AsDto()).ToList(); + + var response = new PagedResponse(commentDtos, query.Page, query.Size, totalItems); + + // Log the response to the console + Console.WriteLine($"Response: {System.Text.Json.JsonSerializer.Serialize(response)}"); + + return response; + } + catch (Exception ex) + { + // Log exception with more context + Console.Error.WriteLine($"Error in SearchCommentsHandler: {ex}"); + throw; + } +} + + } +} diff --git a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Repositories/OrganizationEventsCommentRepository.cs b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Repositories/OrganizationEventsCommentRepository.cs index 1d595f634..e662efd4e 100644 --- a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Repositories/OrganizationEventsCommentRepository.cs +++ b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Repositories/OrganizationEventsCommentRepository.cs @@ -30,11 +30,24 @@ public async Task GetAsync(Guid id) public async Task AddAsync(Comment comment) { var filter = Builders.Filter.Eq(d => d.OrganizationEventId, comment.ContextId); - var update = Builders.Update.Push(d => d.Comments, comment.ToDocument()); - await _repository.Collection.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = true }); + var update = Builders.Update.Combine( + Builders.Update.Push(d => d.Comments, comment.ToDocument()), + Builders.Update.SetOnInsert(d => d.OrganizationEventId, comment.ContextId), + Builders.Update.SetOnInsert(d => d.Id, Guid.NewGuid()) + ); + + var options = new UpdateOptions { IsUpsert = true }; + var result = await _repository.Collection.UpdateOneAsync(filter, update, options); + + if (!result.IsAcknowledged || result.ModifiedCount == 0) + { + // Handle the case where the update or insert did not succeed + Console.Error.WriteLine("Failed to add or update the comment."); + } } + public async Task UpdateAsync(Comment comment) { var filter = Builders.Filter.And( @@ -45,10 +58,12 @@ public async Task UpdateAsync(Comment comment) var update = Builders.Update .Set($"{nameof(OrganizationEventCommentDocument.Comments)}.$.{nameof(CommentDocument.TextContent)}", comment.TextContent) .Set($"{nameof(OrganizationEventCommentDocument.Comments)}.$.{nameof(CommentDocument.LastUpdatedAt)}", comment.LastUpdatedAt) - .Set($"{nameof(OrganizationEventCommentDocument.Comments)}.$.{nameof(CommentDocument.IsDeleted)}", comment.IsDeleted); + .Set($"{nameof(OrganizationEventCommentDocument.Comments)}.$.{nameof(CommentDocument.IsDeleted)}", comment.IsDeleted) + .Set($"{nameof(OrganizationEventCommentDocument.Comments)}.$.{nameof(CommentDocument.Likes)}", comment.Likes); await _repository.Collection.UpdateOneAsync(filter, update); } + public async Task DeleteAsync(Guid id) { diff --git a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Repositories/OrganizationPostsCommentRepository.cs b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Repositories/OrganizationPostsCommentRepository.cs index 13988ceed..268ff6e1d 100644 --- a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Repositories/OrganizationPostsCommentRepository.cs +++ b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Repositories/OrganizationPostsCommentRepository.cs @@ -30,9 +30,20 @@ public async Task GetAsync(Guid id) public async Task AddAsync(Comment comment) { var filter = Builders.Filter.Eq(d => d.OrganizationPostId, comment.ContextId); - var update = Builders.Update.Push(d => d.Comments, comment.ToDocument()); - await _repository.Collection.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = true }); + var update = Builders.Update.Combine( + Builders.Update.Push(d => d.Comments, comment.ToDocument()), + Builders.Update.SetOnInsert(d => d.OrganizationPostId, comment.ContextId), + Builders.Update.SetOnInsert(d => d.Id, Guid.NewGuid()) + ); + + var options = new UpdateOptions { IsUpsert = true }; + var result = await _repository.Collection.UpdateOneAsync(filter, update, options); + + if (!result.IsAcknowledged || result.ModifiedCount == 0) + { + Console.Error.WriteLine("Failed to add or update the comment."); + } } public async Task UpdateAsync(Comment comment) @@ -45,7 +56,8 @@ public async Task UpdateAsync(Comment comment) var update = Builders.Update .Set($"{nameof(OrganizationPostCommentDocument.Comments)}.$.{nameof(CommentDocument.TextContent)}", comment.TextContent) .Set($"{nameof(OrganizationPostCommentDocument.Comments)}.$.{nameof(CommentDocument.LastUpdatedAt)}", comment.LastUpdatedAt) - .Set($"{nameof(OrganizationPostCommentDocument.Comments)}.$.{nameof(CommentDocument.IsDeleted)}", comment.IsDeleted); + .Set($"{nameof(OrganizationPostCommentDocument.Comments)}.$.{nameof(CommentDocument.IsDeleted)}", comment.IsDeleted) + .Set($"{nameof(OrganizationPostCommentDocument.Comments)}.$.{nameof(CommentDocument.Likes)}", comment.Likes); await _repository.Collection.UpdateOneAsync(filter, update); } @@ -67,17 +79,17 @@ public async Task> GetByPostIdAsync(Guid postId) public async Task> BrowseCommentsAsync(BrowseCommentsRequest request) { var filterDefinition = Builders.Filter.Eq(d => d.OrganizationPostId, request.ContextId); - var sortDefinition = OrganizationPostCommentExtensions.ToSortDefinition(request.SortBy, request.SortDirection); - var pagedEvents = await _repository.Collection.AggregateByPage( - filterDefinition, - sortDefinition, - request.PageNumber, - request.PageSize - ); + var document = await _repository.Collection.Find(filterDefinition).FirstOrDefaultAsync(); + + if (document == null) + { + return new PagedResponse(Enumerable.Empty(), request.PageNumber, request.PageSize, 0); + } - var comments = pagedEvents.data.SelectMany(d => d.Comments.Select(c => c.AsEntity())); - return new PagedResponse(comments, request.PageNumber, request.PageSize, pagedEvents.totalElements); + var comments = document.Comments.Select(c => c.AsEntity()).ToList(); + + return new PagedResponse(comments, request.PageNumber, request.PageSize, comments.Count); } } } diff --git a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Repositories/UserEventsCommentRepository.cs b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Repositories/UserEventsCommentRepository.cs index c82166a86..53591739a 100644 --- a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Repositories/UserEventsCommentRepository.cs +++ b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Repositories/UserEventsCommentRepository.cs @@ -30,9 +30,21 @@ public async Task GetAsync(Guid id) public async Task AddAsync(Comment comment) { var filter = Builders.Filter.Eq(d => d.UserEventId, comment.ContextId); - var update = Builders.Update.Push(d => d.Comments, comment.ToDocument()); - await _repository.Collection.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = true }); + var update = Builders.Update.Combine( + Builders.Update.Push(d => d.Comments, comment.ToDocument()), + Builders.Update.SetOnInsert(d => d.UserEventId, comment.ContextId), + Builders.Update.SetOnInsert(d => d.Id, Guid.NewGuid()) + ); + + var options = new UpdateOptions { IsUpsert = true }; + var result = await _repository.Collection.UpdateOneAsync(filter, update, options); + + if (!result.IsAcknowledged || result.ModifiedCount == 0) + { + // Handle the case whre the update or insert did not succeed + Console.Error.WriteLine("Failed to add or update the comment."); + } } public async Task UpdateAsync(Comment comment) @@ -45,7 +57,8 @@ public async Task UpdateAsync(Comment comment) var update = Builders.Update .Set($"{nameof(UserEventCommentDocument.Comments)}.$.{nameof(CommentDocument.TextContent)}", comment.TextContent) .Set($"{nameof(UserEventCommentDocument.Comments)}.$.{nameof(CommentDocument.LastUpdatedAt)}", comment.LastUpdatedAt) - .Set($"{nameof(UserEventCommentDocument.Comments)}.$.{nameof(CommentDocument.IsDeleted)}", comment.IsDeleted); + .Set($"{nameof(UserEventCommentDocument.Comments)}.$.{nameof(CommentDocument.IsDeleted)}", comment.IsDeleted) + .Set($"{nameof(UserEventCommentDocument.Comments)}.$.{nameof(CommentDocument.Likes)}", comment.Likes); await _repository.Collection.UpdateOneAsync(filter, update); } diff --git a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Repositories/UserPostsCommentRepository.cs b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Repositories/UserPostsCommentRepository.cs index 7084b0335..3edf48cba 100644 --- a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Repositories/UserPostsCommentRepository.cs +++ b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Mongo/Repositories/UserPostsCommentRepository.cs @@ -30,11 +30,22 @@ public async Task GetAsync(Guid id) public async Task AddAsync(Comment comment) { var filter = Builders.Filter.Eq(d => d.UserPostId, comment.ContextId); - var update = Builders.Update.Push(d => d.Comments, comment.ToDocument()); - await _repository.Collection.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = true }); + var update = Builders.Update.Combine( + Builders.Update.Push(d => d.Comments, comment.ToDocument()), + Builders.Update.SetOnInsert(d => d.UserPostId, comment.ContextId), + Builders.Update.SetOnInsert(d => d.Id, Guid.NewGuid()) + ); + + var options = new UpdateOptions { IsUpsert = true }; + var result = await _repository.Collection.UpdateOneAsync(filter, update, options); + + if (!result.IsAcknowledged || result.ModifiedCount == 0) + { + } } + public async Task UpdateAsync(Comment comment) { var filter = Builders.Filter.And( @@ -45,7 +56,8 @@ public async Task UpdateAsync(Comment comment) var update = Builders.Update .Set($"{nameof(UserPostCommentDocument.Comments)}.$.{nameof(CommentDocument.TextContent)}", comment.TextContent) .Set($"{nameof(UserPostCommentDocument.Comments)}.$.{nameof(CommentDocument.LastUpdatedAt)}", comment.LastUpdatedAt) - .Set($"{nameof(UserPostCommentDocument.Comments)}.$.{nameof(CommentDocument.IsDeleted)}", comment.IsDeleted); + .Set($"{nameof(UserPostCommentDocument.Comments)}.$.{nameof(CommentDocument.IsDeleted)}", comment.IsDeleted) + .Set($"{nameof(UserPostCommentDocument.Comments)}.$.{nameof(CommentDocument.Likes)}", comment.Likes); await _repository.Collection.UpdateOneAsync(filter, update); } diff --git a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Services/Clients/StudentsServiceClient.cs b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Services/Clients/StudentsServiceClient.cs new file mode 100644 index 000000000..81e3883d4 --- /dev/null +++ b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Services/Clients/StudentsServiceClient.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; +using Convey.HTTP; +using MiniSpace.Services.Comments.Application.Dto; +using MiniSpace.Services.Comments.Application.Services.Clients; + +namespace MiniSpace.Services.Comments.Infrastructure.Services.Clients +{ + [ExcludeFromCodeCoverage] + public class StudentsServiceClient : IStudentsServiceClient + { + private readonly IHttpClient _httpClient; + private readonly string _url; + + public StudentsServiceClient(IHttpClient httpClient, HttpClientOptions options) + { + _httpClient = httpClient; + _url = options.Services["students"]; + } + + public Task GetAsync(Guid id) + => _httpClient.GetAsync($"{_url}/students/{id}"); + + } +} \ No newline at end of file diff --git a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Services/CommentService.cs b/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Services/CommentService.cs deleted file mode 100644 index 557809844..000000000 --- a/MiniSpace.Services.Comments/src/MiniSpace.Services.Comments.Infrastructure/Services/CommentService.cs +++ /dev/null @@ -1,58 +0,0 @@ -using MiniSpace.Services.Comments.Application; -using MiniSpace.Services.Comments.Application.Commands; -using MiniSpace.Services.Comments.Application.Dto; -using MiniSpace.Services.Comments.Application.Exceptions; -using MiniSpace.Services.Comments.Application.Services; -using MiniSpace.Services.Comments.Core.Wrappers; -using MiniSpace.Services.Comments.Core.Entities; -using MiniSpace.Services.Comments.Core.Repositories; -using System.Linq; -using System.Threading.Tasks; -using System.Diagnostics.CodeAnalysis; - -namespace MiniSpace.Services.Comments.Infrastructure.Services -{ - [ExcludeFromCodeCoverage] - public class CommentService : ICommentService - { - private readonly ICommentRepository _commentRepository; - - public CommentService(ICommentRepository commentRepository) - { - _commentRepository = commentRepository; - } - - public async Task> BrowseCommentsAsync(SearchComments command) - { - if (!Enum.TryParse(command.CommentContext, true, out var context)) - { - throw new InvalidCommentContextException(command.CommentContext); - } - - var pageNumber = command.Pageable.Page < 1 ? 1 : command.Pageable.Page; - var pageSize = command.Pageable.Size > 10 ? 10 : command.Pageable.Size; - - var request = new BrowseCommentsRequest( - pageNumber, - pageSize, - command.ContextId, - context, - command.ParentId, - command.Pageable.Sort.SortBy, - command.Pageable.Sort.Direction - ); - - var result = await _commentRepository.BrowseCommentsAsync(request); - - var commentDtos = result.Items.Select(c => new CommentDto(c)); - var pagedResponse = new PagedResponse( - commentDtos, - result.Page, - result.PageSize, - result.TotalItems - ); - - return pagedResponse; - } - } -} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Api/Program.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Api/Program.cs index d31b1d386..50209eaab 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Api/Program.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Api/Program.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using MiniSpace.Services.Friends.Application; +using MiniSpace.Services.Friends.Core.Wrappers; using MiniSpace.Services.Friends.Application.Commands; using MiniSpace.Services.Friends.Application.Dto; // using MiniSpace.Services.Friends.Application.Events; @@ -35,26 +36,32 @@ public static async Task Main(string[] args) .UseInfrastructure() .UseDispatcherEndpoints(endpoints => endpoints .Get("", ctx => ctx.Response.WriteAsync(ctx.RequestServices.GetService().Name)) + .Post("friends/requests/{userId}/accept", afterDispatch: (cmd, ctx) => ctx.Response.Ok()) + .Post("friends/requests/{userId}/decline", afterDispatch: (cmd, ctx) => ctx.Response.Ok()) + .Post("friends/{userId}/invite", afterDispatch: (cmd, ctx) => ctx.Response.Created($"friends/{ctx.Request.RouteValues["userId"]}/invite")) + + .Get>("friends/{userId}") + .Get>("friends/requests/{userId}") + .Get>("friends/pending/all") + .Get>("friends/requests/sent/{userId}") + + + + .Put("friends/requests/{userId}/withdraw", afterDispatch: (cmd, ctx) => ctx.Response.Ok()) + + // .Get>("friends/{studentId}", // ctx => new GetFriends { StudentId = Guid.Parse(ctx.Request.RouteValues["studentId"].ToString()) }, // (query, ctx) => ctx.Response.WriteAsJsonAsync(query), // Correctly define delegate with parameters // afterDispatch: ctx => ctx.Response.Ok()) - .Get>("friends/{studentId}") - .Get>("friends/requests/{studentId}") - // .Get>("friends/pending") - .Get>("friends/pending/all") - .Get>("friends/requests/sent/{studentId}") + // .Get("friends/requests/sent", ctx => // { // var query = new GetSentFriendRequests { StudentId = ctx.User.GetUserId() }; // return ctx.QueryDispatcher.QueryAsync(query); // }, afterDispatch: ctx => ctx.Response.WriteAsJsonAsync(ctx.Result)) - - .Post("friends/requests/{studentId}/accept", afterDispatch: (cmd, ctx) => ctx.Response.Ok()) - .Post("friends/requests/{studentId}/decline", afterDispatch: (cmd, ctx) => ctx.Response.Ok()) - .Put("friends/requests/{studentId}/withdraw", afterDispatch: (cmd, ctx) => ctx.Response.Ok()) .Delete("friends/{requesterId}/{friendId}/remove") - .Post("friends/{studentId}/invite", afterDispatch: (cmd, ctx) => ctx.Response.Created($"friends/{ctx.Request.RouteValues["studentId"]}/invite")))) + )) .UseLogging() .UseLogging() .Build() diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/AddFriend.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/AddFriend.cs deleted file mode 100644 index dc0491a11..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/AddFriend.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Convey.CQRS.Commands; - -namespace MiniSpace.Services.Friends.Application.Commands -{ - public class AddFriend : ICommand - { - public Guid RequesterId { get; } - public Guid FriendId { get; } - - public AddFriend(Guid requesterId, Guid friendId) - { - RequesterId = requesterId; - FriendId = friendId; - } - } -} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/AddFriendHandler.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/AddFriendHandler.cs deleted file mode 100644 index f12812c33..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/AddFriendHandler.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Threading.Tasks; -using Convey.CQRS.Commands; -using MiniSpace.Services.Friends.Application.Exceptions; -using MiniSpace.Services.Friends.Core.Entities; -using MiniSpace.Services.Friends.Core.Repositories; -using MiniSpace.Services.Friends.Application.Services; - -namespace MiniSpace.Services.Friends.Application.Commands.Handlers -{ - public class AddFriendHandler : ICommandHandler - { - private readonly IFriendRepository _friendRepository; - private readonly IAppContext _appContext; - private readonly IMessageBroker _messageBroker; - private readonly IEventMapper _eventMapper; - - public AddFriendHandler(IFriendRepository friendRepository, IAppContext appContext, - IMessageBroker messageBroker, IEventMapper eventMapper) - { - _friendRepository = friendRepository; - _appContext = appContext; - _messageBroker = messageBroker; - _eventMapper = eventMapper; - } - - public async Task HandleAsync(AddFriend command, CancellationToken cancellationToken = default) - { - if (!ValidateAccessOrFail(command.RequesterId)) - { - throw new UnauthorizedFriendActionException(command.RequesterId, command.FriendId); - } - - - var alreadyFriends = await _friendRepository.IsFriendAsync(command.RequesterId, command.FriendId); - if (alreadyFriends) - { - throw new AlreadyFriendsException(command.RequesterId, command.FriendId); - } - - var requester = await _friendRepository.GetFriendshipAsync(command.RequesterId, command.FriendId); - if (requester == null) - { - throw new FriendshipNotFoundException(command.RequesterId, command.FriendId); - } - await _friendRepository.UpdateFriendshipAsync(requester); - var events = _eventMapper.MapAll(requester.Events); - - await _messageBroker.PublishAsync(events); - } - - private bool ValidateAccessOrFail(Guid requesterId) - { - var identity = _appContext.Identity; - return identity.IsAuthenticated && identity.Id == requesterId; - } - } -} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/FriendAddedHandler.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/FriendAddedHandler.cs deleted file mode 100644 index 4fedfe525..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/FriendAddedHandler.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Convey.CQRS.Events; -using MiniSpace.Services.Friends.Application.Events; -using MiniSpace.Services.Friends.Application.Exceptions; -using MiniSpace.Services.Friends.Application.Services; -using MiniSpace.Services.Friends.Core.Repositories; - -namespace MiniSpace.Services.Friends.Application.Commands.Handlers -{ - public class FriendAddedHandler : IEventHandler - { - private readonly IFriendRepository _friendRepository; - private readonly IEventMapper _eventMapper; - private readonly IMessageBroker _messageBroker; - - public FriendAddedHandler(IFriendRepository friendRepository, IEventMapper eventMapper, IMessageBroker messageBroker) - { - _friendRepository = friendRepository; - _eventMapper = eventMapper; - _messageBroker = messageBroker; - } - - public async Task HandleAsync(FriendAdded @event, CancellationToken cancellationToken) - { - var friendship = await _friendRepository.GetFriendshipAsync(@event.RequesterId, @event.FriendId); - if (friendship is null) - { - throw new FriendshipNotFoundException(@event.RequesterId, @event.FriendId); - } - - friendship.MarkAsConfirmed(); - await _friendRepository.UpdateAsync(friendship); - - var events = _eventMapper.MapAll(friendship.Events); - await _messageBroker.PublishAsync(events.ToArray()); - } - } -} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/InviteFriendHandler.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/InviteFriendHandler.cs index 6da33e78f..4b7a042d4 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/InviteFriendHandler.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/InviteFriendHandler.cs @@ -12,20 +12,20 @@ namespace MiniSpace.Services.Friends.Application.Commands.Handlers public class InviteFriendHandler : ICommandHandler { private readonly IFriendRequestRepository _friendRequestRepository; - private readonly IStudentRequestsRepository _studentRequestsRepository; + private readonly IUserRequestsRepository _userRequestsRepository; private readonly IMessageBroker _messageBroker; private readonly IEventMapper _eventMapper; private readonly IAppContext _appContext; public InviteFriendHandler( IFriendRequestRepository friendRequestRepository, - IStudentRequestsRepository studentRequestsRepository, + IUserRequestsRepository userRequestsRepository, IMessageBroker messageBroker, IEventMapper eventMapper, IAppContext appContext) { _friendRequestRepository = friendRequestRepository; - _studentRequestsRepository = studentRequestsRepository; + _userRequestsRepository = userRequestsRepository; _messageBroker = messageBroker; _eventMapper = eventMapper; _appContext = appContext; @@ -54,36 +54,26 @@ public async Task HandleAsync(InviteFriend command, CancellationToken cancellati state: FriendState.Requested ); - await AddOrUpdateStudentRequest(command.InviterId, friendRequest, FriendState.Requested); - await AddOrUpdateStudentRequest(command.InviteeId, friendRequest, FriendState.Pending); + await AddOrUpdateUserRequest(command.InviterId, friendRequest, FriendState.Requested); + await AddOrUpdateUserRequest(command.InviteeId, friendRequest, FriendState.Pending); // Publish FriendInvited Event var friendInvitedEvent = new FriendInvited(command.InviterId, command.InviteeId); string friendInvitedJson = JsonSerializer.Serialize(friendInvitedEvent); await _messageBroker.PublishAsync(friendInvitedEvent); - - // Publish FriendRequestCreated Event - var friendRequestCreatedEvent = new FriendRequestCreated(command.InviterId, command.InviteeId); - string friendRequestCreatedJson = JsonSerializer.Serialize(friendRequestCreatedEvent); - await _messageBroker.PublishAsync(friendRequestCreatedEvent); - - // Publish FriendRequestSent Event - var friendRequestSentEvent = new FriendRequestSent(command.InviterId, command.InviteeId); - string friendRequestSentJson = JsonSerializer.Serialize(friendRequestSentEvent); - await _messageBroker.PublishAsync(friendRequestSentEvent); } - private async Task AddOrUpdateStudentRequest(Guid studentId, FriendRequest friendRequest, FriendState state) + private async Task AddOrUpdateUserRequest(Guid userId, FriendRequest friendRequest, FriendState state) { - var studentRequests = await _studentRequestsRepository.GetAsync(studentId); - if (studentRequests == null) + var userRequests = await _userRequestsRepository.GetAsync(userId); + if (userRequests == null) { - studentRequests = new StudentRequests(studentId); - await _studentRequestsRepository.AddAsync(studentRequests); + userRequests = new UserRequests(userId); + await _userRequestsRepository.AddAsync(userRequests); } - studentRequests.AddRequest(friendRequest.InviterId, friendRequest.InviteeId, friendRequest.RequestedAt, state); - await _studentRequestsRepository.UpdateAsync(studentRequests.StudentId, studentRequests.FriendRequests); + userRequests.AddRequest(friendRequest.InviterId, friendRequest.InviteeId, friendRequest.RequestedAt, state); + await _userRequestsRepository.UpdateAsync(userRequests.UserId, userRequests.FriendRequests); } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/PendingFriendAcceptHandler.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/PendingFriendAcceptHandler.cs index 017142249..e2817a671 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/PendingFriendAcceptHandler.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/PendingFriendAcceptHandler.cs @@ -10,58 +10,45 @@ namespace MiniSpace.Services.Friends.Application.Commands.Handlers { public class PendingFriendAcceptHandler : ICommandHandler { - private readonly IStudentRequestsRepository _studentRequestsRepository; - private readonly IStudentFriendsRepository _studentFriendsRepository; + private readonly IUserRequestsRepository _userRequestsRepository; + private readonly IUserFriendsRepository _userFriendsRepository; private readonly IMessageBroker _messageBroker; private readonly IEventMapper _eventMapper; public PendingFriendAcceptHandler( - // IFriendRequestRepository friendRequestRepository, - IStudentFriendsRepository studentFriendsRepository, - IStudentRequestsRepository studentRequestsRepository, - // IFriendRepository friendRepository, + IUserRequestsRepository userFriendsRepository, + IUserFriendsRepository userRequestsRepository, IMessageBroker messageBroker, IEventMapper eventMapper) { - // _friendRequestRepository = friendRequestRepository; - _studentFriendsRepository = studentFriendsRepository; - _studentRequestsRepository = studentRequestsRepository; - // _friendRepository = friendRepository; + _userRequestsRepository = userFriendsRepository; + _userFriendsRepository = userRequestsRepository; _messageBroker = messageBroker; _eventMapper = eventMapper; } public async Task HandleAsync(PendingFriendAccept command, CancellationToken cancellationToken = default) { - // Retrieve and validate the friend request between the inviter and invitee - var inviterRequests = await _studentRequestsRepository.GetAsync(command.RequesterId); - var inviteeRequests = await _studentRequestsRepository.GetAsync(command.FriendId); + var inviterRequests = await _userRequestsRepository.GetAsync(command.RequesterId); + var inviteeRequests = await _userRequestsRepository.GetAsync(command.FriendId); var friendRequest = FindFriendRequest(inviterRequests, inviteeRequests, command.RequesterId, command.FriendId); var friendRequestInvitee = FindFriendRequest(inviteeRequests, inviterRequests, command.RequesterId, command.FriendId); - // Update the friend request state to accepted + friendRequest.State = FriendState.Accepted; friendRequestInvitee.State = FriendState.Accepted; - // Save the updated FriendRequest states - await _studentRequestsRepository.UpdateAsync(command.RequesterId, inviterRequests.FriendRequests); - await _studentRequestsRepository.UpdateAsync(command.FriendId, inviteeRequests.FriendRequests); + await _userRequestsRepository.UpdateAsync(command.RequesterId, inviterRequests.FriendRequests); + await _userRequestsRepository.UpdateAsync(command.FriendId, inviteeRequests.FriendRequests); + + CreateAndAddFriends(command.RequesterId, command.FriendId, FriendState.Accepted); var pendingFriendAcceptedEvent = new PendingFriendAccepted(command.RequesterId, command.FriendId); await _messageBroker.PublishAsync(pendingFriendAcceptedEvent); - // Create Friend relationships in both directions - CreateAndAddFriends(command.RequesterId, command.FriendId, FriendState.Accepted); - - // Publish related events - // var events = _eventMapper.MapAll(new Core.Events.PendingFriendAccepted(command.RequesterId, command.FriendId)); - // await _messageBroker.PublishAsync(events); - - } - private FriendRequest FindFriendRequest(StudentRequests inviter, StudentRequests invitee, Guid inviterId, Guid inviteeId) + private FriendRequest FindFriendRequest(UserRequests inviter, UserRequests invitee, Guid inviterId, Guid inviteeId) { - // Find the FriendRequest in both inviter and invitee collections return inviter.FriendRequests.FirstOrDefault(fr => fr.InviterId == inviterId && fr.InviteeId == inviteeId) ?? invitee.FriendRequests.FirstOrDefault(fr => fr.InviterId == inviterId && fr.InviteeId == inviteeId) ?? throw new FriendRequestNotFoundException(inviterId, inviteeId); @@ -69,17 +56,14 @@ private FriendRequest FindFriendRequest(StudentRequests inviter, StudentRequests private async void CreateAndAddFriends(Guid inviterId, Guid inviteeId, FriendState state) { - // Retrieve or initialize the StudentFriends for both inviter and invitee - var inviterFriends = await _studentFriendsRepository.GetAsync(inviterId) ?? new StudentFriends(inviterId); - var inviteeFriends = await _studentFriendsRepository.GetAsync(inviteeId) ?? new StudentFriends(inviteeId); + var inviterFriends = await _userFriendsRepository.GetAsync(inviterId) ?? new UserFriends(inviterId); + var inviteeFriends = await _userFriendsRepository.GetAsync(inviteeId) ?? new UserFriends(inviteeId); - // Add new Friend instances with accepted state inviterFriends.AddFriend(new Friend(inviterId, inviteeId, DateTime.UtcNow, state)); inviteeFriends.AddFriend(new Friend(inviteeId, inviterId, DateTime.UtcNow, state)); - // Update the StudentFriends repositories - await _studentFriendsRepository.AddOrUpdateAsync(inviterFriends); - await _studentFriendsRepository.AddOrUpdateAsync(inviteeFriends); + await _userFriendsRepository.AddOrUpdateAsync(inviterFriends); + await _userFriendsRepository.AddOrUpdateAsync(inviteeFriends); } } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/PendingFriendDeclineHandler.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/PendingFriendDeclineHandler.cs index 1be3e5263..4f1f685b3 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/PendingFriendDeclineHandler.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/PendingFriendDeclineHandler.cs @@ -2,7 +2,10 @@ using MiniSpace.Services.Friends.Core.Repositories; using MiniSpace.Services.Friends.Application.Exceptions; using MiniSpace.Services.Friends.Application.Services; +using MiniSpace.Services.Friends.Core.Entities; +using MiniSpace.Services.Friends.Application.Events.External; using System; +using System.Threading; using System.Threading.Tasks; namespace MiniSpace.Services.Friends.Application.Commands.Handlers @@ -31,17 +34,17 @@ public async Task HandleAsync(PendingFriendDecline command, CancellationToken ca throw new FriendshipNotFoundException(command.RequesterId, command.FriendId); } - if (friendRequest.State != Core.Entities.FriendState.Requested) + if (friendRequest.State != FriendState.Requested) { - throw new InvalidOperationException("Friend request is not in the correct state to be declined."); + throw new InvalidFriendRequestStateException(command.RequesterId, command.FriendId, friendRequest.State.ToString()); } friendRequest.Decline(); - friendRequest.State = Core.Entities.FriendState.Declined; + friendRequest.State = FriendState.Declined; await _friendRequestRepository.UpdateAsync(friendRequest); - // var events = _eventMapper.MapAll(friendRequest.Events); - // await _messageBroker.PublishAsync(events.ToArray()); + var pendingFriendDeclinedEvent = new PendingFriendDeclined(command.RequesterId, command.FriendId); + await _messageBroker.PublishAsync(pendingFriendDeclinedEvent); } } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/RemoveFriendHandler.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/RemoveFriendHandler.cs index 6978c1686..a59addd46 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/RemoveFriendHandler.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/RemoveFriendHandler.cs @@ -5,25 +5,31 @@ using MiniSpace.Services.Friends.Application.Services; using MiniSpace.Services.Friends.Core.Repositories; +/* + This will require an update: + - RemoveFriendHandler.cs should be responsible for handling the RemoveFriend command with + that in mind that it will remove a friend from the user's friend list but should leave the + removed frined requests as avaising lite to accept (make the user subscriber). +*/ namespace MiniSpace.Services.Friends.Application.Commands.Handlers { public class RemoveFriendHandler : ICommandHandler { - private readonly IStudentFriendsRepository _studentFriendsRepository; - private readonly IStudentRequestsRepository _studentRequestsRepository; + private readonly IUserFriendsRepository _userFriendsRepository; + private readonly IUserRequestsRepository _userRequestsRepository; private readonly IMessageBroker _messageBroker; private readonly IEventMapper _eventMapper; private readonly IAppContext _appContext; public RemoveFriendHandler( - IStudentFriendsRepository studentFriendsRepository, - IStudentRequestsRepository studentRequestsRepository, + IUserFriendsRepository userFriendsRepository, + IUserRequestsRepository userRequestsRepository, IMessageBroker messageBroker, IEventMapper eventMapper, IAppContext appContext) { - _studentFriendsRepository = studentFriendsRepository; - _studentRequestsRepository = studentRequestsRepository; + _userFriendsRepository = userFriendsRepository; + _userRequestsRepository = userRequestsRepository; _messageBroker = messageBroker; _eventMapper = eventMapper; _appContext = appContext; @@ -34,26 +40,23 @@ public async Task HandleAsync(RemoveFriend command, CancellationToken cancellati var identity = _appContext.Identity; Console.WriteLine($"Handling RemoveFriend for RequesterId: {command.RequesterId} and FriendId: {command.FriendId}. Authenticated: {identity.IsAuthenticated}"); - var requesterFriends = await _studentFriendsRepository.GetAsync(command.RequesterId); - var friendFriends = await _studentFriendsRepository.GetAsync(command.FriendId); + var requesterFriends = await _userFriendsRepository.GetAsync(command.RequesterId); + var friendFriends = await _userRequestsRepository.GetAsync(command.FriendId); if (requesterFriends == null || friendFriends == null) { throw new FriendshipNotFoundException(command.RequesterId, command.FriendId); } - // Call specific methods to remove the friend connection - await _studentFriendsRepository.RemoveFriendAsync(command.RequesterId, command.FriendId); - await _studentFriendsRepository.RemoveFriendAsync(command.FriendId, command.RequesterId); + await _userFriendsRepository.RemoveFriendAsync(command.RequesterId, command.FriendId); + await _userFriendsRepository.RemoveFriendAsync(command.FriendId, command.RequesterId); - // Remove the corresponding friend requests - await _studentRequestsRepository.RemoveFriendRequestAsync(command.RequesterId, command.FriendId); - await _studentRequestsRepository.RemoveFriendRequestAsync(command.FriendId, command.RequesterId); + await _userRequestsRepository.RemoveFriendRequestAsync(command.RequesterId, command.FriendId); + await _userRequestsRepository.RemoveFriendRequestAsync(command.FriendId, command.RequesterId); - // Publish events indicating the removal of pending friend requests - var eventToPublish = new PendingFriendDeclined(command.RequesterId, command.FriendId); + var eventToPublish = new FriendRemoved(command.RequesterId, command.FriendId); await _messageBroker.PublishAsync(eventToPublish); - var reciprocalEventToPublish = new PendingFriendDeclined(command.FriendId, command.RequesterId); + var reciprocalEventToPublish = new FriendRemoved(command.FriendId, command.RequesterId); await _messageBroker.PublishAsync(reciprocalEventToPublish); } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/SentFriendRequestWithdrawHandler.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/SentFriendRequestWithdrawHandler.cs index 3399942f5..e16fbcb98 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/SentFriendRequestWithdrawHandler.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/Handlers/SentFriendRequestWithdrawHandler.cs @@ -15,20 +15,20 @@ namespace MiniSpace.Services.Friends.Application.Commands.Handlers public class SentFriendRequestWithdrawHandler : ICommandHandler { private readonly IFriendRequestRepository _friendRequestRepository; - private readonly IStudentRequestsRepository _studentRequestsRepository; + private readonly IUserRequestsRepository _userRequestsRepository; private readonly IMessageBroker _messageBroker; private readonly IAppContext _appContext; private readonly ILogger _logger; public SentFriendRequestWithdrawHandler( IFriendRequestRepository friendRequestRepository, - IStudentRequestsRepository studentRequestsRepository, + IUserRequestsRepository userRequestsRepository, IMessageBroker messageBroker, IAppContext appContext, ILogger logger) { _friendRequestRepository = friendRequestRepository; - _studentRequestsRepository = studentRequestsRepository; + _userRequestsRepository = userRequestsRepository; _messageBroker = messageBroker; _appContext = appContext; _logger = logger; @@ -38,11 +38,9 @@ public async Task HandleAsync(SentFriendRequestWithdraw command, CancellationTok { _logger.LogInformation("Handling SentFriendRequestWithdraw command: InviterId: {InviterId}, InviteeId: {InviteeId}", command.InviterId, command.InviteeId); - // Fetching request details for both inviter and invitee - var inviterRequests = await _studentRequestsRepository.GetAsync(command.InviterId); - var inviteeRequests = await _studentRequestsRepository.GetAsync(command.InviteeId); + var inviterRequests = await _userRequestsRepository.GetAsync(command.InviterId); + var inviteeRequests = await _userRequestsRepository.GetAsync(command.InviteeId); - // Checking existence of friend requests in both inviter's and invitee's lists var friendRequestForInviter = inviterRequests?.FriendRequests.FirstOrDefault(fr => fr.InviterId == command.InviterId && fr.InviteeId == command.InviteeId); var friendRequestForInvitee = inviteeRequests?.FriendRequests.FirstOrDefault(fr => fr.InviteeId == command.InviteeId && fr.InviterId == command.InviterId); @@ -52,42 +50,38 @@ public async Task HandleAsync(SentFriendRequestWithdraw command, CancellationTok throw new FriendRequestNotFoundException(command.InviterId, command.InviteeId); } - // Update the state to Cancelled for both inviter and invitee friendRequestForInviter.State = FriendState.Cancelled; friendRequestForInvitee.State = FriendState.Cancelled; _logger.LogInformation("Updating friend request state to Cancelled for request ID: {RequestId}", friendRequestForInviter.Id); - // Remove the friend request from both inviter's and invitee's lists await UpdateAndSaveRequests(inviterRequests, friendRequestForInviter); await UpdateAndSaveRequests(inviteeRequests, friendRequestForInvitee); - // Optionally delete the friend request if no longer needed await _friendRequestRepository.DeleteAsync(friendRequestForInviter.Id); - // Publish the event await _messageBroker.PublishAsync(new FriendRequestWithdrawn(friendRequestForInviter.InviterId, friendRequestForInvitee.InviteeId)); - _logger.LogInformation("Published FriendRequestWithdrawn event for InviterId: {InviterId} and InviteeId: {InviteeId}", friendRequestForInviter.InviterId, friendRequestForInvitee.InviteeId); + _logger.LogInformation("Published FriendRequestWithdrawn event for InviterId: {InviterId} and InviteeId: {InviteeId}", + friendRequestForInviter.InviterId, friendRequestForInvitee.InviteeId); } - private async Task UpdateAndSaveRequests(StudentRequests requests, FriendRequest friendRequest) + private async Task UpdateAndSaveRequests(UserRequests requests, FriendRequest friendRequest) { if (requests == null) { - _logger.LogWarning("Received null StudentRequests object for FriendRequest ID: {FriendRequestId}", friendRequest.Id); + _logger.LogWarning("Received null UserRequests object for FriendRequest ID: {FriendRequestId}", friendRequest.Id); return; } if (!requests.FriendRequests.Any(fr => fr.Id == friendRequest.Id)) { - _logger.LogWarning("FriendRequest ID: {FriendRequestId} not found in the requests of Student ID: {StudentId}", friendRequest.Id, requests.StudentId); + _logger.LogWarning("FriendRequest ID: {FriendRequestId} not found in the requests of User ID: {UserId}", friendRequest.Id, requests.UserId); return; } requests.RemoveRequest(friendRequest.Id); - // Save the updated list back to the repository - await _studentRequestsRepository.UpdateAsync(requests.StudentId, requests.FriendRequests.ToList()); - _logger.LogInformation("Updated and saved requests successfully for StudentId: {StudentId}, Total Requests: {Count}", requests.StudentId, requests.FriendRequests.Count()); + await _userRequestsRepository.UpdateAsync(requests.UserId, requests.FriendRequests.ToList()); + _logger.LogInformation("Updated and saved requests successfully for UserId: {UserId}, Total Requests: {Count}", requests.UserId, requests.FriendRequests.Count()); } } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/StudentCreated.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/StudentCreated.cs deleted file mode 100644 index 3313d86bc..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/StudentCreated.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Convey.CQRS.Commands; - -namespace MiniSpace.Services.Friends.Application.Events -{ - public class StudentCreated : ICommand - { - public Guid StudentId { get; } - public string Email { get; } - - public StudentCreated(Guid studentId, string email) - { - StudentId = studentId; - Email = email; - } - } -} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/StudentDeleted.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/StudentDeleted.cs deleted file mode 100644 index c0962d17f..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Commands/StudentDeleted.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Convey.CQRS.Commands; - -namespace MiniSpace.Services.Friends.Application.Events -{ - public class StudentDeleted : ICommand - { - public Guid StudentId { get; } - - public StudentDeleted(Guid studentId) - { - StudentId = studentId; - } - } -} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/FriendDto.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/FriendDto.cs index 59502bf48..fdbf5b978 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/FriendDto.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/FriendDto.cs @@ -5,12 +5,8 @@ namespace MiniSpace.Services.Friends.Application.Dto public class FriendDto { public Guid Id { get; set; } - public string Email { get; set; } - public string FirstName { get; set; } - public Guid StudentId { get; set; } + public Guid UserId { get; set; } public Guid FriendId { get; set; } - public string LastName { get; set; } - public string FullName => $"{FirstName} {LastName}"; public DateTime CreatedAt { get; set; } public FriendState State { get; set; } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/FriendRequestDto.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/FriendRequestDto.cs index 6af0e7b5f..f3adf6415 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/FriendRequestDto.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/FriendRequestDto.cs @@ -9,7 +9,7 @@ public class FriendRequestDto public Guid InviteeId { get; set; } public DateTime RequestedAt { get; set; } public FriendState State { get; set; } - public Guid StudentId { get; set; } + public Guid UserId { get; set; } } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/StudentDto.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/UserDto.cs similarity index 96% rename from MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/StudentDto.cs rename to MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/UserDto.cs index 31b917273..402597018 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/StudentDto.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/UserDto.cs @@ -3,7 +3,7 @@ namespace MiniSpace.Services.Friends.Application.Dto { - public class StudentDto + public class UserDto { public Guid Id { get; set; } public string Email { get; set; } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/StudentFriendsDto.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/UserFriendsDto.cs similarity index 71% rename from MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/StudentFriendsDto.cs rename to MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/UserFriendsDto.cs index f4f3b744e..3763cc476 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/StudentFriendsDto.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/UserFriendsDto.cs @@ -3,9 +3,9 @@ namespace MiniSpace.Services.Friends.Application.Dto { - public class StudentFriendsDto + public class UserFriendsDto { - public Guid StudentId { get; set; } + public Guid UserId { get; set; } public List Friends { get; set; } = new List(); } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/StudentRequestsDto.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/UserRequestsDto.cs similarity index 76% rename from MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/StudentRequestsDto.cs rename to MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/UserRequestsDto.cs index 7418eb157..c7ab90762 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/StudentRequestsDto.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Dto/UserRequestsDto.cs @@ -3,10 +3,10 @@ namespace MiniSpace.Services.Friends.Application.Dto { - public class StudentRequestsDto + public class UserRequestsDto { public Guid Id { get; set; } - public Guid StudentId { get; set; } + public Guid UserId { get; set; } public List FriendRequests { get; set; } = new List(); } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/FriendAdded.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/FriendAdded.cs deleted file mode 100644 index d99a8603b..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/FriendAdded.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Convey.CQRS.Events; -using Convey.MessageBrokers; - -namespace MiniSpace.Services.Friends.Application.Events.External -{ - [Message("notifications")] - public class FriendAdded : IEvent - { - public Guid RequesterId { get; } - public Guid FriendId { get; } - - public FriendAdded(Guid requesterId, Guid friendId) - { - RequesterId = requesterId; - FriendId = friendId; - } - } -} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/Handlers/FriendInvitedHandler.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/Handlers/FriendInvitedHandler.cs deleted file mode 100644 index c3220cd56..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/Handlers/FriendInvitedHandler.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Convey.CQRS.Events; -using MiniSpace.Services.Friends.Application.Exceptions; -using MiniSpace.Services.Friends.Application.Services; -using MiniSpace.Services.Friends.Core.Entities; -using MiniSpace.Services.Friends.Core.Repositories; - -namespace MiniSpace.Services.Friends.Application.Events.External.Handlers -{ - public class FriendInvitedHandler : IEventHandler - { - private readonly IFriendRepository _friendRepository; - private readonly IEventMapper _eventMapper; - private readonly IMessageBroker _messageBroker; - - public FriendInvitedHandler(IFriendRepository friendRepository, IEventMapper eventMapper, IMessageBroker messageBroker) - { - _friendRepository = friendRepository; - _eventMapper = eventMapper; - _messageBroker = messageBroker; - } - - public async Task HandleAsync(FriendInvited @event, CancellationToken cancellationToken) - { - var now = DateTime.UtcNow; - var request = new FriendRequest( - inviterId: @event.InviterId, - inviteeId: @event.InviteeId, - requestedAt: now, - state: FriendState.Requested - ); - - await _friendRepository.AddInvitationAsync(request); - - var events = _eventMapper.MapAll(request.Events); - await _messageBroker.PublishAsync(events.ToArray()); - } - - } -} \ No newline at end of file diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/Handlers/FriendRequestCreatedHandler.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/Handlers/FriendRequestCreatedHandler.cs deleted file mode 100644 index c486dadb6..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/Handlers/FriendRequestCreatedHandler.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Convey.CQRS.Events; -using MiniSpace.Services.Friends.Application.Exceptions; -using MiniSpace.Services.Friends.Application.Services; -using MiniSpace.Services.Friends.Core.Entities; -using MiniSpace.Services.Friends.Core.Repositories; - -namespace MiniSpace.Services.Friends.Application.Events.External.Handlers -{ - public class FriendRequestCreatedHandler : IEventHandler - { - private readonly IFriendRepository _friendRepository; - private readonly IEventMapper _eventMapper; - private readonly IMessageBroker _messageBroker; - - public FriendRequestCreatedHandler(IFriendRepository friendRepository, IEventMapper eventMapper, IMessageBroker messageBroker) - { - _friendRepository = friendRepository; - _eventMapper = eventMapper; - _messageBroker = messageBroker; - } - - public async Task HandleAsync(FriendRequestCreated @event, CancellationToken cancellationToken) - { - var now = DateTime.UtcNow; - var request = new FriendRequest( - inviterId: @event.RequesterId, - inviteeId: @event.FriendId, - requestedAt: now, - state: FriendState.Requested - ); - - await _friendRepository.AddRequestAsync(request); - - var events = _eventMapper.MapAll(request.Events); - await _messageBroker.PublishAsync(events.ToArray()); - } - } -} \ No newline at end of file diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/Handlers/FriendRequestSentHandler.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/Handlers/FriendRequestSentHandler.cs deleted file mode 100644 index cabfd3801..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/Handlers/FriendRequestSentHandler.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Convey.CQRS.Events; -using MiniSpace.Services.Friends.Application.Events.External; -using MiniSpace.Services.Friends.Application.Exceptions; -using MiniSpace.Services.Friends.Application.Services; -using MiniSpace.Services.Friends.Core.Repositories; -using MiniSpace.Services.Friends.Core.Entities; - -namespace MiniSpace.Services.Friends.Application.Commands.Handlers -{ - public class FriendRequestSentHandler : IEventHandler - { - private readonly IFriendRepository _friendRepository; - private readonly IEventMapper _eventMapper; - private readonly IMessageBroker _messageBroker; - private readonly IAppContext _appContext; - - public FriendRequestSentHandler(IFriendRepository friendRepository, IEventMapper eventMapper, IMessageBroker messageBroker, IAppContext appContext) - { - _friendRepository = friendRepository; - _eventMapper = eventMapper; - _messageBroker = messageBroker; - _appContext = appContext; - } - - // public async Task HandleAsync(FriendRequestSent @event, CancellationToken cancellationToken) - // { - // var now = DateTime.UtcNow; - // var request = new FriendRequest( - // inviterId: @event.InviterId, - // inviteeId: @event.InviteeId, - // requestedAt: now, - // state: FriendState.Requested - // ); - // await _friendRepository.AddRequestAsync(request); - - // var events = _eventMapper.MapAll(request.Events); - // await _messageBroker.PublishAsync(events.ToArray()); - // // Console.WriteLine($"FriendInvited event published: InviterId={@event.InviterId}, InviteeId={@event.InviteeId}"); - // } - - public async Task HandleAsync(FriendRequestSent @event, CancellationToken cancellationToken) - { - try - { - var request = new FriendRequest( - inviterId: @event.InviterId, - inviteeId: @event.InviteeId, - requestedAt: DateTime.UtcNow, - state: FriendState.Requested - ); - - // await _friendRepository.AddRequestAsync(request); - var events = _eventMapper.MapAll(request.Events); - await _messageBroker.PublishAsync(events.ToArray()); - } - catch (Exception ex) - { - // Console.WriteLine($"An error occurred while handling FriendRequestSent: {ex.Message}"); - throw; - } - } - } -} \ No newline at end of file diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/Handlers/PendingFriendAcceptedHandler.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/Handlers/PendingFriendAcceptedHandler.cs deleted file mode 100644 index f54ed37ef..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/Handlers/PendingFriendAcceptedHandler.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Convey.CQRS.Events; -using MiniSpace.Services.Friends.Application.Exceptions; -using MiniSpace.Services.Friends.Application.Services; -using MiniSpace.Services.Friends.Core.Repositories; - -namespace MiniSpace.Services.Friends.Application.Events.External.Handlers -{ - public class PendingFriendAcceptedHandler : IEventHandler - { - private readonly IFriendRepository _friendRepository; - private readonly IEventMapper _eventMapper; - private readonly IMessageBroker _messageBroker; - private readonly IAppContext _appContext; - - public PendingFriendAcceptedHandler(IFriendRepository friendRepository, IEventMapper eventMapper, IMessageBroker messageBroker, IAppContext appContext) - { - _friendRepository = friendRepository; - _eventMapper = eventMapper; - _messageBroker = messageBroker; - _appContext = appContext; - } - - public async Task HandleAsync(PendingFriendAccepted @event, CancellationToken cancellationToken) - { - var friendship = await _friendRepository.GetFriendshipAsync(@event.RequesterId, @event.FriendId); - if (friendship == null) - { - throw new FriendshipNotFoundException(@event.RequesterId, @event.FriendId); - } - - friendship.MarkAsConfirmed(); - await _friendRepository.UpdateFriendshipAsync(friendship); - - if (await _friendRepository.GetFriendshipAsync(@event.FriendId, @event.RequesterId) == null) - { - var reciprocalFriendship = new Core.Entities.Friend(@event.FriendId, @event.RequesterId, DateTime.UtcNow, Core.Entities.FriendState.Accepted); - await _friendRepository.AddAsync(reciprocalFriendship); - reciprocalFriendship.MarkAsConfirmed(); - await _friendRepository.UpdateFriendshipAsync(reciprocalFriendship); - } - - // Publish the confirmation event - var confirmationEvent = new FriendshipConfirmed(@event.RequesterId, @event.FriendId); - await _messageBroker.PublishAsync(confirmationEvent); - } - } -} \ No newline at end of file diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/Handlers/PendingFriendDeclinedHandler.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/Handlers/PendingFriendDeclinedHandler.cs deleted file mode 100644 index 9e9ef8c60..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/Handlers/PendingFriendDeclinedHandler.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Convey.CQRS.Events; -using MiniSpace.Services.Friends.Application.Exceptions; -using MiniSpace.Services.Friends.Application.Services; -using MiniSpace.Services.Friends.Core.Entities; -using MiniSpace.Services.Friends.Core.Repositories; -using System.Text.Json; -using System; - -namespace MiniSpace.Services.Friends.Application.Events.External.Handlers -{ - public class PendingFriendDeclinedHandler : IEventHandler - { - private readonly IFriendRequestRepository _friendRequestRepository; - private readonly IEventMapper _eventMapper; - private readonly IMessageBroker _messageBroker; - private readonly IAppContext _appContext; - - public PendingFriendDeclinedHandler( - IFriendRequestRepository friendRequestRepository, - IEventMapper eventMapper, - IMessageBroker messageBroker, - IAppContext appContext) - { - _friendRequestRepository = friendRequestRepository; - _eventMapper = eventMapper; - _messageBroker = messageBroker; - _appContext = appContext; - } - - public async Task HandleAsync(PendingFriendDeclined @event, CancellationToken cancellationToken) - { - Console.WriteLine($"Handling event: {JsonSerializer.Serialize(@event)}"); - - Console.WriteLine($"Searching for friend request between {@event.RequesterId} and {@event.FriendId}"); - var friendRequest = await _friendRequestRepository.FindByInviterAndInvitee(@event.RequesterId, @event.FriendId); - - if (friendRequest == null) - { - Console.WriteLine("No friend request found, throwing exception."); - throw new FriendshipNotFoundException(@event.RequesterId, @event.FriendId); - } - - if (friendRequest.State != FriendState.Declined) - { - Console.WriteLine("Friend request found but not declined, declining now."); - friendRequest.Decline(); - friendRequest.State = FriendState.Declined; - await _friendRequestRepository.UpdateAsync(friendRequest); - } - - Console.WriteLine("Publishing events related to the decline."); - // var events = _eventMapper.MapAll(friendRequest.Events); - // await _messageBroker.PublishAsync(events.ToArray()); - } - } -} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/FriendAdded.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/FriendAdded.cs deleted file mode 100644 index 2e36a5504..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/FriendAdded.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Convey.CQRS.Events; - -namespace MiniSpace.Services.Friends.Application.Events -{ - public class FriendAdded : IEvent - { - public Guid RequesterId { get; } - public Guid FriendId { get; } - - public FriendAdded(Guid requesterId, Guid friendId) - { - RequesterId = requesterId; - FriendId = friendId; - } - } -} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/FriendInvited.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/FriendInvited.cs similarity index 100% rename from MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/FriendInvited.cs rename to MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/FriendInvited.cs diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/FriendRequestAccepted.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/FriendRequestAccepted.cs deleted file mode 100644 index 618700354..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/FriendRequestAccepted.cs +++ /dev/null @@ -1,18 +0,0 @@ -// TODO: REMOVE. - -// using Convey.CQRS.Events; - -// namespace MiniSpace.Services.Friends.Application.Events -// { -// public class FriendRequestAccepted : IEvent -// { -// public Guid RequesterId { get; } -// public Guid FriendId { get; } - -// public FriendRequestAccepted(Guid requesterId, Guid friendId) -// { -// RequesterId = requesterId; -// FriendId = friendId; -// } -// } -// } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/FriendRequestCreated.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/FriendRequestCreated.cs similarity index 100% rename from MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/FriendRequestCreated.cs rename to MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/FriendRequestCreated.cs diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/FriendRequestRejected.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/FriendRequestRejected.cs deleted file mode 100644 index 2f298649a..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/FriendRequestRejected.cs +++ /dev/null @@ -1,18 +0,0 @@ -// TODO: REMOVE. - -// using Convey.CQRS.Events; - -// namespace MiniSpace.Services.Friends.Application.Events -// { -// public class FriendRequestRejected : IEvent -// { -// public Guid RequesterId { get; } -// public Guid FriendId { get; } - -// public FriendRequestRejected(Guid requesterId, Guid friendId) -// { -// RequesterId = requesterId; -// FriendId = friendId; -// } -// } -// } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/FriendRequestSent.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/FriendRequestSent.cs similarity index 100% rename from MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/FriendRequestSent.cs rename to MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/FriendRequestSent.cs diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/FriendshipConfirmed.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/FriendshipConfirmed.cs deleted file mode 100644 index d585d850d..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/FriendshipConfirmed.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Convey.CQRS.Events; - -namespace MiniSpace.Services.Friends.Application.Events -{ - public class FriendshipConfirmed : IEvent - { - public Guid RequesterId { get; } - public Guid FriendId { get; } - - public FriendshipConfirmed(Guid requesterId, Guid friendId) - { - RequesterId = requesterId; - FriendId = friendId; - } - } -} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/PendingFriendAccepted.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/PendingFriendAccepted.cs similarity index 100% rename from MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/PendingFriendAccepted.cs rename to MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/PendingFriendAccepted.cs diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/PendingFriendDeclined.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/PendingFriendDeclined.cs similarity index 100% rename from MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/External/PendingFriendDeclined.cs rename to MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/PendingFriendDeclined.cs diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/SentFriendRequestWithdraw.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/SentFriendRequestWithdraw.cs deleted file mode 100644 index dfadb41f5..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Events/SentFriendRequestWithdraw.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Convey.CQRS.Commands; -using Convey.CQRS.Events; -using System; - -namespace MiniSpace.Services.Friends.Application.Events -{ - public class SentFriendRequestWithdrawHandler : IEvent - { - public Guid RequesterId { get; } - public Guid FriendId { get; } - - public SentFriendRequestWithdrawHandler(Guid requesterId, Guid friendId) - { - RequesterId = requesterId; - FriendId = friendId; - } - } -} - diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Exceptions/InvalidFriendRequestStateException.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Exceptions/InvalidFriendRequestStateException.cs new file mode 100644 index 000000000..bb3447f6b --- /dev/null +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Exceptions/InvalidFriendRequestStateException.cs @@ -0,0 +1,18 @@ +namespace MiniSpace.Services.Friends.Application.Exceptions +{ + public class InvalidFriendRequestStateException : AppException + { + public override string Code { get; } = "invalid_friend_request_state"; + public Guid RequesterId { get; } + public Guid FriendId { get; } + public string CurrentState { get; } + + public InvalidFriendRequestStateException(Guid requesterId, Guid friendId, string currentState) + : base($"Friend request between requester ID {requesterId} and friend ID {friendId} is in an invalid state '{currentState}' for this operation.") + { + RequesterId = requesterId; + FriendId = friendId; + CurrentState = currentState; + } + } +} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Exceptions/InvalidRoleException.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Exceptions/InvalidRoleException.cs index 7b14f309e..1625a4307 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Exceptions/InvalidRoleException.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Exceptions/InvalidRoleException.cs @@ -5,7 +5,7 @@ public class InvalidRoleException : AppException public override string Code { get; } = "invalid_role"; public InvalidRoleException(Guid userId, string role, string requiredRole) - : base($"Student account will not be created for the user with id: {userId} " + + : base($"User account will not be created for the user with id: {userId} " + $"due to the invalid role: {role} (required: {requiredRole}).") { } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Exceptions/StudentAlreadyCreatedException.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Exceptions/StudentAlreadyCreatedException.cs deleted file mode 100644 index ef79f514b..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Exceptions/StudentAlreadyCreatedException.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MiniSpace.Services.Friends.Application.Exceptions -{ - public class StudentAlreadyCreatedException : AppException - { - public override string Code { get; } = "student_already_created"; - public Guid StudentId { get; } - - public StudentAlreadyCreatedException(Guid studentId) - : base($"Student with id: {studentId} was already created.") - { - StudentId = studentId; - } - } -} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Exceptions/UnauthorizedStudentAccessException.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Exceptions/UnauthorizedStudentAccessException.cs deleted file mode 100644 index 2bcbf7d72..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Exceptions/UnauthorizedStudentAccessException.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace MiniSpace.Services.Friends.Application.Exceptions -{ - public class UnauthorizedStudentAccessException : AppException - { - public override string Code { get; } = "unauthorized_student_access"; - public Guid StudentId { get; } - public Guid UserId { get; } - - public UnauthorizedStudentAccessException(Guid studentId, Guid userId) - : base($"Unauthorized access to student with id: '{studentId}' by user with id: '{userId}'.") - { - StudentId = studentId; - UserId = userId; - } - } -} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Exceptions/UnauthorizedUserAccessException.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Exceptions/UnauthorizedUserAccessException.cs new file mode 100644 index 000000000..001776cfc --- /dev/null +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Exceptions/UnauthorizedUserAccessException.cs @@ -0,0 +1,16 @@ +namespace MiniSpace.Services.Friends.Application.Exceptions +{ + public class UnauthorizedUserAccessException : AppException + { + public override string Code { get; } = "unauthorized_user_access"; + public Guid UserId { get; } + public Guid AccessUserId { get; } + + public UnauthorizedUserAccessException(Guid userId, Guid accessUserId) + : base($"Unauthorized access to user with id: '{userId}' by access user with id: '{userId}'.") + { + UserId = userId; + AccessUserId = accessUserId; + } + } +} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/IIdentityContext.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/IIdentityContext.cs index a3535849b..7502b0ec4 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/IIdentityContext.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/IIdentityContext.cs @@ -9,7 +9,6 @@ public interface IIdentityContext bool IsAuthenticated { get; } bool IsAdmin { get; } bool IsBanned { get; } - bool IsOrganizer { get; } IDictionary Claims { get; } } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetFriend.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetFriend.cs index 4845571bf..dcc6d4f35 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetFriend.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetFriend.cs @@ -6,6 +6,6 @@ namespace MiniSpace.Services.Friends.Application.Queries { public class GetFriend : IQuery { - public Guid StudentId { get; set; } + public Guid UserId { get; set; } } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetFriendRequests.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetFriendRequests.cs index 546f8ffbd..dd9aa644e 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetFriendRequests.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetFriendRequests.cs @@ -1,18 +1,22 @@ using Convey.CQRS.Queries; using System.Text.Json.Serialization; using MiniSpace.Services.Friends.Application.Dto; +using MiniSpace.Services.Friends.Core.Wrappers; using System.Collections.Generic; namespace MiniSpace.Services.Friends.Application.Queries { - public class GetFriendRequests : IQuery>, IQuery + public class GetFriendRequests : IQuery> { - public Guid StudentId { get; set; } + public Guid UserId { get; set; } + public int Page { get; set; } + public int PageSize { get; set; } - [JsonConstructor] - public GetFriendRequests([property: JsonPropertyName("studentId")] Guid studentId) + public GetFriendRequests(Guid userId, int page = 1, int pageSize = 10) { - StudentId = studentId; + UserId = userId; + Page = page; + PageSize = pageSize; } } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetFriends.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetFriends.cs index 7d315d294..e3c0ee2d3 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetFriends.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetFriends.cs @@ -1,11 +1,21 @@ using Convey.CQRS.Queries; using MiniSpace.Services.Friends.Application.Dto; +using MiniSpace.Services.Friends.Core.Wrappers; using System.Collections.Generic; namespace MiniSpace.Services.Friends.Application.Queries { - public class GetFriends : IQuery> + public class GetFriends : IQuery> { - public Guid StudentId { get; set; } - } + public Guid UserId { get; set; } + public int Page { get; set; } + public int PageSize { get; set; } + + public GetFriends(Guid userId, int page = 1, int pageSize = 10) + { + UserId = userId; + Page = page; + PageSize = pageSize; + } + } } \ No newline at end of file diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetIncomingFriendRequests.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetIncomingFriendRequests.cs index e99a8a30d..ef72a4c6c 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetIncomingFriendRequests.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetIncomingFriendRequests.cs @@ -1,12 +1,22 @@ using Convey.CQRS.Queries; using MiniSpace.Services.Friends.Application.Dto; +using MiniSpace.Services.Friends.Core.Wrappers; using System; using System.Collections.Generic; namespace MiniSpace.Services.Friends.Application.Queries { - public class GetIncomingFriendRequests : IQuery> + public class GetIncomingFriendRequests : IQuery> { - public Guid StudentId { get; set; } + public Guid UserId { get; set; } + public int Page { get; set; } + public int PageSize { get; set; } + + public GetIncomingFriendRequests(Guid userId, int page = 1, int pageSize = 10) + { + UserId = userId; + Page = page; + PageSize = pageSize; + } } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetSentFriendRequests.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetSentFriendRequests.cs index 11cf088da..566e36651 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetSentFriendRequests.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Queries/GetSentFriendRequests.cs @@ -1,11 +1,21 @@ using Convey.CQRS.Queries; using MiniSpace.Services.Friends.Application.Dto; +using MiniSpace.Services.Friends.Core.Wrappers; using System; namespace MiniSpace.Services.Friends.Application.Queries { - public class GetSentFriendRequests : IQuery> + public class GetSentFriendRequests : IQuery> { - public Guid StudentId { get; set; } + public Guid UserId { get; set; } + public int Page { get; set; } + public int PageSize { get; set; } + + public GetSentFriendRequests(Guid userId, int page = 1, int pageSize = 10) + { + UserId = userId; + Page = page; + PageSize = pageSize; + } } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Services/Clients/IStudentsServiceClient.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Services/Clients/IStudentsServiceClient.cs index 0232b6dfc..e743773cd 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Services/Clients/IStudentsServiceClient.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Services/Clients/IStudentsServiceClient.cs @@ -8,7 +8,7 @@ namespace MiniSpace.Services.Friends.Application.Services.Clients { public interface IStudentsServiceClient { - Task GetAsync(Guid id); - public Task> GetAllAsync(); + Task GetAsync(Guid id); + public Task> GetAllAsync(); } } \ No newline at end of file diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Services/IEventMapper.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Services/IEventMapper.cs index e4ebf0a50..bd82e6c73 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Services/IEventMapper.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Application/Services/IEventMapper.cs @@ -8,7 +8,6 @@ public interface IEventMapper { IEvent Map(IDomainEvent @event); IEnumerable MapAll(IEnumerable events); - // IEnumerable MapAll(IDomainEvent @event); IEnumerable MapAll(PendingFriendAccepted pendingFriendAccept); } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/Friend.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/Friend.cs index f65bd27c2..0dfa24868 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/Friend.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/Friend.cs @@ -7,25 +7,25 @@ namespace MiniSpace.Services.Friends.Core.Entities public class Friend : AggregateRoot { public Guid FriendId { get; private set; } - public Guid StudentId { get; private set; } + public Guid UserId { get; private set; } public FriendState FriendState { get; private set; } public DateTime CreatedAt { get; private set; } - public Friend(Guid studentId, Guid friendId, DateTime createdAt, FriendState state) + public Friend(Guid userId, Guid friendId, DateTime createdAt, FriendState state) { Id = Guid.NewGuid(); - StudentId = studentId; + UserId = userId; FriendId = friendId; CreatedAt = createdAt; FriendState = state; } - public static Friend CreateNewFriendship(Guid studentId, Guid friendId) + public static Friend CreateNewFriendship(Guid userId, Guid friendId) { - return new Friend(studentId, friendId, DateTime.UtcNow, FriendState.Accepted); + return new Friend(userId, friendId, DateTime.UtcNow, FriendState.Accepted); } - public void InviteFriend(Student inviter, Student invitee) + public void InviteFriend(User inviter, User invitee) { if (FriendState != FriendState.Unknown) { @@ -36,7 +36,7 @@ public void InviteFriend(Student inviter, Student invitee) AddEvent(new FriendInvited(this, newFriend)); } - public void AcceptFriendship(Student friend) + public void AcceptFriendship(User friend) { if (FriendState != FriendState.Requested) { @@ -76,7 +76,7 @@ public void MarkAsDeclined() - public void RemoveFriend(Student requester, Student friend) + public void RemoveFriend(User requester, User friend) { if (FriendState != FriendState.Accepted) { diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/Friendship.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/Friendship.cs deleted file mode 100644 index f7a8d6cb7..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/Friendship.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using MiniSpace.Services.Friends.Core.Events; -using MiniSpace.Services.Friends.Core.Exceptions; - -namespace MiniSpace.Services.Friends.Core.Entities -{ - public class Friendship : AggregateRoot - { - public Guid RequesterId { get; private set; } - public Guid FriendId { get; private set; } - public DateTime CreatedAt { get; private set; } - public FriendState State { get; private set; } - - public Friendship(Guid requesterId, Guid friendId) - { - Id = Guid.NewGuid(); - RequesterId = requesterId; - FriendId = friendId; - State = FriendState.Requested; - CreatedAt = DateTime.UtcNow; - } - - public void MarkAsConfirmed() - { - if (State != FriendState.Requested) - throw new InvalidFriendshipStateException(Id, State.ToString(), "Requested"); - State = FriendState.Confirmed; - AddEvent(new FriendshipConfirmed(Id)); - } - - public void DeclineFriendship() - { - if (State != FriendState.Requested) - throw new InvalidOperationException("Friendship can only be declined if it is in the requested state."); - - State = FriendState.Declined; - // Assuming 'Id' is the ID of the current object and 'FriendId' is the ID of the friend. - AddEvent(new FriendshipDeclined(RequesterId, FriendId)); - } - - - } - -} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/Student.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/Student.cs deleted file mode 100644 index 29af756f4..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/Student.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace MiniSpace.Services.Friends.Core.Entities -{ - public class Student - { - public Guid Id { get; private set; } - public string FullName { get; private set; } - public string Email { get; private set; } - public string FirstName { get; private set; } - public string LastName { get; private set; } - - public Student(Guid id, string fullName) - { - Id = id; - FullName = fullName; - } - } -} \ No newline at end of file diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/User.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/User.cs new file mode 100644 index 000000000..3656850d3 --- /dev/null +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/User.cs @@ -0,0 +1,12 @@ +namespace MiniSpace.Services.Friends.Core.Entities +{ + public class User + { + public Guid Id { get; private set; } + + public User(Guid id) + { + Id = id; + } + } +} \ No newline at end of file diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/StudentFriends.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/UserFriends.cs similarity index 57% rename from MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/StudentFriends.cs rename to MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/UserFriends.cs index ace983866..92e812d1b 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/StudentFriends.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/UserFriends.cs @@ -5,29 +5,23 @@ namespace MiniSpace.Services.Friends.Core.Entities { - public class StudentFriends : AggregateRoot + public class UserFriends : AggregateRoot { - public Guid StudentId { get; private set; } + public Guid UserId { get; private set; } private List _friends; public IEnumerable Friends => _friends.AsReadOnly(); - public StudentFriends(Guid studentId) + public UserFriends(Guid userId) { - Id = studentId; - StudentId = studentId; + Id = userId; + UserId = userId; _friends = new List(); } - - // public void AddFriend(Friend friend) - // { - // _friends.Add(friend); - // } - public void AddFriend(Friend friend) { if (_friends.Any(f => f.FriendId == friend.FriendId)) { - throw new InvalidOperationException("This friend is already added."); + throw new FriendAlreadyAddedException(); } _friends.Add(friend); } @@ -35,10 +29,7 @@ public void AddFriend(Friend friend) public void RemoveFriend(Guid friendId) { var friend = _friends.FirstOrDefault(f => f.FriendId == friendId); - // if (friend == null) - // { - // throw new InvalidOperationException("Friend not found."); - // } + _friends.Remove(friend); } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/StudentRequests.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/UserRequests.cs similarity index 84% rename from MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/StudentRequests.cs rename to MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/UserRequests.cs index 4bacf82b6..29c9dc999 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/StudentRequests.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Entities/UserRequests.cs @@ -4,25 +4,22 @@ namespace MiniSpace.Services.Friends.Core.Entities { - public class StudentRequests : AggregateRoot + public class UserRequests : AggregateRoot { - public Guid StudentId { get; private set; } + public Guid UserId { get; private set; } private List _friendRequests; public IEnumerable FriendRequests => _friendRequests.AsReadOnly(); - public StudentRequests(Guid studentId) + public UserRequests(Guid userId) { Id = Guid.NewGuid(); - StudentId = studentId; + UserId = userId; _friendRequests = new List(); } public void AddRequest(Guid inviterId, Guid inviteeId, DateTime requestedAt, FriendState state) { - // if (state != FriendState.Requested || state != FriendState.Pending) - // throw new ArgumentException("Initial state must be 'Requested' or 'Pending' when adding a new friend request."); - var friendRequest = new FriendRequest(inviterId, inviteeId, requestedAt, state); _friendRequests.Add(friendRequest); AddEvent(new FriendRequestCreated(friendRequest)); diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Events/FriendAdded.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Events/FriendAdded.cs index 5ce2e7349..396edcc08 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Events/FriendAdded.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Events/FriendAdded.cs @@ -4,10 +4,10 @@ namespace MiniSpace.Services.Friends.Core.Events { public class FriendAdded : IDomainEvent { - public Student Requester { get; private set; } - public Student Friend { get; private set; } + public User Requester { get; private set; } + public User Friend { get; private set; } - public FriendAdded(Student requester, Student friend) + public FriendAdded(User requester, User friend) { Requester = requester; Friend = friend; diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Events/FriendRemoved.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Events/FriendRemoved.cs index 55e170c7d..61f6864c2 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Events/FriendRemoved.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Events/FriendRemoved.cs @@ -4,10 +4,10 @@ namespace MiniSpace.Services.Friends.Core.Events { public class FriendRemoved : IDomainEvent { - public Student Requester { get; private set; } - public Student Friend { get; private set; } + public User Requester { get; private set; } + public User Friend { get; private set; } - public FriendRemoved(Student requester, Student friend) + public FriendRemoved(User requester, User friend) { Requester = requester; Friend = friend; diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Events/FriendshipDeclined.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Events/FriendshipDeclined.cs index c7d11ab68..15182527c 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Events/FriendshipDeclined.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Events/FriendshipDeclined.cs @@ -2,7 +2,6 @@ namespace MiniSpace.Services.Friends.Core.Events { public class FriendshipDeclined : IDomainEvent { - // Adding separate properties for the requester and the friend public Guid RequesterId { get; } public Guid FriendId { get; } public FriendshipDeclined(Guid requesterId, Guid friendId) diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Exceptions/FriendAlreadyAddedException.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Exceptions/FriendAlreadyAddedException.cs new file mode 100644 index 000000000..46ba88a5b --- /dev/null +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Exceptions/FriendAlreadyAddedException.cs @@ -0,0 +1,11 @@ +namespace MiniSpace.Services.Friends.Core.Exceptions +{ + public class FriendAlreadyAddedException : DomainException + { + public override string Code { get; } = "friend_already_added"; + + public FriendAlreadyAddedException() : base("This friend is already added.") + { + } + } +} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Exceptions/FriendshipStateException.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Exceptions/FriendshipStateException.cs index ce636398d..06d41da29 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Exceptions/FriendshipStateException.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Exceptions/FriendshipStateException.cs @@ -5,14 +5,14 @@ namespace MiniSpace.Services.Friends.Core.Exceptions public class FriendshipStateException : DomainException { public override string Code { get; } = "friendship_state_error"; - public Guid StudentId { get; } + public Guid UserId { get; } public FriendState AttemptedState { get; } public FriendState CurrentState { get; } - public FriendshipStateException(Guid studentId, FriendState attemptedState, FriendState currentState) - : base($"Attempt to change friendship state to {attemptedState} from {currentState} failed for student ID {studentId}.") + public FriendshipStateException(Guid userId, FriendState attemptedState, FriendState currentState) + : base($"Attempt to change friendship state to {attemptedState} from {currentState} failed for user ID {userId}.") { - StudentId = studentId; + UserId = userId; AttemptedState = attemptedState; CurrentState = currentState; } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IFriendRepository.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IFriendRepository.cs index 993323a81..ee1152ca7 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IFriendRepository.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IFriendRepository.cs @@ -8,8 +8,8 @@ namespace MiniSpace.Services.Friends.Core.Repositories public interface IFriendRepository { Task AddFriendAsync(Guid requesterId, Guid friendId); - Task> GetFriendsAsync(Guid studentId); - Task IsFriendAsync(Guid studentId, Guid potentialFriendId); + Task> GetFriendsAsync(Guid userId); + Task IsFriendAsync(Guid userId, Guid potentialFriendId); Task RemoveFriendAsync(Guid requesterId, Guid friendId); Task AcceptFriendInvitationAsync(Guid requesterId, Guid friendId); Task DeclineFriendInvitationAsync(Guid requesterId, Guid friendId); diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IStudentFriendsRepository.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IStudentFriendsRepository.cs deleted file mode 100644 index 9989e2e47..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IStudentFriendsRepository.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using MiniSpace.Services.Friends.Core.Entities; - -namespace MiniSpace.Services.Friends.Core.Repositories -{ - public interface IStudentFriendsRepository - { - Task GetAsync(Guid studentId); - Task> GetAllAsync(); - Task AddAsync(StudentFriends studentFriends); - Task UpdateAsync(StudentFriends studentFriends); - Task DeleteAsync(Guid studentId); - Task ExistsAsync(Guid studentId); - Task> GetFriendsAsync(Guid studentId); - Task AddOrUpdateAsync(StudentFriends studentFriends); - Task RemoveFriendAsync(Guid studentId, Guid friendId); - } -} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IStudentRequestsRepository.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IStudentRequestsRepository.cs deleted file mode 100644 index 5130e7d23..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IStudentRequestsRepository.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using MiniSpace.Services.Friends.Core.Entities; - -namespace MiniSpace.Services.Friends.Core.Repositories -{ - public interface IStudentRequestsRepository - { - Task GetAsync(Guid studentId); - Task> GetAllAsync(); - Task AddAsync(StudentRequests studentRequests); - Task UpdateAsync(StudentRequests studentRequests); - Task UpdateAsync(Guid studentId, IEnumerable updatedFriendRequests); - Task DeleteAsync(Guid studentId); - Task RemoveFriendRequestAsync(Guid requesterId, Guid friendId); - } -} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IUserFriendsRepository.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IUserFriendsRepository.cs new file mode 100644 index 000000000..198c28d54 --- /dev/null +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IUserFriendsRepository.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using MiniSpace.Services.Friends.Core.Entities; + +namespace MiniSpace.Services.Friends.Core.Repositories +{ + public interface IUserFriendsRepository + { + Task GetAsync(Guid userId); + Task> GetAllAsync(); + Task AddAsync(UserFriends userFriends); + Task UpdateAsync(UserFriends userFriends); + Task DeleteAsync(Guid userId); + Task ExistsAsync(Guid userId); + Task> GetFriendsAsync(Guid userId); + Task AddOrUpdateAsync(UserFriends userFriends); + Task RemoveFriendAsync(Guid userId, Guid friendId); + } +} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IStudentRepository.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IUserRepository.cs similarity index 61% rename from MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IStudentRepository.cs rename to MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IUserRepository.cs index 4e6c92d38..915405fba 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IStudentRepository.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IUserRepository.cs @@ -2,11 +2,11 @@ namespace MiniSpace.Services.Friends.Core.Repositories { - public interface IStudentRepository + public interface IUserRepository { - Task GetAsync(Guid id); + Task GetAsync(Guid id); Task ExistsAsync(Guid id); - Task AddAsync(Student student); + Task AddAsync(User user); Task DeleteAsync(Guid id); } } \ No newline at end of file diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IUserRequestsRepository.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IUserRequestsRepository.cs new file mode 100644 index 000000000..49a3f4776 --- /dev/null +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Repositories/IUserRequestsRepository.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using MiniSpace.Services.Friends.Core.Entities; + +namespace MiniSpace.Services.Friends.Core.Repositories +{ + public interface IUserRequestsRepository + { + Task GetAsync(Guid userId); + Task> GetAllAsync(); + Task AddAsync(UserRequests userRequests); + Task UpdateAsync(UserRequests userRequests); + Task UpdateAsync(Guid userId, IEnumerable updatedFriendRequests); + Task DeleteAsync(Guid userId); + Task RemoveFriendRequestAsync(Guid requesterId, Guid friendId); + } +} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Wrappers/PagedResponse.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Wrappers/PagedResponse.cs new file mode 100644 index 000000000..e91ff5f14 --- /dev/null +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Core/Wrappers/PagedResponse.cs @@ -0,0 +1,28 @@ +namespace MiniSpace.Services.Friends.Core.Wrappers +{ + public class PagedResponse + { + public IEnumerable Items { get; } + public int TotalPages { get; } + public int TotalItems { get; } + public int PageSize { get; } + public int Page { get; } + public bool First { get; } + public bool Last { get; } + public bool Empty { get; } + public int? NextPage => Page < TotalPages ? Page + 1 : (int?)null; + public int? PreviousPage => Page > 1 ? Page - 1 : (int?)null; + + public PagedResponse(IEnumerable items, int page, int pageSize, int totalItems) + { + Items = items; + PageSize = pageSize; + TotalItems = totalItems; + TotalPages = pageSize > 0 ? (int)Math.Ceiling((decimal)totalItems / pageSize) : 0; + Page = page; + First = page == 1; + Last = page == TotalPages; + Empty = !items.Any(); + } + } +} \ No newline at end of file diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Contexts/IdentityContext.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Contexts/IdentityContext.cs index 6b2c5b72c..653605cee 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Contexts/IdentityContext.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Contexts/IdentityContext.cs @@ -11,7 +11,6 @@ internal class IdentityContext : IIdentityContext public bool IsAuthenticated { get; } public bool IsAdmin { get; } public bool IsBanned { get; } - public bool IsOrganizer { get; } public IDictionary Claims { get; } = new Dictionary(); internal IdentityContext() @@ -30,7 +29,6 @@ internal IdentityContext(string id, string role, bool isAuthenticated, IDictiona IsAuthenticated = isAuthenticated; IsAdmin = Role.Equals("admin", StringComparison.InvariantCultureIgnoreCase); IsBanned = Role.Equals("banned", StringComparison.InvariantCultureIgnoreCase); - IsOrganizer = Role.Equals("organizer", StringComparison.InvariantCultureIgnoreCase); Claims = claims ?? new Dictionary(); Name = Claims.TryGetValue("name", out var name) ? name : string.Empty; Email = Claims.TryGetValue("email", out var email) ? email : string.Empty; diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Exceptions/ExceptionToMessageMapper.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Exceptions/ExceptionToMessageMapper.cs index d125750d0..8a8aacdc0 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Exceptions/ExceptionToMessageMapper.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Exceptions/ExceptionToMessageMapper.cs @@ -15,7 +15,6 @@ public object Map(Exception exception, object message) AlreadyFriendsException ex => new FriendAddingFailed(ex.RequesterId, ex.FriendId, ex.Message, "already_friends"), FriendshipNotFoundException ex => message switch { - AddFriend _ => new FriendAddingFailed(ex.RequesterId, ex.FriendId, ex.Message, "friendship_not_found"), RemoveFriend _ => new FriendRemovalFailed(ex.RequesterId, ex.FriendId, ex.Message, "friendship_not_found"), _ => null }, diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Extensions.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Extensions.cs index 657ba3b5e..b8cf933e6 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Extensions.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Extensions.cs @@ -29,7 +29,6 @@ using MiniSpace.Services.Friends.Application; using MiniSpace.Services.Friends.Application.Commands; using MiniSpace.Services.Friends.Application.Events.External; -using MiniSpace.Services.Friends.Application.Events.External.Handlers; using MiniSpace.Services.Friends.Application.Services; using MiniSpace.Services.Friends.Core.Repositories; using MiniSpace.Services.Friends.Infrastructure.Contexts; @@ -42,6 +41,7 @@ using MiniSpace.Services.Friends.Application.Events; using MiniSpace.Services.Notifications.Infrastructure.Services.Clients; using MiniSpace.Services.Friends.Application.Services.Clients; +using Convey.Logging.CQRS; namespace MiniSpace.Services.Friends.Infrastructure { @@ -51,8 +51,9 @@ public static IConveyBuilder AddInfrastructure(this IConveyBuilder builder) { builder.Services.AddTransient(); builder.Services.AddTransient(); - builder.Services.AddTransient(); - builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddTransient(); @@ -76,11 +77,11 @@ public static IConveyBuilder AddInfrastructure(this IConveyBuilder builder) .AddRedis() .AddMetrics() .AddJaeger() - .AddHandlersLogging() + .AddEventHandlersLogging() .AddMongoRepository("friendRequests") .AddMongoRepository("friends") - .AddMongoRepository("student-friends") - .AddMongoRepository("student-requests") + .AddMongoRepository("user-friends") + .AddMongoRepository("user-requests") .AddWebApiSwaggerDocs() .AddCertificateAuthentication() .AddSecurity(); @@ -96,18 +97,12 @@ public static IApplicationBuilder UseInfrastructure(this IApplicationBuilder app .UseMetrics() .UseCertificateAuthentication() .UseRabbitMq() - .SubscribeCommand() .SubscribeCommand() .SubscribeCommand() .SubscribeCommand() .SubscribeCommand() .SubscribeCommand() - // .SubscribeEvent() .SubscribeEvent() - // .SubscribeEvent() - // .SubscribeEvent() - // .SubscribeEvent() - // .SubscribeEvent() .SubscribeEvent(); return app; diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Logging/Extensions.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Logging/Extensions.cs deleted file mode 100644 index 37c5ee989..000000000 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Logging/Extensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Convey; -using Convey.Logging.CQRS; -using Microsoft.Extensions.DependencyInjection; -using MiniSpace.Services.Friends.Application.Commands; - -namespace MiniSpace.Services.Friends.Infrastructure.Logging -{ - internal static class Extensions - { - public static IConveyBuilder AddHandlersLogging(this IConveyBuilder builder) - { - var assembly = typeof(AddFriend).Assembly; - - builder.Services.AddSingleton(new MessageToLogTemplateMapper()); - - return builder - .AddCommandHandlersLogging(assembly) - .AddEventHandlersLogging(assembly); - } - } -} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Logging/MessageToLogTemplateMapper.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Logging/MessageToLogTemplateMapper.cs index b22cfb76f..166feb9b5 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Logging/MessageToLogTemplateMapper.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Logging/MessageToLogTemplateMapper.cs @@ -10,12 +10,6 @@ internal sealed class MessageToLogTemplateMapper : IMessageToLogTemplateMapper private static IReadOnlyDictionary MessageTemplates => new Dictionary { - { - typeof(AddFriend), new HandlerLogTemplate - { - After = "Friendship added between requester: {RequesterId} and friend: {FriendId}." - } - }, { typeof(RemoveFriend), new HandlerLogTemplate { diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/Extensions.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/Extensions.cs index 3595667fd..f9480d1d5 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/Extensions.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/Extensions.cs @@ -7,13 +7,13 @@ namespace MiniSpace.Services.Friends.Infrastructure.Mongo.Documents public static class Extensions { public static Friend AsEntity(this FriendDocument document) - => new Friend(document.StudentId, document.FriendId, document.CreatedAt, document.State); + => new Friend(document.UserId, document.FriendId, document.CreatedAt, document.State); public static FriendDocument AsDocument(this Friend entity) => new FriendDocument { Id = entity.Id, - StudentId = entity.StudentId, + UserId = entity.UserId, FriendId = entity.FriendId, CreatedAt = entity.CreatedAt, State = entity.FriendState @@ -23,13 +23,10 @@ public static FriendDto AsDto(this FriendDocument document) => new FriendDto { Id = document.Id, - StudentId = document.StudentId, + UserId = document.UserId, FriendId = document.FriendId, CreatedAt = document.CreatedAt, State = document.State, - // Email = document.Email, - // FirstName = document.FirstName, - // LastName = document.LastName }; public static FriendRequest AsEntity(this FriendRequestDocument document) @@ -65,48 +62,36 @@ public static FriendRequestDto AsDto(this FriendRequestDocument document) InviteeId = document.InviteeId, RequestedAt = document.RequestedAt, State = document.State, - StudentId = document.InviteeId + UserId = document.InviteeId }; - public static StudentFriendsDocument AsDocument(this StudentFriends entity) - => new StudentFriendsDocument + public static UserFriendsDocument AsDocument(this UserFriends entity) + => new UserFriendsDocument { Id = entity.Id, - StudentId = entity.StudentId, + UserId = entity.UserId, Friends = entity.Friends.Select(friend => friend.AsDocument()).ToList() }; - public static StudentFriends AsEntity(this StudentFriendsDocument document) - => new StudentFriends(document.StudentId); - - // With the correct definitions of the Object-Value method in Core. - // ... - // public static StudentFriends AsEntity(this StudentFriendsDocument document) - // { - // var studentFriends = new StudentFriends(document.StudentId); - // foreach (var friendDoc in document.Friends) - // { - // studentFriends.AddFriend(friendDoc.AsEntity()); - // } - // return studentFriends; - // } - - public static StudentRequestsDocument AsDocument(this StudentRequests entity) - => new StudentRequestsDocument + public static UserFriends AsEntity(this UserFriendsDocument document) + => new UserFriends(document.UserId); + + public static UserRequestsDocument AsDocument(this UserRequests entity) + => new UserRequestsDocument { Id = entity.Id, - StudentId = entity.StudentId, + UserId = entity.UserId, FriendRequests = entity.FriendRequests.Select(fr => fr.AsDocument()).ToList() }; - public static StudentRequests AsEntity(this StudentRequestsDocument document) + public static UserRequests AsEntity(this UserRequestsDocument document) { if (document == null) { throw new ArgumentNullException(nameof(document), "StudentRequestsDocument cannot be null."); } - var studentRequests = new StudentRequests(document.StudentId); + var studentRequests = new UserRequests(document.UserId); foreach (var friendRequestDoc in document.FriendRequests) { studentRequests.AddRequest(friendRequestDoc.InviterId, friendRequestDoc.InviteeId, friendRequestDoc.RequestedAt, friendRequestDoc.State); @@ -114,11 +99,11 @@ public static StudentRequests AsEntity(this StudentRequestsDocument document) return studentRequests; } - public static StudentRequestsDto AsDto(this StudentRequestsDocument document) - => new StudentRequestsDto + public static UserRequestsDto AsDto(this UserRequestsDocument document) + => new UserRequestsDto { Id = document.Id, - StudentId = document.StudentId, + UserId = document.UserId, FriendRequests = document.FriendRequests.Select(fr => fr.AsDto()).ToList() }; diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/FriendDocument.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/FriendDocument.cs index 4e1d6f6c5..a63b8ef00 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/FriendDocument.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/FriendDocument.cs @@ -7,12 +7,8 @@ public class FriendDocument : IIdentifiable { public Guid Id { get; set; } public Guid FriendId { get; set; } - public Guid StudentId { get; set; } + public Guid UserId { get; set; } public DateTime CreatedAt { get; set; } public FriendState State { get; set; } - - // public string Email { get; set; } - // public string FirstName { get; set; } - // public string LastName { get; set; } } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/StudentFriendsDocument.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/UserFriendsDocument.cs similarity index 65% rename from MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/StudentFriendsDocument.cs rename to MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/UserFriendsDocument.cs index f09c326ef..3572c1c95 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/StudentFriendsDocument.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/UserFriendsDocument.cs @@ -5,10 +5,10 @@ namespace MiniSpace.Services.Friends.Infrastructure.Mongo.Documents { - public class StudentFriendsDocument : IIdentifiable + public class UserFriendsDocument : IIdentifiable { public Guid Id { get; set; } - public Guid StudentId { get; set; } - public List Friends { get; set; } = new List(); // List of friend documents + public Guid UserId { get; set; } + public List Friends { get; set; } = new List(); } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/StudentRequestsDocument.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/UserRequestsDocument.cs similarity index 76% rename from MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/StudentRequestsDocument.cs rename to MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/UserRequestsDocument.cs index ee250c2dd..0b30dd949 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/StudentRequestsDocument.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Documents/UserRequestsDocument.cs @@ -5,10 +5,10 @@ namespace MiniSpace.Services.Friends.Infrastructure.Mongo.Documents { - public class StudentRequestsDocument : IIdentifiable + public class UserRequestsDocument : IIdentifiable { public Guid Id { get; set; } - public Guid StudentId { get; set; } + public Guid UserId { get; set; } public List FriendRequests { get; set; } = new List(); } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetFriendHandler.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetFriendHandler.cs index 4f7bd2a45..4038f64f4 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetFriendHandler.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetFriendHandler.cs @@ -18,9 +18,9 @@ public GetFriendHandler(IMongoRepository friendRepository) public async Task HandleAsync(GetFriend query, CancellationToken cancellationToken) { - var document = await _friendRepository.GetAsync(p => p.Id == query.StudentId); + var document = await _friendRepository.GetAsync(p => p.Id == query.UserId); if (document == null) - throw new FriendshipNotFoundException(query.StudentId); + throw new FriendshipNotFoundException(query.UserId); return document.AsDto(); } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetFriendRequestsHandler.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetFriendRequestsHandler.cs index 338f4a43d..31ae8fbbb 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetFriendRequestsHandler.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetFriendRequestsHandler.cs @@ -3,38 +3,44 @@ using MiniSpace.Services.Friends.Application.Dto; using MiniSpace.Services.Friends.Application.Queries; using MiniSpace.Services.Friends.Core.Entities; +using MiniSpace.Services.Friends.Core.Wrappers; using MiniSpace.Services.Friends.Infrastructure.Mongo.Documents; -using System.Text.Json; +using MongoDB.Driver; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace MiniSpace.Services.Friends.Infrastructure.Mongo.Queries.Handlers { - public class GetFriendRequestsHandler : IQueryHandler> + public class GetFriendRequestsHandler : IQueryHandler> { private readonly IMongoRepository _friendRequestRepository; - public GetFriendRequestsHandler(IMongoRepository friendRequestRepository) { _friendRequestRepository = friendRequestRepository; } - public async Task> HandleAsync(GetFriendRequests query, CancellationToken cancellationToken) + public async Task> HandleAsync(GetFriendRequests query, CancellationToken cancellationToken) { - string queryJson = JsonSerializer.Serialize(query); - // Console.WriteLine($"Handling GetFriendRequests: {queryJson}"); - // Console.WriteLine($"Handling GetFriendRequests for UserId: {query.StudentId}"); + var filter = Builders.Filter.And( + Builders.Filter.Eq(fr => fr.InviteeId, query.UserId), + Builders.Filter.Eq(fr => fr.State, FriendState.Requested) + ); - var documents = await _friendRequestRepository.FindAsync(p => p.InviteeId == query.StudentId && p.State == FriendState.Requested); - // Console.WriteLine($"Found {documents.Count()} friend requests."); + var totalItems = (int)await _friendRequestRepository.Collection.CountDocumentsAsync(filter, cancellationToken: cancellationToken); - if (!documents.Any()) - { - // Console.WriteLine($"No friend requests found for UserId: {query.StudentId}."); - return Enumerable.Empty(); - } + var documents = await _friendRequestRepository.Collection + .Find(filter) + .Skip((query.Page - 1) * query.PageSize) + .Limit(query.PageSize) + .ToListAsync(cancellationToken); - return documents.Select(doc => doc.AsDto()); - } + var friendRequests = documents.Select(doc => doc.AsDto()); + return new PagedResponse(friendRequests, query.Page, query.PageSize, totalItems); + } } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetFriendsHandler.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetFriendsHandler.cs index dfb1094f1..432282976 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetFriendsHandler.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetFriendsHandler.cs @@ -2,7 +2,7 @@ using MiniSpace.Services.Friends.Application.Dto; using MiniSpace.Services.Friends.Application.Queries; using MiniSpace.Services.Friends.Core.Repositories; -using MiniSpace.Services.Friends.Infrastructure.Mongo.Documents; +using MiniSpace.Services.Friends.Core.Wrappers; using System; using System.Collections.Generic; using System.Linq; @@ -11,39 +11,48 @@ namespace MiniSpace.Services.Friends.Infrastructure.Mongo.Queries.Handlers { - public class GetFriendsHandler : IQueryHandler> + public class GetFriendsHandler : IQueryHandler> { - private readonly IStudentFriendsRepository _studentFriendsRepository; + private readonly IUserFriendsRepository _userFriendsRepository; - public GetFriendsHandler(IStudentFriendsRepository studentFriendsRepository) + public GetFriendsHandler(IUserFriendsRepository userFriendsRepository) { - _studentFriendsRepository = studentFriendsRepository; + _userFriendsRepository = userFriendsRepository; } - public async Task> HandleAsync(GetFriends query, CancellationToken cancellationToken) + public async Task> HandleAsync(GetFriends query, CancellationToken cancellationToken) { - var friends = await _studentFriendsRepository.GetFriendsAsync(query.StudentId); - if (!friends.Any()) + var allFriends = await _userFriendsRepository.GetFriendsAsync(query.UserId); + + if (allFriends == null || !allFriends.Any()) { - return Enumerable.Empty(); + return new PagedResponse(Enumerable.Empty(), query.Page, query.PageSize, 0); } - return new List - { - new StudentFriendsDto + var totalItems = allFriends.Count(); + + var friendsToReturn = allFriends + .Skip((query.Page - 1) * query.PageSize) + .Take(query.PageSize) + .Select(f => new FriendDto { - StudentId = query.StudentId, - Friends = friends.Select(f => new FriendDto - { - Id = f.Id, - StudentId = f.StudentId, - FriendId = f.FriendId, - CreatedAt = f.CreatedAt, - State = f.FriendState - }).ToList() - } + Id = f.Id, + UserId = f.UserId, + FriendId = f.FriendId, + CreatedAt = f.CreatedAt, + State = f.FriendState + }) + .ToList(); + + var userFriendsDto = new UserFriendsDto + { + UserId = query.UserId, + Friends = friendsToReturn }; + + var userFriendsDtos = new List { userFriendsDto }; + + return new PagedResponse(userFriendsDtos, query.Page, query.PageSize, totalItems); } } - } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetIncomingFriendRequestsHandler.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetIncomingFriendRequestsHandler.cs index e58d0791b..7db86c872 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetIncomingFriendRequestsHandler.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetIncomingFriendRequestsHandler.cs @@ -2,6 +2,7 @@ using Convey.Persistence.MongoDB; using MiniSpace.Services.Friends.Application.Dto; using MiniSpace.Services.Friends.Application.Queries; +using MiniSpace.Services.Friends.Core.Wrappers; using MiniSpace.Services.Friends.Infrastructure.Mongo.Documents; using MongoDB.Driver; using System; @@ -12,33 +13,38 @@ namespace MiniSpace.Services.Friends.Infrastructure.Mongo.Queries.Handlers { - public class GetIncomingFriendRequestsHandler : IQueryHandler> + public class GetIncomingFriendRequestsHandler : IQueryHandler> { - private readonly IMongoRepository _studentRequestsRepository; + private readonly IMongoRepository _userRequestsRepository; - public GetIncomingFriendRequestsHandler(IMongoRepository studentRequestsRepository) + public GetIncomingFriendRequestsHandler(IMongoRepository userRequestsRepository) { - _studentRequestsRepository = studentRequestsRepository; + _userRequestsRepository = userRequestsRepository; } - public async Task> HandleAsync(GetIncomingFriendRequests query, CancellationToken cancellationToken) + public async Task> HandleAsync(GetIncomingFriendRequests query, CancellationToken cancellationToken) { - var studentRequests = await _studentRequestsRepository.Collection - .Find(doc => doc.StudentId == query.StudentId) + var filter = Builders.Filter.Eq(doc => doc.UserId, query.UserId); + var totalItems = (int)await _userRequestsRepository.Collection.CountDocumentsAsync(filter, cancellationToken: cancellationToken); + + var userRequests = await _userRequestsRepository.Collection + .Find(filter) + .Skip((query.Page - 1) * query.PageSize) + .Limit(query.PageSize) .ToListAsync(cancellationToken); - if (studentRequests == null || !studentRequests.Any()) + if (userRequests == null || !userRequests.Any()) { - return Enumerable.Empty(); + return new PagedResponse(Enumerable.Empty(), query.Page, query.PageSize, 0); } - var incomingRequests = studentRequests - .Select(doc => new StudentRequestsDto + var incomingRequests = userRequests + .Select(doc => new UserRequestsDto { Id = doc.Id, - StudentId = doc.StudentId, + UserId = doc.UserId, FriendRequests = doc.FriendRequests - .Where(request => request.InviteeId == query.StudentId && request.State != Core.Entities.FriendState.Accepted) + .Where(request => request.InviteeId == query.UserId && request.State != Core.Entities.FriendState.Accepted) .Select(request => new FriendRequestDto { Id = request.Id, @@ -52,7 +58,7 @@ public async Task> HandleAsync(GetIncomingFriend .Where(dto => dto.FriendRequests.Any()) .ToList(); - return incomingRequests; + return new PagedResponse(incomingRequests, query.Page, query.PageSize, totalItems); } } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetSentFriendRequestsHandler.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetSentFriendRequestsHandler.cs index f144efc12..236f96111 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetSentFriendRequestsHandler.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Queries/Handlers/GetSentFriendRequestsHandler.cs @@ -2,6 +2,7 @@ using Convey.Persistence.MongoDB; using MiniSpace.Services.Friends.Application.Dto; using MiniSpace.Services.Friends.Application.Queries; +using MiniSpace.Services.Friends.Core.Wrappers; using MiniSpace.Services.Friends.Infrastructure.Mongo.Documents; using MongoDB.Driver; using System; @@ -12,48 +13,57 @@ namespace MiniSpace.Services.Friends.Infrastructure.Mongo.Queries.Handlers { - public class GetSentFriendRequestsHandler : IQueryHandler> + public class GetSentFriendRequestsHandler : IQueryHandler> { - private readonly IMongoRepository _studentRequestsRepository; + private readonly IMongoRepository _userRequestsRepository; - public GetSentFriendRequestsHandler(IMongoRepository studentRequestsRepository) + public GetSentFriendRequestsHandler(IMongoRepository userRequestsRepository) { - _studentRequestsRepository = studentRequestsRepository; + _userRequestsRepository = userRequestsRepository; } - public async Task> HandleAsync(GetSentFriendRequests query, CancellationToken cancellationToken) + public async Task> HandleAsync(GetSentFriendRequests query, CancellationToken cancellationToken) { - var studentRequests = await _studentRequestsRepository.Collection - .Find(doc => doc.StudentId == query.StudentId) + // Fetch user requests from the database + var userRequests = await _userRequestsRepository.Collection + .Find(doc => doc.UserId == query.UserId) .ToListAsync(cancellationToken); - if (studentRequests == null || !studentRequests.Any()) + if (userRequests == null || !userRequests.Any()) { - return Enumerable.Empty(); + return new PagedResponse(Enumerable.Empty(), query.Page, query.PageSize, 0); } - var sentRequests = studentRequests - .Select(doc => new StudentRequestsDto + // Filter sent friend requests and map them to DTOs + var sentRequests = userRequests + .Select(doc => new UserRequestsDto { Id = doc.Id, - StudentId = doc.StudentId, + UserId = doc.UserId, FriendRequests = doc.FriendRequests - .Where(request => request.InviterId == query.StudentId && request.State == Core.Entities.FriendState.Requested) + .Where(request => request.InviterId == query.UserId && request.State == Core.Entities.FriendState.Requested) .Select(request => new FriendRequestDto { Id = request.Id, InviterId = request.InviterId, InviteeId = request.InviteeId, RequestedAt = request.RequestedAt, - State = request.State, - StudentId = request.InviterId + State = request.State }) .ToList() }) .Where(dto => dto.FriendRequests.Any()) .ToList(); - return sentRequests; + // Implement pagination + var totalItems = sentRequests.Count; + var paginatedRequests = sentRequests + .Skip((query.Page - 1) * query.PageSize) + .Take(query.PageSize) + .ToList(); + + // Return the paginated response + return new PagedResponse(paginatedRequests, query.Page, query.PageSize, totalItems); } } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/FriendMongoRepository.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/FriendMongoRepository.cs index 4658140b4..f662ceab1 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/FriendMongoRepository.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/FriendMongoRepository.cs @@ -32,7 +32,7 @@ public Task AddFriendAsync(Guid requesterId, Guid friendId) var friend = new FriendDocument { Id = Guid.NewGuid(), - StudentId = requesterId, + UserId = requesterId, FriendId = friendId, CreatedAt = DateTime.UtcNow, State = FriendState.Requested @@ -58,25 +58,25 @@ public async Task ExistsAsync(Guid id) public async Task> GetFriendsAsync(Guid studentId) { - var documents = await _repository.FindAsync(f => f.StudentId == studentId); + var documents = await _repository.FindAsync(f => f.UserId == studentId); return documents?.Select(d => d.AsEntity()).ToList(); } public async Task GetFriendshipAsync(Guid requesterId, Guid friendId) { - var document = await _repository.GetAsync(f => f.StudentId == requesterId && f.FriendId == friendId); + var document = await _repository.GetAsync(f => f.UserId == requesterId && f.FriendId == friendId); return document?.AsEntity(); } public async Task IsFriendAsync(Guid studentId, Guid potentialFriendId) { - var friend = await _repository.GetAsync(f => f.StudentId == studentId && f.FriendId == potentialFriendId); + var friend = await _repository.GetAsync(f => f.UserId == studentId && f.FriendId == potentialFriendId); return friend != null; } public async Task RemoveFriendAsync(Guid requesterId, Guid friendId) { - var friend = await _repository.GetAsync(f => f.StudentId == requesterId && f.FriendId == friendId); + var friend = await _repository.GetAsync(f => f.UserId == requesterId && f.FriendId == friendId); if (friend != null) { await _repository.DeleteAsync(friend.Id); @@ -85,7 +85,7 @@ public async Task RemoveFriendAsync(Guid requesterId, Guid friendId) public async Task AcceptFriendInvitationAsync(Guid requesterId, Guid friendId) { - var friend = await _repository.GetAsync(f => f.StudentId == requesterId && f.FriendId == friendId); + var friend = await _repository.GetAsync(f => f.UserId == requesterId && f.FriendId == friendId); if (friend != null) { friend.State = FriendState.Accepted; @@ -95,7 +95,7 @@ public async Task AcceptFriendInvitationAsync(Guid requesterId, Guid friendId) public async Task DeclineFriendInvitationAsync(Guid requesterId, Guid friendId) { - var friend = await _repository.GetAsync(f => f.StudentId == requesterId && f.FriendId == friendId); + var friend = await _repository.GetAsync(f => f.UserId == requesterId && f.FriendId == friendId); if (friend != null) { friend.State = FriendState.Declined; @@ -107,7 +107,7 @@ public async Task InviteFriendAsync(Guid inviterId, Guid inviteeId) { var newFriend = new FriendDocument { - StudentId = inviterId, + UserId = inviterId, FriendId = inviteeId, CreatedAt = DateTime.UtcNow, State = FriendState.Requested @@ -137,7 +137,7 @@ public async Task AddAsync(Friend friend) var document = new FriendDocument { Id = friend.Id, - StudentId = friend.StudentId, + UserId = friend.UserId, FriendId = friend.FriendId, CreatedAt = friend.CreatedAt, State = friend.FriendState diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/FriendRequestMongoRepository.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/FriendRequestMongoRepository.cs index d910bb3a4..55406e03e 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/FriendRequestMongoRepository.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/FriendRequestMongoRepository.cs @@ -33,16 +33,11 @@ public async Task AddAsync(FriendRequest friendRequest) public async Task UpdateAsync(FriendRequest friendRequest) { - - var documentToUpdate = friendRequest.AsDocument(); + var documentToUpdate = friendRequest.AsDocument(); - documentToUpdate.State = friendRequest.State; + documentToUpdate.State = friendRequest.State; - // Console.WriteLine("Attempting to update document in database: " + JsonSerializer.Serialize(documentToUpdate)); - await _repository.UpdateAsync(documentToUpdate); - - var documentAfterUpdate = await _repository.GetAsync(friendRequest.Id); - // Console.WriteLine("Document after update: " + JsonSerializer.Serialize(documentAfterUpdate)); + await _repository.UpdateAsync(documentToUpdate); } public async Task DeleteAsync(Guid id) @@ -61,6 +56,5 @@ public async Task FindByInviterAndInvitee(Guid inviterId, Guid in var document = await _repository.FindAsync(fr => fr.InviterId == inviterId && fr.InviteeId == inviteeId); return document.FirstOrDefault()?.AsEntity(); } - } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/StudentFriendsMongoRepository.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/StudentFriendsMongoRepository.cs index 872613e24..953e50e18 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/StudentFriendsMongoRepository.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/StudentFriendsMongoRepository.cs @@ -9,34 +9,34 @@ using MiniSpace.Services.Friends.Infrastructure.Mongo.Documents; using MongoDB.Driver; -public class StudentFriendsMongoRepository : IStudentFriendsRepository +public class UserFriendsMongoRepository : IUserFriendsRepository { - private readonly IMongoRepository _repository; + private readonly IMongoRepository _repository; - public StudentFriendsMongoRepository(IMongoRepository repository) + public UserFriendsMongoRepository(IMongoRepository repository) { _repository = repository; } - public async Task GetAsync(Guid studentId) + public async Task GetAsync(Guid studentId) { var document = await _repository.GetAsync(studentId); return document?.AsEntity(); } - public async Task> GetAllAsync() + public async Task> GetAllAsync() { var documents = await _repository.FindAsync(_ => true); return documents.Select(doc => doc.AsEntity()); } - public async Task AddAsync(StudentFriends studentFriends) + public async Task AddAsync(UserFriends studentFriends) { var document = studentFriends.AsDocument(); await _repository.AddAsync(document); } - public async Task UpdateAsync(StudentFriends studentFriends) + public async Task UpdateAsync(UserFriends studentFriends) { var document = studentFriends.AsDocument(); await _repository.UpdateAsync(document); @@ -55,79 +55,37 @@ public async Task ExistsAsync(Guid studentId) public async Task> GetFriendsAsync(Guid studentId) { - // Using a LINQ expression instead of a MongoDB filter - var documents = await _repository.FindAsync(doc => doc.StudentId == studentId); + var documents = await _repository.FindAsync(doc => doc.UserId == studentId); if (documents == null || !documents.Any()) { - // Console.WriteLine($"No document found for student ID: {studentId}"); return Enumerable.Empty(); } - var document = documents.First(); // Assuming you expect only one document per studentId or taking the first one - // Console.WriteLine($"Document found: {document.StudentId}, Friends Count: {document.Friends.Count}"); + var document = documents.First(); return document.Friends.Select(doc => new Friend( - doc.StudentId, + doc.UserId, doc.FriendId, doc.CreatedAt, doc.State)).ToList(); } - - - public async Task AddOrUpdateAsync(StudentFriends studentFriends) -{ - // Ensuring that the document ID (MongoDB _id) is explicitly set to StudentId - var filter = Builders.Filter.Eq(doc => doc.StudentId, studentFriends.StudentId); - var update = Builders.Update - .SetOnInsert(doc => doc.StudentId, studentFriends.StudentId) // Ensuring the document _id is set to StudentId on insert - .Set(doc => doc.Id, studentFriends.StudentId) // Setting the document _id field explicitly - .AddToSetEach(doc => doc.Friends, studentFriends.Friends.Select(f => f.AsDocument())); // Use AddToSetEach to append new items to the list - - var options = new UpdateOptions { IsUpsert = true }; - var result = await _repository.Collection.UpdateOneAsync(filter, update, options); - - // Console.WriteLine("********************************************************"); - // Check if the document was actually inserted or updated - if (result.ModifiedCount > 0 || result.UpsertedId != null) - { - // Retrieve the updated or inserted document - var updatedDocument = await _repository.GetAsync(studentFriends.StudentId); - if (updatedDocument != null) - { - // Serialize the updated document to JSON and log it - var json = JsonSerializer.Serialize(updatedDocument, new JsonSerializerOptions { WriteIndented = true }); - // Console.WriteLine("Updated StudentFriends document:"); - // Console.WriteLine(json); - } - else - { - // Console.WriteLine("Failed to retrieve the updated document."); - } - } - else + public async Task AddOrUpdateAsync(UserFriends studentFriends) { - // Console.WriteLine("No changes were made to the document."); + var filter = Builders.Filter.Eq(doc => doc.UserId, studentFriends.UserId); + var update = Builders.Update + .SetOnInsert(doc => doc.UserId, studentFriends.UserId) + .Set(doc => doc.Id, studentFriends.UserId) + .AddToSetEach(doc => doc.Friends, studentFriends.Friends.Select(f => f.AsDocument())); + + var options = new UpdateOptions { IsUpsert = true }; + await _repository.Collection.UpdateOneAsync(filter, update, options); } -} -public async Task RemoveFriendAsync(Guid studentId, Guid friendId) -{ - var filter = Builders.Filter.Eq(doc => doc.StudentId, studentId); - var update = Builders.Update.PullFilter(doc => doc.Friends, Builders.Filter.Eq("FriendId", friendId)); - - var result = await _repository.Collection.UpdateOneAsync(filter, update); - - if (result.ModifiedCount == 0) + public async Task RemoveFriendAsync(Guid studentId, Guid friendId) { - // Console.WriteLine($"No friend removed for Student ID: {studentId} with Friend ID: {friendId}"); - } - else - { - // Console.WriteLine($"Friend ID: {friendId} removed from Student ID: {studentId}'s friends list."); - } -} - - - + var filter = Builders.Filter.Eq(doc => doc.UserId, studentId); + var update = Builders.Update.PullFilter(doc => doc.Friends, Builders.Filter.Eq("FriendId", friendId)); + await _repository.Collection.UpdateOneAsync(filter, update); + } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/StudentRequestsMongoRepository.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/StudentRequestsMongoRepository.cs index cf2e01c00..36394b1a7 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/StudentRequestsMongoRepository.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/StudentRequestsMongoRepository.cs @@ -11,19 +11,18 @@ namespace MiniSpace.Services.Friends.Infrastructure.Mongo.Repositories { - public class StudentRequestsMongoRepository : IStudentRequestsRepository + public class UserRequestsMongoRepository : IUserRequestsRepository { - private readonly IMongoRepository _repository; + private readonly IMongoRepository _repository; - public StudentRequestsMongoRepository(IMongoRepository repository) + public UserRequestsMongoRepository(IMongoRepository repository) { _repository = repository; } - public async Task GetAsync(Guid studentId) + public async Task GetAsync(Guid studentId) { - // Console.WriteLine($"{studentId}"); - var document = await _repository.FindAsync(doc => doc.StudentId == studentId); + var document = await _repository.FindAsync(doc => doc.UserId == studentId); var studentRequestDocument = document.SingleOrDefault(); if (studentRequestDocument == null) { @@ -32,25 +31,24 @@ public async Task GetAsync(Guid studentId) var entity = studentRequestDocument.AsEntity(); var json = JsonSerializer.Serialize(entity, new JsonSerializerOptions { WriteIndented = true }); - // Console.WriteLine(json); return entity; } - public async Task> GetAllAsync() + public async Task> GetAllAsync() { var documents = await _repository.FindAsync(_ => true); return documents.Select(doc => doc.AsEntity()); } - public async Task AddAsync(StudentRequests studentRequests) + public async Task AddAsync(UserRequests studentRequests) { var document = studentRequests.AsDocument(); await _repository.AddAsync(document); } - public async Task UpdateAsync(StudentRequests studentRequests) + public async Task UpdateAsync(UserRequests studentRequests) { var document = studentRequests.AsDocument(); await _repository.UpdateAsync(document); @@ -58,45 +56,23 @@ public async Task UpdateAsync(StudentRequests studentRequests) public async Task UpdateAsync(Guid studentId, IEnumerable updatedFriendRequests) { - var document = await _repository.FindAsync(doc => doc.StudentId == studentId); + var document = await _repository.FindAsync(doc => doc.UserId == studentId); var studentRequestDocument = document.SingleOrDefault(); - // Console.WriteLine($"*******************************************************************************"); if (studentRequestDocument == null) { - // Console.WriteLine($"No document found with Student ID: {studentId}"); return; // Consider handling this case appropriately, possibly by adding a new document. } - - // Console.WriteLine($"Before update - Document JSON: {JsonSerializer.Serialize(studentRequestDocument, new JsonSerializerOptions { WriteIndented = true })}"); - - // Convert each FriendRequest to a FriendRequestDocument before assignment studentRequestDocument.FriendRequests = updatedFriendRequests.Select(fr => fr.AsDocument()).ToList(); - var filter = Builders.Filter.Eq(doc => doc.StudentId, studentRequestDocument.StudentId); - var update = Builders.Update.Set(doc => doc.FriendRequests, studentRequestDocument.FriendRequests); - - var result = await _repository.Collection.UpdateOneAsync(filter, update); - - // Fetch the updated document to log its new state - var updatedDocument = await _repository.FindAsync(doc => doc.StudentId == studentId); - var updatedStudentRequestDocument = updatedDocument.SingleOrDefault(); + var filter = Builders.Filter.Eq(doc => doc.UserId, studentRequestDocument.UserId); + var update = Builders.Update.Set(doc => doc.FriendRequests, studentRequestDocument.FriendRequests); - // Console.WriteLine($"After update - Document JSON: {JsonSerializer.Serialize(updatedStudentRequestDocument, new JsonSerializerOptions { WriteIndented = true })}"); - - if (result.ModifiedCount == 0) - { - // Console.WriteLine("No documents were modified during the update operation."); - throw new Exception("Update failed, no document was modified."); - } - else - { - // Console.WriteLine($"Document with Student ID: {studentId} was successfully updated. Modified count: {result.ModifiedCount}"); - } + await _repository.Collection.UpdateOneAsync(filter, update); } public async Task DeleteAsync(Guid studentId) { - var documents = await _repository.FindAsync(doc => doc.StudentId == studentId); + var documents = await _repository.FindAsync(doc => doc.UserId == studentId); var document = documents.SingleOrDefault(); if (document != null) { @@ -106,25 +82,19 @@ public async Task DeleteAsync(Guid studentId) public async Task RemoveFriendRequestAsync(Guid requesterId, Guid friendId) { - var filter = Builders.Filter.Eq(doc => doc.StudentId, requesterId) & - Builders.Filter.Or( - Builders.Filter.ElemMatch(doc => doc.FriendRequests, Builders.Filter.Eq(fr => fr.InviterId, friendId)), - Builders.Filter.ElemMatch(doc => doc.FriendRequests, Builders.Filter.Eq(fr => fr.InviteeId, friendId)) + var filter = Builders.Filter.Eq(doc => doc.UserId, requesterId) & + Builders.Filter.Or( + Builders.Filter.ElemMatch(doc => doc.FriendRequests, Builders.Filter.Eq(fr => fr.InviterId, friendId)), + Builders.Filter.ElemMatch(doc => doc.FriendRequests, Builders.Filter.Eq(fr => fr.InviteeId, friendId)) ); - var update = Builders.Update.PullFilter(doc => doc.FriendRequests, + var update = Builders.Update.PullFilter(doc => doc.FriendRequests, Builders.Filter.Or( Builders.Filter.Eq(fr => fr.InviterId, friendId), Builders.Filter.Eq(fr => fr.InviteeId, friendId) )); - var result = await _repository.Collection.UpdateOneAsync(filter, update); - - if (result.ModifiedCount == 0) - { - throw new Exception("No friend request was removed."); - } + await _repository.Collection.UpdateOneAsync(filter, update); } - } } diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/UserFriendsMongoRepository.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/UserFriendsMongoRepository.cs new file mode 100644 index 000000000..58037c4c5 --- /dev/null +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Mongo/Repositories/UserFriendsMongoRepository.cs @@ -0,0 +1,90 @@ +using Convey.Persistence.MongoDB; +using MiniSpace.Services.Friends.Core.Entities; +using MiniSpace.Services.Friends.Core.Repositories; +using MiniSpace.Services.Friends.Infrastructure.Mongo.Documents; +using MongoDB.Driver; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MiniSpace.Services.Friends.Infrastructure.Mongo.Repositories +{ + public class UserFriendsMongoRepository : IUserFriendsRepository + { + private readonly IMongoRepository _repository; + + public UserFriendsMongoRepository(IMongoRepository repository) + { + _repository = repository; + } + + public async Task GetAsync(Guid userId) + { + var document = await _repository.GetAsync(f => f.UserId == userId); + return document?.AsEntity(); + } + + public async Task> GetAllAsync() + { + var documents = await _repository.FindAsync(_ => true); + return documents.Select(d => d.AsEntity()); + } + + public async Task AddAsync(UserFriends userFriends) + { + var document = userFriends.AsDocument(); + await _repository.AddAsync(document); + } + + public async Task UpdateAsync(UserFriends userFriends) + { + var document = userFriends.AsDocument(); + await _repository.UpdateAsync(document); + } + + public async Task DeleteAsync(Guid userId) + { + await _repository.DeleteAsync(userId); + } + + public async Task ExistsAsync(Guid userId) + { + var exists = await _repository.ExistsAsync(f => f.UserId == userId); + return exists; + } + + public async Task> GetFriendsAsync(Guid userId) + { + var document = await _repository.GetAsync(f => f.UserId == userId); + return document?.Friends.Select(f => f.AsEntity()) ?? Enumerable.Empty(); + } + + public async Task AddOrUpdateAsync(UserFriends userFriends) + { + var exists = await ExistsAsync(userFriends.UserId); + if (exists) + { + await UpdateAsync(userFriends); + } + else + { + await AddAsync(userFriends); + } + } + + public async Task RemoveFriendAsync(Guid userId, Guid friendId) + { + var document = await _repository.GetAsync(f => f.UserId == userId); + if (document != null) + { + var friend = document.Friends.FirstOrDefault(f => f.FriendId == friendId); + if (friend != null) + { + document.Friends.Remove(friend); + await _repository.UpdateAsync(document); + } + } + } + } +} diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Services/Clients/StudentsServiceClient.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Services/Clients/StudentsServiceClient.cs index 5e71d3cf1..e0432f70e 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Services/Clients/StudentsServiceClient.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Services/Clients/StudentsServiceClient.cs @@ -17,11 +17,11 @@ public StudentsServiceClient(IHttpClient httpClient, HttpClientOptions options) _url = options.Services["students"]; } - public Task GetAsync(Guid id) - => _httpClient.GetAsync($"{_url}/students/{id}"); + public Task GetAsync(Guid id) + => _httpClient.GetAsync($"{_url}/students/{id}"); - public Task> GetAllAsync() - => _httpClient.GetAsync>($"{_url}/students"); + public Task> GetAllAsync() + => _httpClient.GetAsync>($"{_url}/students"); } } \ No newline at end of file diff --git a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Services/EventMapper.cs b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Services/EventMapper.cs index 6a5336143..53e480d66 100644 --- a/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Services/EventMapper.cs +++ b/MiniSpace.Services.Friends/src/MiniSpace.Services.Friends.Infrastructure/Services/EventMapper.cs @@ -16,9 +16,7 @@ public IEvent Map(IDomainEvent @event) { switch (@event) { - case Core.Events.FriendAdded e: - return new Application.Events.FriendAdded(e.Requester.Id, e.Friend.Id); - + case Core.Events.FriendRemoved e: return new Application.Events.FriendRemoved(e.Requester.Id, e.Friend.Id); diff --git a/MiniSpace.Services.MediaFiles/src/MiniSpace.Services.MediaFiles.Api/Program.cs b/MiniSpace.Services.MediaFiles/src/MiniSpace.Services.MediaFiles.Api/Program.cs index 56c7d8e45..914e85b0e 100644 --- a/MiniSpace.Services.MediaFiles/src/MiniSpace.Services.MediaFiles.Api/Program.cs +++ b/MiniSpace.Services.MediaFiles/src/MiniSpace.Services.MediaFiles.Api/Program.cs @@ -48,7 +48,6 @@ await WebHost.CreateDefaultBuilder(args) { var fileId = await ctx.RequestServices.GetService().UploadFileAsync(cmd); await ctx.Response.WriteJsonAsync(fileId); - /// the probblems is that is is like writing the file id ? to responce only }) ) .UseDispatcherEndpoints(endpoints => endpoints diff --git a/MiniSpace.Services.Organizations/src/MiniSpace.Services.Organizations.Api/Program.cs b/MiniSpace.Services.Organizations/src/MiniSpace.Services.Organizations.Api/Program.cs index 7f767584f..4b3d09f62 100644 --- a/MiniSpace.Services.Organizations/src/MiniSpace.Services.Organizations.Api/Program.cs +++ b/MiniSpace.Services.Organizations/src/MiniSpace.Services.Organizations.Api/Program.cs @@ -38,7 +38,7 @@ public static async Task Main(string[] args) .Get>("organizations/{organizationId}/children") .Get>("organizations/{organizationId}/children/all") // the organizations users is the organizer - .Get>("users/{userId}/organizations") + .Get>("users/{userId}/organizations") // organizations, user is a part of .Get>("users/{userId}/organizations/follow") .Get("organizations/{organizationId}/details/gallery-users") diff --git a/MiniSpace.Services.Organizations/src/MiniSpace.Services.Organizations.Application/Queries/GetPaginatedUserOrganizations.cs b/MiniSpace.Services.Organizations/src/MiniSpace.Services.Organizations.Application/Queries/GetPaginatedUserOrganizations.cs new file mode 100644 index 000000000..75bbb18ce --- /dev/null +++ b/MiniSpace.Services.Organizations/src/MiniSpace.Services.Organizations.Application/Queries/GetPaginatedUserOrganizations.cs @@ -0,0 +1,20 @@ +using Convey.CQRS.Queries; +using MiniSpace.Services.Organizations.Application.DTO; +using System; + +namespace MiniSpace.Services.Organizations.Application.Queries +{ + public class GetPaginatedUserOrganizations : IQuery> + { + public Guid UserId { get; set; } + public int Page { get; set; } + public int PageSize { get; set; } + + public GetPaginatedUserOrganizations(Guid userId, int page, int pageSize) + { + UserId = userId; + Page = page; + PageSize = pageSize; + } + } +} diff --git a/MiniSpace.Services.Organizations/src/MiniSpace.Services.Organizations.Infrastructure/Mongo/Queries/Handlers/GetPaginatedOrganizationsHandler.cs b/MiniSpace.Services.Organizations/src/MiniSpace.Services.Organizations.Infrastructure/Mongo/Queries/Handlers/GetPaginatedOrganizationsHandler.cs index bfe49cdaf..29e3d8878 100644 --- a/MiniSpace.Services.Organizations/src/MiniSpace.Services.Organizations.Infrastructure/Mongo/Queries/Handlers/GetPaginatedOrganizationsHandler.cs +++ b/MiniSpace.Services.Organizations/src/MiniSpace.Services.Organizations.Infrastructure/Mongo/Queries/Handlers/GetPaginatedOrganizationsHandler.cs @@ -1,5 +1,6 @@ using Convey.CQRS.Queries; using MiniSpace.Services.Organizations.Application.DTO; +using MiniSpace.Services.Organizations.Application.Queries; using MiniSpace.Services.Organizations.Core.Repositories; using MiniSpace.Services.Organizations.Infrastructure.Mongo.Documents; using MiniSpace.Services.Organizations.Infrastructure.Mongo.Repositories; @@ -9,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; -namespace MiniSpace.Services.Organizations.Application.Queries.Handlers +namespace MiniSpace.Services.Organizations.Infrastructure.Queries.Handlers { public class GetPaginatedOrganizationsHandler : IQueryHandler> { diff --git a/MiniSpace.Services.Organizations/src/MiniSpace.Services.Organizations.Infrastructure/Mongo/Queries/Handlers/GetPaginatedUserOrganizationsHandler.cs b/MiniSpace.Services.Organizations/src/MiniSpace.Services.Organizations.Infrastructure/Mongo/Queries/Handlers/GetPaginatedUserOrganizationsHandler.cs new file mode 100644 index 000000000..e86aecb3c --- /dev/null +++ b/MiniSpace.Services.Organizations/src/MiniSpace.Services.Organizations.Infrastructure/Mongo/Queries/Handlers/GetPaginatedUserOrganizationsHandler.cs @@ -0,0 +1,78 @@ +using Convey.CQRS.Queries; +using MiniSpace.Services.Organizations.Application.DTO; +using MiniSpace.Services.Organizations.Core.Entities; +using MiniSpace.Services.Organizations.Core.Repositories; +using MiniSpace.Services.Organizations.Application.Queries; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MiniSpace.Services.Organizations.Infrastructure.Mongo.Queries.Handlers +{ + public class GetPaginatedUserOrganizationsHandler : IQueryHandler> + { + private readonly IOrganizationRepository _organizationRepository; + private readonly IOrganizationMembersRepository _organizationMembersRepository; + + public GetPaginatedUserOrganizationsHandler( + IOrganizationRepository organizationRepository, + IOrganizationMembersRepository organizationMembersRepository) + { + _organizationRepository = organizationRepository; + _organizationMembersRepository = organizationMembersRepository; + } + + public async Task> HandleAsync(GetPaginatedUserOrganizations query, CancellationToken cancellationToken) + { + var organizations = await _organizationRepository.GetOrganizationsByUserAsync(query.UserId); + + var matchedOrganizations = new List(); + + foreach (var org in organizations) + { + var organizationDto = await ConvertToDtoAsync(org); + matchedOrganizations.Add(organizationDto); + + if (org.SubOrganizations != null && org.SubOrganizations.Any()) + { + foreach (var subOrg in org.SubOrganizations) + { + var subOrganizationDto = await ConvertToDtoAsync(subOrg); + matchedOrganizations.Add(subOrganizationDto); + } + } + } + + var totalItems = matchedOrganizations.Count; + var paginatedOrganizations = matchedOrganizations + .Skip((query.Page - 1) * query.PageSize) + .Take(query.PageSize) + .ToList(); + + return new MiniSpace.Services.Organizations.Application.DTO.PagedResult(paginatedOrganizations, query.Page, query.PageSize, totalItems); + } + + private async Task ConvertToDtoAsync(Organization organization) + { + var members = await _organizationMembersRepository.GetMembersAsync(organization.Id); + + return new OrganizationDto + { + Id = organization.Id, + Name = organization.Name, + Description = organization.Description, + ImageUrl = organization.ImageUrl, + BannerUrl = organization.BannerUrl, + OwnerId = organization.OwnerId, + DefaultRoleName = organization.DefaultRoleName, + Address = organization.Address, + Country = organization.Country, + City = organization.City, + Telephone = organization.Telephone, + Email = organization.Email, + Users = members?.Select(user => new UserDto(user)).ToList(), + }; + } + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Api/Program.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Api/Program.cs index 4561f84a3..bae6998ba 100644 --- a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Api/Program.cs +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Api/Program.cs @@ -39,6 +39,7 @@ public static async Task Main(string[] args) .Get("", ctx => ctx.Response.WriteAsync(ctx.RequestServices.GetService().Name)) .Get("posts/{postId}") .Get>("posts/search") + .Get>("posts/users/{userId}/feed") .Get>("posts/organizer/{organizerId}") .Put("posts/{postId}") .Delete("posts/{postId}") diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Dto/EducationDto.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Dto/EducationDto.cs new file mode 100644 index 000000000..d6ce3e726 --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Dto/EducationDto.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace MiniSpace.Services.Posts.Application.Dto +{ + [ExcludeFromCodeCoverage] + public class EducationDto + { + public string InstitutionName { get; set; } + public string Degree { get; set; } + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } + public string Description { get; set; } + } +} \ No newline at end of file diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Dto/FriendDto.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Dto/FriendDto.cs new file mode 100644 index 000000000..af175afc8 --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Dto/FriendDto.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MiniSpace.Services.Posts.Application.Dto +{ + public class FriendDto + { + public Guid Id { get; set; } + public Guid StudentId { get; set; } + public Guid FriendId { get; set; } + public DateTime CreatedAt { get; set; } + public string FriendState { get; set; } + } +} \ No newline at end of file diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Dto/UserDto.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Dto/UserDto.cs new file mode 100644 index 000000000..60ed82e35 --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Dto/UserDto.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + + +namespace MiniSpace.Services.Posts.Application.Dto +{ + [ExcludeFromCodeCoverage] + public class UserDto + { + public Guid Id { get; set; } + public string Email { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string ProfileImageUrl { get; set; } + public string Description { get; set; } + public DateTime? DateOfBirth { get; set; } + public bool EmailNotifications { get; set; } + public bool IsBanned { get; set; } + public string State { get; set; } + public DateTime CreatedAt { get; set; } + public string ContactEmail { get; set; } + public string BannerUrl { get; set; } + public string PhoneNumber { get; set; } + public IEnumerable Languages { get; set; } + public IEnumerable Interests { get; set; } + public IEnumerable Education { get; set; } + public IEnumerable Work { get; set; } + public bool IsTwoFactorEnabled { get; set; } + public string TwoFactorSecret { get; set; } + public IEnumerable InterestedInEvents { get; set; } + public IEnumerable SignedUpEvents { get; set; } + public string Country { get; set; } + public string City { get; set; } + } +} \ No newline at end of file diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Dto/UserFriendsDto.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Dto/UserFriendsDto.cs new file mode 100644 index 000000000..8b4cdeae7 --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Dto/UserFriendsDto.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MiniSpace.Services.Posts.Application.Dto +{ + public interface UserFriendsDto + { + public Guid StudentId { get; set; } + public List Friends { get; set; } + } +} \ No newline at end of file diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Dto/WorkDto.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Dto/WorkDto.cs new file mode 100644 index 000000000..edaf90f6a --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Dto/WorkDto.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace MiniSpace.Services.Posts.Application.Dto +{ + [ExcludeFromCodeCoverage] + public class WorkDto + { + public string Company { get; set; } + public string Position { get; set; } + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } + public string Description { get; set; } + } +} \ No newline at end of file diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Events/External/CommentCreated.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Events/External/CommentCreated.cs new file mode 100644 index 000000000..ed6d6a435 --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Events/External/CommentCreated.cs @@ -0,0 +1,37 @@ +using Convey.CQRS.Events; +using Convey.MessageBrokers; +using System; + +namespace MiniSpace.Services.Posts.Application.Events.External +{ + [Message("comments")] + public class CommentCreated : IEvent + { + public Guid CommentId { get; } + public Guid ContextId { get; } + public string CommentContext { get; } + public Guid UserId { get; } + public Guid ParentId { get; } + public string TextContent { get; } + public DateTime CreatedAt { get; } + public DateTime LastUpdatedAt { get; } + public int RepliesCount { get; } + public bool IsDeleted { get; } + + public CommentCreated(Guid commentId, Guid contextId, string commentContext, Guid userId, + Guid parentId, string textContent, DateTime createdAt, + DateTime lastUpdatedAt, int repliesCount, bool isDeleted) + { + CommentId = commentId; + ContextId = contextId; + CommentContext = commentContext; + UserId = userId; + ParentId = parentId; + TextContent = textContent; + CreatedAt = createdAt; + LastUpdatedAt = lastUpdatedAt; + RepliesCount = repliesCount; + IsDeleted = isDeleted; + } + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Events/External/Handlers/CommentCreatedHandler.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Events/External/Handlers/CommentCreatedHandler.cs new file mode 100644 index 000000000..170b02832 --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Events/External/Handlers/CommentCreatedHandler.cs @@ -0,0 +1,48 @@ +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Convey.CQRS.Events; +using MiniSpace.Services.Posts.Application.Events.External; +using MiniSpace.Services.Posts.Core.Entities; +using MiniSpace.Services.Posts.Core.Repositories; + +namespace MiniSpace.Services.Posts.Application.Events.External.Handlers +{ + public class CommentCreatedHandler : IEventHandler + { + private readonly IUserCommentsHistoryRepository _userCommentsHistoryRepository; + + public CommentCreatedHandler(IUserCommentsHistoryRepository userCommentsHistoryRepository) + { + _userCommentsHistoryRepository = userCommentsHistoryRepository; + } + + public async Task HandleAsync(CommentCreated @event, CancellationToken cancellationToken = default) + { + + var eventJson = JsonSerializer.Serialize(@event, new JsonSerializerOptions + { + WriteIndented = true // Optional: For pretty-printing the JSON + }); + Console.WriteLine("Received CommentCreated event:"); + Console.WriteLine(eventJson); + + // Create a new Comment entity based on the received event data + var comment = new Comment( + @event.CommentId, + @event.ContextId, + @event.CommentContext, + @event.UserId, + @event.ParentId, + @event.TextContent, + @event.CreatedAt, + @event.LastUpdatedAt, + @event.RepliesCount, + @event.IsDeleted + ); + + // Save the comment to the user's comment history + await _userCommentsHistoryRepository.SaveCommentAsync(@event.UserId, comment); + } + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Events/External/Handlers/ReactionCreatedHandler.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Events/External/Handlers/ReactionCreatedHandler.cs new file mode 100644 index 000000000..38612beeb --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Events/External/Handlers/ReactionCreatedHandler.cs @@ -0,0 +1,42 @@ +using System; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Convey.CQRS.Events; +using MiniSpace.Services.Posts.Application.Events.External; +using MiniSpace.Services.Posts.Core.Entities; +using MiniSpace.Services.Posts.Core.Repositories; + +namespace MiniSpace.Services.Posts.Application.Events.External.Handlers +{ + public class ReactionCreatedHandler : IEventHandler + { + private readonly IUserReactionsHistoryRepository _userReactionsHistoryRepository; + + public ReactionCreatedHandler(IUserReactionsHistoryRepository userReactionsHistoryRepository) + { + _userReactionsHistoryRepository = userReactionsHistoryRepository; + } + + public async Task HandleAsync(ReactionCreated @event, CancellationToken cancellationToken = default) + { + var eventJson = JsonSerializer.Serialize(@event, new JsonSerializerOptions + { + WriteIndented = true + }); + Console.WriteLine("Received ReactionCreated event:"); + Console.WriteLine(eventJson); + + var reaction = Reaction.Create( + @event.ReactionId, + @event.UserId, + @event.ReactionType, + @event.ContentId, + @event.ContentType, + @event.TargetType + ); + + await _userReactionsHistoryRepository.SaveReactionAsync(@event.UserId, reaction); + } + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Events/External/ReactionCreated.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Events/External/ReactionCreated.cs new file mode 100644 index 000000000..87d2283e9 --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Events/External/ReactionCreated.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Convey.CQRS.Events; +using Convey.MessageBrokers; + +namespace MiniSpace.Services.Posts.Application.Events.External +{ + [Message("reactions")] + public class ReactionCreated : IEvent + { + public Guid ReactionId { get; } + public Guid UserId { get; } + public Guid ContentId { get; } + public string ContentType { get; } + public string ReactionType { get; } + public string TargetType { get; } + + public ReactionCreated(Guid reactionId, Guid userId, Guid contentId, string contentType, string reactionType, string targetType) + { + ReactionId = reactionId; + UserId = userId; + ContentId = contentId; + ContentType = contentType; + ReactionType = reactionType; + TargetType = targetType; + } + } +} \ No newline at end of file diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Queries/GetUserFeed.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Queries/GetUserFeed.cs new file mode 100644 index 000000000..aa49850f9 --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Queries/GetUserFeed.cs @@ -0,0 +1,28 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Convey.CQRS.Queries; +using MiniSpace.Services.Posts.Application.Dto; +using MiniSpace.Services.Posts.Core.Wrappers; + +namespace MiniSpace.Services.Posts.Application.Queries +{ + [ExcludeFromCodeCoverage] + public class GetUserFeed : IQuery> + { + public Guid UserId { get; set; } + public int PageNumber { get; set; } = 1; + public int PageSize { get; set; } = 10; + public string SortBy { get; set; } = "PublishDate"; + public string Direction { get; set; } = "asc"; + + public GetUserFeed(Guid userId, int pageNumber = 1, int pageSize = 10, + string sortBy = "PublishDate", string direction = "asc") + { + UserId = userId; + PageNumber = pageNumber; + PageSize = pageSize; + SortBy = sortBy; + Direction = direction; + } + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Services/Clients/IFriendsServiceClient.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Services/Clients/IFriendsServiceClient.cs new file mode 100644 index 000000000..d4fc9f008 --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Services/Clients/IFriendsServiceClient.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MiniSpace.Services.Posts.Application.Dto; + +namespace MiniSpace.Services.Posts.Application.Services.Clients +{ + public interface IFriendsServiceClient + { + Task> GetAsync(Guid userId); + } +} \ No newline at end of file diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Services/Clients/IStudentsServiceClient.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Services/Clients/IStudentsServiceClient.cs index 849818345..0be4f83a0 100644 --- a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Services/Clients/IStudentsServiceClient.cs +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Services/Clients/IStudentsServiceClient.cs @@ -1,9 +1,12 @@ -using MiniSpace.Services.Posts.Application.Dto; +using System; +using System.Threading.Tasks; +using MiniSpace.Services.Posts.Application.Dto; namespace MiniSpace.Services.Posts.Application.Services.Clients { public interface IStudentsServiceClient { Task GetAsync(Guid id); + Task GetStudentByIdAsync(Guid studentId); } -} \ No newline at end of file +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Services/IPostRecommendationService.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Services/IPostRecommendationService.cs new file mode 100644 index 000000000..8f7a5f11d --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Application/Services/IPostRecommendationService.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using MiniSpace.Services.Posts.Application.Dto; + +namespace MiniSpace.Services.Posts.Application.Services +{ + public interface IPostRecommendationService + { + Task> RankPostsByUserInterestAsync(Guid userId, + IEnumerable posts, IDictionary userInterests); + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Core/Entities/Comment.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Core/Entities/Comment.cs new file mode 100644 index 000000000..408987a4d --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Core/Entities/Comment.cs @@ -0,0 +1,59 @@ +using System; + +namespace MiniSpace.Services.Posts.Core.Entities +{ + public class Comment + { + public Guid Id { get; private set; } + public Guid ContextId { get; private set; } + public string CommentContext { get; private set; } + public Guid UserId { get; private set; } + public Guid ParentId { get; private set; } + public string TextContent { get; private set; } + public DateTime CreatedAt { get; private set; } + public DateTime LastUpdatedAt { get; private set; } + public int RepliesCount { get; private set; } + public bool IsDeleted { get; private set; } + + public Comment(Guid id, Guid contextId, string commentContext, Guid userId, + Guid parentId, string textContent, DateTime createdAt, + DateTime lastUpdatedAt, int repliesCount, bool isDeleted) + { + Id = id; + ContextId = contextId; + CommentContext = commentContext; + UserId = userId; + ParentId = parentId; + TextContent = textContent; + CreatedAt = createdAt; + LastUpdatedAt = lastUpdatedAt; + RepliesCount = repliesCount; + IsDeleted = isDeleted; + } + + public void UpdateText(string newText, DateTime updatedAt) + { + TextContent = newText; + LastUpdatedAt = updatedAt; + } + + public void MarkAsDeleted() + { + IsDeleted = true; + TextContent = "[deleted]"; + } + + public void IncrementRepliesCount() + { + RepliesCount++; + } + + public void DecrementRepliesCount() + { + if (RepliesCount > 0) + { + RepliesCount--; + } + } + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Core/Entities/Reaction.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Core/Entities/Reaction.cs new file mode 100644 index 000000000..9237c83d4 --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Core/Entities/Reaction.cs @@ -0,0 +1,58 @@ +using System; + +namespace MiniSpace.Services.Posts.Core.Entities +{ + public class Reaction + { + public Guid Id { get; private set; } + public Guid UserId { get; private set; } + public Guid ContentId { get; private set; } + public string Type { get; private set; } + public string ContentType { get; private set; } + public string TargetType { get; private set; } + public DateTime CreatedAt { get; private set; } + + private Reaction() { } + + private Reaction(Guid id, Guid userId, string type, Guid contentId, string contentType, string targetType) + { + if (string.IsNullOrWhiteSpace(type)) + { + throw new ArgumentException("Reaction type cannot be null or empty.", nameof(type)); + } + + if (string.IsNullOrWhiteSpace(contentType)) + { + throw new ArgumentException("Content type cannot be null or empty.", nameof(contentType)); + } + + if (string.IsNullOrWhiteSpace(targetType)) + { + throw new ArgumentException("Target type cannot be null or empty.", nameof(targetType)); + } + + Id = id != Guid.Empty ? id : throw new ArgumentException("Reaction ID cannot be empty.", nameof(id)); + UserId = userId != Guid.Empty ? userId : throw new ArgumentException("User ID cannot be empty.", nameof(userId)); + ContentId = contentId != Guid.Empty ? contentId : throw new ArgumentException("Content ID cannot be empty.", nameof(contentId)); + Type = type; + ContentType = contentType; + TargetType = targetType; + CreatedAt = DateTime.UtcNow; + } + + public static Reaction Create(Guid id, Guid userId, string type, Guid contentId, string contentType, string targetType) + { + return new Reaction(id, userId, type, contentId, contentType, targetType); + } + + public void UpdateReactionType(string newType) + { + if (string.IsNullOrWhiteSpace(newType)) + { + throw new ArgumentException("Reaction type cannot be null or empty.", nameof(newType)); + } + + Type = newType; + } + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Core/Repositories/IUserCommentsHistoryRepository.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Core/Repositories/IUserCommentsHistoryRepository.cs new file mode 100644 index 000000000..e82c0c15f --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Core/Repositories/IUserCommentsHistoryRepository.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using MiniSpace.Services.Posts.Core.Entities; +using MiniSpace.Services.Posts.Core.Wrappers; + +namespace MiniSpace.Services.Posts.Core.Repositories +{ + public interface IUserCommentsHistoryRepository + { + /// + /// Saves a comment to the user's comment history. + /// + /// The ID of the user. + /// The comment to be saved. + /// A task representing the asynchronous operation. + Task SaveCommentAsync(Guid userId, Comment comment); + + /// + /// Retrieves all comments created by a specific user. + /// + /// The ID of the user. + /// A task representing the asynchronous operation, with a result of a list of comments. + Task> GetUserCommentsAsync(Guid userId); + + /// + /// Retrieves a paged response of comments created by a specific user. + /// + /// The ID of the user. + /// The page number to retrieve. + /// The number of comments per page. + /// A task representing the asynchronous operation, with a result of a paged response of comments. + Task> GetUserCommentsPagedAsync(Guid userId, int pageNumber, int pageSize); + + /// + /// Deletes a specific comment from the user's comment history. + /// + /// The ID of the user. + /// The ID of the comment to be deleted. + /// A task representing the asynchronous operation. + Task DeleteCommentAsync(Guid userId, Guid commentId); + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Core/Repositories/IUserReactionsHistoryRepository.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Core/Repositories/IUserReactionsHistoryRepository.cs new file mode 100644 index 000000000..39a03a855 --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Core/Repositories/IUserReactionsHistoryRepository.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using MiniSpace.Services.Posts.Core.Entities; +using MiniSpace.Services.Posts.Core.Wrappers; + +namespace MiniSpace.Services.Posts.Core.Repositories +{ + public interface IUserReactionsHistoryRepository + { + /// + /// Saves a reaction to the user's reaction history. + /// + /// The ID of the user. + /// The reaction to be saved. + /// A task representing the asynchronous operation. + Task SaveReactionAsync(Guid userId, Reaction reaction); + + /// + /// Retrieves all reactions created by a specific user. + /// + /// The ID of the user. + /// A task representing the asynchronous operation, with a result of a list of reactions. + Task> GetUserReactionsAsync(Guid userId); + + /// + /// Retrieves a paged response of reactions created by a specific user. + /// + /// The ID of the user. + /// The page number to retrieve. + /// The number of reactions per page. + /// A task representing the asynchronous operation, with a result of a paged response of reactions. + Task> GetUserReactionsPagedAsync(Guid userId, int pageNumber, int pageSize); + + /// + /// Deletes a specific reaction from the user's reaction history. + /// + /// The ID of the user. + /// The ID of the reaction to be deleted. + /// A task representing the asynchronous operation. + Task DeleteReactionAsync(Guid userId, Guid reactionId); + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Extensions.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Extensions.cs index f57c4672a..1909891bd 100644 --- a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Extensions.cs +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Extensions.cs @@ -42,6 +42,7 @@ using System.Diagnostics.CodeAnalysis; using MiniSpace.Services.Events.Infrastructure.Services.Clients; using MiniSpace.Services.Posts.Application.Services.Clients; +using Microsoft.ML; namespace MiniSpace.Services.Posts.Infrastructure { @@ -56,10 +57,17 @@ public static IConveyBuilder AddInfrastructure(this IConveyBuilder builder) builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); + builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + + builder.Services.AddSingleton(new MLContext()); + builder.Services.AddTransient(); + builder.Services.AddTransient(); builder.Services.AddTransient(ctx => ctx.GetRequiredService().Create()); builder.Services.TryDecorate(typeof(ICommandHandler<>), typeof(OutboxCommandHandlerDecorator<>)); @@ -86,6 +94,8 @@ public static IConveyBuilder AddInfrastructure(this IConveyBuilder builder) .AddMongoRepository("user_events_posts") .AddMongoRepository("user_posts") .AddMongoRepository("posts") + .AddMongoRepository("user_comments_history") + .AddMongoRepository("user_reactions_history") .AddWebApiSwaggerDocs() .AddCertificateAuthentication() .AddSecurity(); @@ -106,7 +116,9 @@ public static IApplicationBuilder UseInfrastructure(this IApplicationBuilder app .SubscribeCommand() .SubscribeCommand() .SubscribeCommand() - .SubscribeEvent(); + .SubscribeEvent() + .SubscribeEvent() + .SubscribeEvent(); return app; } diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/MiniSpace.Services.Posts.Infrastructure.csproj b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/MiniSpace.Services.Posts.Infrastructure.csproj index 34640728d..f7e40f538 100644 --- a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/MiniSpace.Services.Posts.Infrastructure.csproj +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/MiniSpace.Services.Posts.Infrastructure.csproj @@ -30,6 +30,7 @@ + diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Documents/CommentDocument.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Documents/CommentDocument.cs new file mode 100644 index 000000000..f88b51164 --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Documents/CommentDocument.cs @@ -0,0 +1,19 @@ +using System; +using Convey.Types; + +namespace MiniSpace.Services.Posts.Infrastructure.Mongo.Documents +{ + public class CommentDocument : IIdentifiable + { + public Guid Id { get; set; } + public Guid ContextId { get; set; } + public string CommentContext { get; set; } + public Guid UserId { get; set; } + public Guid ParentId { get; set; } + public string TextContent { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime LastUpdatedAt { get; set; } + public int RepliesCount { get; set; } + public bool IsDeleted { get; set; } + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Documents/Extensions.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Documents/Extensions.cs index 2a40e02a2..18f83a151 100644 --- a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Documents/Extensions.cs +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Documents/Extensions.cs @@ -22,6 +22,8 @@ public static Post AsEntity(this PostDocument document) document.Visibility, document.UpdatedAt); + + public static PostDocument AsDocument(this Post entity) => new PostDocument() { @@ -54,5 +56,112 @@ public static PostDto AsDto(this PostDocument document) PublishDate = document.PublishDate, Visibility = document.Visibility.ToString().ToLowerInvariant() }; + + public static PostDto AsDto(this Post post) + { + return new PostDto + { + Id = post.Id, + UserId = post.UserId, + OrganizationId = post.OrganizationId, + EventId = post.EventId, + TextContent = post.TextContent, + MediaFiles = post.MediaFiles, + CreatedAt = post.CreatedAt, + UpdatedAt = post.UpdatedAt, + State = post.State.ToString().ToLowerInvariant(), + PublishDate = post.PublishDate, + Visibility = post.Visibility.ToString().ToLowerInvariant() + }; + } + + public static CommentDocument AsDocument(this Comment comment) + { + return new CommentDocument + { + Id = comment.Id, + ContextId = comment.ContextId, + CommentContext = comment.CommentContext, + UserId = comment.UserId, + ParentId = comment.ParentId, + TextContent = comment.TextContent, + CreatedAt = comment.CreatedAt, + LastUpdatedAt = comment.LastUpdatedAt, + RepliesCount = comment.RepliesCount, + IsDeleted = comment.IsDeleted + }; + } + + public static Comment AsEntity(this CommentDocument document) + { + return new Comment( + document.Id, + document.ContextId, + document.CommentContext, + document.UserId, + document.ParentId, + document.TextContent, + document.CreatedAt, + document.LastUpdatedAt, + document.RepliesCount, + document.IsDeleted + ); + } + + public static UserCommentsDocument AsDocument(this IEnumerable comments, Guid userId) + { + return new UserCommentsDocument + { + Id = Guid.NewGuid(), + UserId = userId, + Comments = comments.Select(comment => comment.AsDocument()).ToList() + }; + } + + public static IEnumerable AsEntities(this UserCommentsDocument document) + { + return document.Comments.Select(doc => doc.AsEntity()); + } + + public static ReactionDocument AsDocument(this Reaction reaction) + { + return new ReactionDocument + { + Id = reaction.Id, + UserId = reaction.UserId, + ContentId = reaction.ContentId, + ContentType = reaction.ContentType, + ReactionType = reaction.Type, + TargetType = reaction.TargetType, + CreatedAt = reaction.CreatedAt + }; + } + + public static Reaction AsEntity(this ReactionDocument document) + { + return Reaction.Create( + document.Id, + document.UserId, + document.ReactionType, + document.ContentId, + document.ContentType, + document.TargetType + ); + } + + public static UserReactionDocument AsDocument(this IEnumerable reactions, Guid userId) + { + return new UserReactionDocument + { + Id = Guid.NewGuid(), + UserId = userId, + Reactions = reactions.Select(reaction => reaction.AsDocument()).ToList() + }; + } + + public static IEnumerable AsEntities(this UserReactionDocument document) + { + return document.Reactions.Select(doc => doc.AsEntity()); + } } } diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Documents/ReactionDocument.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Documents/ReactionDocument.cs new file mode 100644 index 000000000..6e4b50a49 --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Documents/ReactionDocument.cs @@ -0,0 +1,15 @@ +using System; + +namespace MiniSpace.Services.Posts.Infrastructure.Mongo.Documents +{ + public class ReactionDocument + { + public Guid Id { get; set; } + public Guid UserId { get; set; } + public Guid ContentId { get; set; } + public string ContentType { get; set; } + public string ReactionType { get; set; } + public string TargetType { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Documents/UserCommentsDocument.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Documents/UserCommentsDocument.cs new file mode 100644 index 000000000..bdd649a2c --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Documents/UserCommentsDocument.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using Convey.Types; +using MongoDB.Bson.Serialization.Attributes; + +namespace MiniSpace.Services.Posts.Infrastructure.Mongo.Documents +{ + public class UserCommentsDocument : IIdentifiable + { + [BsonId] + [BsonRepresentation(MongoDB.Bson.BsonType.String)] + public Guid Id { get; set; } + public Guid UserId { get; set; } + public IEnumerable Comments { get; set; } + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Documents/UserReactionDocument.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Documents/UserReactionDocument.cs new file mode 100644 index 000000000..190bef22e --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Documents/UserReactionDocument.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using Convey.Types; +using MongoDB.Bson.Serialization.Attributes; + +namespace MiniSpace.Services.Posts.Infrastructure.Mongo.Documents +{ + public class UserReactionDocument : IIdentifiable + { + [BsonId] + [BsonRepresentation(MongoDB.Bson.BsonType.String)] + public Guid Id { get; set; } + public Guid UserId { get; set; } + public IEnumerable Reactions { get; set; } + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Queries/Handlers/GetPostHandler.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Queries/Handlers/GetPostHandler.cs index 4e88709a2..35a56b605 100644 --- a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Queries/Handlers/GetPostHandler.cs +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Queries/Handlers/GetPostHandler.cs @@ -1,8 +1,12 @@ using System.Diagnostics.CodeAnalysis; using Convey.CQRS.Queries; -using Convey.Persistence.MongoDB; using MiniSpace.Services.Posts.Application.Dto; using MiniSpace.Services.Posts.Application.Queries; +using MiniSpace.Services.Posts.Core.Repositories; +using System; +using System.Threading; +using System.Threading.Tasks; +using MiniSpace.Services.Posts.Core.Entities; using MiniSpace.Services.Posts.Infrastructure.Mongo.Documents; namespace MiniSpace.Services.Posts.Infrastructure.Mongo.Queries.Handlers @@ -10,18 +14,52 @@ namespace MiniSpace.Services.Posts.Infrastructure.Mongo.Queries.Handlers [ExcludeFromCodeCoverage] public class GetPostHandler : IQueryHandler { - private readonly IMongoRepository _repository; + private readonly IOrganizationEventPostRepository _organizationEventPostRepository; + private readonly IOrganizationPostRepository _organizationPostRepository; + private readonly IUserEventPostRepository _userEventPostRepository; + private readonly IUserPostRepository _userPostRepository; - public GetPostHandler(IMongoRepository repository) + public GetPostHandler( + IOrganizationEventPostRepository organizationEventPostRepository, + IOrganizationPostRepository organizationPostRepository, + IUserEventPostRepository userEventPostRepository, + IUserPostRepository userPostRepository) { - _repository = repository; + _organizationEventPostRepository = organizationEventPostRepository; + _organizationPostRepository = organizationPostRepository; + _userEventPostRepository = userEventPostRepository; + _userPostRepository = userPostRepository; } public async Task HandleAsync(GetPost query, CancellationToken cancellationToken) { - var post = await _repository.GetAsync(query.PostId); + Post post = null; - return post?.AsDto(); + post = await _organizationEventPostRepository.GetAsync(query.PostId); + if (post != null) + { + return post.AsDto(); + } + + post = await _organizationPostRepository.GetAsync(query.PostId); + if (post != null) + { + return post.AsDto(); + } + + post = await _userEventPostRepository.GetAsync(query.PostId); + if (post != null) + { + return post.AsDto(); + } + + post = await _userPostRepository.GetAsync(query.PostId); + if (post != null) + { + return post.AsDto(); + } + + return null; } } } diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Queries/Handlers/GetUserFeedHandler.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Queries/Handlers/GetUserFeedHandler.cs new file mode 100644 index 000000000..c6e4095cb --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Queries/Handlers/GetUserFeedHandler.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Convey.CQRS.Queries; +using MiniSpace.Services.Posts.Application.Dto; +using MiniSpace.Services.Posts.Application.Queries; +using MiniSpace.Services.Posts.Application.Services; +using MiniSpace.Services.Posts.Core.Repositories; +using MiniSpace.Services.Posts.Core.Wrappers; +using Microsoft.Extensions.Logging; +using MiniSpace.Services.Posts.Core.Entities; +using MiniSpace.Services.Posts.Core.Requests; +using Newtonsoft.Json; +using MiniSpace.Services.Posts.Application.Services.Clients; + +namespace MiniSpace.Services.Posts.Infrastructure.Mongo.Queries.Handlers +{ + public class GetUserFeedHandler : IQueryHandler> + { + private readonly IPostsService _postsService; + private readonly IUserCommentsHistoryRepository _userCommentsHistoryRepository; + private readonly IUserReactionsHistoryRepository _userReactionsHistoryRepository; + private readonly IPostRecommendationService _postRecommendationService; + private readonly IStudentsServiceClient _studentsServiceClient; + private readonly ILogger _logger; + + public GetUserFeedHandler( + IPostsService postsService, + IUserCommentsHistoryRepository userCommentsHistoryRepository, + IUserReactionsHistoryRepository userReactionsHistoryRepository, + IPostRecommendationService postRecommendationService, + IStudentsServiceClient studentsServiceClient, + ILogger logger) + { + _postsService = postsService; + _userCommentsHistoryRepository = userCommentsHistoryRepository; + _userReactionsHistoryRepository = userReactionsHistoryRepository; + _postRecommendationService = postRecommendationService; + _studentsServiceClient = studentsServiceClient; + _logger = logger; + } + + public async Task> HandleAsync(GetUserFeed query, CancellationToken cancellationToken) + { + _logger.LogInformation("Handling GetUserFeed query: {Query}", JsonConvert.SerializeObject(query)); + + // Retrieve the user details + var user = await _studentsServiceClient.GetStudentByIdAsync(query.UserId); + + // Step 1: Retrieve all posts without pagination (we'll handle ranking and pagination manually) + var allPostsRequest = new BrowseRequest + { + SortBy = new List { query.SortBy }, + Direction = query.Direction + }; + + var allPostsResult = await _postsService.BrowsePostsAsync(allPostsRequest); + + if (allPostsResult == null || !allPostsResult.Items.Any()) + { + return new PagedResponse(Enumerable.Empty(), query.PageNumber, query.PageSize, 0); + } + + // Step 2: Retrieve user interactions (comments and reactions) + var userComments = await _userCommentsHistoryRepository.GetUserCommentsAsync(query.UserId); + var userReactions = await _userReactionsHistoryRepository.GetUserReactionsAsync(query.UserId); + + // Step 3: Analyze user interactions to infer interests and calculate coefficients, including user's interests and languages + var userInterests = AnalyzeUserInteractions(user, userComments, userReactions); + + // Step 4: Rank all posts by relevance to user interests + var rankedPosts = await _postRecommendationService.RankPostsByUserInterestAsync(query.UserId, allPostsResult.Items, userInterests); + + // Step 5: Combine ranked posts with remaining posts, prioritizing more relevant posts + var combinedPosts = CombineRankedAndUnrankedPosts(rankedPosts, allPostsResult.Items); + + // Step 6: Paginate the combined posts + var pagedPosts = combinedPosts + .Skip((query.PageNumber - 1) * query.PageSize) + .Take(query.PageSize) + .ToList(); + + // Log the result + _logger.LogInformation("User {UserId} feed generated with {PostCount} posts.", query.UserId, pagedPosts.Count); + + return new PagedResponse(pagedPosts, query.PageNumber, query.PageSize, combinedPosts.Count()); + } + + + private IDictionary AnalyzeUserInteractions(UserDto user, IEnumerable userComments, IEnumerable userReactions) + { + var interestKeywords = new Dictionary(); + + // Include user interests in the analysis + foreach (var interest in user.Interests) + { + if (interestKeywords.ContainsKey(interest)) + { + interestKeywords[interest] += 1; + } + else + { + interestKeywords[interest] = 1; + } + } + + // Include user languages in the analysis + foreach (var language in user.Languages) + { + if (interestKeywords.ContainsKey(language)) + { + interestKeywords[language] += 1; + } + else + { + interestKeywords[language] = 1; + } + } + + // Analyze user comments + foreach (var comment in userComments) + { + var words = comment.TextContent.Split(' ', StringSplitOptions.RemoveEmptyEntries); + foreach (var word in words) + { + if (interestKeywords.ContainsKey(word)) + { + interestKeywords[word] += 1; + } + else + { + interestKeywords[word] = 1; + } + } + } + + // Analyze user reactions + foreach (var reaction in userReactions) + { + var reactionType = reaction.Type; + if (interestKeywords.ContainsKey(reactionType)) + { + interestKeywords[reactionType] += 1; + } + else + { + interestKeywords[reactionType] = 1; + } + } + + // Normalize coefficients to sum to 1 + var total = interestKeywords.Values.Sum(); + var normalizedInterests = interestKeywords.ToDictionary(kvp => kvp.Key, kvp => kvp.Value / total); + + _logger.LogInformation("Inferred user interests with coefficients: {Interests}", string.Join(", ", normalizedInterests.Select(kvp => $"{kvp.Key}: {kvp.Value:F2}"))); + + return normalizedInterests; + } + + + private IEnumerable CombineRankedAndUnrankedPosts(IEnumerable<(PostDto Post, double Score)> rankedPosts, IEnumerable allPosts) + { + // Order posts by the relevance score first and then by publish date if the score is equal + var rankedPostIds = rankedPosts.Select(rp => rp.Post.Id).ToHashSet(); + var unrankedPosts = allPosts.Where(p => !rankedPostIds.Contains(p.Id)); + + return rankedPosts.Select(rp => rp.Post) + .Concat(unrankedPosts.OrderByDescending(p => p.PublishDate)); // Fallback to publish date for unranked posts + } + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Repositories/UserCommentsHistoryRepository.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Repositories/UserCommentsHistoryRepository.cs new file mode 100644 index 000000000..860529725 --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Repositories/UserCommentsHistoryRepository.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MongoDB.Driver; +using MiniSpace.Services.Posts.Core.Entities; +using MiniSpace.Services.Posts.Core.Repositories; +using MiniSpace.Services.Posts.Core.Wrappers; +using MiniSpace.Services.Posts.Infrastructure.Mongo.Documents; + +namespace MiniSpace.Services.Posts.Infrastructure.Mongo.Repositories +{ + public class UserCommentsHistoryRepository : IUserCommentsHistoryRepository + { + private readonly IMongoCollection _collection; + + public UserCommentsHistoryRepository(IMongoDatabase database) + { + _collection = database.GetCollection("user_comments_history"); + } + + public async Task SaveCommentAsync(Guid userId, Comment comment) + { + var filter = Builders.Filter.Eq(uc => uc.UserId, userId); + + var commentDocument = comment.AsDocument(); + + var update = Builders.Update.Combine( + Builders.Update.Push(uc => uc.Comments, commentDocument), + Builders.Update.SetOnInsert(uc => uc.UserId, userId), + Builders.Update.SetOnInsert(uc => uc.Id, Guid.NewGuid()) + ); + + await _collection.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = true }); + } + + public async Task> GetUserCommentsAsync(Guid userId) + { + var userCommentsDocument = await _collection.Find(uc => uc.UserId == userId).FirstOrDefaultAsync(); + return userCommentsDocument?.AsEntities() ?? Enumerable.Empty(); + } + + public async Task> GetUserCommentsPagedAsync(Guid userId, int pageNumber, int pageSize) + { + var userCommentsDocument = await _collection.Find(uc => uc.UserId == userId).FirstOrDefaultAsync(); + + if (userCommentsDocument == null) + { + return new PagedResponse(Enumerable.Empty(), pageNumber, pageSize, 0); + } + + var pagedComments = userCommentsDocument.Comments + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .Select(doc => doc.AsEntity()); + + return new PagedResponse(pagedComments, pageNumber, pageSize, userCommentsDocument.Comments.Count()); + } + + public async Task DeleteCommentAsync(Guid userId, Guid commentId) + { + var filter = Builders.Filter.And( + Builders.Filter.Eq(uc => uc.UserId, userId), + Builders.Filter.ElemMatch(uc => uc.Comments, c => c.Id == commentId) + ); + + var update = Builders.Update.PullFilter(uc => uc.Comments, c => c.Id == commentId); + await _collection.UpdateOneAsync(filter, update); + } + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Repositories/UserReactionsHistoryRepository.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Repositories/UserReactionsHistoryRepository.cs new file mode 100644 index 000000000..c2174362e --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Mongo/Repositories/UserReactionsHistoryRepository.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MongoDB.Driver; +using MiniSpace.Services.Posts.Core.Entities; +using MiniSpace.Services.Posts.Core.Repositories; +using MiniSpace.Services.Posts.Core.Wrappers; +using MiniSpace.Services.Posts.Infrastructure.Mongo.Documents; + +namespace MiniSpace.Services.Posts.Infrastructure.Mongo.Repositories +{ + public class UserReactionsHistoryRepository : IUserReactionsHistoryRepository + { + private readonly IMongoCollection _collection; + + public UserReactionsHistoryRepository(IMongoDatabase database) + { + _collection = database.GetCollection("user_reactions_history"); + } + + public async Task SaveReactionAsync(Guid userId, Reaction reaction) + { + var filter = Builders.Filter.Eq(ur => ur.UserId, userId); + + var reactionDocument = reaction.AsDocument(); + + var update = Builders.Update.Combine( + Builders.Update.Push(ur => ur.Reactions, reactionDocument), + Builders.Update.SetOnInsert(ur => ur.UserId, userId), + Builders.Update.SetOnInsert(ur => ur.Id, Guid.NewGuid()) + ); + + await _collection.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = true }); + } + + public async Task> GetUserReactionsAsync(Guid userId) + { + var userReactionsDocument = await _collection.Find(ur => ur.UserId == userId).FirstOrDefaultAsync(); + return userReactionsDocument?.AsEntities() ?? Enumerable.Empty(); + } + + public async Task> GetUserReactionsPagedAsync(Guid userId, int pageNumber, int pageSize) + { + var userReactionsDocument = await _collection.Find(ur => ur.UserId == userId).FirstOrDefaultAsync(); + + if (userReactionsDocument == null) + { + return new PagedResponse(Enumerable.Empty(), pageNumber, pageSize, 0); + } + + var pagedReactions = userReactionsDocument.Reactions + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .Select(doc => doc.AsEntity()); + + return new PagedResponse(pagedReactions, pageNumber, pageSize, userReactionsDocument.Reactions.Count()); + } + + public async Task DeleteReactionAsync(Guid userId, Guid reactionId) + { + var filter = Builders.Filter.And( + Builders.Filter.Eq(ur => ur.UserId, userId), + Builders.Filter.ElemMatch(ur => ur.Reactions, r => r.Id == reactionId) + ); + + var update = Builders.Update.PullFilter(ur => ur.Reactions, r => r.Id == reactionId); + await _collection.UpdateOneAsync(filter, update); + } + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Services/Clients/FriendsServiceClient.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Services/Clients/FriendsServiceClient.cs new file mode 100644 index 000000000..2951f8610 --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Services/Clients/FriendsServiceClient.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Convey.HTTP; +using MiniSpace.Services.Posts.Application.Dto; +using MiniSpace.Services.Posts.Application.Services.Clients; + +namespace MiniSpace.Services.Posts.Infrastructure.Services.Clients +{ + [ExcludeFromCodeCoverage] + public class FriendsServiceClient : IFriendsServiceClient + { + private readonly IHttpClient _httpClient; + private readonly string _url; + + public FriendsServiceClient(IHttpClient httpClient, HttpClientOptions options) + { + _httpClient = httpClient; + _url = options.Services["friends"]; + } + + public Task> GetAsync(Guid userId) + => _httpClient.GetAsync>($"{_url}/friends/{userId}"); + } +} \ No newline at end of file diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Services/Clients/StudentsServiceClient.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Services/Clients/StudentsServiceClient.cs index 39963ce07..41530b2e4 100644 --- a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Services/Clients/StudentsServiceClient.cs +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Services/Clients/StudentsServiceClient.cs @@ -22,5 +22,7 @@ public StudentsServiceClient(IHttpClient httpClient, HttpClientOptions options) public Task GetAsync(Guid id) => _httpClient.GetAsync($"{_url}/students/{id}/events"); + public Task GetStudentByIdAsync(Guid studentId) + => _httpClient.GetAsync($"{_url}/students/{studentId}"); } -} \ No newline at end of file +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Services/PostRecommendationService.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Services/PostRecommendationService.cs new file mode 100644 index 000000000..7e8a40158 --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Services/PostRecommendationService.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.ML; +using Microsoft.ML.Data; +using MiniSpace.Services.Posts.Application.Dto; +using MiniSpace.Services.Posts.Application.Services; +using MiniSpace.Services.Posts.Infrastructure.Services.Recommendation; + +namespace MiniSpace.Services.Posts.Infrastructure.Services +{ + public class PostRecommendationService : IPostRecommendationService + { + private readonly MLContext _mlContext; + + public PostRecommendationService(MLContext mlContext) + { + _mlContext = mlContext; + } + + public async Task> RankPostsByUserInterestAsync(Guid userId, IEnumerable posts, IDictionary userInterests) + { + // Step 1: Prepare the training data + var trainingData = PrepareTrainingData(userInterests, posts); + + // Step 2: Train the model + var model = TrainModel(trainingData); + + // Step 3: Use the trained model to rank posts + var rankedPosts = new List<(PostDto Post, double Score)>(); + var predictionEngine = _mlContext.Model.CreatePredictionEngine(model); + + foreach (var post in posts) + { + var input = new PostInputModel + { + TextContent = post.TextContent, + TextLength = post.TextContent.Length, + KeywordMatchCount = (float)userInterests.Where(ui => post.TextContent.Contains(ui.Key)).Sum(ui => ui.Value), + PostAgeDays = (post.PublishDate.HasValue ? (float)(DateTime.UtcNow - post.PublishDate.Value).TotalDays : 0) + }; + + var prediction = predictionEngine.Predict(input); + rankedPosts.Add((post, prediction.Score)); + } + + return rankedPosts.OrderByDescending(x => x.Score); + } + + private IDataView PrepareTrainingData(IDictionary userInterests, IEnumerable posts) + { + var inputData = posts.Select(post => + { + var keywordMatches = userInterests.Where(ui => post.TextContent.Contains(ui.Key)).Sum(ui => ui.Value); + return new PostInputModel + { + TextContent = post.TextContent, + Label = (float)keywordMatches, + TextLength = post.TextContent.Length, + KeywordMatchCount = (float)keywordMatches, + PostAgeDays = (post.PublishDate.HasValue ? (float)(DateTime.UtcNow - post.PublishDate.Value).TotalDays : 0) + }; + }); + + return _mlContext.Data.LoadFromEnumerable(inputData); + } + + private ITransformer TrainModel(IDataView trainingData) + { + var dataProcessPipeline = _mlContext.Transforms.Text.FeaturizeText("Features", nameof(PostInputModel.TextContent)); + + var trainer = _mlContext.Regression.Trainers.Sdca(labelColumnName: "Label", featureColumnName: "Features"); + + var trainingPipeline = dataProcessPipeline.Append(trainer); + + var model = trainingPipeline.Fit(trainingData); + return model; + } + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Services/Recommendation/PostInputModel.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Services/Recommendation/PostInputModel.cs new file mode 100644 index 000000000..3718ccab9 --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Services/Recommendation/PostInputModel.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using Microsoft.ML.Data; + +namespace MiniSpace.Services.Posts.Infrastructure.Services.Recommendation +{ + public class PostInputModel + { + [LoadColumn(0)] + public string TextContent { get; set; } + + [LoadColumn(1)] + public float Label { get; set; } + + [LoadColumn(2)] + public float TextLength { get; set; } + + [LoadColumn(3)] + public float KeywordMatchCount { get; set; } + + [LoadColumn(4)] + public float PostAgeDays { get; set; } + } +} diff --git a/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Services/Recommendation/PostPrediction.cs b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Services/Recommendation/PostPrediction.cs new file mode 100644 index 000000000..529bd630c --- /dev/null +++ b/MiniSpace.Services.Posts/src/MiniSpace.Services.Posts.Infrastructure/Services/Recommendation/PostPrediction.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.ML.Data; + +namespace MiniSpace.Services.Posts.Infrastructure.Services.Recommendation +{ + public class PostPrediction + { + [ColumnName("Score")] + public float Score { get; set; } + } +} \ No newline at end of file diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Api/Program.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Api/Program.cs index a1961adcf..2d0f580b2 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Api/Program.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Api/Program.cs @@ -37,6 +37,8 @@ public static async Task Main(string[] args) .Get("reactions/summary") .Post("reactions", afterDispatch: (cmd, ctx) => ctx.Response.Created($"reactions/{cmd.ReactionId}")) + .Put("reactions/{reactionId}", + afterDispatch: (cmd, ctx) => ctx.Response.NoContent()) .Delete("reactions/{reactionId}") )) .UseLogging() diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Application/Commands/Handlers/CreateReactionHandler.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Application/Commands/Handlers/CreateReactionHandler.cs index de5991a05..9f39e64c0 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Application/Commands/Handlers/CreateReactionHandler.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Application/Commands/Handlers/CreateReactionHandler.cs @@ -68,21 +68,81 @@ public async Task HandleAsync(CreateReaction command, CancellationToken cancella var targetType = ParseTargetType(command.TargetType); var reactionType = ParseReactionType(command.ReactionType); - await EnsureReactionDoesNotExistAsync(command.ContentId, contentType, command.UserId); + var existingReaction = await FindExistingReactionAsync(command.ContentId, command.UserId, contentType, targetType); - // Check if the comment exists using the CommentServiceClient - if (contentType == ReactionContentType.Comment) + if (existingReaction != null) { - if (!await _commentServiceClient.CommentExistsAsync(command.ContentId)) - { - throw new CommentNotFoundException(command.ContentId); - } + // Update the existing reaction + existingReaction.UpdateReactionType(reactionType); + await UpdateReactionAsync(existingReaction, contentType, targetType); + } + else + { + // Create a new reaction + var reaction = Reaction.Create(command.ReactionId, command.UserId, reactionType, + command.ContentId, contentType, targetType); + + // Add the reaction to the appropriate repository + await AddReactionAsync(reaction, contentType, targetType); + } + + var reactionCreatedEvent = new ReactionCreated( + command.ReactionId, + command.UserId, + command.ContentId, + command.ContentType, + command.ReactionType, + command.TargetType + ); + + await _messageBroker.PublishAsync(reactionCreatedEvent); + } + + private async Task FindExistingReactionAsync(Guid contentId, Guid userId, ReactionContentType contentType, ReactionTargetType targetType) + { + // Check in each repository if a reaction already exists for the user and content + switch (contentType) + { + case ReactionContentType.Event when targetType == ReactionTargetType.Organization: + return await _orgEventRepository.GetAsync(contentId, userId); + + case ReactionContentType.Event when targetType == ReactionTargetType.User: + return await _userEventRepository.GetAsync(contentId, userId); + + case ReactionContentType.Post when targetType == ReactionTargetType.Organization: + return await _orgPostRepository.GetAsync(contentId, userId); + + case ReactionContentType.Post when targetType == ReactionTargetType.User: + return await _userPostRepository.GetAsync(contentId, userId); + + case ReactionContentType.Comment when targetType == ReactionTargetType.Organization: + if (await _orgEventCommentsRepository.ExistsAsync(contentId)) + { + return await _orgEventCommentsRepository.GetAsync(contentId, userId); + } + else if (await _orgPostCommentsRepository.ExistsAsync(contentId)) + { + return await _orgPostCommentsRepository.GetAsync(contentId, userId); + } + break; + + case ReactionContentType.Comment when targetType == ReactionTargetType.User: + if (await _userEventCommentsRepository.ExistsAsync(contentId)) + { + return await _userEventCommentsRepository.GetAsync(contentId, userId); + } + else if (await _userPostCommentsRepository.ExistsAsync(contentId)) + { + return await _userPostCommentsRepository.GetAsync(contentId, userId); + } + break; } - var reaction = Reaction.Create(command.ReactionId, command.UserId, reactionType, - command.ContentId, contentType, targetType); + return null; + } - // Add the reaction to the appropriate repository based on content and target types + private async Task AddReactionAsync(Reaction reaction, ReactionContentType contentType, ReactionTargetType targetType) + { switch (contentType) { case ReactionContentType.Event when targetType == ReactionTargetType.Organization: @@ -102,32 +162,77 @@ public async Task HandleAsync(CreateReaction command, CancellationToken cancella break; case ReactionContentType.Comment when targetType == ReactionTargetType.Organization: - if (await _orgEventCommentsRepository.ExistsAsync(command.ContentId)) + if (await _orgEventCommentsRepository.ExistsAsync(reaction.ContentId)) { await _orgEventCommentsRepository.AddAsync(reaction); } - else if (await _orgPostCommentsRepository.ExistsAsync(command.ContentId)) + else if (await _orgPostCommentsRepository.ExistsAsync(reaction.ContentId)) { await _orgPostCommentsRepository.AddAsync(reaction); } break; case ReactionContentType.Comment when targetType == ReactionTargetType.User: - if (await _userEventCommentsRepository.ExistsAsync(command.ContentId)) + if (await _userEventCommentsRepository.ExistsAsync(reaction.ContentId)) { await _userEventCommentsRepository.AddAsync(reaction); } - else if (await _userPostCommentsRepository.ExistsAsync(command.ContentId)) + else if (await _userPostCommentsRepository.ExistsAsync(reaction.ContentId)) { await _userPostCommentsRepository.AddAsync(reaction); } break; default: - throw new InvalidReactionContentTypeException(command.ContentType); + throw new InvalidReactionContentTypeException(reaction.ContentType.ToString()); } + } - await _messageBroker.PublishAsync(new ReactionCreated(command.ReactionId)); + private async Task UpdateReactionAsync(Reaction reaction, ReactionContentType contentType, ReactionTargetType targetType) + { + switch (contentType) + { + case ReactionContentType.Event when targetType == ReactionTargetType.Organization: + await _orgEventRepository.UpdateAsync(reaction); + break; + + case ReactionContentType.Event when targetType == ReactionTargetType.User: + await _userEventRepository.UpdateAsync(reaction); + break; + + case ReactionContentType.Post when targetType == ReactionTargetType.Organization: + await _orgPostRepository.UpdateAsync(reaction); + break; + + case ReactionContentType.Post when targetType == ReactionTargetType.User: + await _userPostRepository.UpdateAsync(reaction); + break; + + case ReactionContentType.Comment when targetType == ReactionTargetType.Organization: + if (await _orgEventCommentsRepository.ExistsAsync(reaction.ContentId)) + { + await _orgEventCommentsRepository.UpdateAsync(reaction); + } + else if (await _orgPostCommentsRepository.ExistsAsync(reaction.ContentId)) + { + await _orgPostCommentsRepository.UpdateAsync(reaction); + } + break; + + case ReactionContentType.Comment when targetType == ReactionTargetType.User: + if (await _userEventCommentsRepository.ExistsAsync(reaction.ContentId)) + { + await _userEventCommentsRepository.UpdateAsync(reaction); + } + else if (await _userPostCommentsRepository.ExistsAsync(reaction.ContentId)) + { + await _userPostCommentsRepository.UpdateAsync(reaction); + } + break; + + default: + throw new InvalidReactionContentTypeException(reaction.ContentType.ToString()); + } } private void ValidateStudentIdentity(CreateReaction command) @@ -177,13 +282,5 @@ private ReactionType ParseReactionType(string reactionType) return parsedReactionType; } - - private async Task EnsureReactionDoesNotExistAsync(Guid contentId, ReactionContentType contentType, Guid userId) - { - if (await _reactionRepository.ExistsAsync(contentId, contentType, userId)) - { - throw new StudentAlreadyGaveReactionException(userId, contentId, contentType); - } - } } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Application/Commands/Handlers/UpdateReactionHandler.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Application/Commands/Handlers/UpdateReactionHandler.cs new file mode 100644 index 000000000..2abec7466 --- /dev/null +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Application/Commands/Handlers/UpdateReactionHandler.cs @@ -0,0 +1,170 @@ +using Convey.CQRS.Commands; +using MiniSpace.Services.Reactions.Application.Exceptions; +using MiniSpace.Services.Reactions.Core.Entities; +using MiniSpace.Services.Reactions.Core.Repositories; +using System; +using System.Threading; +using System.Threading.Tasks; +using MiniSpace.Services.Reactions.Core.Exceptions; + +namespace MiniSpace.Services.Reactions.Application.Commands.Handlers +{ + public class UpdateReactionHandler : ICommandHandler + { + private readonly IReactionsOrganizationsEventRepository _orgEventRepository; + private readonly IReactionsOrganizationsPostRepository _orgPostRepository; + private readonly IReactionsUserEventRepository _userEventRepository; + private readonly IReactionsUserPostRepository _userPostRepository; + private readonly IReactionsOrganizationsEventCommentsRepository _orgEventCommentsRepository; + private readonly IReactionsOrganizationsPostCommentsRepository _orgPostCommentsRepository; + private readonly IReactionsUserEventCommentsRepository _userEventCommentsRepository; + private readonly IReactionsUserPostCommentsRepository _userPostCommentsRepository; + private readonly IAppContext _appContext; + + public UpdateReactionHandler( + IReactionsOrganizationsEventRepository orgEventRepository, + IReactionsOrganizationsPostRepository orgPostRepository, + IReactionsUserEventRepository userEventRepository, + IReactionsUserPostRepository userPostRepository, + IReactionsOrganizationsEventCommentsRepository orgEventCommentsRepository, + IReactionsOrganizationsPostCommentsRepository orgPostCommentsRepository, + IReactionsUserEventCommentsRepository userEventCommentsRepository, + IReactionsUserPostCommentsRepository userPostCommentsRepository, + IAppContext appContext) + { + _orgEventRepository = orgEventRepository; + _orgPostRepository = orgPostRepository; + _userEventRepository = userEventRepository; + _userPostRepository = userPostRepository; + _orgEventCommentsRepository = orgEventCommentsRepository; + _orgPostCommentsRepository = orgPostCommentsRepository; + _userEventCommentsRepository = userEventCommentsRepository; + _userPostCommentsRepository = userPostCommentsRepository; + _appContext = appContext; + } + + public async Task HandleAsync(UpdateReaction command, CancellationToken cancellationToken = default) + { + ValidateUserIdentity(command); + + var contentType = ParseContentType(command.ContentType); + var targetType = ParseTargetType(command.TargetType); + + // Attempt to find the reaction in the appropriate repository using the ReactionId + var reaction = await FindExistingReactionAsync(command.ReactionId, contentType, targetType); + if (reaction == null) + { + throw new ReactionNotFoundException(command.ReactionId); + } + + // Ensure the reaction belongs to the correct user + if (reaction.UserId != command.UserId) + { + throw new UnauthorizedReactionAccessException(command.ReactionId, command.UserId); + } + + // Update the reaction type + var newReactionType = ParseReactionType(command.NewReactionType); + reaction.UpdateReactionType(newReactionType); + + // Update the reaction in the appropriate repository + await UpdateReactionAsync(reaction, contentType, targetType); + } + + private async Task FindExistingReactionAsync(Guid reactionId, ReactionContentType contentType, ReactionTargetType targetType) + { + // Retrieve the reaction from the correct repository based on content and target type + return contentType switch + { + ReactionContentType.Event when targetType == ReactionTargetType.Organization => await _orgEventRepository.GetByIdAsync(reactionId), + ReactionContentType.Event when targetType == ReactionTargetType.User => await _userEventRepository.GetByIdAsync(reactionId), + ReactionContentType.Post when targetType == ReactionTargetType.Organization => await _orgPostRepository.GetByIdAsync(reactionId), + ReactionContentType.Post when targetType == ReactionTargetType.User => await _userPostRepository.GetByIdAsync(reactionId), + ReactionContentType.Comment when targetType == ReactionTargetType.Organization => await _orgEventCommentsRepository.GetByIdAsync(reactionId) ?? await _orgPostCommentsRepository.GetByIdAsync(reactionId), + ReactionContentType.Comment when targetType == ReactionTargetType.User => await _userEventCommentsRepository.GetByIdAsync(reactionId) ?? await _userPostCommentsRepository.GetByIdAsync(reactionId), + _ => null, + }; + } + + private async Task UpdateReactionAsync(Reaction reaction, ReactionContentType contentType, ReactionTargetType targetType) + { + switch (contentType) + { + case ReactionContentType.Event when targetType == ReactionTargetType.Organization: + await _orgEventRepository.UpdateAsync(reaction); + break; + case ReactionContentType.Event when targetType == ReactionTargetType.User: + await _userEventRepository.UpdateAsync(reaction); + break; + case ReactionContentType.Post when targetType == ReactionTargetType.Organization: + await _orgPostRepository.UpdateAsync(reaction); + break; + case ReactionContentType.Post when targetType == ReactionTargetType.User: + await _userPostRepository.UpdateAsync(reaction); + break; + case ReactionContentType.Comment when targetType == ReactionTargetType.Organization: + if (await _orgEventCommentsRepository.ExistsAsync(reaction.ContentId)) + { + await _orgEventCommentsRepository.UpdateAsync(reaction); + } + else if (await _orgPostCommentsRepository.ExistsAsync(reaction.ContentId)) + { + await _orgPostCommentsRepository.UpdateAsync(reaction); + } + break; + case ReactionContentType.Comment when targetType == ReactionTargetType.User: + if (await _userEventCommentsRepository.ExistsAsync(reaction.ContentId)) + { + await _userEventCommentsRepository.UpdateAsync(reaction); + } + else if (await _userPostCommentsRepository.ExistsAsync(reaction.ContentId)) + { + await _userPostCommentsRepository.UpdateAsync(reaction); + } + break; + default: + throw new InvalidReactionContentTypeException(contentType.ToString()); + } + } + + private void ValidateUserIdentity(UpdateReaction command) + { + var identity = _appContext.Identity; + + if (identity.IsAuthenticated && identity.Id != command.UserId) + { + throw new UnauthorizedReactionAccessException(command.ReactionId, command.UserId); + } + } + + private ReactionContentType ParseContentType(string contentType) + { + if (!Enum.TryParse(contentType, true, out var parsedContentType)) + { + throw new InvalidReactionContentTypeException(contentType); + } + + return parsedContentType; + } + + private ReactionTargetType ParseTargetType(string targetType) + { + if (!Enum.TryParse(targetType, true, out var parsedTargetType)) + { + throw new InvalidReactionTargetTypeException(targetType); + } + + return parsedTargetType; + } + + private ReactionType ParseReactionType(string reactionType) + { + if (!Enum.TryParse(reactionType, true, out var parsedReactionType)) + { + throw new InvalidReactionTypeException(reactionType); + } + + return parsedReactionType; + } + } +} diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Application/Commands/UpdateReaction.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Application/Commands/UpdateReaction.cs new file mode 100644 index 000000000..985807695 --- /dev/null +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Application/Commands/UpdateReaction.cs @@ -0,0 +1,23 @@ +using System; +using Convey.CQRS.Commands; + +namespace MiniSpace.Services.Reactions.Application.Commands +{ + public class UpdateReaction : ICommand + { + public Guid ReactionId { get; } + public Guid UserId { get; } + public string NewReactionType { get; } + public string ContentType { get; } + public string TargetType { get; } + + public UpdateReaction(Guid reactionId, Guid userId, string newReactionType, string contentType, string targetType) + { + ReactionId = reactionId; + UserId = userId; + NewReactionType = newReactionType; + ContentType = contentType; + TargetType = targetType; + } + } +} diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Application/Dto/ReactionsSummaryDto.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Application/Dto/ReactionsSummaryDto.cs index 76ba0bbf1..d8e2cc3dc 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Application/Dto/ReactionsSummaryDto.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Application/Dto/ReactionsSummaryDto.cs @@ -11,5 +11,6 @@ public class ReactionsSummaryDto(int numerOfReactions, ReactionType? dominant, G public ReactionType? DominantReaction { get; set; } = dominant; public Guid? AuthUserReactionId { get; set; } = authUserReactionId; public ReactionType? AuthUserReactionType { get; set; } = authUserReactionType; + public Dictionary ReactionsWithCounts { get; set; } = new Dictionary(); } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Application/Events/ReactionCreated.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Application/Events/ReactionCreated.cs index c3b5031a9..facdcb81c 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Application/Events/ReactionCreated.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Application/Events/ReactionCreated.cs @@ -1,16 +1,25 @@ -using System.Net.Mime; using Convey.CQRS.Events; -using Microsoft.AspNetCore.Connections; -using MiniSpace.Services.Reactions.Core.Entities; +using System; namespace MiniSpace.Services.Reactions.Application.Events { public class ReactionCreated : IEvent { - public Guid ReactionId {get;set;} - public ReactionCreated(Guid reactionId) + public Guid ReactionId { get; } + public Guid UserId { get; } + public Guid ContentId { get; } + public string ContentType { get; } + public string ReactionType { get; } + public string TargetType { get; } + + public ReactionCreated(Guid reactionId, Guid userId, Guid contentId, string contentType, string reactionType, string targetType) { - ReactionId=reactionId; + ReactionId = reactionId; + UserId = userId; + ContentId = contentId; + ContentType = contentType; + ReactionType = reactionType; + TargetType = targetType; } } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionRepository.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionRepository.cs index 6c995d83a..0bbed931d 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionRepository.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionRepository.cs @@ -10,6 +10,7 @@ public interface IReactionRepository Task AddAsync(Reaction reaction); Task DeleteAsync(Guid id); Task ExistsAsync(Guid contentId, ReactionContentType contentType, Guid studentId); + Task GetByIdAsync(Guid id, Guid userId); } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsOrganizationsEventCommentsRepository.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsOrganizationsEventCommentsRepository.cs index 064e6c05b..4aba43906 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsOrganizationsEventCommentsRepository.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsOrganizationsEventCommentsRepository.cs @@ -11,5 +11,7 @@ public interface IReactionsOrganizationsEventCommentsRepository Task AddAsync(Reaction reaction); Task UpdateAsync(Reaction reaction); Task DeleteAsync(Guid id); + Task> GetByContentIdAsync(Guid contentId); + Task GetAsync(Guid contentId, Guid userId); } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsOrganizationsEventRepository.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsOrganizationsEventRepository.cs index c0fcb095e..521f761bc 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsOrganizationsEventRepository.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsOrganizationsEventRepository.cs @@ -11,5 +11,7 @@ public interface IReactionsOrganizationsEventRepository Task AddAsync(Reaction reaction); Task UpdateAsync(Reaction reaction); Task DeleteAsync(Guid id); + Task> GetByContentIdAsync(Guid contentId); + Task GetAsync(Guid contentId, Guid userId); } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsOrganizationsPostCommentsRepository.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsOrganizationsPostCommentsRepository.cs index f07356bba..83b1c56be 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsOrganizationsPostCommentsRepository.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsOrganizationsPostCommentsRepository.cs @@ -11,5 +11,7 @@ public interface IReactionsOrganizationsPostCommentsRepository Task AddAsync(Reaction reaction); Task UpdateAsync(Reaction reaction); Task DeleteAsync(Guid id); + Task> GetByContentIdAsync(Guid contentId); + Task GetAsync(Guid contentId, Guid userId); } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsOrganizationsPostRepository.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsOrganizationsPostRepository.cs index 49d53c68a..a581670fb 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsOrganizationsPostRepository.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsOrganizationsPostRepository.cs @@ -11,5 +11,7 @@ public interface IReactionsOrganizationsPostRepository Task AddAsync(Reaction reaction); Task UpdateAsync(Reaction reaction); Task DeleteAsync(Guid id); + Task> GetByContentIdAsync(Guid contentId); + Task GetAsync(Guid contentId, Guid userId); } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsUserEventCommentsRepository.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsUserEventCommentsRepository.cs index 45462e143..f4d68bc21 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsUserEventCommentsRepository.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsUserEventCommentsRepository.cs @@ -11,5 +11,7 @@ public interface IReactionsUserEventCommentsRepository Task AddAsync(Reaction reaction); Task UpdateAsync(Reaction reaction); Task DeleteAsync(Guid id); + Task> GetByContentIdAsync(Guid contentId); + Task GetAsync(Guid contentId, Guid userId); } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsUserEventRepository.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsUserEventRepository.cs index bfe750bc7..acc9a4cfa 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsUserEventRepository.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsUserEventRepository.cs @@ -11,5 +11,7 @@ public interface IReactionsUserEventRepository Task AddAsync(Reaction reaction); Task UpdateAsync(Reaction reaction); Task DeleteAsync(Guid id); + Task> GetByContentIdAsync(Guid contentId); + Task GetAsync(Guid contentId, Guid userId); } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsUserPostCommentsRepository.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsUserPostCommentsRepository.cs index 5426156c5..559697bbe 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsUserPostCommentsRepository.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsUserPostCommentsRepository.cs @@ -11,5 +11,7 @@ public interface IReactionsUserPostCommentsRepository Task AddAsync(Reaction reaction); Task UpdateAsync(Reaction reaction); Task DeleteAsync(Guid id); + Task> GetByContentIdAsync(Guid contentId); + Task GetAsync(Guid contentId, Guid userId); } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsUserPostRepository.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsUserPostRepository.cs index 3440bc61b..8dbc36b22 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsUserPostRepository.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Core/Repositories/IReactionsUserPostRepository.cs @@ -10,6 +10,8 @@ public interface IReactionsUserPostRepository Task GetByIdAsync(Guid id); Task AddAsync(Reaction reaction); Task UpdateAsync(Reaction reaction); - Task DeleteAsync(Guid id); + Task DeleteAsync(Guid id); + Task> GetByContentIdAsync(Guid contentId); + Task GetAsync(Guid contentId, Guid userId); } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Extensions.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Extensions.cs index 99fe5159d..296a30ac0 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Extensions.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Extensions.cs @@ -42,6 +42,8 @@ using MiniSpace.Services.Reactions.Application.Events; using System.Diagnostics.CodeAnalysis; +using MiniSpace.Services.Reactions.Application.Services.Clients; +using MiniSpace.Services.Reactions.Infrastructure.Services.Clients; namespace MiniSpace.Services.Reactions.Infrastructure { @@ -61,6 +63,9 @@ public static IConveyBuilder AddInfrastructure(this IConveyBuilder builder) builder.Services.AddTransient(); builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddTransient(); diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Documents/Extensions.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Documents/Extensions.cs index 160097a89..61ae50dfd 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Documents/Extensions.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Documents/Extensions.cs @@ -175,6 +175,32 @@ public static IEnumerable ToEntities(this UserPostCommentsReactionDocu return document.Reactions.Select(r => r.ToEntity()); } + public static ReactionDto AsDto(this Reaction reaction) + { + return new ReactionDto + { + Id = reaction.Id, + UserId = reaction.UserId, + ContentId = reaction.ContentId, + ContentType = reaction.ContentType, + ReactionType = reaction.ReactionType, + TargetType = reaction.TargetType + }; + } + + // public static ReactionDto AsDto(this ReactionDocument document) + // { + // return new ReactionDto + // { + // Id = document.Id, + // UserId = document.UserId, + // ContentId = document.ContentId, + // ContentType = document.ContentType, + // ReactionType = document.ReactionType, + // TargetType = document.TargetType + // }; + // } + public static UpdateDefinition Push( this UpdateDefinitionBuilder builder, diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Documents/ReactionDocument.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Documents/ReactionDocument.cs index 8edffceb9..3508b1563 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Documents/ReactionDocument.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Documents/ReactionDocument.cs @@ -1,6 +1,8 @@ using System; using Convey.Types; using MiniSpace.Services.Reactions.Core.Entities; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; namespace MiniSpace.Services.Reactions.Infrastructure.Mongo.Documents { diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Queries/Handlers/GetReactionsHandler.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Queries/Handlers/GetReactionsHandler.cs index f94777694..7792cbbdd 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Queries/Handlers/GetReactionsHandler.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Queries/Handlers/GetReactionsHandler.cs @@ -1,34 +1,83 @@ -using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Convey.CQRS.Queries; -using Convey.Persistence.MongoDB; -using DnsClient; using MiniSpace.Services.Reactions.Application.Dto; using MiniSpace.Services.Reactions.Application.Queries; using MiniSpace.Services.Reactions.Core.Entities; -using MiniSpace.Services.Reactions.Infrastructure.Mongo.Documents; +using MiniSpace.Services.Reactions.Core.Repositories; using MiniSpace.Services.Reactions.Infrastructure.Mongo.Extensions; -using MongoDB.Driver; -using MongoDB.Driver.Linq; namespace MiniSpace.Services.Reactions.Infrastructure.Mongo.Queries.Handlers { - [ExcludeFromCodeCoverage] public class GetReactionsHandler : IQueryHandler> { - private readonly IMongoRepository _reactionRepository; + private readonly IReactionsUserPostRepository _userPostRepository; + private readonly IReactionsOrganizationsPostRepository _orgPostRepository; + private readonly IReactionsUserEventRepository _userEventRepository; + private readonly IReactionsOrganizationsEventRepository _orgEventRepository; + private readonly IReactionsUserPostCommentsRepository _userPostCommentsRepository; + private readonly IReactionsOrganizationsPostCommentsRepository _orgPostCommentsRepository; + private readonly IReactionsUserEventCommentsRepository _userEventCommentsRepository; + private readonly IReactionsOrganizationsEventCommentsRepository _orgEventCommentsRepository; - public GetReactionsHandler(IMongoRepository reactionRepository) + public GetReactionsHandler( + IReactionsUserPostRepository userPostRepository, + IReactionsOrganizationsPostRepository orgPostRepository, + IReactionsUserEventRepository userEventRepository, + IReactionsOrganizationsEventRepository orgEventRepository, + IReactionsUserPostCommentsRepository userPostCommentsRepository, + IReactionsOrganizationsPostCommentsRepository orgPostCommentsRepository, + IReactionsUserEventCommentsRepository userEventCommentsRepository, + IReactionsOrganizationsEventCommentsRepository orgEventCommentsRepository) { - _reactionRepository = reactionRepository; + _userPostRepository = userPostRepository; + _orgPostRepository = orgPostRepository; + _userEventRepository = userEventRepository; + _orgEventRepository = orgEventRepository; + _userPostCommentsRepository = userPostCommentsRepository; + _orgPostCommentsRepository = orgPostCommentsRepository; + _userEventCommentsRepository = userEventCommentsRepository; + _orgEventCommentsRepository = orgEventCommentsRepository; } - + public async Task> HandleAsync(GetReactions query, CancellationToken cancellationToken) { - var documents = _reactionRepository.Collection.AsQueryable(); - documents = documents.Where(p => p.ContentId == query.ContentId && p.ContentType == query.ContentType); + List reactions = new(); + + switch (query.ContentType) + { + case ReactionContentType.Post: + var userPostReactions = await _userPostRepository.GetByContentIdAsync(query.ContentId); + var orgPostReactions = await _orgPostRepository.GetByContentIdAsync(query.ContentId); + + reactions.AddRange(userPostReactions); + reactions.AddRange(orgPostReactions); + break; + + case ReactionContentType.Event: + var userEventReactions = await _userEventRepository.GetByContentIdAsync(query.ContentId); + var orgEventReactions = await _orgEventRepository.GetByContentIdAsync(query.ContentId); + + reactions.AddRange(userEventReactions); + reactions.AddRange(orgEventReactions); + break; + + case ReactionContentType.Comment: + var userPostCommentReactions = await _userPostCommentsRepository.GetByContentIdAsync(query.ContentId); + var orgPostCommentReactions = await _orgPostCommentsRepository.GetByContentIdAsync(query.ContentId); + var userEventCommentReactions = await _userEventCommentsRepository.GetByContentIdAsync(query.ContentId); + var orgEventCommentReactions = await _orgEventCommentsRepository.GetByContentIdAsync(query.ContentId); + + reactions.AddRange(userPostCommentReactions); + reactions.AddRange(orgPostCommentReactions); + reactions.AddRange(userEventCommentReactions); + reactions.AddRange(orgEventCommentReactions); + break; + } - var reactions = await documents.ToListAsync(); - return reactions.Select(p => p.AsDto()); + return reactions.Select(r => r.AsDto()); } - } + } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Queries/Handlers/GetReactionsSummaryHandler.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Queries/Handlers/GetReactionsSummaryHandler.cs index 52af13fcf..652eb63d5 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Queries/Handlers/GetReactionsSummaryHandler.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Queries/Handlers/GetReactionsSummaryHandler.cs @@ -1,39 +1,89 @@ -using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Convey.CQRS.Queries; -using Convey.Persistence.MongoDB; using MiniSpace.Services.Reactions.Application; using MiniSpace.Services.Reactions.Application.Dto; using MiniSpace.Services.Reactions.Application.Queries; using MiniSpace.Services.Reactions.Core.Entities; -using MiniSpace.Services.Reactions.Infrastructure.Mongo.Documents; -using MongoDB.Driver; -using MongoDB.Driver.Linq; +using MiniSpace.Services.Reactions.Core.Repositories; namespace MiniSpace.Services.Reactions.Infrastructure.Mongo.Queries.Handlers { - [ExcludeFromCodeCoverage] public class GetReactionsSummaryHandler : IQueryHandler { - private readonly IMongoRepository _reactionRepository; - + private readonly IReactionsUserPostRepository _userPostRepository; + private readonly IReactionsOrganizationsPostRepository _orgPostRepository; + private readonly IReactionsUserEventRepository _userEventRepository; + private readonly IReactionsOrganizationsEventRepository _orgEventRepository; + private readonly IReactionsUserPostCommentsRepository _userPostCommentsRepository; + private readonly IReactionsOrganizationsPostCommentsRepository _orgPostCommentsRepository; + private readonly IReactionsUserEventCommentsRepository _userEventCommentsRepository; + private readonly IReactionsOrganizationsEventCommentsRepository _orgEventCommentsRepository; private readonly IAppContext _appContext; - public GetReactionsSummaryHandler(IMongoRepository reactionRepository, IAppContext appContext) + public GetReactionsSummaryHandler( + IReactionsUserPostRepository userPostRepository, + IReactionsOrganizationsPostRepository orgPostRepository, + IReactionsUserEventRepository userEventRepository, + IReactionsOrganizationsEventRepository orgEventRepository, + IReactionsUserPostCommentsRepository userPostCommentsRepository, + IReactionsOrganizationsPostCommentsRepository orgPostCommentsRepository, + IReactionsUserEventCommentsRepository userEventCommentsRepository, + IReactionsOrganizationsEventCommentsRepository orgEventCommentsRepository, + IAppContext appContext) { - _reactionRepository = reactionRepository; + _userPostRepository = userPostRepository; + _orgPostRepository = orgPostRepository; + _userEventRepository = userEventRepository; + _orgEventRepository = orgEventRepository; + _userPostCommentsRepository = userPostCommentsRepository; + _orgPostCommentsRepository = orgPostCommentsRepository; + _userEventCommentsRepository = userEventCommentsRepository; + _orgEventCommentsRepository = orgEventCommentsRepository; _appContext = appContext; } - - public async Task - HandleAsync(GetReactionsSummary query, CancellationToken cancellationToken) + + public async Task HandleAsync(GetReactionsSummary query, CancellationToken cancellationToken) { - var documents = _reactionRepository.Collection.AsQueryable(); - documents = documents.Where(p => p.ContentId == query.ContentId && p.ContentType == query.ContentType); + List reactions = new(); + + switch (query.ContentType) + { + case ReactionContentType.Post: + var userPostReactions = await _userPostRepository.GetByContentIdAsync(query.ContentId); + var orgPostReactions = await _orgPostRepository.GetByContentIdAsync(query.ContentId); + + reactions.AddRange(userPostReactions); + reactions.AddRange(orgPostReactions); + break; + + case ReactionContentType.Event: + var userEventReactions = await _userEventRepository.GetByContentIdAsync(query.ContentId); + var orgEventReactions = await _orgEventRepository.GetByContentIdAsync(query.ContentId); + + reactions.AddRange(userEventReactions); + reactions.AddRange(orgEventReactions); + break; + + case ReactionContentType.Comment: + var userPostCommentReactions = await _userPostCommentsRepository.GetByContentIdAsync(query.ContentId); + var orgPostCommentReactions = await _orgPostCommentsRepository.GetByContentIdAsync(query.ContentId); + var userEventCommentReactions = await _userEventCommentsRepository.GetByContentIdAsync(query.ContentId); + var orgEventCommentReactions = await _orgEventCommentsRepository.GetByContentIdAsync(query.ContentId); + + reactions.AddRange(userPostCommentReactions); + reactions.AddRange(orgPostCommentReactions); + reactions.AddRange(userEventCommentReactions); + reactions.AddRange(orgEventCommentReactions); + break; + } - var reactions = await documents.ToListAsync(); int nrReactions = reactions.Count; - if (nrReactions == 0) { + if (nrReactions == 0) + { return new ReactionsSummaryDto(0, null, null, null); } @@ -41,15 +91,29 @@ public async Task Guid? authUserReactionId = null; ReactionType? authUserReactionType = null; - if (identity.IsAuthenticated && reactions.Exists(x => x.UserId == identity.Id)) { - var reactionDocument = reactions.Find(x => x.UserId == identity.Id); - authUserReactionId = reactionDocument.Id; - authUserReactionType = reactionDocument.ReactionType; + if (identity.IsAuthenticated) + { + var userReaction = reactions.FirstOrDefault(r => r.UserId == identity.Id); + if (userReaction != null) + { + authUserReactionId = userReaction.Id; + authUserReactionType = userReaction.ReactionType; + } } - ReactionType dominant = reactions.GroupBy(x => x.ReactionType) - .OrderBy(x => x.ToList().Count).Last().Key; - return new ReactionsSummaryDto(nrReactions, dominant, authUserReactionId, authUserReactionType); + var dominantReaction = reactions + .GroupBy(r => r.ReactionType) + .OrderByDescending(g => g.Count()) + .First().Key; + + var reactionsWithCounts = reactions + .GroupBy(r => r.ReactionType) + .ToDictionary(g => g.Key, g => g.Count()); + + return new ReactionsSummaryDto(nrReactions, dominantReaction, authUserReactionId, authUserReactionType) + { + ReactionsWithCounts = reactionsWithCounts + }; } - } + } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionMongoRepository.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionMongoRepository.cs index 2de1d1a73..518900901 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionMongoRepository.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionMongoRepository.cs @@ -42,5 +42,11 @@ public Task DeleteAsync(Guid id) public Task ExistsAsync(Guid contentId, ReactionContentType contentType, Guid studentId) => _repository.ExistsAsync(x => x.ContentId == contentId && x.ContentType == contentType && x.UserId == studentId); + + public async Task GetByIdAsync(Guid id, Guid userId) + { + var reaction = await _repository.GetAsync(x => x.Id == id && x.UserId == userId); + return reaction?.AsEntity(); + } } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsOrganizationsEventCommentsMongoRepository.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsOrganizationsEventCommentsMongoRepository.cs index 961b766ac..94373d7f4 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsOrganizationsEventCommentsMongoRepository.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsOrganizationsEventCommentsMongoRepository.cs @@ -29,12 +29,22 @@ public async Task GetByIdAsync(Guid id) public async Task AddAsync(Reaction reaction) { - var filter = Builders.Filter.Eq(x => x.OrganizationEventCommentId, reaction.ContentId); - var update = Builders.Update.Push(x => x.Reactions, reaction.AsDocument()); + var filter = Builders.Filter.Eq(d => d.OrganizationEventCommentId, reaction.ContentId); - await _repository.Collection.UpdateOneAsync(filter, update); - } + var update = Builders.Update.Combine( + Builders.Update.Push(d => d.Reactions, reaction.AsDocument()), + Builders.Update.SetOnInsert(d => d.OrganizationEventCommentId, reaction.ContentId), + Builders.Update.SetOnInsert(d => d.Id, Guid.NewGuid()) + ); + + var options = new UpdateOptions { IsUpsert = true }; + var result = await _repository.Collection.UpdateOneAsync(filter, update, options); + if (!result.IsAcknowledged || result.ModifiedCount == 0) + { + } + } + public async Task UpdateAsync(Reaction reaction) { var filter = Builders.Filter.And( @@ -54,5 +64,23 @@ public async Task DeleteAsync(Guid id) await _repository.Collection.UpdateOneAsync(filter, update); } + + public async Task> GetByContentIdAsync(Guid contentId) + { + var document = await _repository.GetAsync(d => d.OrganizationEventCommentId == contentId); + return document?.Reactions.Select(r => r.AsEntity()) ?? Enumerable.Empty(); + } + + public async Task GetAsync(Guid contentId, Guid userId) + { + var filter = Builders.Filter.And( + Builders.Filter.Eq(x => x.OrganizationEventCommentId, contentId), + Builders.Filter.ElemMatch(x => x.Reactions, r => r.UserId == userId) + ); + + var document = await _repository.Collection.Find(filter).FirstOrDefaultAsync(); + return document?.Reactions.FirstOrDefault(r => r.UserId == userId)?.AsEntity(); + } + } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsOrganizationsEventMongoRepository.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsOrganizationsEventMongoRepository.cs index 4743e54e8..ab76870f4 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsOrganizationsEventMongoRepository.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsOrganizationsEventMongoRepository.cs @@ -30,12 +30,22 @@ public async Task GetByIdAsync(Guid id) public async Task AddAsync(Reaction reaction) { var filter = Builders.Filter.Eq(d => d.OrganizationEventId, reaction.ContentId); - var update = Builders.Update - .Push(d => d.Reactions, reaction.AsDocument()); - await _repository.Collection.UpdateOneAsync(filter, update); + var update = Builders.Update.Combine( + Builders.Update.Push(d => d.Reactions, reaction.AsDocument()), + Builders.Update.SetOnInsert(d => d.OrganizationEventId, reaction.ContentId), + Builders.Update.SetOnInsert(d => d.Id, Guid.NewGuid()) + ); + + var options = new UpdateOptions { IsUpsert = true }; + var result = await _repository.Collection.UpdateOneAsync(filter, update, options); + + if (!result.IsAcknowledged || result.ModifiedCount == 0) + { + } } + public async Task UpdateAsync(Reaction reaction) { var filter = Builders.Filter.And( @@ -57,5 +67,22 @@ public async Task DeleteAsync(Guid id) await _repository.Collection.UpdateOneAsync(filter, update); } + + public async Task> GetByContentIdAsync(Guid contentId) + { + var document = await _repository.GetAsync(d => d.OrganizationEventId == contentId); + return document?.Reactions.Select(r => r.AsEntity()) ?? Enumerable.Empty(); + } + + public async Task GetAsync(Guid contentId, Guid userId) + { + var filter = Builders.Filter.And( + Builders.Filter.Eq(x => x.OrganizationEventId, contentId), + Builders.Filter.ElemMatch(x => x.Reactions, r => r.UserId == userId) + ); + + var document = await _repository.Collection.Find(filter).FirstOrDefaultAsync(); + return document?.Reactions.FirstOrDefault(r => r.UserId == userId)?.AsEntity(); + } } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsOrganizationsPostCommentsMongoRepository.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsOrganizationsPostCommentsMongoRepository.cs index f591d57e7..897a1c769 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsOrganizationsPostCommentsMongoRepository.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsOrganizationsPostCommentsMongoRepository.cs @@ -29,10 +29,20 @@ public async Task GetByIdAsync(Guid id) public async Task AddAsync(Reaction reaction) { - var filter = Builders.Filter.Eq(x => x.OrganizationPostCommentId, reaction.ContentId); - var update = Builders.Update.Push(x => x.Reactions, reaction.AsDocument()); + var filter = Builders.Filter.Eq(d => d.OrganizationPostCommentId, reaction.ContentId); - await _repository.Collection.UpdateOneAsync(filter, update); + var update = Builders.Update.Combine( + Builders.Update.Push(d => d.Reactions, reaction.AsDocument()), + Builders.Update.SetOnInsert(d => d.OrganizationPostCommentId, reaction.ContentId), + Builders.Update.SetOnInsert(d => d.Id, Guid.NewGuid()) + ); + + var options = new UpdateOptions { IsUpsert = true }; + var result = await _repository.Collection.UpdateOneAsync(filter, update, options); + + if (!result.IsAcknowledged || result.ModifiedCount == 0) + { + } } public async Task UpdateAsync(Reaction reaction) @@ -54,5 +64,22 @@ public async Task DeleteAsync(Guid id) await _repository.Collection.UpdateOneAsync(filter, update); } + + public async Task> GetByContentIdAsync(Guid contentId) + { + var document = await _repository.GetAsync(d => d.OrganizationPostCommentId == contentId); + return document?.Reactions.Select(r => r.AsEntity()) ?? Enumerable.Empty(); + } + + public async Task GetAsync(Guid contentId, Guid userId) + { + var filter = Builders.Filter.And( + Builders.Filter.Eq(x => x.OrganizationPostCommentId, contentId), + Builders.Filter.ElemMatch(x => x.Reactions, r => r.UserId == userId) + ); + + var document = await _repository.Collection.Find(filter).FirstOrDefaultAsync(); + return document?.Reactions.FirstOrDefault(r => r.UserId == userId)?.AsEntity(); + } } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsOrganizationsPostMongoRepository.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsOrganizationsPostMongoRepository.cs index 3ab31fc7b..ba3c10d71 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsOrganizationsPostMongoRepository.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsOrganizationsPostMongoRepository.cs @@ -30,10 +30,19 @@ public async Task GetByIdAsync(Guid id) public async Task AddAsync(Reaction reaction) { var filter = Builders.Filter.Eq(d => d.OrganizationPostId, reaction.ContentId); - var update = Builders.Update - .Push(d => d.Reactions, reaction.AsDocument()); - await _repository.Collection.UpdateOneAsync(filter, update); + var update = Builders.Update.Combine( + Builders.Update.Push(d => d.Reactions, reaction.AsDocument()), + Builders.Update.SetOnInsert(d => d.OrganizationPostId, reaction.ContentId), + Builders.Update.SetOnInsert(d => d.Id, Guid.NewGuid()) + ); + + var options = new UpdateOptions { IsUpsert = true }; + var result = await _repository.Collection.UpdateOneAsync(filter, update, options); + + if (!result.IsAcknowledged || result.ModifiedCount == 0) + { + } } public async Task UpdateAsync(Reaction reaction) @@ -57,5 +66,22 @@ public async Task DeleteAsync(Guid id) await _repository.Collection.UpdateOneAsync(filter, update); } + + public async Task> GetByContentIdAsync(Guid contentId) + { + var document = await _repository.GetAsync(d => d.OrganizationPostId == contentId); + return document?.Reactions.Select(r => r.AsEntity()) ?? Enumerable.Empty(); + } + + public async Task GetAsync(Guid contentId, Guid userId) + { + var filter = Builders.Filter.And( + Builders.Filter.Eq(x => x.OrganizationPostId, contentId), + Builders.Filter.ElemMatch(x => x.Reactions, r => r.UserId == userId) + ); + + var document = await _repository.Collection.Find(filter).FirstOrDefaultAsync(); + return document?.Reactions.FirstOrDefault(r => r.UserId == userId)?.AsEntity(); + } } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsUserEventCommentsMongoRepository.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsUserEventCommentsMongoRepository.cs index 7e298d3de..aad68a002 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsUserEventCommentsMongoRepository.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsUserEventCommentsMongoRepository.cs @@ -29,12 +29,23 @@ public async Task GetByIdAsync(Guid id) public async Task AddAsync(Reaction reaction) { - var filter = Builders.Filter.Eq(x => x.UserEventCommentId, reaction.ContentId); - var update = Builders.Update.Push(x => x.Reactions, reaction.AsDocument()); + var filter = Builders.Filter.Eq(d => d.UserEventCommentId, reaction.ContentId); - await _repository.Collection.UpdateOneAsync(filter, update); + var update = Builders.Update.Combine( + Builders.Update.Push(d => d.Reactions, reaction.AsDocument()), + Builders.Update.SetOnInsert(d => d.UserEventCommentId, reaction.ContentId), + Builders.Update.SetOnInsert(d => d.Id, Guid.NewGuid()) + ); + + var options = new UpdateOptions { IsUpsert = true }; + var result = await _repository.Collection.UpdateOneAsync(filter, update, options); + + if (!result.IsAcknowledged || result.ModifiedCount == 0) + { + } } + public async Task UpdateAsync(Reaction reaction) { var filter = Builders.Filter.And( @@ -54,5 +65,22 @@ public async Task DeleteAsync(Guid id) await _repository.Collection.UpdateOneAsync(filter, update); } + + public async Task> GetByContentIdAsync(Guid contentId) + { + var document = await _repository.GetAsync(d => d.UserEventCommentId == contentId); + return document?.Reactions.Select(r => r.AsEntity()) ?? Enumerable.Empty(); + } + + public async Task GetAsync(Guid contentId, Guid userId) + { + var filter = Builders.Filter.And( + Builders.Filter.Eq(x => x.UserEventCommentId, contentId), + Builders.Filter.ElemMatch(x => x.Reactions, r => r.UserId == userId) + ); + + var document = await _repository.Collection.Find(filter).FirstOrDefaultAsync(); + return document?.Reactions.FirstOrDefault(r => r.UserId == userId)?.AsEntity(); + } } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsUserEventMongoRepository.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsUserEventMongoRepository.cs index 91e8881f6..94787f938 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsUserEventMongoRepository.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsUserEventMongoRepository.cs @@ -30,12 +30,22 @@ public async Task GetByIdAsync(Guid id) public async Task AddAsync(Reaction reaction) { var filter = Builders.Filter.Eq(d => d.UserEventId, reaction.ContentId); - var update = Builders.Update - .Push(d => d.Reactions, reaction.AsDocument()); - await _repository.Collection.UpdateOneAsync(filter, update); + var update = Builders.Update.Combine( + Builders.Update.Push(d => d.Reactions, reaction.AsDocument()), + Builders.Update.SetOnInsert(d => d.UserEventId, reaction.ContentId), + Builders.Update.SetOnInsert(d => d.Id, Guid.NewGuid()) // Ensure the document Id is a new Guid + ); + + var options = new UpdateOptions { IsUpsert = true }; + var result = await _repository.Collection.UpdateOneAsync(filter, update, options); + + if (!result.IsAcknowledged || result.ModifiedCount == 0) + { + } } + public async Task UpdateAsync(Reaction reaction) { var filter = Builders.Filter.And( @@ -57,5 +67,22 @@ public async Task DeleteAsync(Guid id) await _repository.Collection.UpdateOneAsync(filter, update); } + + public async Task> GetByContentIdAsync(Guid contentId) + { + var document = await _repository.GetAsync(d => d.UserEventId == contentId); + return document?.Reactions.Select(r => r.AsEntity()) ?? Enumerable.Empty(); + } + + public async Task GetAsync(Guid contentId, Guid userId) + { + var filter = Builders.Filter.And( + Builders.Filter.Eq(x => x.UserEventId, contentId), + Builders.Filter.ElemMatch(x => x.Reactions, r => r.UserId == userId) + ); + + var document = await _repository.Collection.Find(filter).FirstOrDefaultAsync(); + return document?.Reactions.FirstOrDefault(r => r.UserId == userId)?.AsEntity(); + } } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsUserPostCommentsMongoRepository.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsUserPostCommentsMongoRepository.cs index 73006147d..ca207a328 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsUserPostCommentsMongoRepository.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsUserPostCommentsMongoRepository.cs @@ -27,14 +27,30 @@ public async Task GetByIdAsync(Guid id) return document?.Reactions.FirstOrDefault(r => r.Id == id)?.AsEntity(); } - public async Task AddAsync(Reaction reaction) + public async Task AddAsync(Reaction reaction) { + // Ensure the document's Id is set to the reaction's Id var filter = Builders.Filter.Eq(x => x.UserPostCommentId, reaction.ContentId); - var update = Builders.Update.Push(x => x.Reactions, reaction.AsDocument()); - await _repository.Collection.UpdateOneAsync(filter, update); + var update = Builders.Update.Combine( + Builders.Update.Push(x => x.Reactions, reaction.AsDocument()), + Builders.Update.SetOnInsert(x => x.UserPostCommentId, reaction.ContentId), + Builders.Update.SetOnInsert(x => x.Id, reaction.ContentId) // Set the Id to ensure it's not an ObjectId + ); + + var options = new UpdateOptions { IsUpsert = true }; // Upsert: insert if not exists, update if exists + var result = await _repository.Collection.UpdateOneAsync(filter, update, options); + + if (!result.IsAcknowledged || result.ModifiedCount == 0) + { + // Handle the case where the update wasn't acknowledged or nothing was modified + // This could involve logging or throwing an exception based on your application's needs + } } + + + public async Task UpdateAsync(Reaction reaction) { var filter = Builders.Filter.And( @@ -54,5 +70,22 @@ public async Task DeleteAsync(Guid id) await _repository.Collection.UpdateOneAsync(filter, update); } + + public async Task> GetByContentIdAsync(Guid contentId) + { + var document = await _repository.GetAsync(d => d.UserPostCommentId == contentId); + return document?.Reactions.Select(r => r.AsEntity()) ?? Enumerable.Empty(); + } + + public async Task GetAsync(Guid contentId, Guid userId) + { + var filter = Builders.Filter.And( + Builders.Filter.Eq(x => x.UserPostCommentId, contentId), + Builders.Filter.ElemMatch(x => x.Reactions, r => r.UserId == userId) + ); + + var document = await _repository.Collection.Find(filter).FirstOrDefaultAsync(); + return document?.Reactions.FirstOrDefault(r => r.UserId == userId)?.AsEntity(); + } } } diff --git a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsUserPostMongoRepository.cs b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsUserPostMongoRepository.cs index 66e03855a..ec55ed5bc 100644 --- a/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsUserPostMongoRepository.cs +++ b/MiniSpace.Services.Reactions/src/MiniSpace.Services.Reactions.Infrastructure/Mongo/Repositories/ReactionsUserPostMongoRepository.cs @@ -30,9 +30,21 @@ public async Task GetByIdAsync(Guid id) public async Task AddAsync(Reaction reaction) { var filter = Builders.Filter.Eq(x => x.UserPostId, reaction.ContentId); - var update = Builders.Update.Push(x => x.Reactions, reaction.AsDocument()); - await _repository.Collection.UpdateOneAsync(filter, update); + var update = Builders.Update.Combine( + Builders.Update.Push(x => x.Reactions, reaction.AsDocument()), + Builders.Update.SetOnInsert(x => x.UserPostId, reaction.ContentId), + Builders.Update.SetOnInsert(x => x.Id, Guid.NewGuid()) // Ensure the document Id is a new Guid + ); + + var options = new UpdateOptions { IsUpsert = true }; // Upsert: insert if not exists, update if exists + var result = await _repository.Collection.UpdateOneAsync(filter, update, options); + + if (!result.IsAcknowledged || result.ModifiedCount == 0) + { + // Handle the case where the update wasn't acknowledged or nothing was modified + // This could involve logging or throwing an exception based on your application's needs + } } public async Task UpdateAsync(Reaction reaction) @@ -54,5 +66,25 @@ public async Task DeleteAsync(Guid id) await _repository.Collection.UpdateOneAsync(filter, update); } + + public async Task> GetByContentIdAsync(Guid contentId) + { + var filter = Builders.Filter.Eq(d => d.UserPostId, contentId); + var document = await _repository.Collection.Find(filter).FirstOrDefaultAsync(); + + return document?.Reactions.Select(r => r.AsEntity()) ?? Enumerable.Empty(); + } + + public async Task GetAsync(Guid contentId, Guid userId) + { + var filter = Builders.Filter.And( + Builders.Filter.Eq(x => x.UserPostId, contentId), + Builders.Filter.ElemMatch(x => x.Reactions, r => r.UserId == userId) + ); + + var document = await _repository.Collection.Find(filter).FirstOrDefaultAsync(); + return document?.Reactions.FirstOrDefault(r => r.UserId == userId)?.AsEntity(); + } + } } diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommandsDto/AddLikeDto.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommandsDto/AddLikeDto.cs new file mode 100644 index 000000000..2a4b53f41 --- /dev/null +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommandsDto/AddLikeDto.cs @@ -0,0 +1,18 @@ +using System; + +namespace MiniSpace.Web.Areas.Comments.CommandDto +{ + public class AddLikeDto + { + public Guid CommentId { get; } + public Guid UserId { get; } + public string CommentContext { get; } + + public AddLikeDto(Guid commentId, Guid userId, string commentContext) + { + CommentId = commentId; + UserId = userId; + CommentContext = commentContext; + } + } +} diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommandsDto/CreateCommentCommand.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommandsDto/CreateCommentCommand.cs new file mode 100644 index 000000000..c6b436e65 --- /dev/null +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommandsDto/CreateCommentCommand.cs @@ -0,0 +1,25 @@ +using System; + +namespace MiniSpace.Web.Areas.Comments.CommandsDto +{ + public class CreateCommentCommand + { + public Guid CommentId { get; set; } + public Guid ContextId { get; set; } + public string CommentContext { get; set; } + // CommentsContext := UserPost || UserEvent || OrganizationPost || OrganizationEvent + public Guid UserId { get; set; } + public Guid ParentId { get; set; } + public string TextContent { get; set; } + + public CreateCommentCommand(Guid commentId, Guid contextId, string commentContext, Guid userId, Guid parentId, string textContent) + { + CommentId = commentId == Guid.Empty ? Guid.NewGuid() : commentId; + ContextId = contextId; + CommentContext = commentContext; + UserId = userId; + ParentId = parentId; + TextContent = textContent; + } + } +} diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommandsDto/SearchRootCommentsCommand.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommandsDto/SearchRootCommentsCommand.cs new file mode 100644 index 000000000..749f38395 --- /dev/null +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommandsDto/SearchRootCommentsCommand.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MiniSpace.Web.DTO.Wrappers; + +namespace MiniSpace.Web.Areas.Comments.CommandsDto +{ + public class SearchRootCommentsCommand + { + public Guid ContextId { get; set; } + public string CommentContext { get; set; } + public PageableDto Pageable { get; set; } + + public SearchRootCommentsCommand(Guid contextId, string commentContext, PageableDto pageable) + { + ContextId = contextId; + CommentContext = commentContext; + Pageable = pageable; + } + } +} \ No newline at end of file diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommandsDto/SearchSubCommentsCommand.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommandsDto/SearchSubCommentsCommand.cs new file mode 100644 index 000000000..a66d76474 --- /dev/null +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommandsDto/SearchSubCommentsCommand.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MiniSpace.Web.DTO.Wrappers; + +namespace MiniSpace.Web.Areas.Comments.CommandsDto +{ + public class SearchSubCommentsCommand + { + public Guid ContextId { get; set; } + public string CommentContext { get; set; } + public Guid ParentId { get; set; } + public PageableDto Pageable { get; set; } + + public SearchSubCommentsCommand(Guid contextId, string commentContext, Guid parentId, PageableDto pageable) + { + ContextId = contextId; + CommentContext = commentContext; + ParentId = parentId; + Pageable = pageable; + } + } +} \ No newline at end of file diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommandsDto/UpdateCommentCommand.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommandsDto/UpdateCommentCommand.cs new file mode 100644 index 000000000..984c5b4bb --- /dev/null +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommandsDto/UpdateCommentCommand.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MiniSpace.Web.Areas.Comments.CommandsDto +{ + public class UpdateCommentCommand + { + public Guid CommentId { get; set; } + public string TextContent { get; set; } + + public UpdateCommentCommand(Guid commentId, string textContent) + { + CommentId = commentId; + TextContent = textContent; + } + } +} \ No newline at end of file diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommentsService.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommentsService.cs index d95ab120c..9dc6d078e 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommentsService.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/CommentsService.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using System.Web; +using MiniSpace.Web.Areas.Comments.CommandDto; +using MiniSpace.Web.Areas.Comments.CommandsDto; using MiniSpace.Web.Areas.Identity; -using MiniSpace.Web.DTO; -using MiniSpace.Web.Data.Comments; +using MiniSpace.Web.DTO.Comments; using MiniSpace.Web.DTO.Wrappers; using MiniSpace.Web.HttpClients; @@ -20,18 +23,47 @@ public CommentsService(IHttpClient httpClient, IIdentityService identityService) _identityService = identityService; } - public Task>>> SearchRootCommentsAsync(Guid contextId, - string commentContext, PageableDto pageable) + public Task> SearchRootCommentsAsync(SearchRootCommentsCommand command) { - return _httpClient.PostAsync>>("comments/search", - new (contextId, commentContext, pageable)); + var queryString = ToQueryString(command); + + // Log the query string to the console + Console.WriteLine($"Sending request with query string: comments/search{queryString}"); + + return _httpClient.GetAsync>($"comments/search{queryString}"); } - public Task>>> SearchSubCommentsAsync(Guid contextId, - string commentContext, Guid parentId, PageableDto pageable) + + private string ToQueryString(SearchRootCommentsCommand command) + { + var query = HttpUtility.ParseQueryString(string.Empty); + query["ContextId"] = command.ContextId.ToString(); + query["CommentContext"] = command.CommentContext; + + // Flatten the PageableDto into individual query parameters + if (command.Pageable != null) + { + query["Page"] = command.Pageable.Page.ToString(); + query["Size"] = command.Pageable.Size.ToString(); + + if (command.Pageable.Sort != null) + { + // Pass SortBy as a comma-separated list + if (command.Pageable.Sort.SortBy != null && command.Pageable.Sort.SortBy.Any()) + { + query["SortBy"] = string.Join(",", command.Pageable.Sort.SortBy); + } + query["Direction"] = command.Pageable.Sort.Direction; + } + } + + return "?" + query.ToString(); + } + + + public Task>> SearchSubCommentsAsync(SearchSubCommentsCommand command) { - return _httpClient.PostAsync>>("comments/search", - new (contextId, commentContext, parentId, pageable)); + return _httpClient.PostAsync>("comments/search", command); } public Task GetCommentAsync(Guid commentId) @@ -39,18 +71,16 @@ public Task GetCommentAsync(Guid commentId) return _httpClient.GetAsync($"comments/{commentId}"); } - public Task> CreateCommentAsync(Guid commentId, Guid contextId, string commentContext, - Guid studentId, Guid parentId, string comment) + public Task> CreateCommentAsync(CreateCommentCommand command) { _httpClient.SetAccessToken(_identityService.JwtDto.AccessToken); - return _httpClient.PostAsync("comments", - new { commentId, contextId, commentContext, studentId, parentId, comment }); + return _httpClient.PostAsync("comments", command); } - public Task> UpdateCommentAsync(Guid commentId, string textContent) + public Task> UpdateCommentAsync(UpdateCommentCommand command) { _httpClient.SetAccessToken(_identityService.JwtDto.AccessToken); - return _httpClient.PutAsync($"comments/{commentId}", new { commentId, textContent}); + return _httpClient.PutAsync($"comments/{command.CommentId}", command); } public Task DeleteCommentAsync(Guid commentId) @@ -59,10 +89,10 @@ public Task DeleteCommentAsync(Guid commentId) return _httpClient.DeleteAsync($"comments/{commentId}"); } - public Task AddLikeAsync(Guid commentId) + public Task> AddLikeAsync(AddLikeDto command) { _httpClient.SetAccessToken(_identityService.JwtDto.AccessToken); - return _httpClient.PostAsync($"comments/{commentId}/like", new { commentId }); + return _httpClient.PostAsync($"comments/{command.CommentId}/like", command); } public Task DeleteLikeAsync(Guid commentId) diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/ICommentsService.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/ICommentsService.cs index 39c3ca3b4..cf5079101 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/ICommentsService.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Comments/ICommentsService.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using MiniSpace.Web.DTO; -using MiniSpace.Web.DTO.Enums; +using MiniSpace.Web.Areas.Comments.CommandDto; +using MiniSpace.Web.Areas.Comments.CommandsDto; +using MiniSpace.Web.DTO.Comments; using MiniSpace.Web.DTO.Wrappers; using MiniSpace.Web.HttpClients; @@ -10,16 +11,13 @@ namespace MiniSpace.Web.Areas.Comments { public interface ICommentsService { - Task>>> SearchRootCommentsAsync(Guid contextId, - string commentContext, PageableDto pageable); - Task>>> SearchSubCommentsAsync(Guid contextId, - string commentContext, Guid parentId, PageableDto pageable); + Task> SearchRootCommentsAsync(SearchRootCommentsCommand command); + Task>> SearchSubCommentsAsync(SearchSubCommentsCommand command); Task GetCommentAsync(Guid commentId); - Task> CreateCommentAsync(Guid commentId, Guid contextId, string commentContext, - Guid studentId, Guid parentId, string comment); - Task> UpdateCommentAsync(Guid commentId, string textContext); + Task> CreateCommentAsync(CreateCommentCommand command); + Task> UpdateCommentAsync(UpdateCommentCommand command); Task DeleteCommentAsync(Guid commentId); - Task AddLikeAsync(Guid commentId); + Task> AddLikeAsync(AddLikeDto command); Task DeleteLikeAsync(Guid commentId); - } + } } diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/CommandsDto/AddFriendRequestDto.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/CommandsDto/AddFriendRequestDto.cs new file mode 100644 index 000000000..4002ddb4c --- /dev/null +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/CommandsDto/AddFriendRequestDto.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MiniSpace.Web.Areas.Friends.CommandsDto +{ + public class AddFriendRequestDto + { + public Guid FriendId { get; set; } + } +} \ No newline at end of file diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/CommandsDto/FriendRequestActionDto.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/CommandsDto/FriendRequestActionDto.cs new file mode 100644 index 000000000..4271344e2 --- /dev/null +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/CommandsDto/FriendRequestActionDto.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MiniSpace.Web.Areas.Friends.CommandsDto +{ + public class FriendRequestActionDto + { + public Guid RequestId { get; set; } + public Guid RequesterId { get; set; } + public Guid FriendId { get; set; } + } +} \ No newline at end of file diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/CommandsDto/WithdrawFriendRequestDto.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/CommandsDto/WithdrawFriendRequestDto.cs new file mode 100644 index 000000000..82ce9863c --- /dev/null +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/CommandsDto/WithdrawFriendRequestDto.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MiniSpace.Web.Areas.Friends.CommandsDto +{ + public class WithdrawFriendRequestDto + { + public Guid InviterId { get; set; } + public Guid InviteeId { get; set; } + } +} \ No newline at end of file diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/FriendsService.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/FriendsService.cs index 3729fe1cf..2c55781c4 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/FriendsService.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/FriendsService.cs @@ -3,8 +3,11 @@ using System.Linq; using System.Threading.Tasks; using MiniSpace.Web.Areas.Identity; +using MiniSpace.Web.Areas.Friends.CommandsDto; +using MiniSpace.Web.Areas.PagedResult; using MiniSpace.Web.DTO; using MiniSpace.Web.HttpClients; +using MiniSpace.Web.DTO.Friends; namespace MiniSpace.Web.Areas.Friends { @@ -12,10 +15,9 @@ public class FriendsService : IFriendsService { private readonly IHttpClient _httpClient; private readonly IIdentityService _identityService; - public FriendDto FriendDto { get; private set; } - + public FriendsService(IHttpClient httpClient, IIdentityService identityService) { _httpClient = httpClient; @@ -31,7 +33,7 @@ public void ClearFriendDto() { FriendDto = null; } - + public async Task GetFriendAsync(Guid friendId) { string accessToken = await _identityService.GetAccessTokenAsync(); @@ -39,28 +41,30 @@ public async Task GetFriendAsync(Guid friendId) return await _httpClient.GetAsync($"friends/{friendId}"); } - public async Task> GetAllFriendsAsync(Guid studentId) + public async Task> GetAllFriendsAsync(Guid userId, int page = 1, int pageSize = 10) { string accessToken = await _identityService.GetAccessTokenAsync(); _httpClient.SetAccessToken(accessToken); - var url = $"friends/{studentId}"; - var studentFriends = await _httpClient.GetAsync>(url); + + string url = $"friends/{userId}?page={page}&pageSize={pageSize}"; + var userFriends = await _httpClient.GetAsync>(url); - var allFriends = studentFriends.SelectMany(sf => sf.Friends).ToList(); + var allFriends = userFriends.Items.SelectMany(uf => uf.Friends).ToList(); foreach (var friend in allFriends) { friend.StudentDetails = await GetStudentAsync(friend.FriendId); } - return allFriends; + return new PagedResult(allFriends, userFriends.Page, userFriends.PageSize, userFriends.TotalItems); } public async Task> AddFriendAsync(Guid friendId) { string accessToken = await _identityService.GetAccessTokenAsync(); _httpClient.SetAccessToken(accessToken); - return await _httpClient.PostAsync("friends", new { friendId }); + var payload = new AddFriendRequestDto { FriendId = friendId }; + return await _httpClient.PostAsync("friends", payload); } public async Task RemoveFriendAsync(Guid friendId) @@ -68,19 +72,16 @@ public async Task RemoveFriendAsync(Guid friendId) string accessToken = await _identityService.GetAccessTokenAsync(); _httpClient.SetAccessToken(accessToken); var requesterId = _identityService.GetCurrentUserId(); - // Console.WriteLine($"Requester ID: {requesterId}"); // Log the requester ID if (requesterId == Guid.Empty) { - // Console.WriteLine("Invalid Requester ID: ID is empty."); return; // Optionally handle the case where the requester ID is invalid } - var payload = new { RequesterId = requesterId, FriendId = friendId }; - // Console.WriteLine($"Payload: {payload.RequesterId}, {payload.FriendId}"); await _httpClient.DeleteAsync($"friends/{requesterId}/{friendId}/remove"); } + // New method to get all students without pagination or filters public async Task> GetAllStudentsAsync() { if (_httpClient == null) throw new InvalidOperationException("HTTP client is not initialized."); @@ -92,12 +93,13 @@ public async Task> GetAllStudentsAsync() return await _httpClient.GetAsync>("students"); } - public async Task> GetAllStudentsAsync(int page = 1, int resultsPerPage = 10, - string searchTerm = null) + // New method to get paginated students with optional search term + public async Task> GetAllStudentsAsync(int page = 1, int pageSize = 10, string searchTerm = null) { string accessToken = await _identityService.GetAccessTokenAsync(); _httpClient.SetAccessToken(accessToken); - string url = $"students?page={page}&resultsPerPage={resultsPerPage}&name={searchTerm}"; + + string url = $"students?page={page}&pageSize={pageSize}&searchTerm={searchTerm}"; return await _httpClient.GetAsync>(url); } @@ -117,149 +119,81 @@ public async Task InviteStudent(Guid inviterId, Guid inviteeId) await _httpClient.PostAsync>($"friends/{inviteeId}/invite", payload); } - // public async Task> GetSentFriendRequestsAsync() - // { - // var studentId = _identityService.GetCurrentUserId(); - // string accessToken = await _identityService.GetAccessTokenAsync(); - // _httpClient.SetAccessToken(accessToken); - // return await _httpClient.GetAsync>($"friends/requests/sent/{studentId}"); - // } - - public async Task GetUserDetails(Guid userId) + public async Task> GetSentFriendRequestsAsync(int page = 1, int pageSize = 10) { + var studentId = _identityService.GetCurrentUserId(); + if (studentId == Guid.Empty) return new PagedResult(Enumerable.Empty(), page, pageSize, 0); + string accessToken = await _identityService.GetAccessTokenAsync(); _httpClient.SetAccessToken(accessToken); - return await _httpClient.GetAsync($"students/{userId}"); - } + string url = $"friends/requests/sent/{studentId}?page={page}&pageSize={pageSize}"; + var studentRequests = await _httpClient.GetAsync>(url); - public async Task> GetSentFriendRequestsAsync() - { - try + if (studentRequests == null || !studentRequests.Items.Any()) { - var studentId = _identityService.GetCurrentUserId(); - if (studentId == Guid.Empty) - { - throw new InvalidOperationException("User ID is not valid."); - } - - string accessToken = await _identityService.GetAccessTokenAsync(); - if (string.IsNullOrEmpty(accessToken)) - { - throw new InvalidOperationException("Access token is missing or invalid."); - } - - _httpClient.SetAccessToken(accessToken); - var studentRequests = await _httpClient.GetAsync>($"friends/requests/sent/{studentId}"); - - if (studentRequests == null || !studentRequests.Any()) - { - return Enumerable.Empty(); - } - - var friendRequests = studentRequests.SelectMany(request => request.FriendRequests).ToList(); - - var inviteeIds = friendRequests.Select(r => r.InviteeId).Distinct(); - var userDetailsTasks = inviteeIds.Select(id => GetUserDetails(id)); - var userDetailsResults = await Task.WhenAll(userDetailsTasks); - - var userDetailsDict = userDetailsResults.ToDictionary(user => user.Id, user => user); - - foreach (var request in friendRequests) - { - if (userDetailsDict.TryGetValue(request.InviteeId, out var userDetails)) - { - request.InviteeName = $"{userDetails.FirstName} {userDetails.LastName}"; - request.InviteeEmail = userDetails.Email; - // request.InviteeImage = userDetails.ProfileImage; // Uncomment if you have a profile image field - } - } - - return friendRequests; + return new PagedResult(Enumerable.Empty(), page, pageSize, 0); } - catch (Exception ex) + + var friendRequests = studentRequests.Items.SelectMany(request => request.FriendRequests).ToList(); + + foreach (var request in friendRequests) { - // Log the exception (optional) - // Console.WriteLine($"Error retrieving sent friend requests: {ex.Message}"); - return new List(); + var userDetails = await GetStudentAsync(request.InviteeId); + request.InviteeName = $"{userDetails.FirstName} {userDetails.LastName}"; + request.InviteeEmail = userDetails.Email; } + + return new PagedResult(friendRequests, studentRequests.Page, studentRequests.PageSize, studentRequests.TotalItems); } - public async Task> GetIncomingFriendRequestsAsync() + public async Task> GetIncomingFriendRequestsAsync(int page = 1, int pageSize = 10) { - try + var userId = _identityService.GetCurrentUserId(); + if (userId == Guid.Empty) return new PagedResult(Enumerable.Empty(), page, pageSize, 0); + + string accessToken = await _identityService.GetAccessTokenAsync(); + _httpClient.SetAccessToken(accessToken); + + string url = $"friends/requests/{userId}?page={page}&pageSize={pageSize}"; + var studentRequests = await _httpClient.GetAsync>(url); + + if (studentRequests == null || !studentRequests.Items.Any()) { - var userId = _identityService.GetCurrentUserId(); - if (userId == Guid.Empty) - { - throw new InvalidOperationException("User ID is not valid."); - } - - string accessToken = await _identityService.GetAccessTokenAsync(); - _httpClient.SetAccessToken(accessToken); - - var studentRequests = await _httpClient.GetAsync>($"friends/requests/{userId}"); - if (studentRequests == null || !studentRequests.Any()) - { - return Enumerable.Empty(); - } - - var incomingRequests = studentRequests.SelectMany(request => request.FriendRequests).ToList(); - - var inviterIds = incomingRequests.Select(r => r.InviterId).Distinct(); - var userDetailsTasks = inviterIds.Select(id => GetUserDetails(id)); - var userDetailsResults = await Task.WhenAll(userDetailsTasks); - - var userDetailsDict = userDetailsResults.ToDictionary(user => user.Id, user => user); - - foreach (var request in incomingRequests) - { - if (userDetailsDict.TryGetValue(request.InviterId, out var userDetails)) - { - request.InviterName = $"{userDetails.FirstName} {userDetails.LastName}"; - request.InviterEmail = userDetails.Email; - } - } - - return incomingRequests; + return new PagedResult(Enumerable.Empty(), page, pageSize, 0); } - catch (Exception ex) + + var incomingRequests = studentRequests.Items.SelectMany(request => request.FriendRequests).ToList(); + + foreach (var request in incomingRequests) { - // Console.WriteLine($"Error retrieving incoming friend requests: {ex.Message}"); - return new List(); + var userDetails = await GetStudentAsync(request.InviterId); + request.InviterName = $"{userDetails.FirstName} {userDetails.LastName}"; + request.InviterEmail = userDetails.Email; } - } + return new PagedResult(incomingRequests, studentRequests.Page, studentRequests.PageSize, studentRequests.TotalItems); + } - // private async Task GetUserDetails(Guid userId) - // { - // string accessToken = await _identityService.GetAccessTokenAsync(); - // _httpClient.SetAccessToken(accessToken); - // return await _httpClient.GetAsync($"students/{userId}"); - // } - - public async Task AcceptFriendRequestAsync(Guid requestId, Guid requesterId, Guid friendId) + public async Task AcceptFriendRequestAsync(FriendRequestActionDto requestAction) { string accessToken = await _identityService.GetAccessTokenAsync(); _httpClient.SetAccessToken(accessToken); - var payload = new { RequesterId = requesterId, FriendId = friendId }; - await _httpClient.PostAsync($"friends/requests/{requestId}/accept", payload); + await _httpClient.PostAsync($"friends/requests/{requestAction.RequestId}/accept", requestAction); } - public async Task DeclineFriendRequestAsync(Guid requestId, Guid requesterId, Guid friendId) + public async Task DeclineFriendRequestAsync(FriendRequestActionDto requestAction) { string accessToken = await _identityService.GetAccessTokenAsync(); _httpClient.SetAccessToken(accessToken); - var payload = new { RequesterId = requesterId, FriendId = friendId }; - await _httpClient.PostAsync($"friends/requests/{requestId}/decline", payload); + await _httpClient.PostAsync($"friends/requests/{requestAction.RequestId}/decline", requestAction); } - public async Task WithdrawFriendRequestAsync(Guid inviterId, Guid inviteeId) + public async Task WithdrawFriendRequestAsync(WithdrawFriendRequestDto withdrawRequest) { string accessToken = await _identityService.GetAccessTokenAsync(); _httpClient.SetAccessToken(accessToken); - var payload = new { InviterId = inviterId, InviteeId = inviteeId }; - await _httpClient.PutAsync($"friends/requests/{inviteeId}/withdraw", payload); + await _httpClient.PutAsync($"friends/requests/{withdrawRequest.InviteeId}/withdraw", withdrawRequest); } - } + } } diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/IFriendsService.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/IFriendsService.cs index f289bc17b..4c8f946b5 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/IFriendsService.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/IFriendsService.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using MiniSpace.Web.Areas.Friends.CommandsDto; +using MiniSpace.Web.Areas.PagedResult; using MiniSpace.Web.DTO; +using MiniSpace.Web.DTO.Friends; using MiniSpace.Web.HttpClients; namespace MiniSpace.Web.Areas.Friends @@ -9,20 +12,35 @@ namespace MiniSpace.Web.Areas.Friends public interface IFriendsService { FriendDto FriendDto { get; } + Task UpdateFriendDto(Guid friendId); + void ClearFriendDto(); + Task GetFriendAsync(Guid friendId); + + Task> GetAllFriendsAsync(Guid userId, int page = 1, int pageSize = 10); + Task> AddFriendAsync(Guid friendId); + Task RemoveFriendAsync(Guid friendId); - Task> GetAllFriendsAsync(Guid studentId); + Task GetStudentAsync(Guid studentId); + Task> GetAllStudentsAsync(); - Task> GetAllStudentsAsync(int page = 1, int resultsPerPage = 10, string search = null); + + Task> GetAllStudentsAsync(int page = 1, int pageSize = 10, string searchTerm = null); + Task InviteStudent(Guid inviterId, Guid inviteeId); - Task> GetSentFriendRequestsAsync(); - Task> GetIncomingFriendRequestsAsync(); - Task AcceptFriendRequestAsync(Guid requestId, Guid requesterId, Guid friendId); - Task DeclineFriendRequestAsync(Guid requestId, Guid requesterId, Guid friendId); - Task WithdrawFriendRequestAsync(Guid inviterId, Guid inviteeId); + + Task> GetSentFriendRequestsAsync(int page = 1, int pageSize = 10); + + Task> GetIncomingFriendRequestsAsync(int page = 1, int pageSize = 10); + + Task AcceptFriendRequestAsync(FriendRequestActionDto requestAction); + + Task DeclineFriendRequestAsync(FriendRequestActionDto requestAction); + + Task WithdrawFriendRequestAsync(WithdrawFriendRequestDto withdrawRequest); } } diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/PagedResult.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/PagedResult.cs deleted file mode 100644 index 7672f5b5b..000000000 --- a/MiniSpace.Web/src/MiniSpace.Web/Areas/Friends/PagedResult.cs +++ /dev/null @@ -1,13 +0,0 @@ - -using System.Collections; -using System.Collections.Generic; - -namespace MiniSpace.Web.Areas.Friends -{ - public class PagedResult - { - public IEnumerable Data { get; set; } - public int TotalCount { get; set; } - public PagedResult() { } - } -} \ No newline at end of file diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Organizations/IOrganizationsService.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Organizations/IOrganizationsService.cs index 32f2e402d..9cfc3522a 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/Areas/Organizations/IOrganizationsService.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Organizations/IOrganizationsService.cs @@ -28,7 +28,7 @@ public interface IOrganizationsService Task SetOrganizationVisibilityAsync(Guid organizationId, SetOrganizationVisibilityCommand command); Task ManageFeedAsync(Guid organizationId, ManageFeedCommand command); Task> UpdateOrganizationAsync(Guid organizationId, UpdateOrganizationCommand command); - Task> GetUserOrganizationsAsync(Guid userId); + Task> GetPaginatedUserOrganizationsAsync(Guid userId, int page, int pageSize); Task> GetOrganizationRolesAsync(Guid organizationId); Task> GetPaginatedOrganizationsAsync(int page, int pageSize, string search = null); Task FollowOrganizationAsync(Guid organizationId); diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Organizations/OrganizationsService.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Organizations/OrganizationsService.cs index 8e1b9ca48..94e203ccf 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/Areas/Organizations/OrganizationsService.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Organizations/OrganizationsService.cs @@ -130,10 +130,11 @@ public Task> UpdateOrganizationAsync(Guid organizationId, U return _httpClient.PutAsync($"organizations/{organizationId}", command); } - public Task> GetUserOrganizationsAsync(Guid userId) + public async Task> GetPaginatedUserOrganizationsAsync(Guid userId, int page, int pageSize) { _httpClient.SetAccessToken(_identityService.JwtDto.AccessToken); - return _httpClient.GetAsync>($"organizations/users/{userId}/organizations"); + var queryString = $"organizations/users/{userId}/organizations?page={page}&pageSize={pageSize}"; + return await _httpClient.GetAsync>(queryString); } public Task> GetOrganizationRolesAsync(Guid organizationId) diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Posts/IPostsService.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Posts/IPostsService.cs index 523e941cb..b4e0853e4 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/Areas/Posts/IPostsService.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Posts/IPostsService.cs @@ -15,6 +15,7 @@ public interface IPostsService Task ChangePostStateAsync(Guid postId, string state, DateTime publishDate); Task> CreatePostAsync(CreatePostCommand command); Task>> SearchPostsAsync(SearchPosts searchPosts); + Task>> GetUserFeedAsync(Guid userId, int pageNumber, int pageSize, string sortBy = "PublishDate", string direction = "asc"); Task DeletePostAsync(Guid postId); Task> GetPostsAsync(Guid eventId); Task> UpdatePostAsync(Guid postId, string textContent, IEnumerable mediaFiles); diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Posts/PostsService.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Posts/PostsService.cs index 56ca89505..c7d3050df 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/Areas/Posts/PostsService.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Posts/PostsService.cs @@ -43,41 +43,69 @@ public Task> CreatePostAsync(CreatePostCommand command) } public async Task>> SearchPostsAsync(SearchPosts searchPosts) -{ - _httpClient.SetAccessToken(_identityService.JwtDto.AccessToken); + { + _httpClient.SetAccessToken(_identityService.JwtDto.AccessToken); - var query = HttpUtility.ParseQueryString(string.Empty); - if (searchPosts.UserId.HasValue) - query["UserId"] = searchPosts.UserId.ToString(); - if (searchPosts.OrganizationId.HasValue) - query["OrganizationId"] = searchPosts.OrganizationId.ToString(); - if (searchPosts.EventId.HasValue) - query["EventId"] = searchPosts.EventId.ToString(); + var query = HttpUtility.ParseQueryString(string.Empty); + if (searchPosts.UserId.HasValue) + query["UserId"] = searchPosts.UserId.ToString(); + if (searchPosts.OrganizationId.HasValue) + query["OrganizationId"] = searchPosts.OrganizationId.ToString(); + if (searchPosts.EventId.HasValue) + query["EventId"] = searchPosts.EventId.ToString(); - query["PageNumber"] = searchPosts.Pageable.Page.ToString(); - query["PageSize"] = searchPosts.Pageable.Size.ToString(); - if (searchPosts.Pageable.Sort?.SortBy != null) - query["SortBy"] = string.Join(",", searchPosts.Pageable.Sort.SortBy); - query["Direction"] = searchPosts.Pageable.Sort?.Direction; + query["PageNumber"] = searchPosts.Pageable.Page.ToString(); + query["PageSize"] = searchPosts.Pageable.Size.ToString(); + if (searchPosts.Pageable.Sort?.SortBy != null) + query["SortBy"] = string.Join(",", searchPosts.Pageable.Sort.SortBy); + query["Direction"] = searchPosts.Pageable.Sort?.Direction; - string queryString = query.ToString(); - string url = $"posts/search?{queryString}"; + string queryString = query.ToString(); + string url = $"posts/search?{queryString}"; - try - { - var result = await _httpClient.GetAsync>(url); - return new HttpResponse>(result); - } - catch (Exception ex) - { - return new HttpResponse>(new ErrorMessage + try + { + var result = await _httpClient.GetAsync>(url); + return new HttpResponse>(result); + } + catch (Exception ex) + { + return new HttpResponse>(new ErrorMessage + { + Code = ex.Message, + Reason = ex.Message + }); + } + } + + public async Task>> GetUserFeedAsync(Guid userId, int pageNumber, + int pageSize, string sortBy = "PublishDate", string direction = "asc") { - Code = ex.Message, - Reason = ex.Message - }); - } -} + _httpClient.SetAccessToken(_identityService.JwtDto.AccessToken); + var query = HttpUtility.ParseQueryString(string.Empty); + query["PageNumber"] = pageNumber.ToString(); + query["PageSize"] = pageSize.ToString(); + query["SortBy"] = sortBy; + query["Direction"] = direction; + + string queryString = query.ToString(); + string url = $"posts/users/{userId}/feed?{queryString}"; + + try + { + var result = await _httpClient.GetAsync>(url); + return new HttpResponse>(result); + } + catch (Exception ex) + { + return new HttpResponse>(new ErrorMessage + { + Code = ex.Message, + Reason = ex.Message + }); + } + } public Task DeletePostAsync(Guid postId) diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Reactions/CommandDto/CreateReactionDto.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Reactions/CommandDto/CreateReactionDto.cs index ba41277ec..eb1a35892 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/Areas/Reactions/CommandDto/CreateReactionDto.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Reactions/CommandDto/CreateReactionDto.cs @@ -7,14 +7,14 @@ namespace MiniSpace.Web.Areas.Reactions.CommandDto { public class CreateReactionDto { - public Guid ReactionId { get; set;} - public Guid UserId { get; } - public string ReactionType { get; } + public Guid ReactionId { get; set; } = Guid.NewGuid(); + public Guid UserId { get; set; } + public string ReactionType { get; set; } // ReactionType := LoveIt || LikeIt || Wow || ItWasOkay || HateIt - public Guid ContentId { get; } - public string ContentType { get; } + public Guid ContentId { get; set; } + public string ContentType { get; set; } // ContentType := Post || Event || Comment - public string TargetType { get; } + public string TargetType { get; set; } // ReactionTargetType := User || Organization } } \ No newline at end of file diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Reactions/CommandDto/UpdateReactionDto.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Reactions/CommandDto/UpdateReactionDto.cs new file mode 100644 index 000000000..b82e17785 --- /dev/null +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Reactions/CommandDto/UpdateReactionDto.cs @@ -0,0 +1,13 @@ +using System; + +namespace MiniSpace.Web.Areas.Reactions.CommandDto +{ + public class UpdateReactionDto + { + public Guid ReactionId { get; set; } + public Guid UserId { get; set; } + public string NewReactionType { get; set; } + public string ContentType { get; set; } + public string TargetType { get; set; } + } +} diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Reactions/IReactionsService.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Reactions/IReactionsService.cs index 9a51138b6..d646de2ad 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/Areas/Reactions/IReactionsService.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Reactions/IReactionsService.cs @@ -13,6 +13,7 @@ public interface IReactionsService Task> GetReactionsAsync(Guid contentId, ReactionContentType contentType); Task GetReactionsSummaryAsync(Guid contentId, ReactionContentType contentType); Task> CreateReactionAsync(CreateReactionDto command); + Task> UpdateReactionAsync(UpdateReactionDto command); Task DeleteReactionAsync(Guid reactionId); } } diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Reactions/ReactionsService.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Reactions/ReactionsService.cs index b6f86e0b7..73aed86fa 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/Areas/Reactions/ReactionsService.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Reactions/ReactionsService.cs @@ -37,6 +37,13 @@ public Task> CreateReactionAsync(CreateReactionDto command) return _httpClient.PostAsync("reactions", command); } + public Task> UpdateReactionAsync(UpdateReactionDto command) + { + _httpClient.SetAccessToken(_identityService.JwtDto.AccessToken); + return _httpClient.PutAsync($"reactions/{command.ReactionId}", command); + } + + public Task DeleteReactionAsync(Guid reactionId) { diff --git a/MiniSpace.Web/src/MiniSpace.Web/DTO/Comments/CommentContext.cs b/MiniSpace.Web/src/MiniSpace.Web/DTO/Comments/CommentContext.cs new file mode 100644 index 000000000..c6c786038 --- /dev/null +++ b/MiniSpace.Web/src/MiniSpace.Web/DTO/Comments/CommentContext.cs @@ -0,0 +1,10 @@ +namespace MiniSpace.Web.DTO.Comments +{ + public enum CommentContext + { + UserPost, + UserEvent, + OrganizationPost, + OrganizationEvent + } +} \ No newline at end of file diff --git a/MiniSpace.Web/src/MiniSpace.Web/DTO/CommentDto.cs b/MiniSpace.Web/src/MiniSpace.Web/DTO/Comments/CommentDto.cs similarity index 77% rename from MiniSpace.Web/src/MiniSpace.Web/DTO/CommentDto.cs rename to MiniSpace.Web/src/MiniSpace.Web/DTO/Comments/CommentDto.cs index fde257932..aa2b5f380 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/DTO/CommentDto.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/DTO/Comments/CommentDto.cs @@ -1,16 +1,15 @@ using System; using System.Collections.Generic; -namespace MiniSpace.Web.DTO +namespace MiniSpace.Web.DTO.Comments { - public class CommentDto + public class CommentDto { public Guid Id { get; set; } public Guid ContextId { get; set; } public string CommentContext { get; set; } - public Guid StudentId { get; set; } - public string StudentName { get; set; } - public HashSet Likes { get; set; } + public Guid UserId { get; set; } + public IEnumerable Likes { get; set; } public Guid ParentId { get; set; } public string TextContent { get; set; } public DateTime CreatedAt { get; set; } @@ -18,10 +17,14 @@ public class CommentDto public DateTime LastReplyAt { get; set; } public int RepliesCount { get; set; } public bool IsDeleted { get; set; } + public IEnumerable Replies { get; set; } + public bool CanExpand { get; set; } public HashSet SubComments { get; set; } public int SubCommentsPage { get; set; } public CommentDto Parent { get; set; } public bool IsLast { get; set; } + } + } diff --git a/MiniSpace.Web/src/MiniSpace.Web/DTO/Comments/CommentExtensions.cs b/MiniSpace.Web/src/MiniSpace.Web/DTO/Comments/CommentExtensions.cs new file mode 100644 index 000000000..e04ebf7f2 --- /dev/null +++ b/MiniSpace.Web/src/MiniSpace.Web/DTO/Comments/CommentExtensions.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MiniSpace.Web.DTO.Comments +{ + public static class CommentExtensions + { + public static ReplyDto ToReplyDto(this CommentDto comment) + { + if (comment == null) + throw new ArgumentNullException(nameof(comment)); + + return new ReplyDto + { + Id = comment.Id, + ParentId = comment.ParentId, + UserId = comment.UserId, + TextContent = comment.TextContent, + CreatedAt = comment.CreatedAt, + IsDeleted = comment.IsDeleted + }; + } + } +} \ No newline at end of file diff --git a/MiniSpace.Web/src/MiniSpace.Web/DTO/Comments/ReplyDto.cs b/MiniSpace.Web/src/MiniSpace.Web/DTO/Comments/ReplyDto.cs new file mode 100644 index 000000000..1c03d8893 --- /dev/null +++ b/MiniSpace.Web/src/MiniSpace.Web/DTO/Comments/ReplyDto.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MiniSpace.Web.DTO.Comments +{ + public class ReplyDto + { + public Guid Id { get; set; } + public Guid ParentId { get; set; } + public Guid UserId { get; set; } + public string TextContent { get; set; } + public DateTime CreatedAt { get; set; } + public bool IsDeleted { get; set; } + + public ReplyDto() + { + } + + public ReplyDto(ReplyDto reply) + { + Id = reply.Id; + ParentId = reply.ParentId; + UserId = reply.UserId; + TextContent = reply.TextContent; + CreatedAt = reply.CreatedAt; + IsDeleted = reply.IsDeleted; + } + } +} \ No newline at end of file diff --git a/MiniSpace.Web/src/MiniSpace.Web/DTO/Enums/ReactionType.cs b/MiniSpace.Web/src/MiniSpace.Web/DTO/Enums/ReactionType.cs index 7d84623a1..b5bc82a4d 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/DTO/Enums/ReactionType.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/DTO/Enums/ReactionType.cs @@ -20,7 +20,7 @@ public static class ReactionTypeExtensions private const string Hate =@"emoticon-confused"; - public static string GetReactionText(ReactionType? reactionType) + public static string GetReactionText(this ReactionType reactionType) { return reactionType switch { @@ -32,6 +32,7 @@ public static string GetReactionText(ReactionType? reactionType) _ => "No reactions!" }; } + public static string GetReactionIcon(this ReactionType? reactionType) { diff --git a/MiniSpace.Web/src/MiniSpace.Web/DTO/Enums/Reactions/ReactionTargetType.cs b/MiniSpace.Web/src/MiniSpace.Web/DTO/Enums/Reactions/ReactionTargetType.cs new file mode 100644 index 000000000..47547d464 --- /dev/null +++ b/MiniSpace.Web/src/MiniSpace.Web/DTO/Enums/Reactions/ReactionTargetType.cs @@ -0,0 +1,8 @@ +namespace MiniSpace.Web.DTO.Enums.Reactions +{ + public enum ReactionTargetType + { + User, + Organization + } +} diff --git a/MiniSpace.Web/src/MiniSpace.Web/DTO/FriendDto.cs b/MiniSpace.Web/src/MiniSpace.Web/DTO/Friends/FriendDto.cs similarity index 52% rename from MiniSpace.Web/src/MiniSpace.Web/DTO/FriendDto.cs rename to MiniSpace.Web/src/MiniSpace.Web/DTO/Friends/FriendDto.cs index e00cb739a..9b9854a20 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/DTO/FriendDto.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/DTO/Friends/FriendDto.cs @@ -2,20 +2,15 @@ using System.Collections.Generic; using MiniSpace.Web.DTO.States; -namespace MiniSpace.Web.DTO +namespace MiniSpace.Web.DTO.Friends { public class FriendDto { public Guid Id { get; set; } - public string Email { get; set; } - public string FirstName { get; set; } - public Guid StudentId { get; set; } + public Guid UserId { get; set; } public Guid FriendId { get; set; } - public string LastName { get; set; } - public string FullName => $"{FirstName} {LastName}"; public DateTime CreatedAt { get; set; } public FriendState State { get; set; } - public string ProfileImage { get; set; } public StudentDto StudentDetails { get; set; } } } diff --git a/MiniSpace.Web/src/MiniSpace.Web/DTO/FriendRequestDto.cs b/MiniSpace.Web/src/MiniSpace.Web/DTO/Friends/FriendRequestDto.cs similarity index 88% rename from MiniSpace.Web/src/MiniSpace.Web/DTO/FriendRequestDto.cs rename to MiniSpace.Web/src/MiniSpace.Web/DTO/Friends/FriendRequestDto.cs index 2ed375678..43e5dc8c8 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/DTO/FriendRequestDto.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/DTO/Friends/FriendRequestDto.cs @@ -1,7 +1,7 @@ using System; using MiniSpace.Web.DTO.States; -namespace MiniSpace.Web.DTO +namespace MiniSpace.Web.DTO.Friends { public class FriendRequestDto { @@ -10,7 +10,7 @@ public class FriendRequestDto public Guid InviteeId { get; set; } public DateTime RequestedAt { get; set; } public FriendState State { get; set; } - public Guid StudentId { get; set; } + public Guid UserId { get; set; } public string InviteeName { get; set; } public string InviterName { get; set; } diff --git a/MiniSpace.Web/src/MiniSpace.Web/DTO/StudentFriendsDto.cs b/MiniSpace.Web/src/MiniSpace.Web/DTO/Friends/UserFriendsDto.cs similarity index 63% rename from MiniSpace.Web/src/MiniSpace.Web/DTO/StudentFriendsDto.cs rename to MiniSpace.Web/src/MiniSpace.Web/DTO/Friends/UserFriendsDto.cs index f12f8d300..96bf80b8b 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/DTO/StudentFriendsDto.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/DTO/Friends/UserFriendsDto.cs @@ -3,11 +3,11 @@ using System.Linq; using System.Threading.Tasks; -namespace MiniSpace.Web.DTO +namespace MiniSpace.Web.DTO.Friends { - public class StudentFriendsDto + public class UserFriendsDto { - public Guid StudentId { get; set; } + public Guid UserId { get; set; } public List Friends { get; set; } = new List(); } } \ No newline at end of file diff --git a/MiniSpace.Web/src/MiniSpace.Web/DTO/StudentRequestsDto.cs b/MiniSpace.Web/src/MiniSpace.Web/DTO/Friends/UserRequestsDto.cs similarity index 64% rename from MiniSpace.Web/src/MiniSpace.Web/DTO/StudentRequestsDto.cs rename to MiniSpace.Web/src/MiniSpace.Web/DTO/Friends/UserRequestsDto.cs index 542198ded..2c54142d8 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/DTO/StudentRequestsDto.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/DTO/Friends/UserRequestsDto.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; -namespace MiniSpace.Web.DTO +namespace MiniSpace.Web.DTO.Friends { - public class StudentRequestsDto + public class UserRequestsDto { public Guid Id { get; set; } - public Guid StudentId { get; set; } + public Guid UserId { get; set; } public List FriendRequests { get; set; } = new List(); } } diff --git a/MiniSpace.Web/src/MiniSpace.Web/DTO/Organizations/OrganizationDto.cs b/MiniSpace.Web/src/MiniSpace.Web/DTO/Organizations/OrganizationDto.cs index 95f3b98da..b089a51a5 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/DTO/Organizations/OrganizationDto.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/DTO/Organizations/OrganizationDto.cs @@ -24,5 +24,8 @@ public class OrganizationDto public IEnumerable Users { get; set; } = new List(); public int UserCount => Users?.Count() ?? 0; + + public bool IsExpanded { get; set; } = false; + public List SubOrganizations { get; set; } = new List(); } } diff --git a/MiniSpace.Web/src/MiniSpace.Web/DTO/ReactionDto.cs b/MiniSpace.Web/src/MiniSpace.Web/DTO/ReactionDto.cs index 910681383..a006a88cd 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/DTO/ReactionDto.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/DTO/ReactionDto.cs @@ -1,15 +1,16 @@ using System; using MiniSpace.Web.DTO.Enums; +using MiniSpace.Web.DTO.Enums.Reactions; namespace MiniSpace.Web.DTO { public class ReactionDto { public Guid Id { get; set; } - public Guid StudentId { get; set; } - public string StudentFullName { get; set; } + public Guid UserId { get; set; } public Guid ContentId { get; set; } public ReactionContentType ContentType { get; set; } - public ReactionType Type { get; set; } + public ReactionType ReactionType { get; set; } + public ReactionTargetType TargetType { get; set; } } } diff --git a/MiniSpace.Web/src/MiniSpace.Web/DTO/ReactionsSummaryDto.cs b/MiniSpace.Web/src/MiniSpace.Web/DTO/ReactionsSummaryDto.cs index 6e3cfbef3..ea827b8e1 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/DTO/ReactionsSummaryDto.cs +++ b/MiniSpace.Web/src/MiniSpace.Web/DTO/ReactionsSummaryDto.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using MiniSpace.Web.DTO.Enums; namespace MiniSpace.Web.DTO @@ -9,5 +10,6 @@ public class ReactionsSummaryDto public ReactionType? DominantReaction { get; set; } public Guid? AuthUserReactionId { get; set; } public ReactionType? AuthUserReactionType { get; set; } + public Dictionary ReactionsWithCounts { get; set; } = new Dictionary(); } } diff --git a/MiniSpace.Web/src/MiniSpace.Web/Pages/Feeds/Home.razor b/MiniSpace.Web/src/MiniSpace.Web/Pages/Feeds/Home.razor index 231331851..d23b8e85a 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/Pages/Feeds/Home.razor +++ b/MiniSpace.Web/src/MiniSpace.Web/Pages/Feeds/Home.razor @@ -9,170 +9,71 @@ @using MudBlazor - - - - - - - - Discover What's New - @if (pageInitialized) - { - @if (posts.Any()) - { - - @foreach (var post in posts) - { - - - - } - - } - else + + + + + + + + + + Discover What's New + @if (pageInitialized) { - No activity found - Please join an event first + @if (posts.Any()) + { + + @foreach (var post in posts) + { + + + + } + + } + else + { + No activity found + Please join an event first + } } - } - - - - - - + + + + + + + + @code { private IEnumerable posts; private Guid studentId; private bool pageInitialized = false; - private SearchPosts searchModel; protected override async Task OnInitializedAsync() { if (IdentityService != null && IdentityService.IsAuthenticated) { studentId = IdentityService.GetCurrentUserId(); - searchModel = InitializeSearchModel(studentId); - var result = await PostsService.SearchPostsAsync(searchModel); - posts = result.Content.Items; - pageInitialized = true; - } - else - { - NavigationManager.NavigateTo(""); - } - } - - private static SearchPosts InitializeSearchModel(Guid studentId) - { - return new() - { - UserId = studentId, - Pageable = new PageableDto() + var result = await PostsService.GetUserFeedAsync(studentId, 1, 8, "PublishDate", "desc"); + + if (result.IsSuccessStatusCode) { - Page = 1, - Size = 8, - Sort = new SortDto() - { - SortBy = new List() { "publishDate" }, - Direction = "desc" - } + posts = result.Content.Items; + } + else + { + posts = new List(); // Handle error gracefully + Console.WriteLine($"Error: {result.ErrorMessage.Reason}"); } - }; - } -} - - -@* @page "/home" -@using MiniSpace.Web.DTO -@using MiniSpace.Web.Areas.Posts -@using MiniSpace.Web.Data.Posts -@using MiniSpace.Web.Components -@using MiniSpace.Web.DTO.Wrappers -@using Radzen -@using System.Globalization -@inject NavigationManager NavigationManager -@inject IIdentityService IdentityService -@inject IPostsService PostsService - -

Discover What's New

- -@if (pageInitialized) -{ - @if (posts.Any()) - { - - - - - - } - else - { -

No activity found

- - } -} - - - - -@code { - private IEnumerable posts; - private Guid studentId; - private bool pageInitialized = false; - private SearchPosts searchModel; - - protected override async Task OnInitializedAsync() - { - if (IdentityService != null && IdentityService.IsAuthenticated) - { - studentId = IdentityService.GetCurrentUserId(); - searchModel = InitializeSearchModel(studentId); - var result = await PostsService.SearchPostsAsync(searchModel); - posts = result.Content.Content; pageInitialized = true; } else { NavigationManager.NavigateTo(""); - } } - - private static SearchPosts InitializeSearchModel(Guid studentId) - { - return new() - { - StudentId = studentId, - Pageable = new PageableDto() - { - Page = 1, - Size = 8, - Sort = new SortDto() - { - SortBy = new List() {"publishDate"}, - Direction = "des" - } - } - }; - } -} *@ +} diff --git a/MiniSpace.Web/src/MiniSpace.Web/Pages/Feeds/PostCard.razor b/MiniSpace.Web/src/MiniSpace.Web/Pages/Feeds/PostCard.razor index 9a63b514d..bb64e5462 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/Pages/Feeds/PostCard.razor +++ b/MiniSpace.Web/src/MiniSpace.Web/Pages/Feeds/PostCard.razor @@ -1,20 +1,435 @@ @using MiniSpace.Web.DTO @using MudBlazor +@inject IReactionsService ReactionsService +@inject ICommentsService CommentsService +@inject IStudentsService StudentsService +@inject IIdentityService IdentityService +@inject NavigationManager NavigationManager +@using MiniSpace.Web.Areas.Comments.CommandDto - - - - @Post.TextContent - + - @Post.State + + + + + + @GetUserName(Post.UserId) + @Post.CreatedAt.ToString("g") + + + + + + @if (Post.MediaFiles != null && Post.MediaFiles.Any()) + { + + } + + @if (ReactionsSummary != null) + { + + @foreach (var reaction in ReactionsSummary.ReactionsWithCounts.OrderByDescending(r => r.Value)) + { + + + @reaction.Value + + } + + Total: @ReactionsSummary.NumberOfReactions + + + } - - Read More + + + + + + View + + + + + + React + + + + @foreach (var reactionType in Enum.GetValues(typeof(ReactionType)).Cast()) + { + + @reactionType.GetReactionText() + + } + + + + + Comment + + + @if (IsCommentSectionVisible) + { + + + Submit + + @if (Comments != null && Comments.Any()) + { + + @foreach (var comment in Comments) + { + + + + + + + @GetUserName(comment.UserId) + @comment.TextContent + @comment.CreatedAt.ToString("g") + + + @($"{comment.Likes?.Count() ?? 0} people liked this comment") + + + + Like + + + + Reply + + + @if (comment.Id == activeReplyCommentId) + { + + Submit Reply + } + + @if (comment.Replies != null && comment.Replies.Any()) + { + + @foreach (var reply in comment.Replies) + { + + + + + + + @GetUserName(reply.UserId) + @reply.TextContent + @reply.CreatedAt.ToString("g") + + + + + Like + + + + + } + + } + + + + } + + } + else + { + No comments available. + } + + } @code { [Parameter] public PostDto Post { get; set; } + + private ReactionsSummaryDto ReactionsSummary { get; set; } + private List Comments { get; set; } = new(); + private bool IsCommentSectionVisible { get; set; } = false; + private string NewCommentText { get; set; } = string.Empty; + private string newReplyText { get; set; } = string.Empty; + private Guid? activeReplyCommentId { get; set; } = null; + + private Dictionary studentsCache = new(); + + protected override async Task OnInitializedAsync() + { + ReactionsSummary = await ReactionsService.GetReactionsSummaryAsync(Post.Id, ReactionContentType.Post); + Comments = await LoadCommentsForPostAsync(); + + if (Post.UserId.HasValue && !studentsCache.ContainsKey(Post.UserId.Value)) + { + var student = await StudentsService.GetStudentAsync(Post.UserId.Value); + if (student != null) + { + studentsCache[Post.UserId.Value] = student; + } + } + } + + private async Task> LoadCommentsForPostAsync() + { + var command = new SearchRootCommentsCommand( + contextId: Post.Id, + commentContext: DetermineCommentContext().ToString(), + pageable: new PageableDto + { + Page = 1, + Size = 10, + Sort = new SortDto + { + SortBy = new[] { "CreatedAt" }, + Direction = "asc" + } + } + ); + + var response = await CommentsService.SearchRootCommentsAsync(command); + var comments = response.Items?.ToList() ?? new List(); + + foreach (var comment in comments) + { + if (!studentsCache.ContainsKey(comment.UserId)) + { + var student = await StudentsService.GetStudentAsync(comment.UserId); + if (student != null) + { + studentsCache[comment.UserId] = student; + } + } + + if (comment.Replies != null) + { + foreach (var reply in comment.Replies) + { + if (!studentsCache.ContainsKey(reply.UserId)) + { + var replyAuthor = await StudentsService.GetStudentAsync(reply.UserId); + if (replyAuthor != null) + { + studentsCache[reply.UserId] = replyAuthor; + } + } + } + } + } + + return comments; + } + + private CommentContext DetermineCommentContext() + { + if (Post.OrganizationId.HasValue) + { + return Post.EventId.HasValue ? CommentContext.OrganizationEvent : CommentContext.OrganizationPost; + } + else + { + return Post.EventId.HasValue ? CommentContext.UserEvent : CommentContext.UserPost; + } + } + + private void ToggleCommentSection() + { + IsCommentSectionVisible = !IsCommentSectionVisible; + NewCommentText = string.Empty; + } + + private void ToggleReplySection(Guid commentId) + { + if (activeReplyCommentId == commentId) + { + activeReplyCommentId = null; + newReplyText = string.Empty; + } + else + { + activeReplyCommentId = commentId; + newReplyText = string.Empty; + } + } + + private async Task SubmitCommentAsync() + { + if (string.IsNullOrWhiteSpace(NewCommentText)) + { + return; + } + + var commentContext = DetermineCommentContext(); + var userId = IdentityService.GetCurrentUserId(); + + var command = new CreateCommentCommand( + commentId: Guid.NewGuid(), + contextId: Post.Id, + commentContext: commentContext.ToString(), + userId: userId, + parentId: Guid.Empty, + textContent: NewCommentText + ); + + var response = await CommentsService.CreateCommentAsync(command); + + if (response.IsSuccessStatusCode) + { + NewCommentText = string.Empty; + Comments = await LoadCommentsForPostAsync(); + } + else + { + // Handle error (e.g., show a snackbar) + } + } + + private async Task SubmitReplyAsync(CommentDto parentComment) + { + if (string.IsNullOrWhiteSpace(newReplyText)) + { + return; + } + + var commentContext = DetermineCommentContext(); + var userId = IdentityService.GetCurrentUserId(); + + var command = new CreateCommentCommand( + commentId: Guid.NewGuid(), + contextId: Post.Id, + commentContext: commentContext.ToString(), + userId: userId, + parentId: parentComment.Id, + textContent: newReplyText + ); + + var response = await CommentsService.CreateCommentAsync(command); + + if (response.IsSuccessStatusCode) + { + newReplyText = string.Empty; + Comments = await LoadCommentsForPostAsync(); + activeReplyCommentId = null; + } + else + { + // Handle error (e.g., show a snackbar) + } + } + + private async Task HandleReactionAsync(ReactionType reactionType) + { + var reactions = await ReactionsService.GetReactionsAsync(Post.Id, ReactionContentType.Post); + var existingReaction = reactions.FirstOrDefault(r => r.UserId == IdentityService.UserDto.Id); + + if (existingReaction != null) + { + var updateReaction = new UpdateReactionDto + { + ReactionId = existingReaction.Id, + UserId = IdentityService.UserDto.Id, + NewReactionType = reactionType.ToString(), + ContentType = "Post", + TargetType = Post.OrganizationId.HasValue ? "Organization" : "User" + }; + + var updateResult = await ReactionsService.UpdateReactionAsync(updateReaction); + + if (updateResult.IsSuccessStatusCode) + { + // Reaction updated successfully + } + else + { + // Handle error (e.g., show a snackbar) + } + } + else + { + var createReaction = new CreateReactionDto + { + UserId = IdentityService.UserDto.Id, + ContentId = Post.Id, + ContentType = "Post", + ReactionType = reactionType.ToString(), + TargetType = Post.OrganizationId.HasValue ? "Organization" : "User" + }; + + var createResult = await ReactionsService.CreateReactionAsync(createReaction); + + if (createResult.IsSuccessStatusCode) + { + // Reaction added successfully + } + else + { + // Handle error (e.g., show a snackbar) + } + } + + ReactionsSummary = await ReactionsService.GetReactionsSummaryAsync(Post.Id, ReactionContentType.Post); // Refresh reactions summary + } + + private async Task AddLikeToCommentAsync(CommentDto comment) + { + var command = new AddLikeDto(comment.Id, IdentityService.GetCurrentUserId(), DetermineCommentContext().ToString()); + + var response = await CommentsService.AddLikeAsync(command); + + if (response.IsSuccessStatusCode) + { + Comments = await LoadCommentsForPostAsync(); // Refresh comments + } + else + { + // Handle error (e.g., show a snackbar) + } + } + + private async Task AddLikeToReplyAsync(ReplyDto reply) + { + var command = new AddLikeDto(reply.Id, IdentityService.GetCurrentUserId(), DetermineCommentContext().ToString()); + + var response = await CommentsService.AddLikeAsync(command); + + if (response.IsSuccessStatusCode) + { + Comments = await LoadCommentsForPostAsync(); // Refresh comments + } + else + { + // Handle error (e.g., show a snackbar) + } + } + + private string GetUserAvatar(Guid? userId) + { + if (userId.HasValue && studentsCache.ContainsKey(userId.Value)) + { + return studentsCache[userId.Value].ProfileImageUrl ?? string.Empty; + } + return string.Empty; + } + + private string GetUserName(Guid? userId) + { + if (userId.HasValue && studentsCache.ContainsKey(userId.Value)) + { + return $"{studentsCache[userId.Value].FirstName} {studentsCache[userId.Value].LastName}"; + } + return "Unknown User"; + } + + private void NavigateToPostDetails() + { + NavigationManager.NavigateTo($"/posts/details/{Post.Id}"); + } } diff --git a/MiniSpace.Web/src/MiniSpace.Web/Pages/Friends/Dialogs/FriendsDialog.razor b/MiniSpace.Web/src/MiniSpace.Web/Pages/Friends/Dialogs/FriendsDialog.razor deleted file mode 100644 index 0e19b17b8..000000000 --- a/MiniSpace.Web/src/MiniSpace.Web/Pages/Friends/Dialogs/FriendsDialog.razor +++ /dev/null @@ -1,65 +0,0 @@ -@using MiniSpace.Web.Areas.Identity -@using MiniSpace.Web.Areas.Friends -@using MiniSpace.Web.DTO -@using MiniSpace.Web.Models.Events -@using Radzen -@using System.Globalization -@inject Radzen.DialogService DialogService -@inject IIdentityService IdentityService -@inject IFriendsService FriendsService - - - - - - - - - - - - - - - - - - - - -@code { - [Parameter] - public SearchEventsModel SearchEventsModel { get; set; } - - private IEnumerable friends; - - protected override async Task OnInitializedAsync() - { - await base.OnInitializedAsync(); - friends = (await FriendsService.GetAllFriendsAsync(IdentityService.GetCurrentUserId())).Select(f => f.StudentDetails); - foreach (var friend in friends) - { - if (SearchEventsModel.Friends.Contains(friend.Id)) - { - friend.Selected = true; - } - } - StateHasChanged(); - } - - private void Close() - { - DialogService.Close(); - } - - private void SelectFriends() - { - SearchEventsModel.Friends = friends.Where(f => f.Selected).Select(f => f.Id).ToHashSet(); - DialogService.Close(); - } -} \ No newline at end of file diff --git a/MiniSpace.Web/src/MiniSpace.Web/Pages/Friends/Friends.razor b/MiniSpace.Web/src/MiniSpace.Web/Pages/Friends/Friends.razor index 62cc2f15b..ed9c53cff 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/Pages/Friends/Friends.razor +++ b/MiniSpace.Web/src/MiniSpace.Web/Pages/Friends/Friends.razor @@ -9,68 +9,69 @@ @inject Radzen.DialogService DialogService @using MudBlazor - - - -@code { - private List _items = new List - { - new BreadcrumbItem("Home", href: "/", icon: Icons.Material.Filled.Home), - new BreadcrumbItem("Search", href: "/friends/search", icon: Icons.Material.Filled.PersonSearch), - new BreadcrumbItem("Friends", href: "/friends", disabled: true, icon: Icons.Material.Filled.LibraryAddCheck), - }; -} - -
-
- + @code { - private List friends; + private List friends = new List(); private string searchTerm; private bool pageInitialized; + private int currentPage = 1; + private int pageSize = 10; + private int totalFriends; + private bool hasMorePages => friends.Count < totalFriends; + + private List _items = new List + { + new BreadcrumbItem("Home", href: "/", icon: Icons.Material.Filled.Home), + new BreadcrumbItem("Search", href: "/friends/search", icon: Icons.Material.Filled.PersonSearch), + new BreadcrumbItem("Friends", href: "/friends", disabled: true, icon: Icons.Material.Filled.LibraryAddCheck), + }; protected override async Task OnInitializedAsync() { await IdentityService.InitializeAuthenticationState(); if (IdentityService.IsAuthenticated) - { - try - { - var studentId = IdentityService.GetCurrentUserId(); - var friendsResult = await FriendsService.GetAllFriendsAsync(studentId); - friends = friendsResult?.ToList() ?? new List(); - } - catch (Exception ex) - { - NotificationService.Notify(Radzen.NotificationSeverity.Error, "Failed to Load Friends", $"An error occurred: {ex.Message}", 5000); - friends = new List(); - } + { + await LoadFriends(); pageInitialized = true; } else @@ -224,8 +226,8 @@ private async Task RemoveFriend(Guid friendId) { await FriendsService.RemoveFriendAsync(friendId); - friends = friends.Where(f => f.StudentId != friendId).ToList(); - NotificationService.Notify(Radzen.NotificationSeverity.Warning, "Friend Removed", $"You have removed a friend.", 5000); + friends = friends.Where(f => f.FriendId != friendId).ToList(); + NotificationService.Notify(Radzen.NotificationSeverity.Warning, "Friend Removed", "You have removed a friend.", 5000); StateHasChanged(); } @@ -253,17 +255,33 @@ } private async Task ReloadFriends() + { + currentPage = 1; + friends.Clear(); + await LoadFriends(); + } + + private async Task LoadFriends() { try { var studentId = IdentityService.GetCurrentUserId(); - var friendsResult = await FriendsService.GetAllFriendsAsync(studentId); - friends = friendsResult?.ToList() ?? new List(); + var result = await FriendsService.GetAllFriendsAsync(studentId, currentPage, pageSize); + if (result != null) + { + friends.AddRange(result.Items); + totalFriends = result.TotalItems; + } } catch (Exception ex) { NotificationService.Notify(Radzen.NotificationSeverity.Error, "Failed to Load Friends", $"An error occurred: {ex.Message}", 5000); - friends = new List(); } } -} + + private async Task LoadMoreFriends() + { + currentPage++; + await LoadFriends(); + } +} \ No newline at end of file diff --git a/MiniSpace.Web/src/MiniSpace.Web/Pages/Friends/FriendsRequests.razor b/MiniSpace.Web/src/MiniSpace.Web/Pages/Friends/FriendsRequests.razor index fb70e4660..702c18535 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/Pages/Friends/FriendsRequests.razor +++ b/MiniSpace.Web/src/MiniSpace.Web/Pages/Friends/FriendsRequests.razor @@ -10,74 +10,72 @@ @inject Radzen.NotificationService NotificationService @inject IJSRuntime JSRuntime - - - - -@code { - private List _items = new List - { - new BreadcrumbItem("Home", href: "/", icon: Icons.Material.Filled.Home), - new BreadcrumbItem("Search", href: "/friends/search", icon: Icons.Material.Filled.PersonSearch), - new BreadcrumbItem("Friends", href: "/friends", icon: Icons.Material.Filled.LibraryAddCheck), - new BreadcrumbItem("Requests", href: "/friends/requests", disabled: true, icon: Icons.Material.Filled.GroupAdd), - }; -} - -

Incoming Friend Requests

- -
-
- + @code { private string searchTerm; private List students = new List(); - private IEnumerable sentRequests; - private StudentDto student; - RadzenNotification notificationComponent; + private IEnumerable sentRequests = Enumerable.Empty(); + private IEnumerable allFriends = Enumerable.Empty(); + private IEnumerable incomingRequests = Enumerable.Empty(); + private bool pageInitialized = false; private int currentPage = 1; private int pageSize = 10; private int totalStudents; - private IEnumerable allFriends; - private IEnumerable incomingRequests; - private bool pageInitialized; - private DotNetObjectReference _dotNetRef; - protected override async Task OnInitializedAsync() + private List _items = new List + { + new BreadcrumbItem("Home", href: "/", icon: Icons.Material.Filled.Home), + new BreadcrumbItem("Search", href: "/friends/search", disabled: true, icon: Icons.Material.Filled.PersonSearch) + }; + + protected override async Task OnInitializedAsync() { - await IdentityService.InitializeAuthenticationState(); + await IdentityService.InitializeAuthenticationState(); if (IdentityService.IsAuthenticated) { - sentRequests = await FriendsService.GetSentFriendRequestsAsync(); - incomingRequests = await FriendsService.GetIncomingFriendRequestsAsync(); - allFriends = await FriendsService.GetAllFriendsAsync(IdentityService.GetCurrentUserId()); - - await LoadStudents(); - pageInitialized = true; - StateHasChanged(); - - _dotNetRef = DotNetObjectReference.Create(this); - await JSRuntime.InvokeVoidAsync("infiniteScroll.initialize", _dotNetRef); + try + { + await LoadStudents(); + } + catch (Exception ex) + { + Snackbar.Add($"Failed to Load Data: {ex.Message}", Severity.Error); + } + finally + { + pageInitialized = true; + } } else { @@ -250,49 +241,59 @@ return string.IsNullOrEmpty(profileImageUrl) ? "images/default_profile_image.webp" : profileImageUrl; } - private async Task LoadStudents(string searchArgument = null) + private async Task LoadStudents(string searchArgument = null) { var response = await FriendsService.GetAllStudentsAsync(currentPage, pageSize, searchArgument); - if (response != null) { + + // Log the response object to the console + var jsonResponse = System.Text.Json.JsonSerializer.Serialize(response, new System.Text.Json.JsonSerializerOptions { WriteIndented = true }); + Console.WriteLine("Response JSON:"); + Console.WriteLine(jsonResponse); + + if (response?.Results != null) + { students.AddRange(response.Results); - totalStudents = response.Total; - StateHasChanged(); + totalStudents = response.Total; } } - [JSInvokable] - public async Task LoadMoreData() + private async Task SearchFriends() { - currentPage++; - await LoadStudents(searchTerm); - } - - private async Task SearchFriends() { currentPage = 1; students.Clear(); await LoadStudents(searchTerm); } - private async Task ConnectWithStudent(Guid studentId, MouseEventArgs e) + private async Task ConnectWithStudent(Guid studentId, MouseEventArgs e) { - var currentUserId = IdentityService.GetCurrentUserId(); - await FriendsService.InviteStudent(currentUserId, studentId); + try + { + var currentUserId = IdentityService.GetCurrentUserId(); + await FriendsService.InviteStudent(currentUserId, studentId); + + var student = students.FirstOrDefault(s => s.Id == studentId); + if (student != null) + { + student.InvitationSent = true; + } - var student = students.FirstOrDefault(s => s.Id == studentId); - if (student != null) + sentRequests = (await FriendsService.GetSentFriendRequestsAsync(currentPage, pageSize))?.Items ?? Enumerable.Empty(); + + Snackbar.Add("The invitation has been successfully sent.", Severity.Success); + await JSRuntime.InvokeVoidAsync("playNotificationSound"); + } + catch (Exception ex) + { + Snackbar.Add($"Failed to Send Invitation: {ex.Message}", Severity.Error); + } + finally { - student.InvitationSent = true; - student.IsInvitationPending = true; + StateHasChanged(); } - sentRequests = await FriendsService.GetSentFriendRequestsAsync(); - NotificationService.Notify(Radzen.NotificationSeverity.Success, "Invitation Sent", "The invitation has been successfully sent.", 10000); - await JSRuntime.InvokeVoidAsync("playNotificationSound"); - StateHasChanged(); } private void ViewDetails(Guid studentId) { NavigationManager.NavigateTo($"/user-details/{studentId}"); } - } diff --git a/MiniSpace.Web/src/MiniSpace.Web/Pages/Friends/OLD_FRINED_SEARCH_BAD_PAGIANTION.razor b/MiniSpace.Web/src/MiniSpace.Web/Pages/Friends/OLD_FRINED_SEARCH_BAD_PAGIANTION.razor deleted file mode 100644 index 57db8f031..000000000 --- a/MiniSpace.Web/src/MiniSpace.Web/Pages/Friends/OLD_FRINED_SEARCH_BAD_PAGIANTION.razor +++ /dev/null @@ -1,341 +0,0 @@ -@page "/bad-friends/bad-search" -@using MiniSpace.Web.HttpClients -@using MudBlazor -@inject NavigationManager NavigationManager -@using MiniSpace.Web.Areas.Friends -@inject IFriendsService FriendsService -@using MiniSpace.Web.DTO -@using MiniSpace.Web.Areas.Identity -@inject IIdentityService IdentityService -@inject Radzen.NotificationService NotificationService -@inject IJSRuntime JSRuntime - - - - -@code { - private List _items = new List - { - new BreadcrumbItem("Home", href: "/", icon: Icons.Material.Filled.Home), - new BreadcrumbItem("Search", href: "/friends/search", disabled: true, icon: Icons.Material.Filled.PersonSearch) - }; -} - -
-
- - @if (!pageInitialized) - { -
- -
- } -
- @foreach (var student in students) - { -
-
- Student Image -
-
@student.FirstName @student.LastName
-

@student.Email

-
-
- - - Details - - @if (student.Id != IdentityService.GetCurrentUserId() && !sentRequests.Any(r => r.InviteeId == student.Id) && !allFriends.Any(f => f.FriendId == student.Id)) - { - - - Connect - - } - else if (allFriends.Any(f => f.FriendId == student.Id)) - { - - - Connected - - } - else if (sentRequests.Any(r => r.InviteeId == student.Id)) - { - - - Pending - - } - else if (incomingRequests.Any(r => r.InviteeId == IdentityService.GetCurrentUserId() && r.State == DTO.States.FriendState.Requested)) - { - - - Incoming Request - - } - else if (student.Id == IdentityService.GetCurrentUserId()) - { - - - It's You - - } -
-
-
- } -
-
-
-
- - - Page @currentPage of @(Math.Ceiling((double)totalStudents / pageSize)) - - -
-
- - - -@code { - private string searchTerm; - private List students = new List(); - private IEnumerable sentRequests; - private StudentDto student; - RadzenNotification notificationComponent; - private int currentPage = 1; - private int pageSize = 10; - private int totalStudents; - private IEnumerable allFriends; - private IEnumerable incomingRequests; - private bool pageInitialized; - - protected override async Task OnInitializedAsync() - { - await IdentityService.InitializeAuthenticationState(); - - if (IdentityService.IsAuthenticated) - { - sentRequests = await FriendsService.GetSentFriendRequestsAsync(); - incomingRequests = await FriendsService.GetIncomingFriendRequestsAsync(); - allFriends = await FriendsService.GetAllFriendsAsync(IdentityService.GetCurrentUserId()); - - await LoadStudents(); - pageInitialized = true; - StateHasChanged(); - } - else - { - NavigationManager.NavigateTo("/login"); - } - } - - private string GetProfileImageUrl(string profileImageUrl) - { - return string.IsNullOrEmpty(profileImageUrl) ? "images/default_profile_image.webp" : profileImageUrl; - } - - private async Task LoadStudents(string searchArgument = null) { - int maxPage = (int)Math.Ceiling((double)totalStudents / pageSize); - if (currentPage > maxPage) currentPage = maxPage; - if (currentPage < 1) currentPage = 1; - - var response = await FriendsService.GetAllStudentsAsync(currentPage, pageSize, searchArgument); - if (response != null) { - students = response.Results; - totalStudents = response.Total; - StateHasChanged(); - } else { - students = new List(); - } - StateHasChanged(); - } - - private void OnDetails(StudentDto selectedStudent) - { - student = selectedStudent; - StateHasChanged(); - } - - private async Task SearchFriends() { - await LoadStudents(searchTerm); - } - - private async Task ConnectWithStudent(Guid studentId, MouseEventArgs e) - { - var currentUserId = IdentityService.GetCurrentUserId(); - await FriendsService.InviteStudent(currentUserId, studentId); - - var student = students.FirstOrDefault(s => s.Id == studentId); - if (student != null) - { - student.InvitationSent = true; - student.IsInvitationPending = true; - } - sentRequests = await FriendsService.GetSentFriendRequestsAsync(); - NotificationService.Notify(Radzen.NotificationSeverity.Success, "Invitation Sent", "The invitation has been successfully sent.", 10000); - await JSRuntime.InvokeVoidAsync("playNotificationSound"); - StateHasChanged(); - } - - private async Task SetPage(int page) - { - if (page < 1 || page > Math.Ceiling((double)totalStudents / pageSize)) { - return; - } - currentPage = page; - student = null; - await LoadStudents(); - } - - private void ViewDetails(Guid studentId) - { - NavigationManager.NavigateTo($"/user-details/{studentId}"); - } - -} \ No newline at end of file diff --git a/MiniSpace.Web/src/MiniSpace.Web/Pages/Friends/SentRequests.razor b/MiniSpace.Web/src/MiniSpace.Web/Pages/Friends/SentRequests.razor index 37035ff02..4d379cdc9 100644 --- a/MiniSpace.Web/src/MiniSpace.Web/Pages/Friends/SentRequests.razor +++ b/MiniSpace.Web/src/MiniSpace.Web/Pages/Friends/SentRequests.razor @@ -11,68 +11,66 @@ @inject IJSRuntime JSRuntime - - - + + -@code { - private List _items = new List - { - new BreadcrumbItem("Home", href: "/", icon: Icons.Material.Filled.Home), - new BreadcrumbItem("Search", href: "/friends/search", icon: Icons.Material.Filled.PersonSearch), - new BreadcrumbItem("Friends", href: "/friends", icon: Icons.Material.Filled.LibraryAddCheck), - new BreadcrumbItem("Requests", href: "/friends/requests", icon: Icons.Material.Filled.GroupAdd), - new BreadcrumbItem("Sent Requests", href: "/friends/sent-requests", disabled: true, icon: Icons.Material.Filled.PersonAddAlt1), - }; -} +

Sent Friend Requests

-
-
- - @if (sentRequests == null) - { -
- -
- } - else if (filteredSentRequests.Any()) - { - @foreach (var request in filteredSentRequests) - { -
-
- Invitee Image -
-
@request.InviteeName
-

@request.InviteeEmail

-

@request.RequestedAt.ToLocalTime().ToString("yyyy-MM-dd")

-

@request.State

-
-
- - - Withdraw - -
-
+
+
+ - } - } - else - { -

No sent requests.

- } -
-
- + @if (!pageInitialized) + { +
+ +
+ } + else if (filteredSentRequests != null && filteredSentRequests.Any()) + { + @foreach (var request in filteredSentRequests) + { +
+
+ Invitee Image +
+
@request.InviteeName
+

@request.InviteeEmail

+

@request.RequestedAt.ToLocalTime().ToString("yyyy-MM-dd")

+

@request.State

+
+
+ + + Withdraw + +
+
+
+ } + + @if (hasMorePages) + { + + + Load More + + } + } + else + { +

No sent requests.

+ } +
+
+