diff --git a/Server/ReasnAPI/ReasnAPI.Tests/Services/CommentServiceTests.cs b/Server/ReasnAPI/ReasnAPI.Tests/Services/CommentServiceTests.cs index 26105028..4a390dcc 100644 --- a/Server/ReasnAPI/ReasnAPI.Tests/Services/CommentServiceTests.cs +++ b/Server/ReasnAPI/ReasnAPI.Tests/Services/CommentServiceTests.cs @@ -5,69 +5,13 @@ using ReasnAPI.Models.DTOs; using ReasnAPI.Models.Enums; using ReasnAPI.Services; +using System.Runtime.Versioning; namespace ReasnAPI.Tests.Services; [TestClass] public class CommentServiceTests { - [TestMethod] - public void GetCommentById_CommentExist_CommentReturned() - { - var mockContext = new Mock(); - - var event1 = new Event - { - Id = 1, - Name = "Event", - Description = "Description", - }; - - var user = new User - { - Id = 1, - Username = "Username", - Email = "Email", - Password = "Password", - }; - - var comment = new Comment - { - Id = 1, - Content = "Content", - EventId = event1.Id, - UserId = user.Id - }; - - var fakeComment = new FakeDbSet { comment }; - var fakeEvent = new FakeDbSet { event1 }; - var fakeUser = new FakeDbSet { user }; - - mockContext.Setup(c => c.Users).Returns(fakeUser); - mockContext.Setup(c => c.Events).Returns(fakeEvent); - mockContext.Setup(c => c.Comments).Returns(fakeComment); - - var commentService = new CommentService(mockContext.Object); - - var result = commentService.GetCommentById(1); - - Assert.IsNotNull(result); - Assert.AreEqual("Content", result.Content); - Assert.AreEqual(1, result.EventId); - Assert.AreEqual(1, result.UserId); - } - - [TestMethod] - public void GetCommentById_CommentDoesNotExist_NullReturned() - { - var mockContext = new Mock(); - mockContext.Setup(c => c.Comments).ReturnsDbSet([]); - - var commentService = new CommentService(mockContext.Object); - - Assert.ThrowsException(() => commentService.GetCommentById(1)); - } - [TestMethod] public void GetAllComments_CommentsExist_CommentsReturned() { @@ -86,22 +30,23 @@ public void GetAllComments_CommentsExist_CommentsReturned() Id = 1, Name = "Event", Description = "Description", + Slug = "Slug" }; var comment1 = new Comment { Id = 1, Content = "Content", - EventId = event1.Id, - UserId = user.Id + Event = event1, + User = user }; var comment2 = new Comment { Id = 2, Content = "Content", - EventId = event1.Id, - UserId = user.Id + Event = event1, + User = user }; mockContext.Setup(c => c.Users).ReturnsDbSet([user]); @@ -148,22 +93,25 @@ public void GetCommentsByFilter_CommentsExist_CommentsReturned() Id = 1, Name = "Event", Description = "Description", + Slug = "Slug" }; var comment1 = new Comment { Id = 1, Content = "Content", - EventId = event1.Id, - UserId = user.Id + Event = event1, + EventId = 1, + User = user }; var comment2 = new Comment { Id = 2, Content = "Content", - EventId = event1.Id, - UserId = user.Id + Event = event1, + EventId = 1, + User = user }; mockContext.Setup(c => c.Users).ReturnsDbSet([user]); @@ -210,6 +158,7 @@ public void CreateComment_CommentCreated_CommentReturned() Id = 1, Name = "Event", Description = "Description", + Slug = "Slug", }; mockContext.Setup(c => c.Users).ReturnsDbSet([user]); @@ -221,16 +170,16 @@ public void CreateComment_CommentCreated_CommentReturned() var commentDto = new CommentDto { Content = "Content", - UserId = 1, - EventId = 1 + Username = user.Username, + EventSlug = event1.Slug, }; - var result = commentService.CreateComment(commentDto); + var result = commentService.CreateComment(commentDto, event1.Id, user.Id); Assert.IsNotNull(result); Assert.AreEqual("Content", result.Content); - Assert.AreEqual(1, result.EventId); - Assert.AreEqual(1, result.UserId); + Assert.AreEqual("Slug", result.EventSlug); + Assert.AreEqual("Username", result.Username); } [TestMethod] @@ -241,81 +190,7 @@ public void CreateComment_CommentDtoIsNull_NullReturned() var commentService = new CommentService(mockContext.Object); - Assert.ThrowsException(() => commentService.CreateComment(null)); - } - - [TestMethod] - public void UpdateComment_CommentUpdated_CommentReturned() - { - var mockContext = new Mock(); - - var user = new User - { - Id = 1, - Username = "Username", - Email = "Email", - Password = "Password", - }; - - var event1 = new Event - { - Id = 1, - Name = "Event", - Description = "Description", - }; - - var comment = new Comment - { - Id = 1, - Content = "Content", - EventId = event1.Id, - UserId = user.Id - }; - - mockContext.Setup(c => c.Users).ReturnsDbSet([user]); - mockContext.Setup(c => c.Events).ReturnsDbSet([event1]); - mockContext.Setup(c => c.Comments).ReturnsDbSet([comment]); - - var commentService = new CommentService(mockContext.Object); - - var result = commentService.UpdateComment(1, new CommentDto - { - Content = "UpdatedContent", - EventId = 2, - UserId = 1 - }); - - Assert.IsNotNull(result); - Assert.AreEqual("UpdatedContent", result.Content); - Assert.AreEqual(1, result.EventId); - Assert.AreEqual(1, result.UserId); - } - - [TestMethod] - public void UpdateComment_CommentDoesNotExist_NullReturned() - { - var mockContext = new Mock(); - mockContext.Setup(c => c.Comments).ReturnsDbSet([]); - - var commentService = new CommentService(mockContext.Object); - - Assert.ThrowsException(() => commentService.UpdateComment(1, new CommentDto - { - Content = "UpdatedContent", - EventId = 2, - UserId = 1 - })); - } - - [TestMethod] - public void UpdateComment_CommentDtoIsNull_NullReturned() - { - var mockContext = new Mock(); - mockContext.Setup(c => c.Comments).ReturnsDbSet([]); - - var commentService = new CommentService(mockContext.Object); - - Assert.ThrowsException(() => commentService.UpdateComment(1, null)); + Assert.ThrowsException(() => commentService.CreateComment(null, 1, 1)); } [TestMethod] diff --git a/Server/ReasnAPI/ReasnAPI.Tests/Services/EventServicesTest.cs b/Server/ReasnAPI/ReasnAPI.Tests/Services/EventServicesTest.cs index fc504b17..2cf89baa 100644 --- a/Server/ReasnAPI/ReasnAPI.Tests/Services/EventServicesTest.cs +++ b/Server/ReasnAPI/ReasnAPI.Tests/Services/EventServicesTest.cs @@ -24,7 +24,6 @@ public void UpdateEvent_EventExists_EventUpdated() var eventDto = new EventDto() { Name = "name1", - AddressId = 1, Description = "description2", OrganizerId = 1, StartAt = DateTime.Now, @@ -82,7 +81,7 @@ public void UpdateEvent_EventExists_EventUpdated() mockContext.Setup(c => c.Parameters).ReturnsDbSet(new List()); mockContext.Setup(c => c.Comments).ReturnsDbSet(new List()); mockContext.Setup(c => c.Participants).ReturnsDbSet(new List()); - var eventService = new EventService(mockContext.Object, new ParameterService(mockContext.Object), new TagService(mockContext.Object), new CommentService(mockContext.Object)); + var eventService = new EventService(mockContext.Object, new ParameterService(mockContext.Object), new TagService(mockContext.Object), new CommentService(mockContext.Object), new AddressService(mockContext.Object), new ImageService(mockContext.Object)); var result = eventService.UpdateEvent(1, eventDto); Assert.AreEqual("name1", result.Name); @@ -102,7 +101,6 @@ public void UpdateEvent_EventDoesNotExist_NullReturned() var eventDto = new EventDto() { Name = "name1", - AddressId = 1, Description = "description2", OrganizerId = 1, StartAt = DateTime.Now, @@ -144,7 +142,7 @@ public void UpdateEvent_EventDoesNotExist_NullReturned() UpdatedAt =DateTime.Now }}); - var eventService = new EventService(mockContext.Object, new ParameterService(mockContext.Object), new TagService(mockContext.Object), new CommentService(mockContext.Object)); + var eventService = new EventService(mockContext.Object, new ParameterService(mockContext.Object), new TagService(mockContext.Object), new CommentService(mockContext.Object), new AddressService(mockContext.Object), new ImageService(mockContext.Object)); Assert.ThrowsException(() => eventService.UpdateEvent(1, eventDto)); } @@ -205,7 +203,7 @@ public void GetEventById_EventExists_EventReturned() UpdatedAt =DateTime.Now }}); - var eventService = new EventService(mockContext.Object, new ParameterService(mockContext.Object), new TagService(mockContext.Object), new CommentService(mockContext.Object)); + var eventService = new EventService(mockContext.Object, new ParameterService(mockContext.Object), new TagService(mockContext.Object), new CommentService(mockContext.Object), new AddressService(mockContext.Object), new ImageService(mockContext.Object)); var result = eventService.GetEventById(1); Assert.IsNotNull(result); @@ -250,7 +248,7 @@ public void GetEventById_EventDoesNotExist_NullReturned() }}); - var eventService = new EventService(mockContext.Object, new ParameterService(mockContext.Object), new TagService(mockContext.Object), new CommentService(mockContext.Object)); + var eventService = new EventService(mockContext.Object, new ParameterService(mockContext.Object), new TagService(mockContext.Object), new CommentService(mockContext.Object), new AddressService(mockContext.Object), new ImageService(mockContext.Object)); Assert.ThrowsException(() => eventService.GetEventById(1)); } @@ -320,7 +318,7 @@ public void DeleteEvent_EventExists_EventDeleted() mockContext.Setup(c => c.Comments).ReturnsDbSet(new List()); mockContext.Setup(c => c.Participants).ReturnsDbSet(new List()); - var eventService = new EventService(mockContext.Object, new ParameterService(mockContext.Object), new TagService(mockContext.Object), new CommentService(mockContext.Object)); + var eventService = new EventService(mockContext.Object, new ParameterService(mockContext.Object), new TagService(mockContext.Object), new CommentService(mockContext.Object), new AddressService(mockContext.Object), new ImageService(mockContext.Object)); eventService.DeleteEvent(1); diff --git a/Server/ReasnAPI/ReasnAPI.Tests/Services/ParticipantServiceTests.cs b/Server/ReasnAPI/ReasnAPI.Tests/Services/ParticipantServiceTests.cs index 80057add..8537a802 100644 --- a/Server/ReasnAPI/ReasnAPI.Tests/Services/ParticipantServiceTests.cs +++ b/Server/ReasnAPI/ReasnAPI.Tests/Services/ParticipantServiceTests.cs @@ -11,62 +11,6 @@ namespace ReasnAPI.Tests.Services; [TestClass] public class ParticipantServiceTests { - [TestMethod] - public void GetParticipantById_ParticipantExists_ParticipantReturned() - { - var mockContext = new Mock(); - - var event1 = new Event - { - Id = 1, - Name = "Event", - Description = "Description" - }; - - var user = new User - { - Id = 1, - Username = "User", - Email = "Email", - Password = "Password" - }; - - var participant = new Participant - { - Id = 1, - EventId = event1.Id, - UserId = user.Id, - Status = ParticipantStatus.Interested - }; - - var fakeParticipant = new FakeDbSet { participant }; - var fakeEvent = new FakeDbSet { event1 }; - var fakeUser = new FakeDbSet { user }; - - mockContext.Setup(c => c.Events).Returns(fakeEvent); - mockContext.Setup(c => c.Users).Returns(fakeUser); - mockContext.Setup(c => c.Participants).Returns(fakeParticipant); - - var participantService = new ParticipantService(mockContext.Object); - - var result = participantService.GetParticipantById(1); - - Assert.IsNotNull(result); - Assert.AreEqual(1, result.EventId); - Assert.AreEqual(1, result.UserId); - Assert.AreEqual(ParticipantStatus.Interested, result.Status); - } - - [TestMethod] - public void GetParticipantById_ParticipantDoesNotExist_NullReturned() - { - var mockContext = new Mock(); - mockContext.Setup(c => c.Participants).ReturnsDbSet([]); - - var participantService = new ParticipantService(mockContext.Object); - - Assert.ThrowsException(() => participantService.GetParticipantById(1)); - } [TestMethod] public void GetAllParticipants_ParticipantsExist_ParticipantsReturned() @@ -99,16 +43,16 @@ public void GetAllParticipants_ParticipantsExist_ParticipantsReturned() var participant1 = new Participant { Id = 1, - EventId = event1.Id, - UserId = user1.Id, + Event = event1, + User = user1, Status = ParticipantStatus.Interested }; var participant2 = new Participant { Id = 2, - EventId = event1.Id, - UserId = user2.Id, + Event = event1, + User = user2, Status = ParticipantStatus.Participating }; @@ -169,16 +113,18 @@ public void GetParticipantsByFilter_ParticipantsExist_ParticipantsReturned() var participant1 = new Participant { Id = 1, + Event = event1, EventId = event1.Id, - UserId = user1.Id, + User = user1, Status = ParticipantStatus.Interested }; var participant2 = new Participant { Id = 2, + Event = event1, EventId = event1.Id, - UserId = user2.Id, + User = user2, Status = ParticipantStatus.Participating }; @@ -217,13 +163,14 @@ public void CreateParticipant_ParticipantCreated_ParticipantReturned() { Id = 1, Name = "Event", - Description = "Description" + Description = "Description", + Slug = "Event-Slug" }; var user = new User { Id = 1, - Username = "User", + Username = "Username", Email = "Email", Password = "Password" }; @@ -236,16 +183,16 @@ public void CreateParticipant_ParticipantCreated_ParticipantReturned() var participantDto = new ParticipantDto { - EventId = 1, - UserId = 1, + EventSlug = "Event-Slug", + Username = "Username", Status = ParticipantStatus.Interested }; - var result = participantService.CreateParticipant(participantDto); + var result = participantService.CreateOrUpdateParticipant(participantDto); Assert.IsNotNull(result); - Assert.AreEqual(1, result.EventId); - Assert.AreEqual(1, result.UserId); + Assert.AreEqual("Event-Slug", result.EventSlug); + Assert.AreEqual("Username", result.Username); Assert.AreEqual(ParticipantStatus.Interested, result.Status); } @@ -257,7 +204,7 @@ public void CreateParticipant_ParticipantDtoIsNull_NullReturned() var participantService = new ParticipantService(mockContext.Object); - Assert.ThrowsException(() => participantService.CreateParticipant(null)); + Assert.ThrowsException(() => participantService.CreateOrUpdateParticipant(null)); } [TestMethod] @@ -269,13 +216,14 @@ public void UpdateParticipant_ParticipantExists_ParticipantReturned() { Id = 1, Name = "Event", - Description = "Description" + Description = "Description", + Slug = "Event-Slug" }; var user = new User { Id = 1, - Username = "User", + Username = "Username", Email = "Email", Password = "Password" }; @@ -283,8 +231,8 @@ public void UpdateParticipant_ParticipantExists_ParticipantReturned() var participant = new Participant { Id = 1, - EventId = event1.Id, - UserId = user.Id, + Event = event1, + User = user, Status = ParticipantStatus.Interested }; @@ -294,16 +242,16 @@ public void UpdateParticipant_ParticipantExists_ParticipantReturned() var participantService = new ParticipantService(mockContext.Object); - var result = participantService.UpdateParticipant(1, new ParticipantDto + var result = participantService.CreateOrUpdateParticipant(new ParticipantDto { - EventId = 2, - UserId = 2, + EventSlug = "Event-Slug", + Username = "Username", Status = ParticipantStatus.Participating }); Assert.IsNotNull(result); - Assert.AreEqual(1, result.EventId); - Assert.AreEqual(1, result.UserId); + Assert.AreEqual("Event-Slug", result.EventSlug); + Assert.AreEqual("Username", result.Username); Assert.AreEqual(ParticipantStatus.Participating, result.Status); } @@ -311,14 +259,16 @@ public void UpdateParticipant_ParticipantExists_ParticipantReturned() public void UpdateParticipant_ParticipantDoesNotExist_NullReturned() { var mockContext = new Mock(); + mockContext.Setup(c => c.Events).ReturnsDbSet([]); + mockContext.Setup(c => c.Users).ReturnsDbSet([]); mockContext.Setup(c => c.Participants).ReturnsDbSet([]); var participantService = new ParticipantService(mockContext.Object); - Assert.ThrowsException(() => participantService.UpdateParticipant(1, new ParticipantDto + Assert.ThrowsException(() => participantService.CreateOrUpdateParticipant(new ParticipantDto { - EventId = 2, - UserId = 2, + EventSlug = "Event-Slug", + Username = "Username", Status = ParticipantStatus.Participating })); } @@ -333,20 +283,45 @@ public void UpdateParticipant_ParticipantDtoIsNull_NullReturned() var participantService = new ParticipantService(mockContext.Object); - Assert.ThrowsException(() => participantService.UpdateParticipant(1, null)); + Assert.ThrowsException(() => participantService.CreateOrUpdateParticipant(null)); } [TestMethod] public void DeleteParticipant_ParticipantExists_ParticipantDeleted() { var mockContext = new Mock(); - mockContext.Setup(c => c.Participants).ReturnsDbSet([ - new() { Id = 1, EventId = 1, UserId = 1, Status = ParticipantStatus.Participating } - ]); + + var event1 = new Event + { + Id = 1, + Name = "Event", + Description = "Description", + Slug = "Event-Slug" + }; + + var user = new User + { + Id = 1, + Username = "Username", + Email = "Email", + Password = "Password" + }; + + var participant = new Participant + { + Id = 1, + Event = event1, + User = user, + Status = ParticipantStatus.Participating + }; + + mockContext.Setup(c => c.Participants).ReturnsDbSet([participant]); + mockContext.Setup(c => c.Events).ReturnsDbSet([event1]); + mockContext.Setup(c => c.Users).ReturnsDbSet([user]); var participantService = new ParticipantService(mockContext.Object); - participantService.DeleteParticipant(1); + participantService.DeleteParticipant(user.Id, event1.Slug); mockContext.Verify(c => c.SaveChanges(), Times.Once); } @@ -356,10 +331,12 @@ public void DeleteParticipant_ParticipantDoesNotExist_NothingDeleted() { var mockContext = new Mock(); mockContext.Setup(c => c.Participants).ReturnsDbSet([]); + mockContext.Setup(c => c.Events).ReturnsDbSet([]); + mockContext.Setup(c => c.Users).ReturnsDbSet([]); var participantService = new ParticipantService(mockContext.Object); - Assert.ThrowsException(() => participantService.DeleteParticipant(1)); + Assert.ThrowsException(() => participantService.DeleteParticipant(1, "Event-Slug")); mockContext.Verify(c => c.SaveChanges(), Times.Never); } } \ No newline at end of file diff --git a/Server/ReasnAPI/ReasnAPI.Tests/Services/UserServiceTests.cs b/Server/ReasnAPI/ReasnAPI.Tests/Services/UserServiceTests.cs index a7758b7e..9638d623 100644 --- a/Server/ReasnAPI/ReasnAPI.Tests/Services/UserServiceTests.cs +++ b/Server/ReasnAPI/ReasnAPI.Tests/Services/UserServiceTests.cs @@ -27,16 +27,28 @@ public void GetUserById_UserExists_UserReturned() { var mockContext = new Mock(); + var address = new Address + { + Id = 1, + City = "City", + Country = "Country", + Street = "Street", + State = "State", + ZipCode = "ZipCode" + }; + var user = new User { Id = 1, Name = "John", Surname = "Doe", Username = "Username", - Email = "Email" + Email = "Email", + Address = address, }; mockContext.Setup(c => c.Users).ReturnsDbSet([user]); + mockContext.Setup(c => c.Addresses).ReturnsDbSet([address]); var userService = new UserService(mockContext.Object); @@ -65,13 +77,24 @@ public void GetUserByUsername_UserExists_UserReturned() { var mockContext = new Mock(); + var address = new Address + { + Id = 1, + City = "City", + Country = "Country", + Street = "Street", + State = "State", + ZipCode = "ZipCode" + }; + var user = new User { Id = 1, Name = "John", Surname = "Doe", Username = "Username", - Email = "Email" + Email = "Email", + Address = address, }; mockContext.Setup(c => c.Users).ReturnsDbSet([user]); @@ -143,6 +166,7 @@ public void UpdateUser_UserUpdated_UserReturned() mockContext.Setup(c => c.Addresses).ReturnsDbSet([address]); mockContext.Setup(c => c.Users).ReturnsDbSet([user]); + mockContext.Setup(c => c.UserInterests).ReturnsDbSet([]); var userService = new UserService(mockContext.Object); @@ -157,7 +181,7 @@ public void UpdateUser_UserUpdated_UserReturned() Role = UserRole.User }; - var result = userService.UpdateUser(1, userDto); + var result = userService.UpdateUser("Username", userDto); Assert.IsNotNull(result); Assert.AreEqual("Jane", result.Name); @@ -187,7 +211,7 @@ public void UpdateUser_UserDtoIsNull_NullReturned() var userService = new UserService(mockContext.Object); - Assert.ThrowsException(() => userService.UpdateUser(1, null)); + Assert.ThrowsException(() => userService.UpdateUser("Username", null)); } [TestMethod] @@ -209,6 +233,6 @@ public void UpdateUser_UserDoesNotExist_NullReturned() AddressId = 1 }; - Assert.ThrowsException(() => userService.UpdateUser(1, userDto)); + Assert.ThrowsException(() => userService.UpdateUser("Username", userDto)); } } \ No newline at end of file diff --git a/Server/ReasnAPI/ReasnAPI.Tests/UnitTests/Exceptions/ServiceExceptionHandlerTests.cs b/Server/ReasnAPI/ReasnAPI.Tests/UnitTests/Exceptions/ServiceExceptionHandlerTests.cs index a9b05cda..c26576d7 100644 --- a/Server/ReasnAPI/ReasnAPI.Tests/UnitTests/Exceptions/ServiceExceptionHandlerTests.cs +++ b/Server/ReasnAPI/ReasnAPI.Tests/UnitTests/Exceptions/ServiceExceptionHandlerTests.cs @@ -90,4 +90,27 @@ public async Task HandleException_WhenVerificationException_ShouldReturnProblemD Assert.AreEqual(exception, problemDetailsContext.Exception); Assert.AreEqual(exception.Message, problemDetailsContext.ProblemDetails.Detail); } + + [TestMethod] + public async Task HandleException_WhenUnauthorizedAccessException_ShouldReturnProblemDetails() + { + var httpContext = new DefaultHttpContext(); + var exception = new UnauthorizedAccessException("Unauthorized access"); + + ProblemDetailsContext? problemDetailsContext = null; + _mockProblemDetailsService.Setup(x => + x.TryWriteAsync(It.IsAny())) + .Callback(context => problemDetailsContext = context) + .ReturnsAsync(true); + + await _handler.TryHandleAsync(httpContext, exception, CancellationToken.None); + + Assert.AreEqual((int)HttpStatusCode.Unauthorized, httpContext.Response.StatusCode); + Assert.IsNotNull(problemDetailsContext); + Assert.AreEqual("https://datatracker.ietf.org/doc/html/rfc7235#section-3.1", + problemDetailsContext.ProblemDetails.Type); + Assert.AreEqual("Unauthorized", problemDetailsContext.ProblemDetails.Title); + Assert.AreEqual(exception, problemDetailsContext.Exception); + Assert.AreEqual(exception.Message, problemDetailsContext.ProblemDetails.Detail); + } } \ No newline at end of file diff --git a/Server/ReasnAPI/ReasnAPI.Tests/UnitTests/Services/Authentication/AuthServiceTests.cs b/Server/ReasnAPI/ReasnAPI.Tests/UnitTests/Services/Authentication/AuthServiceTests.cs index 22b76b49..8b4e0ced 100644 --- a/Server/ReasnAPI/ReasnAPI.Tests/UnitTests/Services/Authentication/AuthServiceTests.cs +++ b/Server/ReasnAPI/ReasnAPI.Tests/UnitTests/Services/Authentication/AuthServiceTests.cs @@ -23,6 +23,7 @@ public void Setup() _mockContext = new Mock(); _hasher = new PasswordHasher(); _service = new AuthService(_mockContext.Object); + var user = new User { Email = "jon.snow@castleblack.com", diff --git a/Server/ReasnAPI/ReasnAPI.Tests/UnitTests/Validators/EventValidatorTests.cs b/Server/ReasnAPI/ReasnAPI.Tests/UnitTests/Validators/EventValidatorTests.cs index e7533a31..47947042 100644 --- a/Server/ReasnAPI/ReasnAPI.Tests/UnitTests/Validators/EventValidatorTests.cs +++ b/Server/ReasnAPI/ReasnAPI.Tests/UnitTests/Validators/EventValidatorTests.cs @@ -119,19 +119,23 @@ public void Validate_WhenStartAtIsAfterEndAt_ShouldReturnFalse() } [TestMethod] - public void Validate_WhenSlugIsEmpty_ShouldReturnFalse() + public void Validate_WhenSlugIsEmpty_ShouldReturnTrue() { var eventDto = new EventDto { + Name = "Event", + Description = "Description", + StartAt = DateTime.UtcNow, + EndAt = DateTime.UtcNow.AddDays(1), + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow, + Status = EventStatus.Approved, Slug = "" }; var result = _validator.Validate(eventDto); - Assert.IsFalse(result.IsValid); - Assert.IsTrue(result.Errors.Exists( - e => e.ErrorMessage == "'Slug' must not be empty." - )); + Assert.IsTrue(result.IsValid); } [TestMethod] diff --git a/Server/ReasnAPI/ReasnAPI/Common/IAssemblyMarker.cs b/Server/ReasnAPI/ReasnAPI/Common/IAssemblyMarker.cs index 916dc1d9..0e8dae06 100644 --- a/Server/ReasnAPI/ReasnAPI/Common/IAssemblyMarker.cs +++ b/Server/ReasnAPI/ReasnAPI/Common/IAssemblyMarker.cs @@ -1,4 +1,4 @@ -namespace ReasnAPI.Validators; +namespace ReasnAPI.Common; public interface IAssemblyMarker { diff --git a/Server/ReasnAPI/ReasnAPI/Controllers/EventsController.cs b/Server/ReasnAPI/ReasnAPI/Controllers/EventsController.cs new file mode 100644 index 00000000..58d945f7 --- /dev/null +++ b/Server/ReasnAPI/ReasnAPI/Controllers/EventsController.cs @@ -0,0 +1,313 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using ReasnAPI.Models.Database; +using ReasnAPI.Models.DTOs; +using ReasnAPI.Models.Enums; +using ReasnAPI.Services; +using FluentValidation; +using Microsoft.AspNetCore.Http.Extensions; +using ReasnAPI.Mappers; +using ReasnAPI.Models.API; + +namespace ReasnAPI.Controllers; + +[ApiController] +[Route("[controller]")] +public class EventsController( + EventService eventService, + UserService userService, + ImageService imageService, + ParameterService parameterService, + TagService tagService, + CommentService commentService) + : ControllerBase +{ + + [HttpGet] + [ProducesResponseType>(StatusCodes.Status200OK)] + public IActionResult GetEvents() + { + var events = eventService.GetAllEvents(); + + return Ok(events); + } + + [HttpPost] + [Authorize(Roles = "Admin, Organizer")] + [ProducesResponseType(StatusCodes.Status201Created)] + public IActionResult CreateEvent([FromBody] EventCreateRequest eventRequest, [FromServices] IValidator validator) + { + var user = userService.GetCurrentUser(); + + var eventDto = eventRequest.ToDto(user.Id); + validator.ValidateAndThrow(eventDto); + eventService.CreateEvent(eventDto, eventRequest.AddressDto); + return Created(); + } + + [HttpGet] + [Route("{slug}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult GetEventBySlug([FromRoute] string slug) + { + var relatedEvent = eventService.GetEventBySlug(slug); + var participating = eventService.GetEventParticipantsCountBySlugAndStatus(slug, ParticipantStatus.Participating); + var interested = eventService.GetEventParticipantsCountBySlugAndStatus(slug, ParticipantStatus.Interested); + var username = relatedEvent.Organizer.Username; + var participants = new Participants(participating, interested); + var images = eventService.GetEventImages(slug); + + var doesImageExist = imageService.DoesImageExistsForUser(username); + string? url = null; + if (doesImageExist) + { + url = $"/api/v1/Users/image/{username}"; + } + + var eventResponse = relatedEvent.ToDto().ToResponse(participants, username, url, relatedEvent.Address.ToDto(), relatedEvent.AddressId, images); + + return Ok(eventResponse); + } + + [HttpPut] + [Authorize(Roles = "Admin, Organizer")] + [Route("{slug}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult UpdateEvent([FromRoute] string slug, [FromBody] EventUpdateRequest eventUpdateRequest, [FromServices] IValidator validator) + { + + var existingEvent = eventService.GetEventBySlug(slug); + var user = userService.GetCurrentUser(); + var eventDto = eventUpdateRequest.ToDto(user.Id); + eventDto.Slug = slug; + validator.ValidateAndThrow(eventDto); + if (existingEvent.OrganizerId != user.Id && user.Role != UserRole.Admin) + { + return Forbid(); + } + + if (existingEvent.Status != eventUpdateRequest.Status && user.Role != UserRole.Admin && eventUpdateRequest.Status != EventStatus.Cancelled) + { + return Forbid(); + } + eventService.UpdateAddressForEvent(eventUpdateRequest.AddressDto, existingEvent.AddressId, slug); + eventService.UpdateEvent(existingEvent.Id, eventDto); + + return Ok(); + } + + [HttpGet] + [Authorize(Roles = "Admin")] + [Route("requests")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public IActionResult GetEventsRequests() + { + var events = eventService.GetEventsByFilter(e => e.Status == EventStatus.PendingApproval); + + return Ok(events); + } + + [HttpPost] + [Authorize(Roles = "Admin")] + [Route("{slug}/approve")] + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult ApproveEventRequest([FromRoute] string slug) + { + var eventToApprove = eventService.GetEventBySlug(slug); + + eventToApprove.Status = EventStatus.Approved; + eventService.UpdateEvent(eventToApprove.Id, eventToApprove.ToDto()); + return Ok(); + } + + [HttpPost] + [Authorize(Roles = "Admin")] + [Route("{slug}/reject")] + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult RejectEventRequest([FromRoute] string slug) + { + var eventToApprove = eventService.GetEventBySlug(slug); + + eventToApprove.Status = EventStatus.Rejected; + eventService.UpdateEvent(eventToApprove.Id, eventToApprove.ToDto()); + return Ok(); + } + + [HttpPost] + [Authorize(Roles = "Admin, Organizer")] + [Route("{slug}/images")] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task AddEventImage([FromRoute] string slug, [FromForm] List images) + { + var relatedEvent = eventService.GetEventBySlug(slug); + var user = userService.GetCurrentUser(); + + if (relatedEvent.OrganizerId != user.Id && user.Role != UserRole.Admin) + { + return Forbid(); + } + + var imageDtos = new List(); + + foreach (var formFile in images.Where(formFile => formFile.Length > 0)) + { + using var ms = new MemoryStream(); + await formFile.CopyToAsync(ms); + var fileBytes = ms.ToArray(); + + imageDtos.Add(new ImageDto + { + ObjectId = relatedEvent.Id, + ObjectType = ObjectType.Event, + ImageData = fileBytes + }); + } + + imageService.CreateImages(imageDtos); + return Ok(); + } + + [HttpPut] + [Authorize(Roles = "Admin, Organizer")] + [Route("{slug}/images")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task UpdateEventImage([FromRoute] string slug, [FromForm] List images) + { + var user = userService.GetCurrentUser(); + var relatedEvent = eventService.GetEventBySlug(slug); + + if (relatedEvent.OrganizerId != user.Id && user.Role != UserRole.Admin) + { + return Forbid(); + } + + var imageDtos = new List(); + + foreach (var formFile in images.Where(formFile => formFile.Length > 0)) + { + using var ms = new MemoryStream(); + await formFile.CopyToAsync(ms); + var fileBytes = ms.ToArray(); + + imageDtos.Add(new ImageDto + { + ObjectId = relatedEvent.Id, + ObjectType = ObjectType.Event, + ImageData = fileBytes + }); + } + + if (relatedEvent.Id != imageDtos[0].ObjectId) + { + return NotFound(); + } + + imageService.UpdateImagesForEvent(relatedEvent.Id, imageDtos); + return Ok(); + } + + [HttpGet] + [Route("{slug}/images")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public IActionResult GetEventImages([FromRoute] string slug) + { + var relatedEvent = eventService.GetEventBySlug(slug); + var images = imageService.GetImagesByEventId(relatedEvent.Id); + var count = images.Count(); + var stringList = new List(); + for (int i = 0; i < count; i++) + { + stringList.Add($"/api/v1/Events/{slug}/image/{i}"); + } + return Ok(stringList); + } + + [HttpGet] + [Route("{slug}/image/{id:int}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult GetEventImage([FromRoute] string slug, [FromRoute] int id) + { + var relatedEvent = eventService.GetEventBySlug(slug); + var count = imageService.GetImageCountByEventId(relatedEvent.Id); + + if (count <= id) + { + return NotFound(); + } + + var image = imageService.GetImageByEventIdAndIndex(relatedEvent.Id, id); + + return File(image.ImageData, "image/jpeg"); + } + + [HttpGet] + [Route("{slug}/participants")] + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult GetEventParticipants([FromRoute] string slug) + { + var interestedDto = eventService.GetEventParticipantsBySlugAndStatus(slug, ParticipantStatus.Interested).ToList(); + var participatingDto = eventService.GetEventParticipantsBySlugAndStatus(slug, ParticipantStatus.Participating).ToList(); + + var participantsResponse = new ParticipantsResponse + { + Interested = interestedDto, + Participating = participatingDto + }; + + return Ok(participantsResponse); + } + + [HttpGet] + [Route("{slug}/comments")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public IActionResult GetEventComments([FromRoute] string slug) + { + var commentsDto = eventService.GetEventCommentsBySlug(slug); + return Ok(commentsDto); + } + + [HttpPost] + [Authorize] + [Route("{slug}/comments")] + public IActionResult AddEventComment([FromRoute] string slug, [FromBody] CommentRequest commentRequest, [FromServices] IValidator validator) + { + var user = userService.GetCurrentUser(); + var relatedEvent = eventService.GetEventBySlug(slug); + + var commentDto = commentRequest.ToDtoFromRequest(user.Username, slug); + validator.ValidateAndThrow(commentDto); + + commentService.CreateComment(commentDto, user.Id, relatedEvent.Id); + return Ok(); + } + + [HttpGet] + [Authorize(Roles = "Admin, Organizer")] + [Route("/parameters")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public IActionResult GetEventsParameters() + { + var parameters = parameterService.GetAllParameterKeys().ToList(); + return Ok(parameters); + } + + [HttpGet] + [Authorize(Roles = "Admin, Organizer")] + [Route("/tags")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public IActionResult GetEventsTags() + { + var tags = tagService.GetAllTagsNames().ToList(); + return Ok(tags); + } + + [HttpDelete] + [Authorize(Roles = "Admin")] + [Route("tags/{tagId:int}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public IActionResult DeleteEventsTag([FromRoute] int tagId) + { + tagService.DeleteTag(tagId); + return NoContent(); + } +} \ No newline at end of file diff --git a/Server/ReasnAPI/ReasnAPI/Controllers/MeController.cs b/Server/ReasnAPI/ReasnAPI/Controllers/MeController.cs new file mode 100644 index 00000000..9bd2eaab --- /dev/null +++ b/Server/ReasnAPI/ReasnAPI/Controllers/MeController.cs @@ -0,0 +1,193 @@ +using FluentValidation; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using ReasnAPI.Mappers; +using ReasnAPI.Models.API; +using ReasnAPI.Models.DTOs; +using ReasnAPI.Models.Enums; +using ReasnAPI.Services; + +namespace ReasnAPI.Controllers; + +[ApiController] +[Authorize] +[Route("[controller]")] +public class MeController(UserService userService, EventService eventService, ParticipantService participantService, ImageService imageService) : ControllerBase +{ + private readonly UserService _userService = userService; + private readonly EventService _eventService = eventService; + private readonly ParticipantService _participantService = participantService; + private readonly ImageService _imageService = imageService; + + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult GetCurrentUser() + { + var username = _userService.GetCurrentUser().Username; + var user = _userService.GetUserByUsername(username); + + return Ok(user); + } + + [HttpPut] + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult UpdateCurrentUser( + [FromBody] UserDto userDto, + [FromServices] IValidator validator) + { + validator.ValidateAndThrow(userDto); + + var user = _userService.GetCurrentUser(); + + // Users cant change their role in this endpoint + if (user.Role != userDto.Role) + { + return Forbid(); + } + + var updatedUser = _userService.UpdateUser(user.Username, userDto); + + return Ok(updatedUser); + } + + [HttpGet] + [Route("image")] + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult GetCurrentUserImage() + { + var user = _userService.GetCurrentUser(); + var image = _imageService.GetImageByUserId(user.Id); + + if (image is null) + { + return NotFound(); + } + + return File(image.ImageData, "image/jpeg"); + } + + [HttpPost] + [Route("image")] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task AddCurrentUserImage([FromForm] List images) + { + var userId = _userService.GetCurrentUser().Id; + + foreach (var image in images.Where(image => image.Length > 0)) + { + using var ms = new MemoryStream(); + await image.CopyToAsync(ms); + var fileBytes = ms.ToArray(); + + var imageDto = new ImageDto + { + ObjectId = userId, + ObjectType = ObjectType.User, + ImageData = fileBytes + }; + + _imageService.CreateImages([imageDto]); + } + + return Ok(); + } + + [HttpPut] + [Route("image")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task UpdateCurrentUserImage([FromForm] List images) + { + var userId = _userService.GetCurrentUser().Id; + + foreach (var image in images.Where(image => image.Length > 0)) + { + using var ms = new MemoryStream(); + await image.CopyToAsync(ms); + var fileBytes = ms.ToArray(); + + var imageDto = new ImageDto + { + ObjectId = userId, + ObjectType = ObjectType.User, + ImageData = fileBytes + }; + + _imageService.UpdateImageForUser(userId, imageDto); + } + + return Ok(); + } + + [HttpDelete] + [Route("image")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public IActionResult DeleteCurrentUserImage() + { + var userId = _userService.GetCurrentUser().Id; + _imageService.DeleteImageByObjectIdAndType(userId, ObjectType.User); + + return NoContent(); + } + + [HttpGet] + [Route("events")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public IActionResult GetCurrentUserEvents() + { + var user = _userService.GetCurrentUser(); + var events = _eventService.GetUserEvents(user.Username); + + if (user.Role == UserRole.Organizer) + { + var organizerEvents = _eventService.GetEventsByFilter(e => e.OrganizerId == user.Id); + events = events.Concat(organizerEvents); + } + + return Ok(events); + } + + [HttpPost] + [Route("events/{slug}/enroll")] + [ProducesResponseType(StatusCodes.Status201Created)] + public IActionResult EnrollCurrentUserInEvent([FromRoute] string slug) + { + var user = _userService.GetCurrentUser(); + + var participant = _participantService.CreateOrUpdateParticipant(new ParticipantDto { EventSlug = slug, Username = user.Username, Status = ParticipantStatus.Interested }); + + var location = Url.Action( + action: nameof(GetCurrentUserEvents), + controller: "Me"); + + return Created(location, participant); + } + + [HttpPost] + [Route("events/{slug}/confirm")] + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult ConfirmCurrentUserEventAttendance([FromRoute] string slug) + { + var user = _userService.GetCurrentUser(); + var participant = _participantService.CreateOrUpdateParticipant(new ParticipantDto { EventSlug = slug, Username = user.Username, Status = ParticipantStatus.Participating }); + + return Ok(participant); + } + + [HttpDelete] + [Route("events/{slug}/cancel")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public IActionResult CancelCurrentUserEventAttendance([FromRoute] string slug) + { + var userId = _userService.GetCurrentUser().Id; + _participantService.DeleteParticipant(userId, slug); + + return NoContent(); + } + + [HttpGet] + [Route("events/recommendations")] + public IActionResult GetCurrentUserEventRecommendations() + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Server/ReasnAPI/ReasnAPI/Controllers/UsersController.cs b/Server/ReasnAPI/ReasnAPI/Controllers/UsersController.cs index ca021725..c9d2c839 100644 --- a/Server/ReasnAPI/ReasnAPI/Controllers/UsersController.cs +++ b/Server/ReasnAPI/ReasnAPI/Controllers/UsersController.cs @@ -1,15 +1,96 @@ +using FluentValidation; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using ReasnAPI.Models.DTOs; +using ReasnAPI.Models.Enums; +using ReasnAPI.Services; namespace ReasnAPI.Controllers; [ApiController] [Route("[controller]")] -public class UsersController : ControllerBase +public class UsersController(UserService userService, InterestService interestService, ImageService imageService) : ControllerBase { + private readonly UserService _userService = userService; + private readonly ImageService _imageService = imageService; + private readonly InterestService _interestService = interestService; + + [HttpGet] + [Authorize(Roles = "Admin")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public IActionResult GetUsers() + { + var users = _userService.GetUsersByFilter(u => u.IsActive && u.Role != UserRole.Admin); + return Ok(users); + } + [HttpGet] [Route("{username}")] - public IActionResult GetUserByUsername(string username) + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult GetUserByUsername([FromRoute] string username) { - throw new NotImplementedException(); + var user = _userService.GetUserByUsername(username); + + if (user.Role == UserRole.Admin) + { + return Forbid(); + } + + return Ok(user); } + + [HttpPut] + [Authorize] + [Route("{username}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult UpdateUser( + [FromBody] UserDto userDto, + [FromRoute] string username, + [FromServices] IValidator validator) + { + validator.ValidateAndThrow(userDto); + + var currentUser = _userService.GetCurrentUser(); + + // Only admins can update other users from this endpoint + if (currentUser.Role != UserRole.Admin) + { + return Forbid(); + } + + var updatedUser = _userService.UpdateUser(username, userDto); + + return Ok(updatedUser); + } + + [HttpGet] + [Route("image/{username}")] + public IActionResult GetImageByUsername(string username) + { + var userId = userService.GetUserIdByUsername(username); + var image = _imageService.GetImageByUserId(userId); + + return File(image.ImageData, $"image/jpeg"); + } + + [HttpGet] + [Authorize] + [Route("interests")] + [ProducesResponseType>(StatusCodes.Status200OK)] + public IActionResult GetUsersInterests() + { + var interests = _interestService.GetAllInterests(); + return Ok(interests); + } + + [HttpDelete] + [Authorize(Roles = "Admin")] + [Route("interests/{interestId:int}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public IActionResult DeleteUserInterest([FromRoute] int interestId) + { + _interestService.DeleteInterest(interestId); + return NoContent(); + } + } \ No newline at end of file diff --git a/Server/ReasnAPI/ReasnAPI/Exceptions/ServiceExceptionHandler.cs b/Server/ReasnAPI/ReasnAPI/Exceptions/ServiceExceptionHandler.cs index c3dd9326..900dd864 100644 --- a/Server/ReasnAPI/ReasnAPI/Exceptions/ServiceExceptionHandler.cs +++ b/Server/ReasnAPI/ReasnAPI/Exceptions/ServiceExceptionHandler.cs @@ -43,6 +43,12 @@ public async ValueTask TryHandleAsync( problemDetails.Title = "A verification error occurred"; break; + case UnauthorizedAccessException: + httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7235#section-3.1"; + problemDetails.Title = "Unauthorized"; + break; + default: return false; } diff --git a/Server/ReasnAPI/ReasnAPI/Mappers/CommentMapper.cs b/Server/ReasnAPI/ReasnAPI/Mappers/CommentMapper.cs index fc785ef4..a7b92a00 100644 --- a/Server/ReasnAPI/ReasnAPI/Mappers/CommentMapper.cs +++ b/Server/ReasnAPI/ReasnAPI/Mappers/CommentMapper.cs @@ -1,34 +1,36 @@ -using ReasnAPI.Models.Database; +using ReasnAPI.Models.API; +using ReasnAPI.Models.Database; using ReasnAPI.Models.DTOs; namespace ReasnAPI.Mappers; public static class CommentMapper { - public static CommentDto ToDto(this Comment comment) + public static CommentDto ToDto(this Comment comment, string slug, string username, string imageUrl) { return new CommentDto { - EventId = comment.EventId, + EventSlug = slug, Content = comment.Content, - UserId = comment.UserId, - CreatedAt = comment.CreatedAt + Username = username, + CreatedAt = comment.CreatedAt, + UserImageUrl = imageUrl }; } public static List ToDtoList(this IEnumerable comments) { - return comments.Select(ToDto).ToList(); + return comments.Select(c => c.ToDto(c.Event.Slug, c.User.Username, $"api/v1/Users/image/{c.User.Username}")).ToList(); } - public static Comment ToEntity(this CommentDto commentDto) + public static CommentDto ToDtoFromRequest(this CommentRequest commentRequest, string username, string slug) { - return new Comment + return new CommentDto() { - EventId = commentDto.EventId, - Content = commentDto.Content, - UserId = commentDto.UserId, - CreatedAt = commentDto.CreatedAt + EventSlug = slug, + Content = commentRequest.Content, + CreatedAt = DateTime.Now, + Username = username }; } -} \ No newline at end of file +} diff --git a/Server/ReasnAPI/ReasnAPI/Mappers/EventMapper.cs b/Server/ReasnAPI/ReasnAPI/Mappers/EventMapper.cs index 0cf4cea7..2fe04dbd 100644 --- a/Server/ReasnAPI/ReasnAPI/Mappers/EventMapper.cs +++ b/Server/ReasnAPI/ReasnAPI/Mappers/EventMapper.cs @@ -1,5 +1,7 @@ -using ReasnAPI.Models.Database; +using ReasnAPI.Models.API; +using ReasnAPI.Models.Database; using ReasnAPI.Models.DTOs; +using ReasnAPI.Models.Enums; namespace ReasnAPI.Mappers { @@ -14,7 +16,6 @@ public static EventDto ToDto(this Event eventToMap) return new EventDto { Name = eventToMap.Name, - AddressId = eventToMap.AddressId, Description = eventToMap.Description, OrganizerId = eventToMap.OrganizerId, StartAt = eventToMap.StartAt, @@ -37,7 +38,6 @@ public static Event ToEntity(this EventDto eventDto) return new Event { Name = eventDto.Name, - AddressId = eventDto.AddressId, Description = eventDto.Description, OrganizerId = eventDto.OrganizerId, StartAt = eventDto.StartAt, @@ -46,5 +46,59 @@ public static Event ToEntity(this EventDto eventDto) }; } + public static EventResponse ToResponse(this EventDto eventDto, Participants participants, string username, string? image, AddressDto addressDto, int addressId, List images) + { + var organizer = new Organizer(username, image); + + return new EventResponse() + { + Name = eventDto.Name, + AddressId = addressId, + Description = eventDto.Description, + Organizer = organizer, + StartAt = eventDto.StartAt, + EndAt = eventDto.EndAt, + CreatedAt = eventDto.CreatedAt, + UpdatedAt = eventDto.UpdatedAt, + Slug = eventDto.Slug, + Status = eventDto.Status, + Tags = eventDto.Tags, + Parameters = eventDto.Parameters, + AddressDto = addressDto, + Participants = participants, + Images = images + }; + } + + public static EventDto ToDto(this EventUpdateRequest eventCreateRequest, int organizerId) + { + return new EventDto() + { + Name = eventCreateRequest.Name, + OrganizerId = organizerId, + Description = eventCreateRequest.Description, + StartAt = eventCreateRequest.StartAt, + EndAt = eventCreateRequest.EndAt, + Tags = eventCreateRequest.Tags, + Status = eventCreateRequest.Status, + Parameters = eventCreateRequest.Parameters, + }; + } + public static EventDto ToDto(this EventCreateRequest eventCreateRequest, int organizerId) + { + return new EventDto() + { + Name = eventCreateRequest.Name, + Description = eventCreateRequest.Description, + StartAt = eventCreateRequest.StartAt, + EndAt = eventCreateRequest.EndAt, + Tags = eventCreateRequest.Tags, + Status = EventStatus.PendingApproval, + Slug = string.Empty, + Parameters = eventCreateRequest.Parameters, + OrganizerId = organizerId + }; + } + } } diff --git a/Server/ReasnAPI/ReasnAPI/Mappers/ParticipantMapper.cs b/Server/ReasnAPI/ReasnAPI/Mappers/ParticipantMapper.cs index 66fee4e6..c214086d 100644 --- a/Server/ReasnAPI/ReasnAPI/Mappers/ParticipantMapper.cs +++ b/Server/ReasnAPI/ReasnAPI/Mappers/ParticipantMapper.cs @@ -1,4 +1,5 @@ -using ReasnAPI.Models.Database; +using ReasnAPI.Models.API; +using ReasnAPI.Models.Database; using ReasnAPI.Models.DTOs; namespace ReasnAPI.Mappers; @@ -8,8 +9,8 @@ public static ParticipantDto ToDto(this Participant participant) { return new ParticipantDto { - EventId = participant.EventId, - UserId = participant.UserId, + EventSlug = participant.Event.Slug, + Username = participant.User.Username, Status = participant.Status }; } @@ -19,13 +20,14 @@ public static List ToDtoList(this IEnumerable parti return participants.Select(ToDto).ToList(); } - public static Participant ToEntity(this ParticipantDto participantDto) + public static ParticipantsResponse ToResponse(this List participating, + List interested) { - return new Participant + return new ParticipantsResponse { - EventId = participantDto.EventId, - UserId = participantDto.UserId, - Status = participantDto.Status + Participating = participating, + Interested = interested }; } + } \ No newline at end of file diff --git a/Server/ReasnAPI/ReasnAPI/Mappers/UserInterestMapper.cs b/Server/ReasnAPI/ReasnAPI/Mappers/UserInterestMapper.cs index 19ac06bb..42c9325b 100644 --- a/Server/ReasnAPI/ReasnAPI/Mappers/UserInterestMapper.cs +++ b/Server/ReasnAPI/ReasnAPI/Mappers/UserInterestMapper.cs @@ -19,11 +19,12 @@ public static List ToDtoList(this IEnumerable use return userInterests.Select(ToDto).ToList(); } - public static UserInterest ToEntity(this UserInterestDto userInterestDto) + public static UserInterest ToEntity(this UserInterestDto userInterestDto, int userId, int interestId) { return new UserInterest { - Interest = userInterestDto.Interest.ToEntity(), + UserId = userId, + InterestId = interestId, Level = userInterestDto.Level }; } diff --git a/Server/ReasnAPI/ReasnAPI/Mappers/UserMapper.cs b/Server/ReasnAPI/ReasnAPI/Mappers/UserMapper.cs index 512756b8..1cd31cbc 100644 --- a/Server/ReasnAPI/ReasnAPI/Mappers/UserMapper.cs +++ b/Server/ReasnAPI/ReasnAPI/Mappers/UserMapper.cs @@ -16,6 +16,7 @@ public static UserDto ToDto(this User user) Phone = user.Phone, Role = user.Role, AddressId = user.AddressId, + Address = user.Address.ToDto(), Interests = user.UserInterests.ToDtoList() }; } diff --git a/Server/ReasnAPI/ReasnAPI/Models/API/CommentRequest.cs b/Server/ReasnAPI/ReasnAPI/Models/API/CommentRequest.cs new file mode 100644 index 00000000..38586816 --- /dev/null +++ b/Server/ReasnAPI/ReasnAPI/Models/API/CommentRequest.cs @@ -0,0 +1,7 @@ +namespace ReasnAPI.Models.API +{ + public class CommentRequest + { + public string Content { get; set; } = null!; + } +} diff --git a/Server/ReasnAPI/ReasnAPI/Models/API/EventCreateRequest.cs b/Server/ReasnAPI/ReasnAPI/Models/API/EventCreateRequest.cs new file mode 100644 index 00000000..3345804a --- /dev/null +++ b/Server/ReasnAPI/ReasnAPI/Models/API/EventCreateRequest.cs @@ -0,0 +1,16 @@ +using ReasnAPI.Models.DTOs; +using ReasnAPI.Models.Enums; + +namespace ReasnAPI.Models.API +{ + public class EventCreateRequest + { + public string Name { get; set; } = null!; + public AddressDto AddressDto { get; set; } = null!; + public string Description { get; set; } = null!; + public DateTime StartAt { get; set; } + public DateTime EndAt { get; set; } + public List? Tags { get; set; } + public List? Parameters { get; set; } + } +} diff --git a/Server/ReasnAPI/ReasnAPI/Models/API/EventResponse.cs b/Server/ReasnAPI/ReasnAPI/Models/API/EventResponse.cs new file mode 100644 index 00000000..375b672d --- /dev/null +++ b/Server/ReasnAPI/ReasnAPI/Models/API/EventResponse.cs @@ -0,0 +1,25 @@ +using ReasnAPI.Models.DTOs; +using ReasnAPI.Models.Enums; + +namespace ReasnAPI.Models.API +{ + public class EventResponse() + { + public string Name { get; set; } = null!; + public int AddressId { get; set; } + public AddressDto AddressDto { get; set; } = null!; + public string Description { get; set; } = null!; + public Organizer Organizer { get; set; } = null!; + public DateTime StartAt { get; set; } + public DateTime EndAt { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + public string Slug { get; set; } = null!; + public EventStatus Status { get; set; } + public List? Tags { get; set; } + public List? Parameters { get; set; } + public Participants? Participants { get; set; } + public List? Images { get; set; } + } + +} diff --git a/Server/ReasnAPI/ReasnAPI/Models/API/EventUpdateRequest.cs b/Server/ReasnAPI/ReasnAPI/Models/API/EventUpdateRequest.cs new file mode 100644 index 00000000..294e1198 --- /dev/null +++ b/Server/ReasnAPI/ReasnAPI/Models/API/EventUpdateRequest.cs @@ -0,0 +1,17 @@ +using ReasnAPI.Models.DTOs; +using ReasnAPI.Models.Enums; + +namespace ReasnAPI.Models.API +{ + public class EventUpdateRequest + { + public string Name { get; set; } = null!; + public AddressDto AddressDto { get; set; } = null!; + public string Description { get; set; } = null!; + public DateTime StartAt { get; set; } + public DateTime EndAt { get; set; } + public EventStatus Status { get; set; } + public List? Tags { get; set; } + public List? Parameters { get; set; } + } +} diff --git a/Server/ReasnAPI/ReasnAPI/Models/API/Organizer.cs b/Server/ReasnAPI/ReasnAPI/Models/API/Organizer.cs new file mode 100644 index 00000000..0412347e --- /dev/null +++ b/Server/ReasnAPI/ReasnAPI/Models/API/Organizer.cs @@ -0,0 +1,8 @@ +namespace ReasnAPI.Models.API +{ + public class Organizer(string username, string? image) + { + public string Username { get; set; } = username; + public string? Image { get; set; } = image; + } +} diff --git a/Server/ReasnAPI/ReasnAPI/Models/API/Participants.cs b/Server/ReasnAPI/ReasnAPI/Models/API/Participants.cs new file mode 100644 index 00000000..7c922f9b --- /dev/null +++ b/Server/ReasnAPI/ReasnAPI/Models/API/Participants.cs @@ -0,0 +1,8 @@ +namespace ReasnAPI.Models.API +{ + public class Participants(int participating, int interested) + { + public int Interested { get; set; } = interested; + public int Participating { get; set; } = participating; + } +} diff --git a/Server/ReasnAPI/ReasnAPI/Models/API/ParticipantsResponse.cs b/Server/ReasnAPI/ReasnAPI/Models/API/ParticipantsResponse.cs new file mode 100644 index 00000000..892e90b8 --- /dev/null +++ b/Server/ReasnAPI/ReasnAPI/Models/API/ParticipantsResponse.cs @@ -0,0 +1,11 @@ +using ReasnAPI.Models.DTOs; + +namespace ReasnAPI.Models.API +{ + public class ParticipantsResponse + { + public List Participating { get; set; } + public List Interested { get; set; } + + } +} diff --git a/Server/ReasnAPI/ReasnAPI/Models/DTOs/CommentDto.cs b/Server/ReasnAPI/ReasnAPI/Models/DTOs/CommentDto.cs index f1c42718..b5217eec 100644 --- a/Server/ReasnAPI/ReasnAPI/Models/DTOs/CommentDto.cs +++ b/Server/ReasnAPI/ReasnAPI/Models/DTOs/CommentDto.cs @@ -2,9 +2,10 @@ namespace ReasnAPI.Models.DTOs { public class CommentDto { - public int EventId { get; set; } + public string EventSlug { get; set; } = null!; public string Content { get; set; } = null!; public DateTime CreatedAt { get; set; } - public int UserId { get; set; } + public string Username { get; set; } = null!; + public string? UserImageUrl { get; set; } } } \ No newline at end of file diff --git a/Server/ReasnAPI/ReasnAPI/Models/DTOs/EventDto.cs b/Server/ReasnAPI/ReasnAPI/Models/DTOs/EventDto.cs index d2e2eb46..fbb7a699 100644 --- a/Server/ReasnAPI/ReasnAPI/Models/DTOs/EventDto.cs +++ b/Server/ReasnAPI/ReasnAPI/Models/DTOs/EventDto.cs @@ -5,7 +5,6 @@ namespace ReasnAPI.Models.DTOs public class EventDto { public string Name { get; set; } = null!; - public int AddressId { get; set; } public string Description { get; set; } = null!; public int OrganizerId { get; set; } public DateTime StartAt { get; set; } diff --git a/Server/ReasnAPI/ReasnAPI/Models/DTOs/ParticipantDto.cs b/Server/ReasnAPI/ReasnAPI/Models/DTOs/ParticipantDto.cs index 55a0b167..910ceab1 100644 --- a/Server/ReasnAPI/ReasnAPI/Models/DTOs/ParticipantDto.cs +++ b/Server/ReasnAPI/ReasnAPI/Models/DTOs/ParticipantDto.cs @@ -4,8 +4,8 @@ namespace ReasnAPI.Models.DTOs { public class ParticipantDto { - public int EventId { get; set; } - public int UserId { get; set; } + public string EventSlug { get; set; } = null!; + public string Username { get; set; } = null!; public ParticipantStatus Status { get; set; } } } \ No newline at end of file diff --git a/Server/ReasnAPI/ReasnAPI/Models/DTOs/UserDto.cs b/Server/ReasnAPI/ReasnAPI/Models/DTOs/UserDto.cs index 3d16dc47..d3642802 100644 --- a/Server/ReasnAPI/ReasnAPI/Models/DTOs/UserDto.cs +++ b/Server/ReasnAPI/ReasnAPI/Models/DTOs/UserDto.cs @@ -11,6 +11,7 @@ public class UserDto public string? Phone { get; set; } public UserRole Role { get; set; } public int AddressId { get; set; } + public AddressDto Address { get; set; } = null!; public List? Interests { get; set; } } } \ No newline at end of file diff --git a/Server/ReasnAPI/ReasnAPI/Program.cs b/Server/ReasnAPI/ReasnAPI/Program.cs index 755c53e9..7599e5d2 100644 --- a/Server/ReasnAPI/ReasnAPI/Program.cs +++ b/Server/ReasnAPI/ReasnAPI/Program.cs @@ -6,12 +6,13 @@ using FluentValidation; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; +using ReasnAPI.Common; using ReasnAPI.Exceptions; using ReasnAPI.Middlewares; using ReasnAPI.Models.Database; +using ReasnAPI.Services; using ReasnAPI.Services.Authentication; using ReasnAPI.Validators; -using ReasnAPI.Services; using Npgsql; using ReasnAPI.Models.Enums; using Microsoft.Extensions.Configuration; @@ -52,9 +53,19 @@ { options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); }); +builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddValidatorsFromAssemblyContaining(); var dataSourceBuilder = new NpgsqlDataSourceBuilder(config.GetConnectionString("DefaultValue")); @@ -68,13 +79,6 @@ options.UseNpgsql(dataSource) .EnableDetailedErrors()); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); - builder.Services.AddControllers(); builder.Services.AddSwaggerGen(options => diff --git a/Server/ReasnAPI/ReasnAPI/Services/CommentService.cs b/Server/ReasnAPI/ReasnAPI/Services/CommentService.cs index 95eac468..05bb2dec 100644 --- a/Server/ReasnAPI/ReasnAPI/Services/CommentService.cs +++ b/Server/ReasnAPI/ReasnAPI/Services/CommentService.cs @@ -1,3 +1,4 @@ +using Microsoft.EntityFrameworkCore; using ReasnAPI.Exceptions; using ReasnAPI.Mappers; using ReasnAPI.Models.Database; @@ -8,42 +9,24 @@ namespace ReasnAPI.Services; public class CommentService(ReasnContext context) { - public CommentDto CreateComment(CommentDto commentDto) + public CommentDto CreateComment(CommentDto commentDto, int eventId, int userId) { ArgumentNullException.ThrowIfNull(commentDto); commentDto.CreatedAt = DateTime.UtcNow; - context.Comments.Add(commentDto.ToEntity()); - context.SaveChanges(); - - return commentDto; - } - - public CommentDto UpdateComment(int commentId, CommentDto commentDto) - { - ArgumentNullException.ThrowIfNull(commentDto); - - var comment = context.Comments.FirstOrDefault(r => r.Id == commentId); - - if (comment is null) - { - throw new NotFoundException("Comment not found"); - } - - var user = context.Users.FirstOrDefault(r => r.Id == commentDto.UserId); - - if (user is null) + var comment = new Comment { - throw new NotFoundException("User not found"); - } - - comment.Content = commentDto.Content; + UserId = userId, + EventId = eventId, + Content = commentDto.Content, + CreatedAt = commentDto.CreatedAt + }; - context.Comments.Update(comment); + context.Comments.Add(comment); context.SaveChanges(); - return comment.ToDto(); + return commentDto; } public void DeleteComment(int commentId) @@ -59,22 +42,12 @@ public void DeleteComment(int commentId) context.SaveChanges(); } - public CommentDto GetCommentById(int commentId) - { - var comment = context.Comments.Find(commentId); - - if (comment is null) - { - throw new NotFoundException("Comment not found"); - } - - return comment.ToDto(); - } - public IEnumerable GetCommentsByFilter(Expression> filter) { return context.Comments .Where(filter) + .Include(c => c.User) + .Include(c => c.Event) .ToDtoList() .AsEnumerable(); } @@ -82,6 +55,8 @@ public IEnumerable GetCommentsByFilter(Expression GetAllComments() { return context.Comments + .Include(c => c.User) + .Include(c => c.Event) .ToDtoList() .AsEnumerable(); } diff --git a/Server/ReasnAPI/ReasnAPI/Services/EventService.cs b/Server/ReasnAPI/ReasnAPI/Services/EventService.cs index 835f45d4..26a8f65c 100644 --- a/Server/ReasnAPI/ReasnAPI/Services/EventService.cs +++ b/Server/ReasnAPI/ReasnAPI/Services/EventService.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using ReasnAPI.Exceptions; using ReasnAPI.Models.Database; using ReasnAPI.Models.DTOs; @@ -6,13 +6,15 @@ using System.Transactions; using System.Text.RegularExpressions; using ReasnAPI.Mappers; +using ReasnAPI.Models.API; using ReasnAPI.Models.Enums; +using Microsoft.Extensions.Logging; namespace ReasnAPI.Services; -public class EventService(ReasnContext context, ParameterService parameterService, TagService tagService, CommentService commentService) +public class EventService(ReasnContext context, ParameterService parameterService, TagService tagService, CommentService commentService, AddressService addressService, ImageService imageService) { - public EventDto CreateEvent(EventDto eventDto) + public EventDto CreateEvent(EventDto eventDto, AddressDto addressDto) { using (var scope = new TransactionScope()) { @@ -20,11 +22,15 @@ public EventDto CreateEvent(EventDto eventDto) var newEvent = eventDto.ToEntity(); newEvent.CreatedAt = createdTime; newEvent.UpdatedAt = createdTime; - newEvent.Slug = CreateSlug(eventDto); - - + newEvent.Slug = CreateSlug(eventDto); + + addressService.CreateAddress(addressDto); + newEvent.Address = addressDto.ToEntity(); + context.SaveChanges(); + context.Events.Add(newEvent); + context.SaveChanges(); if (eventDto.Tags != null) { @@ -62,9 +68,7 @@ public EventDto UpdateEvent(int eventId, EventDto eventDto) } eventToUpdate.Name = eventDto.Name; - eventToUpdate.AddressId = eventDto.AddressId; eventToUpdate.Description = eventDto.Description; - eventToUpdate.OrganizerId = eventDto.OrganizerId; eventToUpdate.StartAt = eventDto.StartAt; eventToUpdate.EndAt = eventDto.EndAt; eventToUpdate.UpdatedAt = DateTime.UtcNow; @@ -111,16 +115,12 @@ private void DetachTagsFromEvent(List tagsToRemove, Event eventToUpdate) { tagsToRemove.ForEach(tag => eventToUpdate.Tags.Remove(tag)); context.SaveChanges(); - - tagService.RemoveTagsNotInAnyEvent(); } private void DetachParametersFromEvent(List parametersToRemove, Event eventToUpdate) { parametersToRemove.ForEach(param => eventToUpdate.Parameters.Remove(param)); context.SaveChanges(); - - parameterService.RemoveParametersNotInAnyEvent(); } public void DeleteEvent(int eventId) @@ -174,7 +174,7 @@ public EventDto GetEventById(int eventId) public Event GetEventBySlug(string slug) { - var eventToReturn = context.Events.Include(e => e.Tags).Include(e => e.Parameters).FirstOrDefault(e => e.Slug == slug); + var eventToReturn = context.Events.Include(e => e.Tags).Include(e => e.Parameters).Include(e => e.Address).Include(e => e.Organizer).FirstOrDefault(e => e.Slug == slug); if (eventToReturn is null) { throw new NotFoundException("Event not found"); @@ -212,41 +212,100 @@ public IEnumerable GetEventParticipantsBySlugAndStatus(string sl public IEnumerable GetEventCommentsBySlug(string slug) { - var eventToReturn = context.Events.Include(e => e.Comments) - .FirstOrDefault(e => e.Slug == slug); + throw new NotImplementedException(); + + var eventToReturn = context.Events + .Include(e => e.Comments) + .ThenInclude(c => c.User) + .FirstOrDefault(e => e.Slug == slug); + if (eventToReturn is null) { throw new NotFoundException("Event not found"); } - var commentDtos = eventToReturn.Comments.Select(p => p.ToDto()); + var commentDtos = eventToReturn.Comments.Select(p => p.ToDto(slug, p.User.Username, $"api/v1/Users/image/{p.User.Username}")); return commentDtos; } - public void AddEventComment(CommentDto commentDto, string slug) + public IEnumerable GetEventsByFilter(Expression> filter) { - commentDto = commentService.CreateComment(commentDto); - var relatedEvent = GetEventBySlug(slug); - relatedEvent.Comments.Add(commentDto.ToEntity()); - context.SaveChanges(); + var events = context.Events.Include(e => e.Parameters) + .Include(e => e.Tags) + .Include(e => e.Address) + .Include(e => e.Organizer).Where(filter).ToList(); + + var eventsResponses = new List(); + foreach (var thisEvent in events) + { + var participating = GetEventParticipantsCountBySlugAndStatus(thisEvent.Slug, ParticipantStatus.Participating); + var interested = GetEventParticipantsCountBySlugAndStatus(thisEvent.Slug, ParticipantStatus.Interested); + var participants = new Participants(participating, interested); + var username = thisEvent.Organizer.Username; + var addressDto = thisEvent.Address.ToDto(); + var addressId = thisEvent.AddressId; + + string? url = null; + if (imageService.DoesImageExistsForUser(username)) + { + url = $"/api/v1/Users/image/{username}"; + } + + var eventDto = thisEvent.ToDto(); + var eventResponse = eventDto.ToResponse(participants, username, url, addressDto, addressId, GetEventImages(thisEvent.Slug)); + eventsResponses.Add(eventResponse); + } + + return eventsResponses.AsEnumerable(); } - public IEnumerable GetEventsByFilter(Expression> filter) + public List GetEventImages(string slug) { - var events = context.Events.Include(e => e.Parameters).Include(e => e.Tags).Where(filter).ToList(); - var eventDtos = events.ToDtoList(); - return eventDtos; + var @event = GetEventBySlug(slug); + var images = imageService.GetImagesByEventId(@event.Id); + var count = images.Count(); + var stringList = new List(); + for (int i = 0; i < count; i++) + { + stringList.Add($"/api/v1/Events/{slug}/image/{i}"); + } + return stringList; } - public IEnumerable GetAllEvents() + public IEnumerable GetAllEvents() { - var events = context.Events.Include(e => e.Parameters).Include(e => e.Tags).ToList(); + var events = context.Events.Include(e => e.Parameters) + .Include(e => e.Tags) + .Include(e => e.Address) + .Include(e => e.Organizer) + .Where(e => e.Status != EventStatus.PendingApproval && + e.Status != EventStatus.Cancelled && + e.Status != EventStatus.Rejected).ToList(); + var eventsResponses = new List(); + foreach (var thisEvent in events) + { + var participating = GetEventParticipantsCountBySlugAndStatus(thisEvent.Slug, ParticipantStatus.Participating); + var interested = GetEventParticipantsCountBySlugAndStatus(thisEvent.Slug, ParticipantStatus.Interested); + var participants = new Participants(participating, interested); + var username = thisEvent.Organizer.Username; + var addressDto = thisEvent.Address.ToDto(); + var addressId = thisEvent.AddressId; + + string? url = null; + if (imageService.DoesImageExistsForUser(username)) + { + url = $"/api/v1/Users/image/{username}"; + } + + var eventDto = thisEvent.ToDto(); + var eventResponse = eventDto.ToResponse(participants, username, url, addressDto, addressId, GetEventImages(thisEvent.Slug)); + eventsResponses.Add(eventResponse); + } - var eventDtos = events.ToDtoList(); - return eventDtos; + return eventsResponses.AsEnumerable(); } - public IEnumerable GetUserEvents(string username) + public IEnumerable GetUserEvents(string username) { var user = context.Users.FirstOrDefault(u => u.Username == username); @@ -255,8 +314,72 @@ public IEnumerable GetUserEvents(string username) throw new NotFoundException("User not found"); } - var userEvents = context.Participants.Include(p => p.Event).Where(p => p.UserId == user.Id).Select(p => p.Event); - return userEvents.ToDtoList().AsEnumerable(); + var userEventIds = context.Participants + .Where(p => p.UserId == user.Id) + .Select(p => p.EventId) + .ToList(); + + var userEvents = context.Events + .Where(e => userEventIds.Contains(e.Id)) + .Include(e => e.Parameters) + .Include(e => e.Tags) + .Include(e => e.Address) + .Include(e => e.Organizer) + .ToList(); + + var eventsResponses = new List(); + + foreach (var thisEvent in userEvents) + { + var participating = GetEventParticipantsCountBySlugAndStatus(thisEvent.Slug, ParticipantStatus.Participating); + var interested = GetEventParticipantsCountBySlugAndStatus(thisEvent.Slug, ParticipantStatus.Interested); + var participants = new Participants(participating, interested); + var organizerUsername = thisEvent.Organizer.Username; + var addressDto = thisEvent.Address.ToDto(); + var addressId = thisEvent.AddressId; + + string? url = null; + if (imageService.DoesImageExistsForUser(username)) + { + url = $"/api/v1/Users/image/{username}"; + } + + var eventDto = thisEvent.ToDto(); + var eventResponse = eventDto.ToResponse(participants, organizerUsername, url, addressDto, addressId, GetEventImages(thisEvent.Slug)); + eventsResponses.Add(eventResponse); + } + + + + return eventsResponses.AsEnumerable(); + } + + public void UpdateAddressForEvent(AddressDto addressDto, int addressId, string slug) + { + var relatedEvent = GetEventBySlug(slug); + + if (relatedEvent.AddressId != addressId) + { + throw new NotFoundException("Address is not related with this event"); + } + + if (addressDto == addressService.GetAddressById(addressId)) return; + addressService.UpdateAddress(addressId, addressDto); + + context.SaveChanges(); + } + + public AddressDto GetRelatedAddress(string slug) + { + var relatedEvent = GetEventBySlug(slug); + var address = context.Addresses.Find(relatedEvent.AddressId); + + if (address is null) + { + throw new NotFoundException("Address not found"); + } + + return address.ToDto(); } private string CreateSlug(EventDto eventDto) diff --git a/Server/ReasnAPI/ReasnAPI/Services/ImageService.cs b/Server/ReasnAPI/ReasnAPI/Services/ImageService.cs index 59d688d4..c4f0970b 100644 --- a/Server/ReasnAPI/ReasnAPI/Services/ImageService.cs +++ b/Server/ReasnAPI/ReasnAPI/Services/ImageService.cs @@ -1,12 +1,12 @@ -using ReasnAPI.Models.Database; -using ReasnAPI.Models.DTOs; -using System.Linq.Expressions; +using ReasnAPI.Models.Database; +using ReasnAPI.Models.DTOs; +using System.Linq.Expressions; using ReasnAPI.Exceptions; -using ReasnAPI.Models.Enums; +using ReasnAPI.Models.Enums; using ReasnAPI.Mappers; -namespace ReasnAPI.Services; +namespace ReasnAPI.Services; public class ImageService(ReasnContext context) { public IEnumerable CreateImages(List imageDtos) @@ -112,9 +112,22 @@ public void UpdateImageForUser(int userId, ImageDto imageDto) context.Images.Update(image); context.SaveChanges(); - } - - public void DeleteImageById(int id) + } + + public bool DoesImageExistsForUser(string username) + { + var user = context.Users.FirstOrDefault(u => u.Username == username); + + if (user is null) + { + throw new NotFoundException("User not found"); + } + + // returns if image exists for user + return context.Images.Any(i => i.ObjectType == ObjectType.User && i.ObjectId == user.Id); + } + + public void DeleteImageById(int id) { var image = context.Images.FirstOrDefault(r => r.Id == id); if (image is null) @@ -147,8 +160,8 @@ public void DeleteImageRelatedToEvent(int id, string slug) context.Images.Remove(image); context.SaveChanges(); - } - + } + public void DeleteImageByObjectIdAndType(int objectId, ObjectType objectType) { var images = context.Images.Where(r => r.ObjectId == objectId && r.ObjectType == objectType).ToList(); @@ -159,9 +172,9 @@ public void DeleteImageByObjectIdAndType(int objectId, ObjectType objectType) context.Images.RemoveRange(images); context.SaveChanges(); - } - - public ImageDto GetImageById(int id) + } + + public ImageDto GetImageById(int id) { var image = context.Images.Find(id); if (image is null) @@ -174,37 +187,32 @@ public ImageDto GetImageById(int id) return imageDto; } - public IEnumerable GetImagesByUserId(int userId) + public ImageDto GetImageByUserId(int userId) { - var images = context.Images - .Where(image => image.ObjectId == userId && image.ObjectType == ObjectType.User) - .ToList(); + var image = context.Images.FirstOrDefault(image => image.ObjectId == userId && image.ObjectType == ObjectType.User); - if (!images.Any()) + if (image is null) { - throw new NotFoundException("Images not found"); + throw new NotFoundException("Image not found"); } - var imageDtos = images.ToDtoList().AsEnumerable(); + return image.ToDto(); + } - return imageDtos; - } - public IEnumerable GetAllImages() { return context.Images .ToDtoList() .AsEnumerable(); - } - - public IEnumerable GetImagesByFilter(Expression> filter) + } + + public IEnumerable GetImagesByFilter(Expression> filter) { return context.Images .Where(filter) .ToDtoList() .AsEnumerable(); } - public IEnumerable GetImagesByEventId(int eventId) { var images = context.Images @@ -213,12 +221,36 @@ public IEnumerable GetImagesByEventId(int eventId) if (!images.Any()) { - throw new NotFoundException("Images not found"); + return Enumerable.Empty(); } var imageDtos = images.ToDtoList().AsEnumerable(); return imageDtos; - } - + } + + public ImageDto GetImageByEventIdAndIndex(int eventId, int index) + { + + var image = context.Images + .Where(image => image.ObjectType == ObjectType.Event && image.ObjectId == eventId) + .Skip(index) + .FirstOrDefault(); + + if (image == null) + { + throw new NotFoundException($"Image at index {index} not found for eventId {eventId}"); + } + + var imageDto = image.ToDto(); + + return imageDto; + } + + public int GetImageCountByEventId(int eventId) + { + return context.Images + .Count(image => image.ObjectType == ObjectType.Event && image.ObjectId == eventId); + } + } \ No newline at end of file diff --git a/Server/ReasnAPI/ReasnAPI/Services/ParameterService.cs b/Server/ReasnAPI/ReasnAPI/Services/ParameterService.cs index 50b75343..84049f12 100644 --- a/Server/ReasnAPI/ReasnAPI/Services/ParameterService.cs +++ b/Server/ReasnAPI/ReasnAPI/Services/ParameterService.cs @@ -1,157 +1,164 @@ -using Microsoft.EntityFrameworkCore; -using ReasnAPI.Exceptions; -using ReasnAPI.Models.Database; -using ReasnAPI.Models.DTOs; -using System.Linq.Expressions; -using ReasnAPI.Mappers; - -namespace ReasnAPI.Services; -public class ParameterService(ReasnContext context) -{ - public ParameterDto CreateParameter(ParameterDto parameterDto) - { - var parameter = context.Parameters.FirstOrDefault(r => r.Key == parameterDto.Key && r.Value == parameterDto.Value); - if (parameter is not null) - { - throw new BadRequestException("Parameter already exists"); - } - - var newParameter = parameterDto.ToEntity(); - context.Parameters.Add(newParameter); - context.SaveChanges(); - return parameterDto; - } - - public ParameterDto UpdateParameter(int parameterId, ParameterDto parameterDto) - { - var parameter = context.Parameters.FirstOrDefault(r => r.Id == parameterId); - - if (parameter is null) - { - throw new NotFoundException("Parameter not found"); - } - - var parameters = context.Events.Include(p => p.Parameters); - - var parameterCheck = parameters.FirstOrDefault(r => r.Parameters.Any(p => p.Id == parameterId)); - - if (parameterCheck is not null) - { - throw new BadRequestException("Parameter is associated with an event"); - } - - parameter.Key = parameterDto.Key; - parameter.Value = parameterDto.Value; - context.Parameters.Update(parameter); - context.SaveChanges(); - return parameterDto; - } - - public void DeleteParameter(int parameterId) - { - var parameter = context.Parameters.FirstOrDefault(r => r.Id == parameterId); - - if (parameter == null) - { - throw new NotFoundException("Parameter not found"); - } - - var eventsWithParameters = context.Events - .Include(e => e.Parameters) - .ToList(); - - var parameterCheck = eventsWithParameters - .Any(e => e.Parameters.Any(p => p.Id == parameterId)); - - if (parameterCheck) - { - throw new BadRequestException("Parameter is associated with an event"); - } - - context.Parameters.Remove(parameter); - context.SaveChanges(); - } - - public void ForceDeleteParameter(ParameterDto parameterDto) - { - var parameter = context.Parameters.FirstOrDefault(r => r.Key == parameterDto.Key && r.Value == parameterDto.Value); - if (parameter is null) - { - throw new NotFoundException("Parameter not found"); +using Microsoft.EntityFrameworkCore; +using ReasnAPI.Exceptions; +using ReasnAPI.Models.Database; +using ReasnAPI.Models.DTOs; +using System.Linq.Expressions; +using ReasnAPI.Mappers; + +namespace ReasnAPI.Services; +public class ParameterService(ReasnContext context) +{ + public ParameterDto CreateParameter(ParameterDto parameterDto) + { + var parameter = context.Parameters.FirstOrDefault(r => r.Key == parameterDto.Key && r.Value == parameterDto.Value); + if (parameter is not null) + { + throw new BadRequestException("Parameter already exists"); + } + + var newParameter = parameterDto.ToEntity(); + context.Parameters.Add(newParameter); + context.SaveChanges(); + return parameterDto; + } + + public ParameterDto UpdateParameter(int parameterId, ParameterDto parameterDto) + { + var parameter = context.Parameters.FirstOrDefault(r => r.Id == parameterId); + + if (parameter is null) + { + throw new NotFoundException("Parameter not found"); + } + + var parameters = context.Events.Include(p => p.Parameters); + + var parameterCheck = parameters.FirstOrDefault(r => r.Parameters.Any(p => p.Id == parameterId)); + + if (parameterCheck is not null) + + { + throw new BadRequestException("Parameter is associated with an event"); + } + + parameter.Key = parameterDto.Key; + parameter.Value = parameterDto.Value; + context.Parameters.Update(parameter); + context.SaveChanges(); + return parameterDto; + } + + public void DeleteParameter(int parameterId) + { + var parameter = context.Parameters.FirstOrDefault(r => r.Id == parameterId); + + if (parameter == null) + { + throw new NotFoundException("Parameter not found"); + } + + var eventsWithParameters = context.Events + .Include(e => e.Parameters) + .ToList(); + + var parameterCheck = eventsWithParameters + .Any(e => e.Parameters.Any(p => p.Id == parameterId)); + + if (parameterCheck) + { + throw new BadRequestException("Parameter is associated with an event"); + } + + context.Parameters.Remove(parameter); + context.SaveChanges(); + } + + public void ForceDeleteParameter(ParameterDto parameterDto) + { + var parameter = context.Parameters.FirstOrDefault(r => r.Key == parameterDto.Key && r.Value == parameterDto.Value); + if (parameter is null) + { + throw new NotFoundException("Parameter not found"); + } + + var eventsWithParameter = context.Events + .Where(e => e.Parameters.Any(p => p.Key == parameterDto.Key && p.Value == parameterDto.Value)) + .Include(e => e.Parameters) + .ToList(); + + foreach (var eventWithParameter in eventsWithParameter) + { + eventWithParameter.Parameters.Remove(parameter); + } + + context.Parameters.Remove(parameter); + context.SaveChanges(); + } + + public void RemoveParametersNotInAnyEvent() + { + var parametersNotInAnyEvent = context.Parameters + .Where(p => !context.Events.Any(e => e.Parameters.Contains(p))) + .ToList(); + + context.Parameters.RemoveRange(parametersNotInAnyEvent); + context.SaveChanges(); + } + + public void AttachParametersToEvent(List parametersToAdd, Event eventToUpdate) + { + var parameterKeyValuePairsToAdd = parametersToAdd.Select(p => new { p.Key, p.Value }).ToList(); + + var keysToAdd = parametersToAdd.Select(p => p.Key).Distinct().ToList(); + var valuesToAdd = parametersToAdd.Select(p => p.Value).Distinct().ToList(); + + var parametersFromDb = context.Parameters + .Where(param => keysToAdd.Contains(param.Key) && valuesToAdd.Contains(param.Value)) + .ToList(); + + parametersFromDb.ForEach(eventToUpdate.Parameters.Add); + + var newParametersToAdd = parametersToAdd.Where(paramToAdd => + !parametersFromDb.Any(existingParam => existingParam.Key == paramToAdd.Key && existingParam.Value == paramToAdd.Value)) + .ToList(); + + newParametersToAdd.ForEach(eventToUpdate.Parameters.Add); + + context.SaveChanges(); + } + + public ParameterDto GetParameterById(int parameterId) + { + var parameter = context.Parameters.Find(parameterId); + if (parameter is null) + { + throw new NotFoundException("Parameter not found"); } - var eventsWithParameter = context.Events - .Where(e => e.Parameters.Any(p => p.Key == parameterDto.Key && p.Value == parameterDto.Value)) - .Include(e => e.Parameters) - .ToList(); - - foreach (var eventWithParameter in eventsWithParameter) - { - eventWithParameter.Parameters.Remove(parameter); - } - - context.Parameters.Remove(parameter); - context.SaveChanges(); - } - - public void RemoveParametersNotInAnyEvent() - { - var parametersNotInAnyEvent = context.Parameters - .Where(p => !context.Events.Any(e => e.Parameters.Contains(p))) - .ToList(); - - context.Parameters.RemoveRange(parametersNotInAnyEvent); - context.SaveChanges(); - } - - public void AttachParametersToEvent(List parametersToAdd, Event eventToUpdate) - { - var parameterKeyValuePairsToAdd = parametersToAdd.Select(p => new { p.Key, p.Value }).ToList(); - - var keysToAdd = parametersToAdd.Select(p => p.Key).Distinct().ToList(); - var valuesToAdd = parametersToAdd.Select(p => p.Value).Distinct().ToList(); - - var parametersFromDb = context.Parameters - .Where(param => keysToAdd.Contains(param.Key) && valuesToAdd.Contains(param.Value)) - .ToList(); - - parametersFromDb.ForEach(eventToUpdate.Parameters.Add); - - var newParametersToAdd = parametersToAdd.Where(paramToAdd => - !parametersFromDb.Any(existingParam => existingParam.Key == paramToAdd.Key && existingParam.Value == paramToAdd.Value)) - .ToList(); - - newParametersToAdd.ForEach(eventToUpdate.Parameters.Add); - - context.SaveChanges(); - } - - public ParameterDto GetParameterById(int parameterId) - { - var parameter = context.Parameters.Find(parameterId); - if (parameter is null) - { - throw new NotFoundException("Parameter not found"); - } - - var parameterDto = parameter.ToDto(); - return parameterDto; - } - - public IEnumerable GetAllParameters() - { - return context.Parameters - .ToDtoList() - .AsEnumerable(); - } - - public IEnumerable GetParametersByFilter(Expression> filter) - { - return context.Parameters - .Where(filter) - .ToDtoList() - .AsEnumerable(); - } - - + var parameterDto = parameter.ToDto(); + return parameterDto; + } + + public IEnumerable GetAllParameters() + { + return context.Parameters + .ToDtoList() + .AsEnumerable(); + } + public IEnumerable GetAllParameterKeys() + { + return context.Parameters + .Select(p => p.Key) + .AsEnumerable(); + } + + public IEnumerable GetParametersByFilter(Expression> filter) + { + return context.Parameters + .Where(filter) + .ToDtoList() + .AsEnumerable(); + } + + } \ No newline at end of file diff --git a/Server/ReasnAPI/ReasnAPI/Services/ParticipantService.cs b/Server/ReasnAPI/ReasnAPI/Services/ParticipantService.cs index 2b568ddb..481bcb5b 100644 --- a/Server/ReasnAPI/ReasnAPI/Services/ParticipantService.cs +++ b/Server/ReasnAPI/ReasnAPI/Services/ParticipantService.cs @@ -1,3 +1,4 @@ +using Microsoft.EntityFrameworkCore; using ReasnAPI.Exceptions; using ReasnAPI.Mappers; using ReasnAPI.Models.Database; @@ -8,84 +9,68 @@ namespace ReasnAPI.Services; public class ParticipantService(ReasnContext context) { - public ParticipantDto CreateParticipant(ParticipantDto participantDto) + public ParticipantDto CreateOrUpdateParticipant(ParticipantDto participantDto) { ArgumentNullException.ThrowIfNull(participantDto); - var participantDb = context.Participants.FirstOrDefault(r => r.Event.Id == participantDto.EventId && r.User.Id == participantDto.UserId); + var eventDb = context.Events.FirstOrDefault(e => e.Slug == participantDto.EventSlug); - if (participantDb is not null) + if (eventDb is null) { - throw new BadRequestException("Participant already exists"); + throw new NotFoundException("Event not found"); } - var userInDb = context.Users.FirstOrDefault(r => r.Id == participantDto.UserId); + var userDb = context.Users.FirstOrDefault(u => u.Username == participantDto.Username); - if (userInDb is null) + if (userDb is null) { throw new NotFoundException("User not found"); } - var eventInDb = context.Events.FirstOrDefault(r => r.Id == participantDto.EventId); + var participant = context.Participants.FirstOrDefault(r => r.Event.Id == eventDb.Id && r.User.Id == userDb.Id); - if (eventInDb is null) + if (participant is null) { - throw new NotFoundException("Event not found"); + context.Participants.Add(new Participant { EventId = eventDb.Id, UserId = userDb.Id, Status = participantDto.Status }); } - context.Participants.Add(participantDto.ToEntity()); - context.SaveChanges(); - - return participantDto; - } - - public ParticipantDto UpdateParticipant(int participantId, ParticipantDto participantDto) - { - ArgumentNullException.ThrowIfNull(participantDto); - - var participant = context.Participants.FirstOrDefault(r => r.Id == participantId); - if (participant is null) + else { - throw new NotFoundException("Participant not found"); + participant.Status = participantDto.Status; + context.Participants.Update(participant); } - participant.Status = participantDto.Status; - - context.Participants.Update(participant); context.SaveChanges(); - return participant.ToDto(); + return participantDto; } - public void DeleteParticipant(int participantId) + public void DeleteParticipant(int userId, string eventSlug) { - var participant = context.Participants.FirstOrDefault(r => r.Id == participantId); + var eventDb = context.Events.FirstOrDefault(e => e.Slug == eventSlug); - if (participant is null) + if (eventDb is null) { - throw new NotFoundException("Participant not found"); + throw new NotFoundException("Event not found"); } - context.Participants.Remove(participant); - context.SaveChanges(); - } - - public ParticipantDto GetParticipantById(int participantId) - { - var participant = context.Participants.Find(participantId); + var participant = context.Participants.FirstOrDefault(r => r.Event.Id == eventDb.Id && r.User.Id == userId); if (participant is null) { throw new NotFoundException("Participant not found"); } - return participant.ToDto(); + context.Participants.Remove(participant); + context.SaveChanges(); } public IEnumerable GetParticipantsByFilter(Expression> filter) { return context.Participants .Where(filter) + .Include(p => p.Event) + .Include(p => p.User) .ToDtoList() .AsEnumerable(); } @@ -93,6 +78,8 @@ public IEnumerable GetParticipantsByFilter(Expression GetAllParticipants() { return context.Participants + .Include(p => p.Event) + .Include(p => p.User) .ToDtoList() .AsEnumerable(); } diff --git a/Server/ReasnAPI/ReasnAPI/Services/TagService.cs b/Server/ReasnAPI/ReasnAPI/Services/TagService.cs index 65f27409..3c5316c5 100644 --- a/Server/ReasnAPI/ReasnAPI/Services/TagService.cs +++ b/Server/ReasnAPI/ReasnAPI/Services/TagService.cs @@ -40,7 +40,6 @@ public TagDto UpdateTag(int tagId, TagDto tagDto) public void AttatchTagsToEvent(List tagsToAdd, Event eventToUpdate) { - var tagNamesToAdd = tagsToAdd.Select(t => t.Name).ToList(); var tagsFromDb = context.Tags.Where(tag => tagNamesToAdd.Contains(tag.Name)).ToList(); @@ -131,6 +130,13 @@ public IEnumerable GetAllTags() .AsEnumerable(); } + public IEnumerable GetAllTagsNames() + { + return context.Tags + .Select(t => t.Name) + .AsEnumerable(); + + } public IEnumerable GetTagsByFilter(Expression> filter) { return context.Tags diff --git a/Server/ReasnAPI/ReasnAPI/Services/UserService.cs b/Server/ReasnAPI/ReasnAPI/Services/UserService.cs index 92a187a7..37340054 100644 --- a/Server/ReasnAPI/ReasnAPI/Services/UserService.cs +++ b/Server/ReasnAPI/ReasnAPI/Services/UserService.cs @@ -3,81 +3,115 @@ using ReasnAPI.Mappers; using ReasnAPI.Models.Database; using ReasnAPI.Models.DTOs; +using Serilog; using System.Linq.Expressions; +using System.Security.Claims; using System.Transactions; namespace ReasnAPI.Services; -public class UserService(ReasnContext context) +public class UserService { - public UserDto UpdateUser(int userId, UserDto userDto) + private readonly ReasnContext _context; + private readonly IHttpContextAccessor _httpContextAccessor; + + public UserService(ReasnContext context) + { + _context = context; + } + + public UserService(ReasnContext context, IHttpContextAccessor httpContextAccessor) + { + _context = context; + _httpContextAccessor = httpContextAccessor; + } + + public User GetCurrentUser() + { + var httpContext = _httpContextAccessor.HttpContext; + + if (httpContext is null) + { + throw new InvalidOperationException("No HTTP context available"); + } + + var email = httpContext.User.FindFirstValue(ClaimTypes.Email); + if (string.IsNullOrEmpty(email)) + { + throw new UnauthorizedAccessException("No email claim found in token"); + } + + var user = _context.Users.FirstOrDefault(u => u.Email == email); + + if (user is null) + { + throw new NotFoundException("User associated with email not found"); + } + + return user; + } + + public UserDto UpdateUser(string username, UserDto userDto) { using (var scope = new TransactionScope()) { ArgumentNullException.ThrowIfNull(userDto); - var user = context.Users + var user = _context.Users .Include(u => u.UserInterests) .ThenInclude(ui => ui.Interest) - .FirstOrDefault(r => r.Id == userId); + .FirstOrDefault(r => r.Username == username); if (user is null) { throw new NotFoundException("User not found"); } - var usernameExists = context.Users.Any(r => r.Username == userDto.Username && r.Id != userId); + var usernameExists = _context.Users.Any(r => r.Username == userDto.Username && r.Id != user.Id); if (usernameExists) { throw new BadRequestException("User with given username already exists"); } - var emailExists = context.Users.Any(r => r.Email == userDto.Email && r.Id != userId); + var emailExists = _context.Users.Any(r => r.Email == userDto.Email && r.Id != user.Id); if (emailExists) { throw new BadRequestException("User with given email already exists"); } - var phoneExists = context.Users.Any(r => r.Phone == userDto.Phone && r.Id != userId); + var phoneExists = _context.Users.Any(r => r.Phone == userDto.Phone && r.Id != user.Id); if (phoneExists) { throw new BadRequestException("User with given phone number already exists"); } - user.Username = userDto.Username; user.Name = userDto.Name; user.Surname = userDto.Surname; user.Username = userDto.Username; user.Email = userDto.Email; user.Phone = userDto.Phone; user.Role = userDto.Role; - user.AddressId = userDto.AddressId; user.UpdatedAt = DateTime.UtcNow; - context.Users.Update(user); + _context.Users.Update(user); + + // Get list of interests to remove + var interestsToRemove = user.UserInterests + .Where(ui => !userDto.Interests!.Exists(uid => uid.Interest.Name == ui.Interest.Name)); + + _context.UserInterests.RemoveRange(interestsToRemove); if (userDto.Interests is null || userDto.Interests.Count == 0) { - context.SaveChanges(); + _context.SaveChanges(); scope.Complete(); return userDto; } - var interestsToRemove = user.UserInterests - .Where(ui => !userDto.Interests.Exists(uid => uid.Interest.Name == ui.Interest.Name)); - - context.UserInterests.RemoveRange(interestsToRemove); - - var interestsToAdd = userDto.Interests - .Where(uid => !user.UserInterests.Any(ui => ui.Interest.Name == uid.Interest.Name)) - .Select(uid => uid.ToEntity()) - .ToList(); - - context.UserInterests.AddRange(interestsToAdd); - + // Get list of interests to update var interestsToUpdate = user.UserInterests .Where(ui => userDto.Interests.Exists(uid => uid.Interest.Name == ui.Interest.Name)) .ToList(); @@ -92,10 +126,24 @@ public UserDto UpdateUser(int userId, UserDto userDto) } interest.Level = updatedInterest.Level; - context.UserInterests.Update(interest); + _context.UserInterests.Update(interest); } - context.SaveChanges(); + // Get list of existing interests in the database + var existingInterests = _context.Interests.ToList(); + + // Get list of interests to add + // Look for interests that are not already in the user's interests + var interestsToAdd = userDto.Interests + .Where(uid => !user.UserInterests.Any(ui => ui.Interest.Name == uid.Interest.Name)) + .Select(uid => uid.ToEntity(user.Id, existingInterests.Find(ei => ei.Name == uid.Interest.Name)!.Id)) + .ToList(); + + // Update interests for + interestsToAdd.ForEach(user.UserInterests.Add); + _context.Users.Update(user); + + _context.SaveChanges(); scope.Complete(); } @@ -104,8 +152,10 @@ public UserDto UpdateUser(int userId, UserDto userDto) public UserDto GetUserById(int userId) { - var user = context.Users + var user = _context.Users + .Include(a => a.Address) .Include(u => u.UserInterests) + .ThenInclude(ui => ui.Interest) .FirstOrDefault(u => u.Id == userId); if (user is null) @@ -118,8 +168,10 @@ public UserDto GetUserById(int userId) public UserDto GetUserByUsername(string username) { - var user = context.Users + var user = _context.Users + .Include(a => a.Address) .Include(u => u.UserInterests) + .ThenInclude(ui => ui.Interest) .FirstOrDefault(u => u.Username == username); if (user is null) @@ -130,9 +182,38 @@ public UserDto GetUserByUsername(string username) return user.ToDto(); } + public int GetUserIdByUsername(string username) + { + var user = _context.Users + .Include(u => u.UserInterests) + .FirstOrDefault(u => u.Username == username); + + if (user is null) + { + throw new NotFoundException("User not found"); + } + + return user.Id; + } + + public string GetUserUsernameById(int id) + { + var user = _context.Users + .FirstOrDefault(u => u.Id == id); + + if (user is null) + { + throw new NotFoundException("User not found"); + } + + return user.Username; + + } + public IEnumerable GetUsersByFilter(Expression> filter) { - return context.Users + return _context.Users + .Include(a => a.Address) .Include(u => u.UserInterests) .ThenInclude(ui => ui.Interest) .Where(filter) @@ -142,7 +223,8 @@ public IEnumerable GetUsersByFilter(Expression> filter public IEnumerable GetAllUsers() { - var users = context.Users + var users = _context.Users + .Include(a => a.Address) .Include(u => u.UserInterests) .ThenInclude(ui => ui.Interest) .ToList(); diff --git a/Server/ReasnAPI/ReasnAPI/Validators/EventValidator.cs b/Server/ReasnAPI/ReasnAPI/Validators/EventValidator.cs index bc4d4d51..29af7388 100644 --- a/Server/ReasnAPI/ReasnAPI/Validators/EventValidator.cs +++ b/Server/ReasnAPI/ReasnAPI/Validators/EventValidator.cs @@ -26,9 +26,9 @@ public EventValidator() .WithMessage("'StartAt' must be before 'EndAt'."); RuleFor(e => e.Slug) - .NotEmpty() .MaximumLength(MaxSlugLength) - .Matches(SlugRegex); + .Matches(SlugRegex) + .When(e => !string.IsNullOrEmpty(e.Slug)); RuleForEach(e => e.Tags) .SetValidator(new TagValidator()) diff --git a/Server/ReasnAPI/ReasnAPI/appsettings.json b/Server/ReasnAPI/ReasnAPI/appsettings.json index 58588f6e..09ee7d87 100644 --- a/Server/ReasnAPI/ReasnAPI/appsettings.json +++ b/Server/ReasnAPI/ReasnAPI/appsettings.json @@ -4,11 +4,14 @@ "Serilog.Sinks.Console" ], "WriteTo": [ - { "Name": "Console" } + { + "Name": "Console" + } ] }, "ConnectionStrings": { - "DefaultValue": "Server=postgres;Port=5432;Database=reasn;User Id=dba;Password=sql;" + "DefaultValue": "Server=postgres;Port=5432;Database=reasn;User Id=dba;Password=sql;", + "LocalConnection": "Server=localhost;Port=5432;Database=reasn;User Id=dba;Password=sql;" }, "JwtSettings": { "Issuer": "http://localhost:5272",