diff --git a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Api/Program.cs b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Api/Program.cs index c1b8e8662..19256117f 100644 --- a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Api/Program.cs +++ b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Api/Program.cs @@ -55,8 +55,10 @@ public static async Task Main(string[] args) .Post("events/{eventId}/show-interest") .Delete("events/{eventId}/show-interest") .Post("events/{eventId}/rate") + .Delete("events/{eventId}/rate") .Get>>("events/student/{studentId}") .Get("events/{eventId}/participants") + .Get("events/{eventId}/rating") .Post("events/{eventId}/participants") .Delete("events/{eventId}/participants") ) diff --git a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/Commands/CancelRateEvent.cs b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/Commands/CancelRateEvent.cs new file mode 100644 index 000000000..8aeeaf5f4 --- /dev/null +++ b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/Commands/CancelRateEvent.cs @@ -0,0 +1,11 @@ +using System; +using Convey.CQRS.Commands; + +namespace MiniSpace.Services.Events.Application.Commands +{ + public class CancelRateEvent: ICommand + { + public Guid EventId { get; set; } + public Guid StudentId { get; set; } + } +} \ No newline at end of file diff --git a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/Commands/Handlers/CancelRateEventHandler.cs b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/Commands/Handlers/CancelRateEventHandler.cs new file mode 100644 index 000000000..a3daeacb0 --- /dev/null +++ b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/Commands/Handlers/CancelRateEventHandler.cs @@ -0,0 +1,30 @@ +using System.Threading; +using System.Threading.Tasks; +using Convey.CQRS.Commands; +using MiniSpace.Services.Events.Application.Exceptions; +using MiniSpace.Services.Events.Core.Repositories; + +namespace MiniSpace.Services.Events.Application.Commands.Handlers +{ + public class CancelRateEventHandler : ICommandHandler + { + private readonly IEventRepository _eventRepository; + + public CancelRateEventHandler(IEventRepository eventRepository) + { + _eventRepository = eventRepository; + } + + public async Task HandleAsync(CancelRateEvent command, CancellationToken cancellationToken) + { + var @event = await _eventRepository.GetAsync(command.EventId); + if (@event is null) + { + throw new EventNotFoundException(command.EventId); + } + + @event.CancelRate(command.StudentId); + await _eventRepository.UpdateAsync(@event); + } + } +} \ No newline at end of file diff --git a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/DTO/EventDto.cs b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/DTO/EventDto.cs index aa1352062..22881081b 100644 --- a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/DTO/EventDto.cs +++ b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/DTO/EventDto.cs @@ -26,7 +26,7 @@ public class EventDto public DateTime UpdatedAt { get; set; } public bool IsSignedUp { get; set; } public bool IsInterested { get; set; } - public bool HasRated { get; set; } + public int? StudentRating { get; set; } public IEnumerable FriendsInterestedIn { get; set; } public IEnumerable FriendsSignedUp { get; set; } @@ -52,7 +52,7 @@ public EventDto(Event @event, Guid studentId) PublishDate = @event.PublishDate; IsSignedUp = @event.SignedUpStudents.Any(x => x.StudentId == studentId); IsInterested = @event.InterestedStudents.Any(x => x.StudentId == studentId); - HasRated = @event.Ratings.Any(x => x.StudentId == studentId); + StudentRating = @event.Ratings.FirstOrDefault(x => x.StudentId == studentId)?.Value; FriendsInterestedIn = Enumerable.Empty(); FriendsSignedUp = Enumerable.Empty(); } diff --git a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/DTO/EventRatingDto.cs b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/DTO/EventRatingDto.cs new file mode 100644 index 000000000..fa5e434fd --- /dev/null +++ b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/DTO/EventRatingDto.cs @@ -0,0 +1,11 @@ +using System; + +namespace MiniSpace.Services.Events.Application.DTO +{ + public class EventRatingDto + { + public Guid EventId { get; set; } + public int TotalRatings { get; set; } + public double AverageRating { get; set; } + } +} \ No newline at end of file diff --git a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/Queries/GetEventRating.cs b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/Queries/GetEventRating.cs new file mode 100644 index 000000000..68b209d7e --- /dev/null +++ b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/Queries/GetEventRating.cs @@ -0,0 +1,11 @@ +using System; +using Convey.CQRS.Queries; +using MiniSpace.Services.Events.Application.DTO; + +namespace MiniSpace.Services.Events.Application.Queries +{ + public class GetEventRating : IQuery + { + public Guid EventId { get; set; } + } +} \ No newline at end of file diff --git a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/Queries/GetStudentEvents.cs b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/Queries/GetStudentEvents.cs index 9a5891e0d..d3ede5565 100644 --- a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/Queries/GetStudentEvents.cs +++ b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Application/Queries/GetStudentEvents.cs @@ -9,6 +9,8 @@ namespace MiniSpace.Services.Events.Application.Queries public class GetStudentEvents : IQuery>> { public Guid StudentId { get; set; } + public string EngagementType { get; set; } + public int Page { get; set; } public int NumberOfResults { get; set; } } } \ No newline at end of file diff --git a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Core/Entities/Event.cs b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Core/Entities/Event.cs index da9c0ffe0..b894543e7 100644 --- a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Core/Entities/Event.cs +++ b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Core/Entities/Event.cs @@ -178,12 +178,18 @@ public void Rate(Guid studentId, int rating) throw new InvalidRatingValueException(rating); } - if (_ratings.Any(r => r.StudentId == studentId)) + _ratings.Add(new Rating(studentId, rating)); + } + + public void CancelRate(Guid studentId) + { + var rating = _ratings.SingleOrDefault(r => r.StudentId == studentId); + if (rating is null) { - throw new StudentAlreadyRatedEventException(Id, studentId); + throw new StudentNotRatedEventException(studentId, Id); } - _ratings.Add(new Rating(studentId, rating)); + _ratings.Remove(rating); } public bool UpdateState(DateTime now) diff --git a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Core/Exceptions/StudentAlreadyRatedEventException.cs b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Core/Exceptions/StudentAlreadyRatedEventException.cs deleted file mode 100644 index 4d7821307..000000000 --- a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Core/Exceptions/StudentAlreadyRatedEventException.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace MiniSpace.Services.Events.Core.Exceptions -{ - public class StudentAlreadyRatedEventException : DomainException - { - public override string Code { get; } = "student_already_rated_event"; - public Guid EventId { get; } - public Guid StudentId { get; } - - public StudentAlreadyRatedEventException(Guid eventId, Guid studentId) - : base($"Student with ID: '{studentId}' already rated event with ID: '{eventId}'.") - { - EventId = eventId; - StudentId = studentId; - } - } -} \ No newline at end of file diff --git a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Core/Exceptions/StudentNotRatedEventException.cs b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Core/Exceptions/StudentNotRatedEventException.cs new file mode 100644 index 000000000..6023b304e --- /dev/null +++ b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Core/Exceptions/StudentNotRatedEventException.cs @@ -0,0 +1,18 @@ +using System; + +namespace MiniSpace.Services.Events.Core.Exceptions +{ + public class StudentNotRatedEventException : DomainException + { + public override string Code { get; } = "student_not_rated_event"; + public Guid EventId { get; } + public Guid StudentId { get; } + + public StudentNotRatedEventException(Guid eventId, Guid studentId) + : base($"Student with ID: '{studentId}' has not rated event with ID: '{eventId}'.") + { + EventId = eventId; + StudentId = studentId; + } + } +} \ No newline at end of file diff --git a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Core/Repositories/IEventRepository.cs b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Core/Repositories/IEventRepository.cs index bed362306..afd21cfbc 100644 --- a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Core/Repositories/IEventRepository.cs +++ b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Core/Repositories/IEventRepository.cs @@ -16,9 +16,11 @@ public interface IEventRepository Task<(IEnumerable events, int pageNumber,int pageSize, int totalPages, int totalElements)> BrowseEventsAsync( int pageNumber, int pageSize, string name, string organizer, DateTime dateFrom, DateTime dateTo, Category? category, State? state, IEnumerable friends, EventEngagementType? friendsEngagementType, - IEnumerable sortBy, string direction, IEnumerable eventIds = null); + IEnumerable sortBy, string direction); Task<(IEnumerable events, int pageNumber,int pageSize, int totalPages, int totalElements)> BrowseOrganizerEventsAsync( int pageNumber, int pageSize, string name, Guid organizerId, DateTime dateFrom, DateTime dateTo, IEnumerable sortBy, string direction, State? state); + Task<(IEnumerable events, int pageNumber,int pageSize, int totalPages, int totalElements)> BrowseStudentEventsAsync( + int pageNumber, int pageSize, IEnumerable eventIds, IEnumerable sortBy, string direction); } } \ No newline at end of file diff --git a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Documents/Extensions.cs b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Documents/Extensions.cs index 5464f8410..855a05bcc 100644 --- a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Documents/Extensions.cs +++ b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Documents/Extensions.cs @@ -28,7 +28,7 @@ public static EventDto AsDto(this EventDocument document, Guid studentId) UpdatedAt = document.UpdatedAt, IsSignedUp = document.SignedUpStudents.Any(x => x.StudentId == studentId), IsInterested = document.InterestedStudents.Any(x => x.StudentId == studentId), - HasRated = document.Ratings.Any(x => x.StudentId == studentId) + StudentRating = document.Ratings.FirstOrDefault(x => x.StudentId == studentId)?.Value, }; public static EventDto AsDtoWithFriends(this EventDocument document, Guid studentId, IEnumerable friends) @@ -76,6 +76,14 @@ public static EventParticipantsDto AsDto(this EventDocument document) InterestedStudents = document.InterestedStudents.Select(p => p.AsDto()), SignedUpStudents = document.SignedUpStudents.Select(p => p.AsDto()) }; + + public static EventRatingDto AsRatingDto(this EventDocument document) + => new () + { + EventId = document.Id, + TotalRatings = document.Ratings.Count(), + AverageRating = document.Ratings.Any() ? document.Ratings.Average(x => x.Value) : 0 + }; public static AddressDto AsDto(this Address entity) => new () diff --git a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Queries/Handlers/GetEventRatingHandler.cs b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Queries/Handlers/GetEventRatingHandler.cs new file mode 100644 index 000000000..2e947f53d --- /dev/null +++ b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Queries/Handlers/GetEventRatingHandler.cs @@ -0,0 +1,42 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Convey.CQRS.Queries; +using Convey.Persistence.MongoDB; +using MiniSpace.Services.Events.Application; +using MiniSpace.Services.Events.Application.DTO; +using MiniSpace.Services.Events.Application.Queries; +using MiniSpace.Services.Events.Infrastructure.Mongo.Documents; + +namespace MiniSpace.Services.Events.Infrastructure.Mongo.Queries.Handlers +{ + public class GetEventRatingHandler : IQueryHandler + { + private readonly IMongoRepository _eventRepository; + private readonly IAppContext _appContext; + + public GetEventRatingHandler(IMongoRepository eventRepository, + IAppContext appContext) + { + _eventRepository = eventRepository; + _appContext = appContext; + } + + public async Task HandleAsync(GetEventRating query, CancellationToken cancellationToken) + { + var document = await _eventRepository.GetAsync(p => p.Id == query.EventId); + if(document is null) + { + return null; + } + var identity = _appContext.Identity; + if(identity.IsAuthenticated && identity.Id != document.Organizer.Id && !identity.IsAdmin) + { + return null; + } + + return document.AsRatingDto(); + } + + } +} \ No newline at end of file diff --git a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Queries/Handlers/GetStudentEventsHandler.cs b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Queries/Handlers/GetStudentEventsHandler.cs index 14818f556..4950a1b00 100644 --- a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Queries/Handlers/GetStudentEventsHandler.cs +++ b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Queries/Handlers/GetStudentEventsHandler.cs @@ -21,13 +21,15 @@ public class GetStudentEventsHandler : IQueryHandler>> HandleAsync(GetStudentEv 1, query.NumberOfResults, 0, 0); } + var engagementType = _eventValidator.ParseEngagementType(query.EngagementType); + var studentEvents = await _studentsServiceClient.GetAsync(query.StudentId); - var studentEventIds = studentEvents.InterestedInEvents.Union(studentEvents.SignedUpEvents).ToList(); + var studentEventIds = engagementType switch + { + EventEngagementType.SignedUp => studentEvents.SignedUpEvents.ToList(), + EventEngagementType.InterestedIn => studentEvents.InterestedInEvents.ToList(), + _ => [] + }; - var result = await _eventRepository.BrowseEventsAsync(1, query.NumberOfResults, - string.Empty, string.Empty, DateTime.MinValue, DateTime.MinValue, null, null, - Enumerable.Empty(), null, Enumerable.Empty(), "asc", studentEventIds); + var result = await _eventRepository.BrowseStudentEventsAsync(query.Page, + query.NumberOfResults, studentEventIds, Enumerable.Empty(), "asc"); - return new PagedResponse>(result.Item1.Select(e => new EventDto(e, identity.Id)), - result.Item2, result.Item3, result.Item4, result.Item5);; + return new PagedResponse>(result.events.Select(e => new EventDto(e, identity.Id)), + result.pageNumber, result.pageSize, result.totalPages, result.totalElements); } } } \ No newline at end of file diff --git a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Repositories/EventMongoRepository.cs b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Repositories/EventMongoRepository.cs index af2c686d7..cd3e8621d 100644 --- a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Repositories/EventMongoRepository.cs +++ b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Repositories/EventMongoRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -53,9 +54,9 @@ public async Task> GetAllAsync() public async Task<(IEnumerable events, int pageNumber,int pageSize, int totalPages, int totalElements)> BrowseEventsAsync( int pageNumber, int pageSize, string name, string organizer, DateTime dateFrom, DateTime dateTo, Category? category, State? state, IEnumerable friends, EventEngagementType? friendsEngagementType, - IEnumerable sortBy, string direction, IEnumerable eventIds = null) + IEnumerable sortBy, string direction) { - var filterDefinition = Extensions.ToFilterDefinition(name, dateFrom, dateTo, eventIds) + var filterDefinition = Extensions.ToFilterDefinition(name, dateFrom, dateTo) .AddOrganizerNameFilter(organizer) .AddCategoryFilter(category) .AddRestrictedStateFilter(state) @@ -83,6 +84,20 @@ public async Task> GetAllAsync() pagedEvents.totalPages, pagedEvents.totalElements); } + public async Task<(IEnumerable events, int pageNumber, int pageSize, int totalPages, int totalElements)> BrowseStudentEventsAsync( + int pageNumber, int pageSize, IEnumerable eventIds, IEnumerable sortBy, string direction) + { + var filterDefinition = Extensions.CreateFilterDefinition() + .AddEventIdFilter(eventIds); + + var sortDefinition = Extensions.ToSortDefinition(sortBy, direction); + + var pagedEvents = await BrowseAsync(filterDefinition, sortDefinition, pageNumber, pageSize); + + return (pagedEvents.data.Select(e => e.AsEntity()), pageNumber, pageSize, + pagedEvents.totalPages, pagedEvents.totalElements); + } + public Task AddAsync(Event @event) => _repository.AddAsync(@event.AsDocument()); public Task UpdateAsync(Event @event) => _repository.UpdateAsync(@event.AsDocument()); public Task DeleteAsync(Guid id) => _repository.DeleteAsync(id); diff --git a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Repositories/Extensions.cs b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Repositories/Extensions.cs index 1c5d00f6b..1bb098ac5 100644 --- a/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Repositories/Extensions.cs +++ b/MiniSpace.Services.Events/src/MiniSpace.Services.Events.Infrastructure/Mongo/Repositories/Extensions.cs @@ -58,11 +58,15 @@ public static class Extensions return (totalPages, (int)count, data); } + + public static FilterDefinition CreateFilterDefinition() + { + return FilterDefinitionBuilder.Empty; + } - public static FilterDefinition ToFilterDefinition(string name, DateTime dateFrom, - DateTime dateTo, IEnumerable eventIds = null) + public static FilterDefinition ToFilterDefinition(string name, DateTime dateFrom, DateTime dateTo) { - var filterDefinition = FilterDefinitionBuilder.Empty; + var filterDefinition = CreateFilterDefinition(); if (!string.IsNullOrWhiteSpace(name)) { @@ -80,11 +84,6 @@ public static FilterDefinition ToFilterDefinition(string name, Da filterDefinition &= FilterDefinitionBuilder.Lte(x => x.EndDate, dateTo); } - if (eventIds != null) - { - filterDefinition &= FilterDefinitionBuilder.In(x => x.Id, eventIds); - } - return filterDefinition; } @@ -165,6 +164,13 @@ public static FilterDefinition AddFriendsFilter (this FilterDefin return filterDefinition; } + public static FilterDefinition AddEventIdFilter(this FilterDefinition filterDefinition, + IEnumerable eventIds) + { + filterDefinition &= FilterDefinitionBuilder.In(x => x.Id, eventIds); + return filterDefinition; + } + public static SortDefinition ToSortDefinition(IEnumerable sortByArguments, string direction) { var sort = sortByArguments.ToList();