From 6885bee6408d0ed50016c7f69c141a58a4deccbc Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Sat, 16 Sep 2023 14:00:39 +0100 Subject: [PATCH 01/37] Adding gallery to event model and simple service methods --- .../backend/controller/EventController.kt | 12 ++++++++++++ .../pt/up/fe/ni/website/backend/model/Event.kt | 2 ++ .../backend/service/activity/EventService.kt | 16 ++++++++++++++++ .../backend/controller/EventControllerTest.kt | 6 ++++++ 4 files changed, 36 insertions(+) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt index 69692906..57dc3765 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt @@ -72,4 +72,16 @@ class EventController(private val service: EventService) { @PathVariable idEvent: Long, @PathVariable idAccount: Long ) = service.removeTeamMemberById(idEvent, idAccount) + + @PutMapping("/{idEvent}/gallery/addPhoto") + fun addGalleryPhoto( + @PathVariable idEvent: Long, + @RequestParam photoUrl: String + ) = service.addGalleryPhoto(idEvent, photoUrl) + + @PutMapping("/{idEvent}/gallery/removePhoto") + fun removeGalleryPhoto( + @PathVariable idEvent: Long, + @RequestParam photoUrl: String + ) = service.removeGalleryPhoto(idEvent, photoUrl) } diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Event.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Event.kt index 38135f16..6e727797 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Event.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Event.kt @@ -18,6 +18,8 @@ class Event( slug: String? = null, image: String, + val gallery: MutableList = mutableListOf(), + @field:NullOrNotBlank @field:URL var registerUrl: String? = null, diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt index 53dfbe5a..063cc47b 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt @@ -44,4 +44,20 @@ class EventService( repository.deleteById(eventId) } + + fun addGalleryPhoto(eventId: Long, photoUrl: String) { + val event = getEventById(eventId) + + event.gallery.add(photoUrl) + + repository.save(event) + } + + fun removeGalleryPhoto(eventId: Long, photoUrl: String) { + val event = getEventById(eventId) + + event.gallery.remove(photoUrl) + + repository.save(event) + } } diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index f8e1e6c2..8b05b95c 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -69,6 +69,7 @@ internal class EventControllerTest @Autowired constructor( mutableListOf(), "great-event", "cool-image.png", + mutableListOf(), "https://docs.google.com/forms", DateInterval( TestUtils.createDate(2022, Calendar.JULY, 28), @@ -92,6 +93,7 @@ internal class EventControllerTest @Autowired constructor( mutableListOf(), null, "bad-image.png", + mutableListOf(), null, DateInterval( TestUtils.createDate(2021, Calendar.OCTOBER, 27), @@ -242,6 +244,7 @@ internal class EventControllerTest @Autowired constructor( mutableListOf(), null, "bad-image.png", + mutableListOf(), null, DateInterval( TestUtils.createDate(2021, Calendar.OCTOBER, 27), @@ -257,6 +260,7 @@ internal class EventControllerTest @Autowired constructor( mutableListOf(), null, "mid-image.png", + mutableListOf(), null, DateInterval( TestUtils.createDate(2022, Calendar.JANUARY, 15), @@ -272,6 +276,7 @@ internal class EventControllerTest @Autowired constructor( mutableListOf(), null, "cool-image.png", + mutableListOf(), null, DateInterval( TestUtils.createDate(2022, Calendar.SEPTEMBER, 11), @@ -383,6 +388,7 @@ internal class EventControllerTest @Autowired constructor( mutableListOf(), testEvent.slug, "duplicated-slug.png", + mutableListOf(), "https://docs.google.com/forms", DateInterval( TestUtils.createDate(2022, Calendar.AUGUST, 28), From 6eca9e9b33e1e6937121608190816ed4d11d718c Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Tue, 26 Sep 2023 16:01:16 +0100 Subject: [PATCH 02/37] Uploading image --- .../ni/website/backend/controller/EventController.kt | 10 ++++++---- .../website/backend/service/activity/EventService.kt | 12 ++++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt index 57dc3765..7aeeacd8 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt @@ -67,17 +67,19 @@ class EventController(private val service: EventService) { @PathVariable idAccount: Long ) = service.addTeamMemberById(idEvent, idAccount) - @PutMapping("/{idEvent}/removeTeamMember/{idAccount}") + @PutMapping("/{idEvent}/removeTeamMember/{idAccount}", consumes = ["multipart/form-data"]) fun removeTeamMemberById( @PathVariable idEvent: Long, @PathVariable idAccount: Long ) = service.removeTeamMemberById(idEvent, idAccount) - @PutMapping("/{idEvent}/gallery/addPhoto") + @PutMapping("/{idEvent}/gallery/addPhoto", consumes = ["multipart/form-data"]) fun addGalleryPhoto( @PathVariable idEvent: Long, - @RequestParam photoUrl: String - ) = service.addGalleryPhoto(idEvent, photoUrl) + @RequestParam + @ValidImage + image: MultipartFile + ) = service.addGalleryPhoto(idEvent, image) @PutMapping("/{idEvent}/gallery/removePhoto") fun removeGalleryPhoto( diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt index 063cc47b..15d2579a 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt @@ -2,6 +2,7 @@ package pt.up.fe.ni.website.backend.service.activity import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service +import org.springframework.web.multipart.MultipartFile import pt.up.fe.ni.website.backend.dto.entity.EventDto import pt.up.fe.ni.website.backend.model.Event import pt.up.fe.ni.website.backend.repository.EventRepository @@ -45,18 +46,21 @@ class EventService( repository.deleteById(eventId) } - fun addGalleryPhoto(eventId: Long, photoUrl: String) { + fun addGalleryPhoto(eventId: Long, image: MultipartFile) { val event = getEventById(eventId) - event.gallery.add(photoUrl) + val fileName = fileUploader.buildFileName(image, event.title) + val imageName = fileUploader.uploadImage("profile", fileName, image.bytes) + + event.gallery.add(imageName) repository.save(event) } - fun removeGalleryPhoto(eventId: Long, photoUrl: String) { + fun removeGalleryPhoto(eventId: Long, photoName: String) { val event = getEventById(eventId) - event.gallery.remove(photoUrl) + event.gallery.remove(photoName) repository.save(event) } From d6a1f3d1ecfc616f3a2574413ffc981eb9feb4a8 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Tue, 17 Oct 2023 21:50:22 +0100 Subject: [PATCH 03/37] removed code added by accident --- .../pt/up/fe/ni/website/backend/controller/EventController.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt index 7aeeacd8..399284e4 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt @@ -67,7 +67,7 @@ class EventController(private val service: EventService) { @PathVariable idAccount: Long ) = service.addTeamMemberById(idEvent, idAccount) - @PutMapping("/{idEvent}/removeTeamMember/{idAccount}", consumes = ["multipart/form-data"]) + @PutMapping("/{idEvent}/removeTeamMember/{idAccount}") fun removeTeamMemberById( @PathVariable idEvent: Long, @PathVariable idAccount: Long From 0e67a814158989b39d4d7ef6946fdcd8083c0696 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Tue, 17 Oct 2023 23:36:52 +0100 Subject: [PATCH 04/37] Changed galery image folder --- .../fe/ni/website/backend/service/activity/EventService.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt index 15d2579a..be9bc3a8 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt @@ -46,15 +46,15 @@ class EventService( repository.deleteById(eventId) } - fun addGalleryPhoto(eventId: Long, image: MultipartFile) { + fun addGalleryPhoto(eventId: Long, image: MultipartFile): Event { val event = getEventById(eventId) val fileName = fileUploader.buildFileName(image, event.title) - val imageName = fileUploader.uploadImage("profile", fileName, image.bytes) + val imageName = fileUploader.uploadImage("gallery", fileName, image.bytes) event.gallery.add(imageName) - repository.save(event) + return repository.save(event) } fun removeGalleryPhoto(eventId: Long, photoName: String) { From 28c58f13a111f4b06169b23e750eb9890ed526bb Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Tue, 17 Oct 2023 23:38:43 +0100 Subject: [PATCH 05/37] Created addPhoto test --- .../backend/controller/EventControllerTest.kt | 50 +++++++++++++++++++ .../payloadschemas/model/PayloadEvent.kt | 7 +++ 2 files changed, 57 insertions(+) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index 8b05b95c..d9187134 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -770,6 +770,56 @@ internal class EventControllerTest @Autowired constructor( } } + @NestedTest + @DisplayName("PUT /events/{idEvent}/gallery/addPhoto") + inner class AddGalleryPhoto { + + private val uuid: UUID = UUID.randomUUID() + private val mockedSettings = Mockito.mockStatic(UUID::class.java) + + @BeforeAll + fun setupMocks() { + Mockito.`when`(UUID.randomUUID()).thenReturn(uuid) + } + + @AfterAll + fun cleanup() { + mockedSettings.close() + } + + @BeforeEach + fun addToRepositories() { + accountRepository.save(testAccount) + repository.save(testEvent) + } + + @Test + fun `should add a photo`() { + val expectedPhotoPath = "${uploadConfigProperties.staticServe}/gallery/${testEvent.title}-$uuid.jpeg" + + mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/addPhoto") + .asPutMethod() + .addFile("image", contentType = MediaType.IMAGE_JPEG_VALUE) + .perform() + .andExpectAll( + status().isOk, + content().contentType(MediaType.APPLICATION_JSON), + jsonPath("$.title").value(testEvent.title), + jsonPath("$.description").value(testEvent.description), + jsonPath("$.teamMembers.length()").value(testEvent.teamMembers.size), + jsonPath("$.gallery.length()").value(1), + jsonPath("$.gallery[0]").value(expectedPhotoPath), + jsonPath("$.registerUrl").value(testEvent.registerUrl), + jsonPath("$.dateInterval.startDate").value(testEvent.dateInterval.startDate.toJson()), + jsonPath("$.dateInterval.endDate").value(testEvent.dateInterval.endDate.toJson()), + jsonPath("$.location").value(testEvent.location), + jsonPath("$.category").value(testEvent.category), + jsonPath("$.slug").value(testEvent.slug), + jsonPath("$.image").value(testEvent.image) + ) + } + } + @NestedTest @DisplayName("PUT /events/{eventId}") inner class UpdateEvent { diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadEvent.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadEvent.kt index e952163d..ad2dd36b 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadEvent.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadEvent.kt @@ -42,6 +42,13 @@ class PayloadEvent : ModelDocumentation( JsonFieldType.NUMBER, isInResponse = false, optional = true + ), + DocumentedJSONField( + "gallery[]", + "Array of photos associated with the event", + JsonFieldType.ARRAY, + isInRequest = false, + optional = true ) ).addFieldsBeneathPath( "teamMembers[]", From 3f061dc955ccd7ac4c9d2379e4560c90e21a966b Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Wed, 18 Oct 2023 10:17:36 +0100 Subject: [PATCH 06/37] fixed photoUrl type and added errors when event/photo doesn't exist --- .../backend/controller/EventController.kt | 2 +- .../website/backend/service/ErrorMessages.kt | 2 ++ .../backend/service/activity/EventService.kt | 18 +++++++++++++++--- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt index 399284e4..e1d28924 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt @@ -84,6 +84,6 @@ class EventController(private val service: EventService) { @PutMapping("/{idEvent}/gallery/removePhoto") fun removeGalleryPhoto( @PathVariable idEvent: Long, - @RequestParam photoUrl: String + @RequestPart photoUrl: String ) = service.removeGalleryPhoto(idEvent, photoUrl) } diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/ErrorMessages.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/ErrorMessages.kt index ca43d82c..298643a1 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/ErrorMessages.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/ErrorMessages.kt @@ -44,6 +44,8 @@ object ErrorMessages { fun roleNotFound(id: Long): String = "role not found with id $id" + fun photoNotFound(): String = "photo not found" + fun userAlreadyHasRole(roleId: Long, userId: Long): String = "user $userId already has role $roleId" fun userNotInRole(roleId: Long, userId: Long): String = "user $userId doesn't have role $roleId" diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt index be9bc3a8..698ce2de 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt @@ -47,6 +47,10 @@ class EventService( } fun addGalleryPhoto(eventId: Long, image: MultipartFile): Event { + if (!repository.existsById(eventId)) { + throw NoSuchElementException(ErrorMessages.eventNotFound(eventId)) + } + val event = getEventById(eventId) val fileName = fileUploader.buildFileName(image, event.title) @@ -57,11 +61,19 @@ class EventService( return repository.save(event) } - fun removeGalleryPhoto(eventId: Long, photoName: String) { + fun removeGalleryPhoto(eventId: Long, photoName: String): Event { + if (!repository.existsById(eventId)) { + throw NoSuchElementException(ErrorMessages.eventNotFound(eventId)) + } + val event = getEventById(eventId) - event.gallery.remove(photoName) + val photoRemoved = event.gallery.remove(photoName) - repository.save(event) + if (!photoRemoved) { + throw NoSuchElementException(ErrorMessages.photoNotFound()) + } + + return repository.save(event) } } From a8b0c9efa4e1cd550fe558d04645e648ebd7f6a8 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Mon, 23 Oct 2023 17:35:16 +0100 Subject: [PATCH 07/37] test photo add if event does not exist --- .../backend/controller/EventControllerTest.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index d9187134..a5e133a7 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -818,6 +818,22 @@ internal class EventControllerTest @Autowired constructor( jsonPath("$.image").value(testEvent.image) ) } + + @Test + fun `should fail if event does not exist`() { + val unexistentID = 5 + + mockMvc.multipartBuilder("/events/${unexistentID}/gallery/addPhoto") + .asPutMethod() + .addFile("image", contentType = MediaType.IMAGE_JPEG_VALUE) + .perform() + .andExpectAll( + status().isNotFound, + content().contentType(MediaType.APPLICATION_JSON), + jsonPath("$.errors.length()").value(1), + jsonPath("$.errors[0].message").value("event not found with id ${unexistentID}") + ) + } } @NestedTest From e2262dd137bb90c02220f77a445599c013bbb963 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Mon, 23 Oct 2023 17:42:49 +0100 Subject: [PATCH 08/37] test photo add wrong image format test --- .../backend/controller/EventControllerTest.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index a5e133a7..f79cf39f 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -834,6 +834,20 @@ internal class EventControllerTest @Autowired constructor( jsonPath("$.errors[0].message").value("event not found with id ${unexistentID}") ) } + + @Test + fun `should fail if image in wrong format`() { + mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/addPhoto") + .asPutMethod() + .addFile("image", filename = "image.gif", contentType = MediaType.IMAGE_JPEG_VALUE) + .perform() + .andExpectAll( + status().isBadRequest, + content().contentType(MediaType.APPLICATION_JSON), + jsonPath("$.errors.length()").value(1), + jsonPath("$.errors[0].message").value("invalid image type (png, jpg, jpeg or webp)") + ) + } } @NestedTest From 8a2f4b9c5b800c7b2743ea6173ef133f3a13a801 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Mon, 23 Oct 2023 18:02:04 +0100 Subject: [PATCH 09/37] Remove photo from gallery test --- .../backend/controller/EventControllerTest.kt | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index f79cf39f..372251f3 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -850,6 +850,59 @@ internal class EventControllerTest @Autowired constructor( } } + @NestedTest + @DisplayName("PUT /events/{idEvent}/gallery/removePhoto") + inner class RemoveGalleryPhoto { + + private val uuid: UUID = UUID.randomUUID() + private val mockedSettings = Mockito.mockStatic(UUID::class.java) + private val mockPhotoUrl = "${uploadConfigProperties.staticServe}/gallery/${testEvent.title}-$uuid.jpeg" + + @BeforeAll + fun setupMocks() { + Mockito.`when`(UUID.randomUUID()).thenReturn(uuid) + } + + @AfterAll + fun cleanup() { + mockedSettings.close() + } + + @BeforeEach + fun addToRepositories() { + accountRepository.save(testAccount) + + val testEventClone = testEvent + + testEvent.gallery.add(mockPhotoUrl) + repository.save(testEventClone) + } + + @Test + fun `remove a photo`() { + + mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/removePhoto") + .asPutMethod() + .addPart("photoUrl", mockPhotoUrl) + .perform() + .andExpectAll( + status().isOk, + content().contentType(MediaType.APPLICATION_JSON), + jsonPath("$.title").value(testEvent.title), + jsonPath("$.description").value(testEvent.description), + jsonPath("$.teamMembers.length()").value(testEvent.teamMembers.size), + jsonPath("$.gallery.length()").value(0), + jsonPath("$.registerUrl").value(testEvent.registerUrl), + jsonPath("$.dateInterval.startDate").value(testEvent.dateInterval.startDate.toJson()), + jsonPath("$.dateInterval.endDate").value(testEvent.dateInterval.endDate.toJson()), + jsonPath("$.location").value(testEvent.location), + jsonPath("$.category").value(testEvent.category), + jsonPath("$.slug").value(testEvent.slug), + jsonPath("$.image").value(testEvent.image) + ) + } + } + @NestedTest @DisplayName("PUT /events/{eventId}") inner class UpdateEvent { From 79ee8f192809caed04ee12a79eb7a385e9334cea Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Mon, 23 Oct 2023 18:10:48 +0100 Subject: [PATCH 10/37] gallery photo remove from unexistent event test --- .../backend/controller/EventControllerTest.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index 372251f3..fb231bbd 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -901,6 +901,22 @@ internal class EventControllerTest @Autowired constructor( jsonPath("$.image").value(testEvent.image) ) } + + @Test + fun `should fail if event does not exist`() { + val unexistentID = 5 + + mockMvc.multipartBuilder("/events/${unexistentID}/gallery/removePhoto") + .asPutMethod() + .addPart("photoUrl", mockPhotoUrl) + .perform() + .andExpectAll( + status().isNotFound, + content().contentType(MediaType.APPLICATION_JSON), + jsonPath("$.errors.length()").value(1), + jsonPath("$.errors[0].message").value("event not found with id ${unexistentID}") + ) + } } @NestedTest From c8d19ef33f40bcd8b5310861e9049d135e893000 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Mon, 23 Oct 2023 18:23:02 +0100 Subject: [PATCH 11/37] gallery remove unexistent photo test --- .../backend/controller/EventControllerTest.kt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index fb231bbd..dae4c0b2 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -879,7 +879,7 @@ internal class EventControllerTest @Autowired constructor( } @Test - fun `remove a photo`() { + fun `should remove a photo`() { mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/removePhoto") .asPutMethod() @@ -917,6 +917,22 @@ internal class EventControllerTest @Autowired constructor( jsonPath("$.errors[0].message").value("event not found with id ${unexistentID}") ) } + + @Test + fun `should fail if image does not exist`() { + val wrongPhotoUrl = "${uploadConfigProperties.staticServe}/gallery/Another${testEvent.title}-$uuid.jpeg" + + mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/removePhoto") + .asPutMethod() + .addPart("photoUrl", wrongPhotoUrl) + .perform() + .andExpectAll( + status().isNotFound, + content().contentType(MediaType.APPLICATION_JSON), + jsonPath("$.errors.length()").value(1), + jsonPath("$.errors[0].message").value("photo not found") + ) + } } @NestedTest From 4b0634fef706e7a86baf964149b43ce00a3808c3 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Tue, 30 Jan 2024 17:26:39 +0000 Subject: [PATCH 12/37] generalized gallery to activity --- .../pt/up/fe/ni/website/backend/model/Activity.kt | 2 ++ .../pt/up/fe/ni/website/backend/model/Event.kt | 5 ++--- .../pt/up/fe/ni/website/backend/model/Project.kt | 3 ++- .../backend/controller/EventControllerTest.kt | 14 +++++++------- .../backend/controller/GenerationControllerTest.kt | 6 ++++-- .../backend/controller/ProjectControllerTest.kt | 5 +++++ .../payloadschemas/model/PayloadProject.kt | 7 +++++++ 7 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt index 3b2b81c0..29ccf84c 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt @@ -43,6 +43,8 @@ abstract class Activity( @field:NotBlank open var image: String, + val gallery: MutableList = mutableListOf(), + @Id @GeneratedValue open val id: Long? = null diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Event.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Event.kt index 6e727797..b7191a74 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Event.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Event.kt @@ -17,8 +17,7 @@ class Event( associatedRoles: MutableList = mutableListOf(), slug: String? = null, image: String, - - val gallery: MutableList = mutableListOf(), + gallery: MutableList = mutableListOf(), @field:NullOrNotBlank @field:URL @@ -35,4 +34,4 @@ class Event( val category: String?, id: Long? = null -) : Activity(title, description, teamMembers, associatedRoles, slug, image, id) +) : Activity(title, description, teamMembers, associatedRoles, slug, image, gallery, id) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Project.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Project.kt index 962b77bb..f282f190 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Project.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Project.kt @@ -20,6 +20,7 @@ class Project( associatedRoles: MutableList = mutableListOf(), slug: String? = null, image: String, + gallery: MutableList = mutableListOf(), var isArchived: Boolean = false, @@ -48,4 +49,4 @@ class Project( val timeline: List<@Valid TimelineEvent> = emptyList(), id: Long? = null -) : Activity(title, description, teamMembers, associatedRoles, slug, image, id) +) : Activity(title, description, teamMembers, associatedRoles, slug, image, gallery, id) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index dae4c0b2..ad8ecb6c 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -823,7 +823,7 @@ internal class EventControllerTest @Autowired constructor( fun `should fail if event does not exist`() { val unexistentID = 5 - mockMvc.multipartBuilder("/events/${unexistentID}/gallery/addPhoto") + mockMvc.multipartBuilder("/events/$unexistentID/gallery/addPhoto") .asPutMethod() .addFile("image", contentType = MediaType.IMAGE_JPEG_VALUE) .perform() @@ -831,7 +831,7 @@ internal class EventControllerTest @Autowired constructor( status().isNotFound, content().contentType(MediaType.APPLICATION_JSON), jsonPath("$.errors.length()").value(1), - jsonPath("$.errors[0].message").value("event not found with id ${unexistentID}") + jsonPath("$.errors[0].message").value("event not found with id $unexistentID") ) } @@ -839,7 +839,7 @@ internal class EventControllerTest @Autowired constructor( fun `should fail if image in wrong format`() { mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/addPhoto") .asPutMethod() - .addFile("image", filename = "image.gif", contentType = MediaType.IMAGE_JPEG_VALUE) + .addFile("image", filename = "image.gif", contentType = MediaType.IMAGE_JPEG_VALUE) .perform() .andExpectAll( status().isBadRequest, @@ -880,7 +880,6 @@ internal class EventControllerTest @Autowired constructor( @Test fun `should remove a photo`() { - mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/removePhoto") .asPutMethod() .addPart("photoUrl", mockPhotoUrl) @@ -906,7 +905,7 @@ internal class EventControllerTest @Autowired constructor( fun `should fail if event does not exist`() { val unexistentID = 5 - mockMvc.multipartBuilder("/events/${unexistentID}/gallery/removePhoto") + mockMvc.multipartBuilder("/events/$unexistentID/gallery/removePhoto") .asPutMethod() .addPart("photoUrl", mockPhotoUrl) .perform() @@ -914,7 +913,7 @@ internal class EventControllerTest @Autowired constructor( status().isNotFound, content().contentType(MediaType.APPLICATION_JSON), jsonPath("$.errors.length()").value(1), - jsonPath("$.errors[0].message").value("event not found with id ${unexistentID}") + jsonPath("$.errors[0].message").value("event not found with id $unexistentID") ) } @@ -1076,7 +1075,8 @@ internal class EventControllerTest @Autowired constructor( location = newLocation, category = newCategory, image = "image.png", - slug = newSlug + slug = newSlug, + gallery = mutableListOf() ) repository.save(otherEvent) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/GenerationControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/GenerationControllerTest.kt index e3df1711..c8cb8bb0 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/GenerationControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/GenerationControllerTest.kt @@ -1061,7 +1061,8 @@ class GenerationControllerTest @Autowired constructor( "cool project", image = "cool-image.png", targetAudience = "students", - github = "https://github.com/NIAEFEUP/nijobs-be" + github = "https://github.com/NIAEFEUP/nijobs-be", + gallery = mutableListOf() ) ) ) @@ -1084,7 +1085,8 @@ class GenerationControllerTest @Autowired constructor( dateInterval = DateInterval(TestUtils.createDate(2023, 9, 10)), location = null, category = null, - image = "cool-image.png" + image = "cool-image.png", + gallery = mutableListOf() ) ) ) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt index 132ff20c..1577777d 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt @@ -85,6 +85,7 @@ internal class ProjectControllerTest @Autowired constructor( mutableListOf(), "awesome-project", "cool-image.png", + mutableListOf(), false, listOf("Java", "Kotlin", "Spring"), "Nice one", @@ -115,6 +116,7 @@ internal class ProjectControllerTest @Autowired constructor( mutableListOf(), null, "cool-image.png", + mutableListOf(), false, listOf("ExpressJS", "React"), "Nice one", @@ -359,6 +361,7 @@ internal class ProjectControllerTest @Autowired constructor( mutableListOf(), testProject.slug, "cool-project.png", + mutableListOf(), false, listOf("Java", "Kotlin", "Spring"), "Nice project", @@ -800,6 +803,7 @@ internal class ProjectControllerTest @Autowired constructor( description = newDescription, teamMembers = mutableListOf(), image = "image.png", + gallery = mutableListOf(), slug = newSlug, slogan = newSlogan, targetAudience = newTargetAudience, @@ -1246,6 +1250,7 @@ internal class ProjectControllerTest @Autowired constructor( mutableListOf(), null, "cool-image.png", + mutableListOf(), true, listOf("React", "TailwindCSS"), "Nice one", diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadProject.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadProject.kt index 3a6204a5..d425396c 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadProject.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadProject.kt @@ -31,6 +31,13 @@ class PayloadProject : ModelDocumentation( "Path to the image", JsonFieldType.STRING ), + DocumentedJSONField( + "gallery[]", + "Array of photos associated with the project", + JsonFieldType.ARRAY, + isInRequest = false, + optional = true + ), DocumentedJSONField( "targetAudience", "Information about the target audience", From 50acea50db5d02a27e06635ff0d5da8765e50dfe Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Sun, 4 Feb 2024 18:54:49 +0000 Subject: [PATCH 13/37] Move gallery service methods from Event to Activity --- .../activity/AbstractActivityService.kt | 32 +++++++++++++++++++ .../backend/service/activity/EventService.kt | 32 ------------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt index 32127552..37260977 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt @@ -2,6 +2,7 @@ package pt.up.fe.ni.website.backend.service.activity import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service +import org.springframework.web.multipart.MultipartFile import pt.up.fe.ni.website.backend.dto.entity.ActivityDto import pt.up.fe.ni.website.backend.model.Activity import pt.up.fe.ni.website.backend.repository.ActivityRepository @@ -83,4 +84,35 @@ abstract class AbstractActivityService( activity.teamMembers.removeIf { it.id == idAccount } return repository.save(activity) } + + fun addGalleryPhoto(activityId: Long, image: MultipartFile): Activity { + if (!repository.existsById(activityId)) { + throw NoSuchElementException(ErrorMessages.eventNotFound(activityId)) + } + + val activity = getActivityById(activityId) + + val fileName = fileUploader.buildFileName(image, activity.title) + val imageName = fileUploader.uploadImage("gallery", fileName, image.bytes) + + activity.gallery.add(imageName) + + return repository.save(activity) + } + + fun removeGalleryPhoto(activityId: Long, photoName: String): Activity { + if (!repository.existsById(activityId)) { + throw NoSuchElementException(ErrorMessages.eventNotFound(activityId)) + } + + val activity = getActivityById(activityId) + + val photoRemoved = activity.gallery.remove(photoName) + + if (!photoRemoved) { + throw NoSuchElementException(ErrorMessages.photoNotFound()) + } + + return repository.save(activity) + } } diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt index 698ce2de..53dfbe5a 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/EventService.kt @@ -2,7 +2,6 @@ package pt.up.fe.ni.website.backend.service.activity import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service -import org.springframework.web.multipart.MultipartFile import pt.up.fe.ni.website.backend.dto.entity.EventDto import pt.up.fe.ni.website.backend.model.Event import pt.up.fe.ni.website.backend.repository.EventRepository @@ -45,35 +44,4 @@ class EventService( repository.deleteById(eventId) } - - fun addGalleryPhoto(eventId: Long, image: MultipartFile): Event { - if (!repository.existsById(eventId)) { - throw NoSuchElementException(ErrorMessages.eventNotFound(eventId)) - } - - val event = getEventById(eventId) - - val fileName = fileUploader.buildFileName(image, event.title) - val imageName = fileUploader.uploadImage("gallery", fileName, image.bytes) - - event.gallery.add(imageName) - - return repository.save(event) - } - - fun removeGalleryPhoto(eventId: Long, photoName: String): Event { - if (!repository.existsById(eventId)) { - throw NoSuchElementException(ErrorMessages.eventNotFound(eventId)) - } - - val event = getEventById(eventId) - - val photoRemoved = event.gallery.remove(photoName) - - if (!photoRemoved) { - throw NoSuchElementException(ErrorMessages.photoNotFound()) - } - - return repository.save(event) - } } From 91ace92c81f9f4012c61f54ff9300bc00b5fe84b Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Tue, 6 Feb 2024 14:29:55 +0000 Subject: [PATCH 14/37] added gallery to develop tests --- .../up/fe/ni/website/backend/controller/EventControllerTest.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index 8ae68f36..e625985e 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -125,6 +125,7 @@ internal class EventControllerTest @Autowired constructor( mutableListOf(), "bloat", "waldo.jpeg", + mutableListOf(), null, DateInterval( TestUtils.createDate(2022, Calendar.JANUARY, 15), @@ -140,6 +141,7 @@ internal class EventControllerTest @Autowired constructor( mutableListOf(), "ni", "ni.png", + mutableListOf(), null, DateInterval( TestUtils.createDate(2022, Calendar.SEPTEMBER, 11), From a3516e71dc91e881dcc34d74eaa58c51efd697c3 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Tue, 6 Feb 2024 14:43:28 +0000 Subject: [PATCH 15/37] added gallery documentation to activity payload --- .../documentation/payloadschemas/model/PayloadActivity.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadActivity.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadActivity.kt index b46da124..955dd2a5 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadActivity.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadActivity.kt @@ -89,6 +89,13 @@ class PayloadActivity { "Description of the event", JsonFieldType.STRING, optional = true + ), + DocumentedJSONField( + "gallery[]", + "Array of photos associated with the activity", + JsonFieldType.ARRAY, + isInRequest = false, + optional = true ) ).addFieldsBeneathPath( "dateInterval", From cd6f37f565727913e9557de544d36147472c2bb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20=C3=81vila?= <75994325+AvilaAndre@users.noreply.github.com> Date: Thu, 28 Mar 2024 01:21:38 +0000 Subject: [PATCH 16/37] Update src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt Co-authored-by: Rita Lopes <93883163+MRita443@users.noreply.github.com> --- src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt index 29ccf84c..485da985 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt @@ -43,7 +43,8 @@ abstract class Activity( @field:NotBlank open var image: String, - val gallery: MutableList = mutableListOf(), + @ElementCollection + open val gallery: MutableList = mutableListOf(), @Id @GeneratedValue From 971b2c057a71d415bcefde4a59826a1df4a46c67 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Thu, 28 Mar 2024 01:30:23 +0000 Subject: [PATCH 17/37] Added missing import --- src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt index 485da985..49e45cfc 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt @@ -12,6 +12,7 @@ import jakarta.persistence.Inheritance import jakarta.persistence.InheritanceType import jakarta.persistence.JoinColumn import jakarta.persistence.OneToMany +import jakarta.persistence.ElementCollection import jakarta.validation.Valid import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.Size From 6b3e9789f238a241fa3df98e42c3491662bbdc43 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Thu, 28 Mar 2024 01:37:08 +0000 Subject: [PATCH 18/37] Formatted imports --- src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt index 49e45cfc..5c616ea0 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty import jakarta.persistence.CascadeType import jakarta.persistence.Column +import jakarta.persistence.ElementCollection import jakarta.persistence.Entity import jakarta.persistence.FetchType import jakarta.persistence.GeneratedValue @@ -12,7 +13,6 @@ import jakarta.persistence.Inheritance import jakarta.persistence.InheritanceType import jakarta.persistence.JoinColumn import jakarta.persistence.OneToMany -import jakarta.persistence.ElementCollection import jakarta.validation.Valid import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.Size From c6f4c3c772c655814bc471b268d21a6ba1a777fe Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Thu, 28 Mar 2024 01:38:14 +0000 Subject: [PATCH 19/37] Removed unnnecessary code --- .../activity/AbstractActivityService.kt | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt index 37260977..f8dfc682 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt @@ -12,12 +12,13 @@ import pt.up.fe.ni.website.backend.service.upload.FileUploader @Service abstract class AbstractActivityService( - protected val repository: ActivityRepository, - protected val accountService: AccountService, - protected val fileUploader: FileUploader + protected val repository: ActivityRepository, + protected val accountService: AccountService, + protected val fileUploader: FileUploader ) { - fun getActivityById(id: Long): T = repository.findByIdOrNull(id) - ?: throw NoSuchElementException(ErrorMessages.activityNotFound(id)) + fun getActivityById(id: Long): T = + repository.findByIdOrNull(id) + ?: throw NoSuchElementException(ErrorMessages.activityNotFound(id)) fun > createActivity(dto: U, imageFolder: String): T { repository.findBySlug(dto.slug)?.let { @@ -75,21 +76,13 @@ abstract class AbstractActivityService( fun removeTeamMemberById(idActivity: Long, idAccount: Long): T { val activity = getActivityById(idActivity) if (!accountService.doesAccountExist(idAccount)) { - throw NoSuchElementException( - ErrorMessages.accountNotFound( - idAccount - ) - ) + throw NoSuchElementException(ErrorMessages.accountNotFound(idAccount)) } activity.teamMembers.removeIf { it.id == idAccount } return repository.save(activity) } fun addGalleryPhoto(activityId: Long, image: MultipartFile): Activity { - if (!repository.existsById(activityId)) { - throw NoSuchElementException(ErrorMessages.eventNotFound(activityId)) - } - val activity = getActivityById(activityId) val fileName = fileUploader.buildFileName(image, activity.title) @@ -101,10 +94,6 @@ abstract class AbstractActivityService( } fun removeGalleryPhoto(activityId: Long, photoName: String): Activity { - if (!repository.existsById(activityId)) { - throw NoSuchElementException(ErrorMessages.eventNotFound(activityId)) - } - val activity = getActivityById(activityId) val photoRemoved = activity.gallery.remove(photoName) From 31d5a4be45832ed092ad8141d76624bed9c7c741 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Thu, 28 Mar 2024 02:41:26 +0000 Subject: [PATCH 20/37] Replaced photo with image --- .../backend/controller/EventController.kt | 14 +++--- .../website/backend/service/ErrorMessages.kt | 2 +- .../activity/AbstractActivityService.kt | 20 ++++---- .../backend/controller/EventControllerTest.kt | 46 +++++++++---------- 4 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt index 857f7403..0d1fc6d4 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt @@ -75,17 +75,17 @@ class EventController(private val service: EventService) { @PathVariable idAccount: Long ) = service.removeTeamMemberById(idEvent, idAccount) - @PutMapping("/{idEvent}/gallery/addPhoto", consumes = ["multipart/form-data"]) - fun addGalleryPhoto( + @PutMapping("/{idEvent}/gallery/addImage", consumes = ["multipart/form-data"]) + fun addGalleryImage( @PathVariable idEvent: Long, @RequestParam @ValidImage image: MultipartFile - ) = service.addGalleryPhoto(idEvent, image) + ) = service.addGalleryImage(idEvent, image) - @PutMapping("/{idEvent}/gallery/removePhoto") - fun removeGalleryPhoto( + @PutMapping("/{idEvent}/gallery/removeImage") + fun removeGalleryImage( @PathVariable idEvent: Long, - @RequestPart photoUrl: String - ) = service.removeGalleryPhoto(idEvent, photoUrl) + @RequestPart imageUrl: String + ) = service.removeGalleryImage(idEvent, imageUrl) } diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/ErrorMessages.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/ErrorMessages.kt index 298643a1..2b8e3619 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/ErrorMessages.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/ErrorMessages.kt @@ -44,7 +44,7 @@ object ErrorMessages { fun roleNotFound(id: Long): String = "role not found with id $id" - fun photoNotFound(): String = "photo not found" + fun imageNotFound(imageName: String): String = "image not found with name $imageName" fun userAlreadyHasRole(roleId: Long, userId: Long): String = "user $userId already has role $roleId" diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt index f8dfc682..2cec9dd1 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt @@ -12,13 +12,13 @@ import pt.up.fe.ni.website.backend.service.upload.FileUploader @Service abstract class AbstractActivityService( - protected val repository: ActivityRepository, - protected val accountService: AccountService, - protected val fileUploader: FileUploader + protected val repository: ActivityRepository, + protected val accountService: AccountService, + protected val fileUploader: FileUploader ) { fun getActivityById(id: Long): T = - repository.findByIdOrNull(id) - ?: throw NoSuchElementException(ErrorMessages.activityNotFound(id)) + repository.findByIdOrNull(id) + ?: throw NoSuchElementException(ErrorMessages.activityNotFound(id)) fun > createActivity(dto: U, imageFolder: String): T { repository.findBySlug(dto.slug)?.let { @@ -82,7 +82,7 @@ abstract class AbstractActivityService( return repository.save(activity) } - fun addGalleryPhoto(activityId: Long, image: MultipartFile): Activity { + fun addGalleryImage(activityId: Long, image: MultipartFile): Activity { val activity = getActivityById(activityId) val fileName = fileUploader.buildFileName(image, activity.title) @@ -93,13 +93,13 @@ abstract class AbstractActivityService( return repository.save(activity) } - fun removeGalleryPhoto(activityId: Long, photoName: String): Activity { + fun removeGalleryImage(activityId: Long, imageName: String): Activity { val activity = getActivityById(activityId) - val photoRemoved = activity.gallery.remove(photoName) + val imageRemoved = activity.gallery.remove(imageName) - if (!photoRemoved) { - throw NoSuchElementException(ErrorMessages.photoNotFound()) + if (!imageRemoved) { + throw NoSuchElementException(ErrorMessages.imageNotFound(imageName)) } return repository.save(activity) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index e625985e..726c48a1 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -813,8 +813,8 @@ internal class EventControllerTest @Autowired constructor( } @NestedTest - @DisplayName("PUT /events/{idEvent}/gallery/addPhoto") - inner class AddGalleryPhoto { + @DisplayName("PUT /events/{idEvent}/gallery/addImage") + inner class AddGalleryImage { private val uuid: UUID = UUID.randomUUID() private val mockedSettings = Mockito.mockStatic(UUID::class.java) @@ -836,10 +836,10 @@ internal class EventControllerTest @Autowired constructor( } @Test - fun `should add a photo`() { - val expectedPhotoPath = "${uploadConfigProperties.staticServe}/gallery/${testEvent.title}-$uuid.jpeg" + fun `should add an image`() { + val expectedImagePath = "${uploadConfigProperties.staticServe}/gallery/${testEvent.title}-$uuid.jpeg" - mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/addPhoto") + mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/addImage") .asPutMethod() .addFile("image", contentType = MediaType.IMAGE_JPEG_VALUE) .perform() @@ -850,7 +850,7 @@ internal class EventControllerTest @Autowired constructor( jsonPath("$.description").value(testEvent.description), jsonPath("$.teamMembers.length()").value(testEvent.teamMembers.size), jsonPath("$.gallery.length()").value(1), - jsonPath("$.gallery[0]").value(expectedPhotoPath), + jsonPath("$.gallery[0]").value(expectedImagePath), jsonPath("$.registerUrl").value(testEvent.registerUrl), jsonPath("$.dateInterval.startDate").value(testEvent.dateInterval.startDate.toJson()), jsonPath("$.dateInterval.endDate").value(testEvent.dateInterval.endDate.toJson()), @@ -865,7 +865,7 @@ internal class EventControllerTest @Autowired constructor( fun `should fail if event does not exist`() { val unexistentID = 5 - mockMvc.multipartBuilder("/events/$unexistentID/gallery/addPhoto") + mockMvc.multipartBuilder("/events/$unexistentID/gallery/addImage") .asPutMethod() .addFile("image", contentType = MediaType.IMAGE_JPEG_VALUE) .perform() @@ -873,13 +873,13 @@ internal class EventControllerTest @Autowired constructor( status().isNotFound, content().contentType(MediaType.APPLICATION_JSON), jsonPath("$.errors.length()").value(1), - jsonPath("$.errors[0].message").value("event not found with id $unexistentID") + jsonPath("$.errors[0].message").value("activity not found with id $unexistentID") ) } @Test fun `should fail if image in wrong format`() { - mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/addPhoto") + mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/addImage") .asPutMethod() .addFile("image", filename = "image.gif", contentType = MediaType.IMAGE_JPEG_VALUE) .perform() @@ -893,12 +893,12 @@ internal class EventControllerTest @Autowired constructor( } @NestedTest - @DisplayName("PUT /events/{idEvent}/gallery/removePhoto") - inner class RemoveGalleryPhoto { + @DisplayName("PUT /events/{idEvent}/gallery/removeImage") + inner class RemoveGalleryImage { private val uuid: UUID = UUID.randomUUID() private val mockedSettings = Mockito.mockStatic(UUID::class.java) - private val mockPhotoUrl = "${uploadConfigProperties.staticServe}/gallery/${testEvent.title}-$uuid.jpeg" + private val mockImageUrl = "${uploadConfigProperties.staticServe}/gallery/${testEvent.title}-$uuid.jpeg" @BeforeAll fun setupMocks() { @@ -916,15 +916,15 @@ internal class EventControllerTest @Autowired constructor( val testEventClone = testEvent - testEvent.gallery.add(mockPhotoUrl) + testEvent.gallery.add(mockImageUrl) repository.save(testEventClone) } @Test - fun `should remove a photo`() { - mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/removePhoto") + fun `should remove an image`() { + mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/removeImage") .asPutMethod() - .addPart("photoUrl", mockPhotoUrl) + .addPart("imageUrl", mockImageUrl) .perform() .andExpectAll( status().isOk, @@ -947,31 +947,31 @@ internal class EventControllerTest @Autowired constructor( fun `should fail if event does not exist`() { val unexistentID = 5 - mockMvc.multipartBuilder("/events/$unexistentID/gallery/removePhoto") + mockMvc.multipartBuilder("/events/$unexistentID/gallery/removeImage") .asPutMethod() - .addPart("photoUrl", mockPhotoUrl) + .addPart("imageUrl", mockImageUrl) .perform() .andExpectAll( status().isNotFound, content().contentType(MediaType.APPLICATION_JSON), jsonPath("$.errors.length()").value(1), - jsonPath("$.errors[0].message").value("event not found with id $unexistentID") + jsonPath("$.errors[0].message").value("activity not found with id $unexistentID") ) } @Test fun `should fail if image does not exist`() { - val wrongPhotoUrl = "${uploadConfigProperties.staticServe}/gallery/Another${testEvent.title}-$uuid.jpeg" + val wrongImageUrl = "${uploadConfigProperties.staticServe}/gallery/Another${testEvent.title}-$uuid.jpeg" - mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/removePhoto") + mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/removeImage") .asPutMethod() - .addPart("photoUrl", wrongPhotoUrl) + .addPart("imageUrl", wrongImageUrl) .perform() .andExpectAll( status().isNotFound, content().contentType(MediaType.APPLICATION_JSON), jsonPath("$.errors.length()").value(1), - jsonPath("$.errors[0].message").value("photo not found") + jsonPath("$.errors[0].message").value("image not found with name $wrongImageUrl") ) } } From 86396885aa1458fce0eac77a80c3ee797192f010 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Thu, 28 Mar 2024 19:41:59 +0000 Subject: [PATCH 21/37] added image folder to gallery --- .../service/activity/AbstractActivityService.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt index 2cec9dd1..b2c802c3 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt @@ -5,6 +5,8 @@ import org.springframework.stereotype.Service import org.springframework.web.multipart.MultipartFile import pt.up.fe.ni.website.backend.dto.entity.ActivityDto import pt.up.fe.ni.website.backend.model.Activity +import pt.up.fe.ni.website.backend.model.Event +import pt.up.fe.ni.website.backend.model.Project import pt.up.fe.ni.website.backend.repository.ActivityRepository import pt.up.fe.ni.website.backend.service.AccountService import pt.up.fe.ni.website.backend.service.ErrorMessages @@ -16,6 +18,7 @@ abstract class AbstractActivityService( protected val accountService: AccountService, protected val fileUploader: FileUploader ) { + fun getActivityById(id: Long): T = repository.findByIdOrNull(id) ?: throw NoSuchElementException(ErrorMessages.activityNotFound(id)) @@ -85,8 +88,15 @@ abstract class AbstractActivityService( fun addGalleryImage(activityId: Long, image: MultipartFile): Activity { val activity = getActivityById(activityId) + var imageFolder = "activities" + + when (activity::javaClass) { + Event::class -> imageFolder = EventService.IMAGE_FOLDER + Project::class -> imageFolder = ProjectService.IMAGE_FOLDER + } + val fileName = fileUploader.buildFileName(image, activity.title) - val imageName = fileUploader.uploadImage("gallery", fileName, image.bytes) + val imageName = fileUploader.uploadImage(imageFolder + "/gallery", fileName, image.bytes) activity.gallery.add(imageName) From 1863c6fefaa3891f70ee55202e9589385a5a0611 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Thu, 28 Mar 2024 20:38:32 +0000 Subject: [PATCH 22/37] Updated gallery documentation --- .../utils/documentation/payloadschemas/model/PayloadActivity.kt | 2 +- .../utils/documentation/payloadschemas/model/PayloadEvent.kt | 2 +- .../utils/documentation/payloadschemas/model/PayloadProject.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadActivity.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadActivity.kt index 955dd2a5..cf67bc0d 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadActivity.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadActivity.kt @@ -92,7 +92,7 @@ class PayloadActivity { ), DocumentedJSONField( "gallery[]", - "Array of photos associated with the activity", + "Array of paths for the images associated with the activity", JsonFieldType.ARRAY, isInRequest = false, optional = true diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadEvent.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadEvent.kt index ad2dd36b..0a46faae 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadEvent.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadEvent.kt @@ -45,7 +45,7 @@ class PayloadEvent : ModelDocumentation( ), DocumentedJSONField( "gallery[]", - "Array of photos associated with the event", + "Array of paths for the images associated with the event", JsonFieldType.ARRAY, isInRequest = false, optional = true diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadProject.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadProject.kt index d425396c..bd884d89 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadProject.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadProject.kt @@ -33,7 +33,7 @@ class PayloadProject : ModelDocumentation( ), DocumentedJSONField( "gallery[]", - "Array of photos associated with the project", + "Array of paths for the images associated with the project", JsonFieldType.ARRAY, isInRequest = false, optional = true From 250bb1863d6e0cbf10a7b62dc03cc5daad568fac Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Fri, 29 Mar 2024 01:02:22 +0000 Subject: [PATCH 23/37] Fixing tests failing after changes --- .../pt/up/fe/ni/website/backend/model/Activity.kt | 2 +- .../service/activity/AbstractActivityService.kt | 7 ++++--- .../website/backend/controller/EventControllerTest.kt | 11 +++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt index 5c616ea0..d7884b45 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt @@ -44,7 +44,7 @@ abstract class Activity( @field:NotBlank open var image: String, - @ElementCollection + @ElementCollection(fetch = FetchType.EAGER) open val gallery: MutableList = mutableListOf(), @Id diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt index b2c802c3..5207b0ee 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt @@ -90,9 +90,10 @@ abstract class AbstractActivityService( var imageFolder = "activities" - when (activity::javaClass) { - Event::class -> imageFolder = EventService.IMAGE_FOLDER - Project::class -> imageFolder = ProjectService.IMAGE_FOLDER + if (activity is Event) { + imageFolder = EventService.IMAGE_FOLDER + } else if (activity is Project) { + imageFolder = ProjectService.IMAGE_FOLDER } val fileName = fileUploader.buildFileName(image, activity.title) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index 726c48a1..67170474 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -837,7 +837,7 @@ internal class EventControllerTest @Autowired constructor( @Test fun `should add an image`() { - val expectedImagePath = "${uploadConfigProperties.staticServe}/gallery/${testEvent.title}-$uuid.jpeg" + val expectedImagePath = "${uploadConfigProperties.staticServe}/events/gallery/${testEvent.title}-$uuid.jpeg" mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/addImage") .asPutMethod() @@ -898,11 +898,13 @@ internal class EventControllerTest @Autowired constructor( private val uuid: UUID = UUID.randomUUID() private val mockedSettings = Mockito.mockStatic(UUID::class.java) - private val mockImageUrl = "${uploadConfigProperties.staticServe}/gallery/${testEvent.title}-$uuid.jpeg" + private val mockImageUrl = "${uploadConfigProperties.staticServe}/events/gallery/${testEvent.title}-$uuid.jpeg" @BeforeAll fun setupMocks() { Mockito.`when`(UUID.randomUUID()).thenReturn(uuid) + + testEvent.gallery.add(mockImageUrl) } @AfterAll @@ -914,10 +916,7 @@ internal class EventControllerTest @Autowired constructor( fun addToRepositories() { accountRepository.save(testAccount) - val testEventClone = testEvent - - testEvent.gallery.add(mockImageUrl) - repository.save(testEventClone) + repository.save(testEvent) } @Test From dd4818bfdd881be31866d6da407e3f77a00e7bf8 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Sun, 21 Jul 2024 00:09:43 +0100 Subject: [PATCH 24/37] replace endpoints to be restful and add delete method to testing --- .../backend/controller/EventController.kt | 6 ++--- .../activity/AbstractActivityService.kt | 12 +--------- .../backend/controller/EventControllerTest.kt | 23 ++++++++++--------- .../utils/mockmvc/MockMvcMultipartBuilder.kt | 10 ++++++++ 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt index 0d1fc6d4..7f9fe91c 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/EventController.kt @@ -75,15 +75,15 @@ class EventController(private val service: EventService) { @PathVariable idAccount: Long ) = service.removeTeamMemberById(idEvent, idAccount) - @PutMapping("/{idEvent}/gallery/addImage", consumes = ["multipart/form-data"]) + @PutMapping("/{idEvent}/gallery", consumes = ["multipart/form-data"]) fun addGalleryImage( @PathVariable idEvent: Long, @RequestParam @ValidImage image: MultipartFile - ) = service.addGalleryImage(idEvent, image) + ) = service.addGalleryImage(idEvent, image, EventService.IMAGE_FOLDER) - @PutMapping("/{idEvent}/gallery/removeImage") + @DeleteMapping("/{idEvent}/gallery") fun removeGalleryImage( @PathVariable idEvent: Long, @RequestPart imageUrl: String diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt index 5207b0ee..498c3852 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt @@ -5,8 +5,6 @@ import org.springframework.stereotype.Service import org.springframework.web.multipart.MultipartFile import pt.up.fe.ni.website.backend.dto.entity.ActivityDto import pt.up.fe.ni.website.backend.model.Activity -import pt.up.fe.ni.website.backend.model.Event -import pt.up.fe.ni.website.backend.model.Project import pt.up.fe.ni.website.backend.repository.ActivityRepository import pt.up.fe.ni.website.backend.service.AccountService import pt.up.fe.ni.website.backend.service.ErrorMessages @@ -85,17 +83,9 @@ abstract class AbstractActivityService( return repository.save(activity) } - fun addGalleryImage(activityId: Long, image: MultipartFile): Activity { + fun addGalleryImage(activityId: Long, image: MultipartFile, imageFolder: String): Activity { val activity = getActivityById(activityId) - var imageFolder = "activities" - - if (activity is Event) { - imageFolder = EventService.IMAGE_FOLDER - } else if (activity is Project) { - imageFolder = ProjectService.IMAGE_FOLDER - } - val fileName = fileUploader.buildFileName(image, activity.title) val imageName = fileUploader.uploadImage(imageFolder + "/gallery", fileName, image.bytes) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index 67170474..1ddb1bcb 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -813,7 +813,7 @@ internal class EventControllerTest @Autowired constructor( } @NestedTest - @DisplayName("PUT /events/{idEvent}/gallery/addImage") + @DisplayName("PUT /events/{idEvent}/gallery") inner class AddGalleryImage { private val uuid: UUID = UUID.randomUUID() @@ -839,7 +839,7 @@ internal class EventControllerTest @Autowired constructor( fun `should add an image`() { val expectedImagePath = "${uploadConfigProperties.staticServe}/events/gallery/${testEvent.title}-$uuid.jpeg" - mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/addImage") + mockMvc.multipartBuilder("/events/${testEvent.id}/gallery") .asPutMethod() .addFile("image", contentType = MediaType.IMAGE_JPEG_VALUE) .perform() @@ -865,7 +865,7 @@ internal class EventControllerTest @Autowired constructor( fun `should fail if event does not exist`() { val unexistentID = 5 - mockMvc.multipartBuilder("/events/$unexistentID/gallery/addImage") + mockMvc.multipartBuilder("/events/$unexistentID/gallery") .asPutMethod() .addFile("image", contentType = MediaType.IMAGE_JPEG_VALUE) .perform() @@ -879,7 +879,7 @@ internal class EventControllerTest @Autowired constructor( @Test fun `should fail if image in wrong format`() { - mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/addImage") + mockMvc.multipartBuilder("/events/${testEvent.id}/gallery") .asPutMethod() .addFile("image", filename = "image.gif", contentType = MediaType.IMAGE_JPEG_VALUE) .perform() @@ -892,8 +892,9 @@ internal class EventControllerTest @Autowired constructor( } } + @Nested @NestedTest - @DisplayName("PUT /events/{idEvent}/gallery/removeImage") + @DisplayName("DELETE /events/{idEvent}/gallery") inner class RemoveGalleryImage { private val uuid: UUID = UUID.randomUUID() @@ -921,8 +922,8 @@ internal class EventControllerTest @Autowired constructor( @Test fun `should remove an image`() { - mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/removeImage") - .asPutMethod() + mockMvc.multipartBuilder("/events/${testEvent.id}/gallery") + .asDeleteMethod() .addPart("imageUrl", mockImageUrl) .perform() .andExpectAll( @@ -946,8 +947,8 @@ internal class EventControllerTest @Autowired constructor( fun `should fail if event does not exist`() { val unexistentID = 5 - mockMvc.multipartBuilder("/events/$unexistentID/gallery/removeImage") - .asPutMethod() + mockMvc.multipartBuilder("/events/$unexistentID/gallery") + .asDeleteMethod() .addPart("imageUrl", mockImageUrl) .perform() .andExpectAll( @@ -962,8 +963,8 @@ internal class EventControllerTest @Autowired constructor( fun `should fail if image does not exist`() { val wrongImageUrl = "${uploadConfigProperties.staticServe}/gallery/Another${testEvent.title}-$uuid.jpeg" - mockMvc.multipartBuilder("/events/${testEvent.id}/gallery/removeImage") - .asPutMethod() + mockMvc.multipartBuilder("/events/${testEvent.id}/gallery") + .asDeleteMethod() .addPart("imageUrl", wrongImageUrl) .perform() .andExpectAll( diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/mockmvc/MockMvcMultipartBuilder.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/mockmvc/MockMvcMultipartBuilder.kt index 240ed891..187d8726 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/mockmvc/MockMvcMultipartBuilder.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/mockmvc/MockMvcMultipartBuilder.kt @@ -49,6 +49,16 @@ class MockMvcMultipartBuilder( return this } + fun asDeleteMethod(): MockMvcMultipartBuilder { + multipart.with( + { + it.method = "DELETE" + it + } + ) + return this + } + fun perform(): ResultActions { return mockMvc.perform(multipart) } From 1b664c1d9850fa517891f0dfd02b8206e7564086 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Sun, 21 Jul 2024 00:19:46 +0100 Subject: [PATCH 25/37] generalized image to file --- .../kotlin/pt/up/fe/ni/website/backend/service/ErrorMessages.kt | 2 +- .../website/backend/service/activity/AbstractActivityService.kt | 2 +- .../up/fe/ni/website/backend/controller/EventControllerTest.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/ErrorMessages.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/ErrorMessages.kt index 2b8e3619..79b72158 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/ErrorMessages.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/ErrorMessages.kt @@ -44,7 +44,7 @@ object ErrorMessages { fun roleNotFound(id: Long): String = "role not found with id $id" - fun imageNotFound(imageName: String): String = "image not found with name $imageName" + fun fileNotFound(fileName: String): String = "file not found with name $fileName" fun userAlreadyHasRole(roleId: Long, userId: Long): String = "user $userId already has role $roleId" diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt index 498c3852..ace9198a 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt @@ -100,7 +100,7 @@ abstract class AbstractActivityService( val imageRemoved = activity.gallery.remove(imageName) if (!imageRemoved) { - throw NoSuchElementException(ErrorMessages.imageNotFound(imageName)) + throw NoSuchElementException(ErrorMessages.fileNotFound(imageName)) } return repository.save(activity) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index 1ddb1bcb..584287f3 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -971,7 +971,7 @@ internal class EventControllerTest @Autowired constructor( status().isNotFound, content().contentType(MediaType.APPLICATION_JSON), jsonPath("$.errors.length()").value(1), - jsonPath("$.errors[0].message").value("image not found with name $wrongImageUrl") + jsonPath("$.errors[0].message").value("file not found with name $wrongImageUrl") ) } } From 02ced4ee4aa0e379435f37651c6f6fe0d4ddb670 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Sun, 21 Jul 2024 01:58:25 +0100 Subject: [PATCH 26/37] added file deletion to FileUploader class and delete files on removal --- .../backend/service/activity/AbstractActivityService.kt | 7 ++++--- .../backend/service/upload/CloudinaryFileUploader.kt | 7 +++++++ .../fe/ni/website/backend/service/upload/FileUploader.kt | 1 + .../website/backend/service/upload/StaticFileUploader.kt | 7 +++++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt index ace9198a..041a4b7b 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt @@ -97,9 +97,10 @@ abstract class AbstractActivityService( fun removeGalleryImage(activityId: Long, imageName: String): Activity { val activity = getActivityById(activityId) - val imageRemoved = activity.gallery.remove(imageName) - - if (!imageRemoved) { + if (activity.gallery.contains(imageName)) { + fileUploader.deleteImage(imageName) + activity.gallery.remove(imageName) + } else { throw NoSuchElementException(ErrorMessages.fileNotFound(imageName)) } diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/upload/CloudinaryFileUploader.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/upload/CloudinaryFileUploader.kt index 64cf99e1..0b6f60f0 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/upload/CloudinaryFileUploader.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/upload/CloudinaryFileUploader.kt @@ -20,4 +20,11 @@ class CloudinaryFileUploader(private val basePath: String, private val cloudinar return result["url"]?.toString() ?: "" } + + override fun deleteImage(fileName: String) { + cloudinary.uploader().destroy( + fileName, + null + ) + } } diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/upload/FileUploader.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/upload/FileUploader.kt index 665c5a48..597cc938 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/upload/FileUploader.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/upload/FileUploader.kt @@ -6,6 +6,7 @@ import pt.up.fe.ni.website.backend.utils.extensions.filenameExtension abstract class FileUploader { abstract fun uploadImage(folder: String, fileName: String, image: ByteArray): String + abstract fun deleteImage(fileName: String) fun buildFileName(photoFile: MultipartFile, prefix: String = ""): String { val limitedPrefix = prefix.take(100) // File name length has a limit of 256 characters diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/upload/StaticFileUploader.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/upload/StaticFileUploader.kt index 9db9039c..ecc0df8a 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/upload/StaticFileUploader.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/upload/StaticFileUploader.kt @@ -11,4 +11,11 @@ class StaticFileUploader(private val storePath: String, private val servePath: S return "$servePath/$folder/$fileName" } + + override fun deleteImage(fileName: String) { + val storedFileName = fileName.replace(servePath, storePath) + val file = File(storedFileName) + + if (file.exists()) file.delete() + } } From 26df8a7c907ce9449c10856539ed298d98227fa0 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Fri, 18 Oct 2024 13:18:23 +0100 Subject: [PATCH 27/37] feat: serve static files on the /static/ path --- .gitignore | 3 +++ .../up/fe/ni/website/backend/controller/ErrorController.kt | 4 ++-- src/main/resources/application.properties | 7 ++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index b763203f..7abce734 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,6 @@ out/ ### VS Code ### .vscode/ + +### Static Content +static/ diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ErrorController.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ErrorController.kt index c5099fd3..6f16fab2 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ErrorController.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ErrorController.kt @@ -12,12 +12,12 @@ import org.springframework.security.access.AccessDeniedException import org.springframework.security.core.AuthenticationException import org.springframework.web.bind.MethodArgumentNotValidException import org.springframework.web.bind.annotation.ExceptionHandler -import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.ResponseStatus import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestControllerAdvice import org.springframework.web.multipart.MaxUploadSizeExceededException import org.springframework.web.multipart.support.MissingServletRequestPartException +import org.springframework.web.servlet.NoHandlerFoundException import pt.up.fe.ni.website.backend.config.Logging data class SimpleError( @@ -32,7 +32,7 @@ data class CustomError(val errors: List) @RestControllerAdvice class ErrorController(private val objectMapper: ObjectMapper) : ErrorController, Logging { - @RequestMapping("/**") + @ExceptionHandler(NoHandlerFoundException::class) @ResponseStatus(HttpStatus.NOT_FOUND) fun endpointNotFound(): CustomError = wrapSimpleError("invalid endpoint") diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 449647da..68147a33 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -32,9 +32,14 @@ upload.provider=static upload.cloudinary-base-path=website upload.cloudinary-url=GET_YOURS_AT_CLOUDINARY_DASHBOARD # Folder in which files will be stored -upload.static-path=classpath:static +upload.static-path=file:static # URL that will serve static content upload.static-serve=http://localhost:3000/static +# Files are served in the following path +spring.mvc.static-path-pattern=/static/** +# Add NoHandlerFoundException as an exception +spring.mvc.throw-exception-if-no-handler-found=true + # Cors Origin cors.allow-origin = http://localhost:3000 From 7d42ee79ad64a8eedfa97d2ab8a663bd391bd6fa Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Fri, 18 Oct 2024 14:14:00 +0100 Subject: [PATCH 28/37] tweak: change static port from 3000 to 8080 for development --- src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 68147a33..acddd643 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -34,7 +34,7 @@ upload.cloudinary-url=GET_YOURS_AT_CLOUDINARY_DASHBOARD # Folder in which files will be stored upload.static-path=file:static # URL that will serve static content -upload.static-serve=http://localhost:3000/static +upload.static-serve=http://localhost:8080/static # Files are served in the following path spring.mvc.static-path-pattern=/static/** From 0c68988ee97f6ae4eb73ce95bfc49c6f251e73da Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Fri, 18 Oct 2024 18:24:38 +0100 Subject: [PATCH 29/37] fix: add gallery list to Auth test --- .../up/fe/ni/website/backend/controller/AuthControllerTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AuthControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AuthControllerTest.kt index 5919e29e..3c739d10 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AuthControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AuthControllerTest.kt @@ -244,7 +244,7 @@ class AuthControllerTest @Autowired constructor( inner class CheckPermissions { private val testPermissions = listOf(Permission.CREATE_ACCOUNT, Permission.CREATE_ACTIVITY) private val testActivity = Project( - "Test Activity", "Test Description", mutableListOf(), mutableListOf(), "test slug", "test image", false, + "Test Activity", "Test Description", mutableListOf(), mutableListOf(), "test slug", "test image", mutableListOf(), false, emptyList(), null, "test target audience" ) private val testRole = Role("MEMBER", Permissions(testPermissions), false) From 02f45a934c3cf579c28558992f948f11796905b0 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Fri, 18 Oct 2024 18:34:36 +0100 Subject: [PATCH 30/37] lint: line too long --- .../up/fe/ni/website/backend/controller/AuthControllerTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AuthControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AuthControllerTest.kt index 3c739d10..59d4ec44 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AuthControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/AuthControllerTest.kt @@ -244,8 +244,8 @@ class AuthControllerTest @Autowired constructor( inner class CheckPermissions { private val testPermissions = listOf(Permission.CREATE_ACCOUNT, Permission.CREATE_ACTIVITY) private val testActivity = Project( - "Test Activity", "Test Description", mutableListOf(), mutableListOf(), "test slug", "test image", mutableListOf(), false, - emptyList(), null, "test target audience" + "Test Activity", "Test Description", mutableListOf(), mutableListOf(), "test slug", "test image", + mutableListOf(), false, emptyList(), null, "test target audience" ) private val testRole = Role("MEMBER", Permissions(testPermissions), false) private val testPerActivityRole = PerActivityRole(Permissions(listOf(Permission.EDIT_ACTIVITY))) From 693afa87369d86ab43b56e943546ee9da2ab79a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20=C3=81vila?= <75994325+AvilaAndre@users.noreply.github.com> Date: Wed, 6 Nov 2024 12:21:06 +0000 Subject: [PATCH 31/37] Update src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt Co-authored-by: Rita Lopes <93883163+MRita443@users.noreply.github.com> --- .../website/backend/service/activity/AbstractActivityService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt index d0ee6f18..0bc6c675 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt @@ -87,7 +87,7 @@ abstract class AbstractActivityService( val activity = getActivityById(activityId) val fileName = fileUploader.buildFileName(image, activity.title) - val imageName = fileUploader.uploadImage(imageFolder + "/gallery", fileName, image.bytes) + val imageName = fileUploader.uploadImage("$imageFolder/gallery", fileName, image.bytes) activity.gallery.add(imageName) From c796baca827df389a3a3c8799881e124edc8fc58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20=C3=81vila?= <75994325+AvilaAndre@users.noreply.github.com> Date: Wed, 6 Nov 2024 12:21:15 +0000 Subject: [PATCH 32/37] Update src/test/kotlin/pt/up/fe/ni/website/backend/utils/mockmvc/MockMvcMultipartBuilder.kt Co-authored-by: Rita Lopes <93883163+MRita443@users.noreply.github.com> --- .../utils/mockmvc/MockMvcMultipartBuilder.kt | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/mockmvc/MockMvcMultipartBuilder.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/mockmvc/MockMvcMultipartBuilder.kt index 187d8726..35525eda 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/mockmvc/MockMvcMultipartBuilder.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/mockmvc/MockMvcMultipartBuilder.kt @@ -49,16 +49,13 @@ class MockMvcMultipartBuilder( return this } - fun asDeleteMethod(): MockMvcMultipartBuilder { - multipart.with( - { - it.method = "DELETE" - it - } - ) + fun asDeleteMethod(): MockMvcMultipartBuilder { + multipart.with { + it.method = "DELETE" + it + } return this } - fun perform(): ResultActions { return mockMvc.perform(multipart) } From 0b5c2281ca16290b8ae33a53b58a62b73dd2d167 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Wed, 6 Nov 2024 12:22:13 +0000 Subject: [PATCH 33/37] fix: Added explanation to line and replaced variable --- .../backend/service/upload/CloudinaryFileUploader.kt | 1 + .../backend/controller/EventControllerTest.kt | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/upload/CloudinaryFileUploader.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/upload/CloudinaryFileUploader.kt index 0b6f60f0..9f3c066f 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/upload/CloudinaryFileUploader.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/upload/CloudinaryFileUploader.kt @@ -22,6 +22,7 @@ class CloudinaryFileUploader(private val basePath: String, private val cloudinar } override fun deleteImage(fileName: String) { + // Here, fileName is the publicId in Cloudinary, which would be the path to the file cloudinary.uploader().destroy( fileName, null diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index 584287f3..dcfd6583 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -863,9 +863,9 @@ internal class EventControllerTest @Autowired constructor( @Test fun `should fail if event does not exist`() { - val unexistentID = 5 + val nonexistentID = 5 - mockMvc.multipartBuilder("/events/$unexistentID/gallery") + mockMvc.multipartBuilder("/events/$nonexistentID/gallery") .asPutMethod() .addFile("image", contentType = MediaType.IMAGE_JPEG_VALUE) .perform() @@ -873,7 +873,7 @@ internal class EventControllerTest @Autowired constructor( status().isNotFound, content().contentType(MediaType.APPLICATION_JSON), jsonPath("$.errors.length()").value(1), - jsonPath("$.errors[0].message").value("activity not found with id $unexistentID") + jsonPath("$.errors[0].message").value("activity not found with id $nonexistentID") ) } @@ -945,9 +945,9 @@ internal class EventControllerTest @Autowired constructor( @Test fun `should fail if event does not exist`() { - val unexistentID = 5 + val nonexistentID = 5 - mockMvc.multipartBuilder("/events/$unexistentID/gallery") + mockMvc.multipartBuilder("/events/$nonexistentID/gallery") .asDeleteMethod() .addPart("imageUrl", mockImageUrl) .perform() @@ -955,7 +955,7 @@ internal class EventControllerTest @Autowired constructor( status().isNotFound, content().contentType(MediaType.APPLICATION_JSON), jsonPath("$.errors.length()").value(1), - jsonPath("$.errors[0].message").value("activity not found with id $unexistentID") + jsonPath("$.errors[0].message").value("activity not found with id $nonexistentID") ) } From 2735fdc5dff72df9f28ae8b741b90912fe3cf8b9 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Wed, 6 Nov 2024 12:28:32 +0000 Subject: [PATCH 34/37] lint: missing space --- .../ni/website/backend/utils/mockmvc/MockMvcMultipartBuilder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/mockmvc/MockMvcMultipartBuilder.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/mockmvc/MockMvcMultipartBuilder.kt index 35525eda..f8f3d635 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/mockmvc/MockMvcMultipartBuilder.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/mockmvc/MockMvcMultipartBuilder.kt @@ -49,7 +49,7 @@ class MockMvcMultipartBuilder( return this } - fun asDeleteMethod(): MockMvcMultipartBuilder { + fun asDeleteMethod(): MockMvcMultipartBuilder { multipart.with { it.method = "DELETE" it From 12d6616195b689aa1b6ac6de327734b650bae941 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Wed, 6 Nov 2024 13:06:39 +0000 Subject: [PATCH 35/37] docs: started documentation while #144 is not done --- .../backend/controller/EventControllerTest.kt | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index dcfd6583..413baed9 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -835,6 +835,10 @@ internal class EventControllerTest @Autowired constructor( repository.save(testEvent) } + private val parameters = listOf( + parameterWithName("idEvent").description("The id of the event being targeted") + ) + @Test fun `should add an image`() { val expectedImagePath = "${uploadConfigProperties.staticServe}/events/gallery/${testEvent.title}-$uuid.jpeg" @@ -859,6 +863,14 @@ internal class EventControllerTest @Autowired constructor( jsonPath("$.slug").value(testEvent.slug), jsonPath("$.image").value(testEvent.image) ) + /* + .andDocument( + documentation, + "Adds an image to the gallery of the selected event", + "This endpoint adds an image to the gallery of an event, returning the event's updated data.", + urlParameters = parameters + ) + */ } @Test @@ -874,7 +886,7 @@ internal class EventControllerTest @Autowired constructor( content().contentType(MediaType.APPLICATION_JSON), jsonPath("$.errors.length()").value(1), jsonPath("$.errors[0].message").value("activity not found with id $nonexistentID") - ) + ).andDocumentErrorResponse(documentation) } @Test @@ -888,7 +900,7 @@ internal class EventControllerTest @Autowired constructor( content().contentType(MediaType.APPLICATION_JSON), jsonPath("$.errors.length()").value(1), jsonPath("$.errors[0].message").value("invalid image type (png, jpg, jpeg or webp)") - ) + ).andDocumentErrorResponse(documentation) } } @@ -920,6 +932,10 @@ internal class EventControllerTest @Autowired constructor( repository.save(testEvent) } + private val parameters = listOf( + parameterWithName("idEvent").description("The id of the event being targeted") + ) + @Test fun `should remove an image`() { mockMvc.multipartBuilder("/events/${testEvent.id}/gallery") @@ -941,6 +957,14 @@ internal class EventControllerTest @Autowired constructor( jsonPath("$.slug").value(testEvent.slug), jsonPath("$.image").value(testEvent.image) ) + /* + .andDocument( + documentation, + "Removes an image from the gallery of a selected event", + "This endpoint removes an image to the gallery of an event, returning the event's updated data.", + urlParameters = parameters + ) + */ } @Test @@ -956,7 +980,7 @@ internal class EventControllerTest @Autowired constructor( content().contentType(MediaType.APPLICATION_JSON), jsonPath("$.errors.length()").value(1), jsonPath("$.errors[0].message").value("activity not found with id $nonexistentID") - ) + ).andDocumentErrorResponse(documentation) } @Test @@ -972,7 +996,7 @@ internal class EventControllerTest @Autowired constructor( content().contentType(MediaType.APPLICATION_JSON), jsonPath("$.errors.length()").value(1), jsonPath("$.errors[0].message").value("file not found with name $wrongImageUrl") - ) + ).andDocumentErrorResponse(documentation) } } From 2322b148c7f36738c8f738e768f0bcd14e052508 Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Wed, 6 Nov 2024 16:04:44 +0000 Subject: [PATCH 36/37] test: added gallery tests to Project --- .../backend/controller/ProjectController.kt | 14 ++ .../backend/controller/EventControllerTest.kt | 3 +- .../controller/ProjectControllerTest.kt | 192 ++++++++++++++++++ 3 files changed, 208 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ProjectController.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ProjectController.kt index ab4a2c06..71014234 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ProjectController.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/controller/ProjectController.kt @@ -88,4 +88,18 @@ class ProjectController(private val service: ProjectService) { @PathVariable idProject: Long, @PathVariable idAccount: Long ) = service.removeHallOfFameMemberById(idProject, idAccount) + + @PutMapping("/{idProject}/gallery", consumes = ["multipart/form-data"]) + fun addGalleryImage( + @PathVariable idProject: Long, + @RequestParam + @ValidImage + image: MultipartFile + ) = service.addGalleryImage(idProject, image, ProjectService.IMAGE_FOLDER) + + @DeleteMapping("/{idProject}/gallery") + fun removeGalleryImage( + @PathVariable idProject: Long, + @RequestPart imageUrl: String + ) = service.removeGalleryImage(idProject, imageUrl) } diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index 413baed9..3c0a8411 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -985,7 +985,8 @@ internal class EventControllerTest @Autowired constructor( @Test fun `should fail if image does not exist`() { - val wrongImageUrl = "${uploadConfigProperties.staticServe}/gallery/Another${testEvent.title}-$uuid.jpeg" + val wrongImageUrl = + "${uploadConfigProperties.staticServe}/gallery/events/Another${testEvent.title}-$uuid.jpeg" mockMvc.multipartBuilder("/events/${testEvent.id}/gallery") .asDeleteMethod() diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt index 4ef2b38a..7d54062a 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt @@ -1561,6 +1561,198 @@ internal class ProjectControllerTest @Autowired constructor( } } + @NestedTest + @DisplayName("PUT /projects/{idProject}/gallery") + inner class AddGalleryImage { + + private val uuid: UUID = UUID.randomUUID() + private val mockedSettings = Mockito.mockStatic(UUID::class.java) + + @BeforeAll + fun setupMocks() { + Mockito.`when`(UUID.randomUUID()).thenReturn(uuid) + } + + @AfterAll + fun cleanup() { + mockedSettings.close() + } + + @BeforeEach + fun addToRepositories() { + accountRepository.save(testAccount) + repository.save(testProject) + } + + private val parameters = listOf( + parameterWithName("idProject").description("The id of the project being targeted") + ) + + @Test + fun `should add an image`() { + val expectedImagePath = + "${uploadConfigProperties.staticServe}/projects/gallery/${testProject.title}-$uuid.jpeg" + + mockMvc.multipartBuilder("/projects/${testProject.id}/gallery") + .asPutMethod() + .addFile("image", contentType = MediaType.IMAGE_JPEG_VALUE) + .perform() + .andExpectAll( + status().isOk, + content().contentType(MediaType.APPLICATION_JSON), + jsonPath("$.title").value(testProject.title), + jsonPath("$.description").value(testProject.description), + jsonPath("$.teamMembers.length()").value(testProject.teamMembers.size), + jsonPath("$.isArchived").value(testProject.isArchived), + jsonPath("$.slug").value(testProject.slug), + jsonPath("$.slogan").value(testProject.slogan), + jsonPath("$.targetAudience").value(testProject.targetAudience), + jsonPath("$.github").value(testProject.github), + jsonPath("$.links.length()").value(testProject.links.size), + jsonPath("$.timeline.length()").value(testProject.timeline.size), + jsonPath("$.image").value(testProject.image), + jsonPath("$.gallery.length()").value(1), + jsonPath("$.gallery[0]").value(expectedImagePath) + ) + /* + .andDocument( + documentation, + "Adds an image to the gallery of the selected project", + "This endpoint adds an image to the gallery of an project, returning the project's updated data.", + urlParameters = parameters + ) + */ + } + + @Test + fun `should fail if project does not exist`() { + val nonexistentID = 5 + + mockMvc.multipartBuilder("/projects/$nonexistentID/gallery") + .asPutMethod() + .addFile("image", contentType = MediaType.IMAGE_JPEG_VALUE) + .perform() + .andExpectAll( + status().isNotFound, + content().contentType(MediaType.APPLICATION_JSON), + jsonPath("$.errors.length()").value(1), + jsonPath("$.errors[0].message").value("activity not found with id $nonexistentID") + ).andDocumentErrorResponse(documentation) + } + + @Test + fun `should fail if image in wrong format`() { + mockMvc.multipartBuilder("/projects/${testProject.id}/gallery") + .asPutMethod() + .addFile("image", filename = "image.gif", contentType = MediaType.IMAGE_JPEG_VALUE) + .perform() + .andExpectAll( + status().isBadRequest, + content().contentType(MediaType.APPLICATION_JSON), + jsonPath("$.errors.length()").value(1), + jsonPath("$.errors[0].message").value("invalid image type (png, jpg, jpeg or webp)") + ).andDocumentErrorResponse(documentation) + } + } + + @NestedTest + @DisplayName("DELETE /projects/{idProject}/gallery") + inner class RemoveGalleryImage { + + private val uuid: UUID = UUID.randomUUID() + private val mockedSettings = Mockito.mockStatic(UUID::class.java) + private val mockImageUrl = + "${uploadConfigProperties.staticServe}/projects/gallery/${testProject.title}-$uuid.jpeg" + + @BeforeAll + fun setupMocks() { + Mockito.`when`(UUID.randomUUID()).thenReturn(uuid) + + testProject.gallery.add(mockImageUrl) + } + + @AfterAll + fun cleanup() { + mockedSettings.close() + } + + @BeforeEach + fun addToRepositories() { + accountRepository.save(testAccount) + + repository.save(testProject) + } + + private val parameters = listOf( + parameterWithName("idProject").description("The id of the project being targeted") + ) + + @Test + fun `should remove an image`() { + mockMvc.multipartBuilder("/projects/${testProject.id}/gallery") + .asDeleteMethod() + .addPart("imageUrl", mockImageUrl) + .perform() + .andExpectAll( + status().isOk, + content().contentType(MediaType.APPLICATION_JSON), + jsonPath("$.title").value(testProject.title), + jsonPath("$.description").value(testProject.description), + jsonPath("$.teamMembers.length()").value(testProject.teamMembers.size), + jsonPath("$.isArchived").value(testProject.isArchived), + jsonPath("$.slug").value(testProject.slug), + jsonPath("$.slogan").value(testProject.slogan), + jsonPath("$.targetAudience").value(testProject.targetAudience), + jsonPath("$.github").value(testProject.github), + jsonPath("$.links.length()").value(testProject.links.size), + jsonPath("$.timeline.length()").value(testProject.timeline.size), + jsonPath("$.image").value(testProject.image), + jsonPath("$.gallery.length()").value(0) + ) + /* + .andDocument( + documentation, + "Removes an image from the gallery of a selected project", + "This endpoint removes an image to the gallery of a project, returning the project's updated data.", + urlParameters = parameters + ) + */ + } + + @Test + fun `should fail if project does not exist`() { + val nonexistentID = 5 + + mockMvc.multipartBuilder("/projects/$nonexistentID/gallery") + .asDeleteMethod() + .addPart("imageUrl", mockImageUrl) + .perform() + .andExpectAll( + status().isNotFound, + content().contentType(MediaType.APPLICATION_JSON), + jsonPath("$.errors.length()").value(1), + jsonPath("$.errors[0].message").value("activity not found with id $nonexistentID") + ).andDocumentErrorResponse(documentation) + } + + @Test + fun `should fail if image does not exist`() { + val wrongImageUrl = + "${uploadConfigProperties.staticServe}/gallery/projects/Another${testProject.title}-$uuid.jpeg" + + mockMvc.multipartBuilder("/projects/${testProject.id}/gallery") + .asDeleteMethod() + .addPart("imageUrl", wrongImageUrl) + .perform() + .andExpectAll( + status().isNotFound, + content().contentType(MediaType.APPLICATION_JSON), + jsonPath("$.errors.length()").value(1), + jsonPath("$.errors[0].message").value("file not found with name $wrongImageUrl") + ).andDocumentErrorResponse(documentation) + } + } + fun Date?.toJson(): String { val quotedDate = objectMapper.writeValueAsString(this) // objectMapper adds quotes to the date, so remove them From aeef473168d7379d8bbdbef8a545959cbfdb087d Mon Sep 17 00:00:00 2001 From: AvilaAndre Date: Thu, 7 Nov 2024 19:58:51 +0000 Subject: [PATCH 37/37] refactor: make slug non optional --- .../website/backend/dto/entity/ActivityDto.kt | 2 +- .../ni/website/backend/dto/entity/EventDto.kt | 2 +- .../ni/website/backend/dto/entity/PostDto.kt | 2 +- .../website/backend/dto/entity/ProjectDto.kt | 2 +- .../fe/ni/website/backend/model/Activity.kt | 2 +- .../up/fe/ni/website/backend/model/Event.kt | 2 +- .../pt/up/fe/ni/website/backend/model/Post.kt | 2 +- .../up/fe/ni/website/backend/model/Project.kt | 2 +- .../backend/repository/ActivityRepository.kt | 2 +- .../backend/repository/PostRepository.kt | 2 +- .../activity/AbstractActivityService.kt | 6 +-- .../backend/controller/EventControllerTest.kt | 41 +++++++++++++++---- .../controller/GenerationControllerTest.kt | 2 + .../backend/controller/PostControllerTest.kt | 23 ++++++++--- .../controller/ProjectControllerTest.kt | 22 +++++++--- .../backend/controller/RoleControllerTest.kt | 9 +++- .../ni/website/backend/model/AccountTest.kt | 1 + .../payloadschemas/model/PayloadActivity.kt | 5 +++ .../payloadschemas/model/PayloadEvent.kt | 3 +- .../payloadschemas/model/PayloadPost.kt | 3 +- .../payloadschemas/model/PayloadProject.kt | 3 +- 21 files changed, 98 insertions(+), 40 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/dto/entity/ActivityDto.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/dto/entity/ActivityDto.kt index c9d34c29..8b1c6325 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/dto/entity/ActivityDto.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/dto/entity/ActivityDto.kt @@ -8,7 +8,7 @@ abstract class ActivityDto( val title: String, val description: String, val teamMembersIds: List?, - val slug: String?, + val slug: String, var image: String?, @JsonIgnore var imageFile: MultipartFile? = null diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/dto/entity/EventDto.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/dto/entity/EventDto.kt index 19fe4673..3edca148 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/dto/entity/EventDto.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/dto/entity/EventDto.kt @@ -7,7 +7,7 @@ class EventDto( title: String, description: String, teamMembersIds: List?, - slug: String?, + slug: String, image: String?, val registerUrl: String?, diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/dto/entity/PostDto.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/dto/entity/PostDto.kt index bbc32568..0a03f542 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/dto/entity/PostDto.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/dto/entity/PostDto.kt @@ -6,5 +6,5 @@ class PostDto( val title: String, val body: String, val thumbnailPath: String, - val slug: String? + val slug: String ) : EntityDto() diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/dto/entity/ProjectDto.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/dto/entity/ProjectDto.kt index bebe2626..c2230206 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/dto/entity/ProjectDto.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/dto/entity/ProjectDto.kt @@ -6,7 +6,7 @@ class ProjectDto( title: String, description: String, teamMembersIds: List?, - slug: String?, + slug: String, image: String?, val isArchived: Boolean = false, diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt index d7884b45..d8e50b6f 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Activity.kt @@ -39,7 +39,7 @@ abstract class Activity( @Column(unique = true) @field:Size(min = Constants.Slug.minSize, max = Constants.Slug.maxSize) - open val slug: String? = null, + open val slug: String, @field:NotBlank open var image: String, diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Event.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Event.kt index b7191a74..0e548594 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Event.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Event.kt @@ -15,7 +15,7 @@ class Event( description: String, teamMembers: MutableList = mutableListOf(), associatedRoles: MutableList = mutableListOf(), - slug: String? = null, + slug: String, image: String, gallery: MutableList = mutableListOf(), diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Post.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Post.kt index 58be5866..4cab516f 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Post.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Post.kt @@ -44,5 +44,5 @@ class Post( @Column(unique = true) @field:Size(min = Constants.Slug.minSize, max = Constants.Slug.maxSize) - val slug: String? = null + val slug: String ) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Project.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Project.kt index f282f190..879b2c03 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/model/Project.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/model/Project.kt @@ -18,7 +18,7 @@ class Project( description: String, teamMembers: MutableList = mutableListOf(), associatedRoles: MutableList = mutableListOf(), - slug: String? = null, + slug: String, image: String, gallery: MutableList = mutableListOf(), diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/repository/ActivityRepository.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/repository/ActivityRepository.kt index 370f356c..6c5bd1c9 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/repository/ActivityRepository.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/repository/ActivityRepository.kt @@ -6,5 +6,5 @@ import pt.up.fe.ni.website.backend.model.Activity @Repository interface ActivityRepository : CrudRepository { - fun findBySlug(slug: String?): T? + fun findBySlug(slug: String): T? } diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/repository/PostRepository.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/repository/PostRepository.kt index 6bbb9138..846544f8 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/repository/PostRepository.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/repository/PostRepository.kt @@ -6,5 +6,5 @@ import pt.up.fe.ni.website.backend.model.Post @Repository interface PostRepository : JpaRepository { - fun findBySlug(slug: String?): Post? + fun findBySlug(slug: String): Post? } diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt index 0bc6c675..549b22db 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/activity/AbstractActivityService.kt @@ -27,7 +27,7 @@ abstract class AbstractActivityService( } dto.imageFile?.let { - val fileName = fileUploader.buildFileName(it, dto.title) + val fileName = fileUploader.buildFileName(it, dto.slug) dto.image = fileUploader.uploadImage(imageFolder, fileName, it.bytes) } @@ -52,7 +52,7 @@ abstract class AbstractActivityService( if (imageFile == null) { dto.image = activity.image } else { - val fileName = fileUploader.buildFileName(imageFile, dto.title) + val fileName = fileUploader.buildFileName(imageFile, dto.slug) dto.image = fileUploader.uploadImage(imageFolder, fileName, imageFile.bytes) } @@ -86,7 +86,7 @@ abstract class AbstractActivityService( fun addGalleryImage(activityId: Long, image: MultipartFile, imageFolder: String): Activity { val activity = getActivityById(activityId) - val fileName = fileUploader.buildFileName(image, activity.title) + val fileName = fileUploader.buildFileName(image, activity.slug) val imageName = fileUploader.uploadImage("$imageFolder/gallery", fileName, image.bytes) activity.gallery.add(imageName) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt index 3c0a8411..abc73b4c 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/EventControllerTest.kt @@ -284,7 +284,7 @@ internal class EventControllerTest @Autowired constructor( "This event was a failure", mutableListOf(testAccount), mutableListOf(), - null, + "bad-event", "bad-image.png", mutableListOf(), null, @@ -300,7 +300,7 @@ internal class EventControllerTest @Autowired constructor( "This event was ok", mutableListOf(), mutableListOf(), - null, + "mid-event", "mid-image.png", mutableListOf(), null, @@ -316,7 +316,7 @@ internal class EventControllerTest @Autowired constructor( "This event was a awesome", mutableListOf(testAccount), mutableListOf(), - null, + "cool-event", "cool-image.png", mutableListOf(), null, @@ -362,7 +362,7 @@ internal class EventControllerTest @Autowired constructor( inner class CreateEvent { private val uuid: UUID = UUID.randomUUID() private val mockedSettings = Mockito.mockStatic(UUID::class.java) - private val expectedImagePath = "${uploadConfigProperties.staticServe}/events/${testEvent.title}-$uuid.jpeg" + private val expectedImagePath = "${uploadConfigProperties.staticServe}/events/${testEvent.slug}-$uuid.jpeg" @BeforeEach fun addAccount() { @@ -841,7 +841,7 @@ internal class EventControllerTest @Autowired constructor( @Test fun `should add an image`() { - val expectedImagePath = "${uploadConfigProperties.staticServe}/events/gallery/${testEvent.title}-$uuid.jpeg" + val expectedImagePath = "${uploadConfigProperties.staticServe}/events/gallery/${testEvent.slug}-$uuid.jpeg" mockMvc.multipartBuilder("/events/${testEvent.id}/gallery") .asPutMethod() @@ -1108,7 +1108,7 @@ internal class EventControllerTest @Autowired constructor( @Test fun `should update the event with same slug`() { - eventPart["slug"] = testEvent.slug!! + eventPart["slug"] = testEvent.slug mockMvc.multipartBuilder("/events/${testEvent.id}") .asPutMethod() .addPart("event", objectMapper.writeValueAsString(eventPart)) @@ -1162,7 +1162,7 @@ internal class EventControllerTest @Autowired constructor( @Test fun `should update the event with image`() { - val expectedImagePath = "${uploadConfigProperties.staticServe}/events/$newTitle-$uuid.jpeg" + val expectedImagePath = "${uploadConfigProperties.staticServe}/events/$newSlug-$uuid.jpeg" mockMvc.multipartBuilder("/events/${testEvent.id}") .asPutMethod() @@ -1204,11 +1204,12 @@ internal class EventControllerTest @Autowired constructor( } @Test - fun `should fail if the event does not exist`() { + fun`should fail if the event does not exist`() { val eventPart = objectMapper.writeValueAsString( mapOf( "title" to "New Title", "description" to "New Description", + "slug" to "new-slug", "dateInterval" to DateInterval(TestUtils.createDate(2022, Calendar.DECEMBER, 1), null), "associatedRoles" to testEvent.associatedRoles ) @@ -1289,6 +1290,7 @@ internal class EventControllerTest @Autowired constructor( requiredFields = mapOf( "title" to testEvent.title, "description" to testEvent.description, + "slug" to testEvent.slug, "dateInterval" to testEvent.dateInterval, "image" to testEvent.image ) @@ -1340,6 +1342,29 @@ internal class EventControllerTest @Autowired constructor( ) } + @NestedTest + @DisplayName("slug") + inner class SlugValidation { + @BeforeAll + fun setParam() { + validationTester.param = "slug" + } + + @Test + fun `should be required`() = validationTester.isRequired() + + @Test + @DisplayName( + "size should be between ${ActivityConstants.Slug.minSize}" + + " and ${ActivityConstants.Slug.maxSize}()" + ) + fun size() = + validationTester.hasSizeBetween( + ActivityConstants.Slug.minSize, + ActivityConstants.Slug.maxSize + ) + } + @NestedTest @DisplayName("registerUrl") inner class UrlValidation { diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/GenerationControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/GenerationControllerTest.kt index f047fea9..06c8bfba 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/GenerationControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/GenerationControllerTest.kt @@ -1059,6 +1059,7 @@ class GenerationControllerTest @Autowired constructor( Project( "NIJobs", "cool project", + slug = "ni-jobs", image = "cool-image.png", targetAudience = "students", github = "https://github.com/NIAEFEUP/nijobs-be", @@ -1082,6 +1083,7 @@ class GenerationControllerTest @Autowired constructor( Event( title = "SINF", description = "cool event", + slug = "sinf", dateInterval = DateInterval(TestUtils.createDate(2023, 9, 10)), location = null, category = null, diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/PostControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/PostControllerTest.kt index a21edf3e..9021f56d 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/PostControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/PostControllerTest.kt @@ -56,7 +56,8 @@ internal class PostControllerTest @Autowired constructor( Post( "NIAEFEUP gets a new president", "New president promised to buy new chairs", - "https://thumbnails/pres.png" + "https://thumbnails/pres.png", + slug = "new-president" ) ) @@ -255,6 +256,7 @@ internal class PostControllerTest @Autowired constructor( requiredFields = mapOf( "title" to testPost.title, "body" to testPost.body, + "slug" to testPost.slug, "thumbnailPath" to testPost.thumbnailPath ) ) @@ -314,6 +316,9 @@ internal class PostControllerTest @Autowired constructor( validationTester.param = "slug" } + @Test + fun `should be required`() = validationTester.isRequired() + @Test @DisplayName("size should be between ${Constants.Slug.minSize} and ${Constants.Slug.maxSize}()") fun size() = validationTester.hasSizeBetween(Constants.Slug.minSize, Constants.Slug.maxSize) @@ -392,7 +397,7 @@ internal class PostControllerTest @Autowired constructor( ) @Test - fun `should update the post without the slug`() { + fun `should update the post without changing the slug`() { val newTitle = "New Title" val newBody = "New Body of the post" val newThumbnailPath = "https://thumbnails/new.png" @@ -405,7 +410,8 @@ internal class PostControllerTest @Autowired constructor( mapOf( "title" to newTitle, "body" to newBody, - "thumbnailPath" to newThumbnailPath + "thumbnailPath" to newThumbnailPath, + "slug" to testPost.slug ) ) ) @@ -436,7 +442,7 @@ internal class PostControllerTest @Autowired constructor( } @Test - fun `should update the post with the slug`() { + fun `should update the post changing the slug`() { val newTitle = "New Title" val newBody = "New Body of the post" val newThumbnailPath = "https://thumbnails/new.png" @@ -491,7 +497,8 @@ internal class PostControllerTest @Autowired constructor( mapOf( "title" to "New Title", "body" to "New Body of the post", - "thumbnailPath" to "thumbnails/new.png" + "thumbnailPath" to "thumbnails/new.png", + "slug" to "new-slug" ) ) ) @@ -562,7 +569,8 @@ internal class PostControllerTest @Autowired constructor( requiredFields = mapOf( "title" to testPost.title, "body" to testPost.body, - "thumbnailPath" to testPost.thumbnailPath + "thumbnailPath" to testPost.thumbnailPath, + "slug" to testPost.slug ) ) @@ -621,6 +629,9 @@ internal class PostControllerTest @Autowired constructor( validationTester.param = "slug" } + @Test + fun `should be required`() = validationTester.isRequired() + @Test @DisplayName("size should be between ${Constants.Slug.minSize} and ${Constants.Slug.maxSize}()") fun size() = validationTester.hasSizeBetween(Constants.Slug.minSize, Constants.Slug.maxSize) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt index 7d54062a..8179301a 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/ProjectControllerTest.kt @@ -114,7 +114,7 @@ internal class ProjectControllerTest @Autowired constructor( "Job platform for students", mutableListOf(), mutableListOf(), - null, + "ni-jobs", "cool-image.png", mutableListOf(), false, @@ -280,7 +280,7 @@ internal class ProjectControllerTest @Autowired constructor( inner class CreateProject { private val uuid: UUID = UUID.randomUUID() private val mockedSettings = Mockito.mockStatic(UUID::class.java) - private val expectedImagePath = "${uploadConfigProperties.staticServe}/projects/${testProject.title}-$uuid.jpeg" + private val expectedImagePath = "${uploadConfigProperties.staticServe}/projects/${testProject.slug}-$uuid.jpeg" @BeforeEach fun addToRepositories() { @@ -450,6 +450,7 @@ internal class ProjectControllerTest @Autowired constructor( requiredFields = mapOf( "title" to testProject.title, "description" to testProject.description, + "slug" to testProject.slug, "targetAudience" to testProject.targetAudience ) ) @@ -504,6 +505,9 @@ internal class ProjectControllerTest @Autowired constructor( validationTester.param = "slug" } + @Test + fun `should be required`() = validationTester.isRequired() + @Test @DisplayName( "size should be between ${ActivityConstants.Slug.minSize} and ${ActivityConstants.Slug.maxSize}()" @@ -757,7 +761,7 @@ internal class ProjectControllerTest @Autowired constructor( @Test fun `should update the project with the same slug`() { - projectPart["slug"] = testProject.slug!! + projectPart["slug"] = testProject.slug mockMvc.multipartBuilder("/projects/${testProject.id}") .asPutMethod() .addPart("project", objectMapper.writeValueAsString(projectPart)) @@ -828,7 +832,7 @@ internal class ProjectControllerTest @Autowired constructor( @Test fun `should update the project with image`() { - val expectedImagePath = "${uploadConfigProperties.staticServe}/projects/$newTitle-$uuid.jpeg" + val expectedImagePath = "${uploadConfigProperties.staticServe}/projects/$newSlug-$uuid.jpeg" mockMvc.multipartBuilder("/projects/${testProject.id}") .asPutMethod() @@ -933,6 +937,7 @@ internal class ProjectControllerTest @Autowired constructor( requiredFields = mapOf( "title" to testProject.title, "description" to testProject.description, + "slug" to testProject.slug, "targetAudience" to testProject.targetAudience ) ) @@ -987,6 +992,9 @@ internal class ProjectControllerTest @Autowired constructor( validationTester.param = "slug" } + @Test + fun `should be required`() = validationTester.isRequired() + @Test @DisplayName( "size should be between ${ActivityConstants.Slug.minSize} and ${ActivityConstants.Slug.maxSize}()" @@ -1050,6 +1058,7 @@ internal class ProjectControllerTest @Autowired constructor( mapOf( "title" to testProject.title, "description" to testProject.description, + "slug" to testProject.slug, "targetAudience" to testProject.targetAudience, "links" to listOf(params) ) @@ -1142,6 +1151,7 @@ internal class ProjectControllerTest @Autowired constructor( mapOf( "title" to testProject.title, "description" to testProject.description, + "slug" to testProject.slug, "targetAudience" to testProject.targetAudience, "timeline" to listOf(params) ) @@ -1248,7 +1258,7 @@ internal class ProjectControllerTest @Autowired constructor( "very cool project", mutableListOf(), mutableListOf(), - null, + "proj1", "cool-image.png", mutableListOf(), true, @@ -1591,7 +1601,7 @@ internal class ProjectControllerTest @Autowired constructor( @Test fun `should add an image`() { val expectedImagePath = - "${uploadConfigProperties.staticServe}/projects/gallery/${testProject.title}-$uuid.jpeg" + "${uploadConfigProperties.staticServe}/projects/gallery/${testProject.slug}-$uuid.jpeg" mockMvc.multipartBuilder("/projects/${testProject.id}/gallery") .asPutMethod() diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/RoleControllerTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/RoleControllerTest.kt index 41ec65d7..a71c15b4 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/controller/RoleControllerTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/controller/RoleControllerTest.kt @@ -76,6 +76,7 @@ internal class RoleControllerTest @Autowired constructor( val project = Project( "UNI", "Melhor app", + slug = "uni-app", image = "image.png", targetAudience = "Estudantes" ) @@ -738,7 +739,13 @@ internal class RoleControllerTest @Autowired constructor( @BeforeEach fun addAll() { - project = Project("test project", "test", image = "image.png", targetAudience = "estudantes") + project = Project( + "test project", + "test", + slug = "test-proj", + image = "image.png", + targetAudience = "estudantes" + ) roleRepository.save(testRole) projectRepository.save(project) val perActivityRole = PerActivityRole(Permissions(listOf(Permission.EDIT_ACTIVITY))) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/model/AccountTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/model/AccountTest.kt index dd2967d7..252e98d7 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/model/AccountTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/model/AccountTest.kt @@ -32,6 +32,7 @@ class AccountTest { val websiteProject = Project( "NI Website", "NI's website is where everything about NI is shown to the public", + slug = "ni-website", image = "cool-image.jpg", targetAudience = "Everyone" diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadActivity.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadActivity.kt index cf67bc0d..d1b594e6 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadActivity.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadActivity.kt @@ -13,6 +13,11 @@ class PayloadActivity { DocumentedJSONField("id", "Id of the activity", JsonFieldType.NUMBER), DocumentedJSONField("title", "Title of the activity", JsonFieldType.STRING), DocumentedJSONField("description", "Description of the activity", JsonFieldType.STRING), + DocumentedJSONField( + "slug", + "Short and friendly textual post identifier", + JsonFieldType.STRING + ), DocumentedJSONField( "hallOfFame", "Array of members that were once associated with the project", diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadEvent.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadEvent.kt index 0a46faae..1a6e4527 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadEvent.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadEvent.kt @@ -20,8 +20,7 @@ class PayloadEvent : ModelDocumentation( DocumentedJSONField( "slug", "Short and friendly textual event identifier", - JsonFieldType.STRING, - optional = true + JsonFieldType.STRING ), DocumentedJSONField("id", "Event ID", JsonFieldType.NUMBER, isInRequest = false), DocumentedJSONField( diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadPost.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadPost.kt index a7e45775..702dbc0f 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadPost.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadPost.kt @@ -15,8 +15,7 @@ class PayloadPost : ModelDocumentation( DocumentedJSONField( "slug", "Short and friendly textual post identifier", - JsonFieldType.STRING, - optional = true + JsonFieldType.STRING ), DocumentedJSONField("id", "Post ID", JsonFieldType.NUMBER, isInRequest = false), DocumentedJSONField( diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadProject.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadProject.kt index bd884d89..8a7d0a82 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadProject.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/utils/documentation/payloadschemas/model/PayloadProject.kt @@ -23,8 +23,7 @@ class PayloadProject : ModelDocumentation( DocumentedJSONField( "slug", "Short and friendly textual event identifier", - JsonFieldType.STRING, - optional = true + JsonFieldType.STRING ), DocumentedJSONField( "image",