From 3d57ecb01ad94c9fac726bc86063fc05cd62fcc9 Mon Sep 17 00:00:00 2001 From: MAILLAL Anas Date: Tue, 29 Apr 2025 13:59:05 +0200 Subject: [PATCH 01/10] fix: fixed some sectionCell fetching logic (previously wasn't grouping by idReference), found out that idRefrence in sectionCell is always being incremented at inserion still looking for a fix --- .../myinpulse/service/SharedApiService.java | 2 +- .../service/database/SectionCellService.java | 35 ++++ .../myinpulse/SharedApiServiceTest.java | 186 +++++++++++------- 3 files changed, 156 insertions(+), 67 deletions(-) diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/SharedApiService.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/SharedApiService.java index 769a6cc..c899534 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/SharedApiService.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/SharedApiService.java @@ -79,7 +79,7 @@ public class SharedApiService { LocalDateTime dateTime = LocalDateTime.parse(date, formatter); Project project = this.projectService.getProjectById(projectId); - return this.sectionCellService.getSectionCellsByProjectAndSectionIdBeforeDate( + return this.sectionCellService.getLatestSectionCellsByIdReferenceBeforeDate( project, sectionId, dateTime); } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/database/SectionCellService.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/database/SectionCellService.java index 843bd0b..d1a444d 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/database/SectionCellService.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/database/SectionCellService.java @@ -14,7 +14,9 @@ import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; @Service @@ -132,4 +134,37 @@ public class SectionCellService { return sectionCellRepository.findByProjectSectionCellAndSectionIdAndModificationDateBefore( project, sectionId, date); } + + public Iterable getLatestSectionCellsByIdReferenceBeforeDate( + Project project, long sectionId, LocalDateTime date) { + + // 1. Fetch ALL relevant SectionCells modified before the date + Iterable allMatchingCells = + sectionCellRepository.findByProjectSectionCellAndSectionIdAndModificationDateBefore( + project, sectionId, date); + + // 2. Find the latest for each idReference + Map latestCellsByIdReference = new HashMap<>(); + + for (SectionCell cell : allMatchingCells) { + Long idReference = cell.getIdReference(); + + // Check if we've seen this idReference before + if (latestCellsByIdReference.containsKey(idReference)) { + // If yes, compare modification dates + SectionCell existingLatest = latestCellsByIdReference.get(idReference); + + // If the current cell is more recent, update the map + if (cell.getModificationDate().isAfter(existingLatest.getModificationDate())) { + latestCellsByIdReference.put(idReference, cell); + } + } else { + // If this is the first time we see this idReference, add it to the map + latestCellsByIdReference.put(idReference, cell); + } + } + + // 3. Return the collection of the latest cells (the values from the map) + return latestCellsByIdReference.values(); + } } diff --git a/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java b/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java index 22c125b..3dc5b77 100644 --- a/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java +++ b/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java @@ -123,20 +123,6 @@ public class SharedApiServiceTest { when(mockUtilsService.isAllowedToCheckProject(eq(staticUnauthorizedMail), anyLong())) .thenReturn(false); // Unauthorized entrepreneur NOT allowed for ANY project ID by // default - - // Add more specific mock setups here if needed for entrepreneur tests - // E.g., If you have a test specifically for an entrepreneur accessing THEIR project: - // Entrepreneur testEntrepreneur = - // entrepreneurService.addEntrepreneur(getTestEntrepreneur("specific_linked_entrepreneur")); - // Project linkedProject = - // projectService.addNewProject(getTestProject("specific_linked_project", - // staticAuthorizedAdmin)); - // // Link testEntrepreneur to linkedProject in the database setup... - // when(mockUtilsService.isAllowedToCheckProject(eq(testEntrepreneur.getPrimaryMail()), - // eq(linkedProject.getIdProject()))).thenReturn(true); - // when(mockUtilsService.isAllowedToCheckProject(eq(testEntrepreneur.getPrimaryMail()), - // anyLong())).thenReturn(false); // Deny for other projects - } // --- Helper Methods (Can remain non-static or static as needed) --- @@ -176,6 +162,17 @@ public class SharedApiServiceTest { return sectionCell; } + private static SectionCell getTestSectionCell( + Project project, Long sectionId, String content, LocalDateTime date, Long refrenceId) { + SectionCell sectionCell = new SectionCell(); + sectionCell.setProjectSectionCell(project); + sectionCell.setSectionId(sectionId); + sectionCell.setContentSectionCell(content); + sectionCell.setModificationDate(date); + sectionCell.setIdReference(refrenceId); + return sectionCell; + } + private static Appointment getTestAppointment( LocalDate date, LocalTime time, @@ -307,6 +304,115 @@ public class SharedApiServiceTest { assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatusCode()); } + /* + * Tests retrieving section cells for a specific project and section ID before a given date + * when the user is authorized and matching cells exist. + * Verifies that only the correct cells are returned. + */ + /*@Test*/ + // Commenting out failing test + void testGetSectionCells_Authorized_Found() { + Long targetSectionId = 1L; + // Set a date filter slightly in the future so our "latest before" cell is included + LocalDateTime dateFilter = LocalDateTime.now().plusMinutes(5); + + // Creating versions of the SAME SectionCell (share the same idReference) + + // the first version. This will get a GENERATED idReference. + SectionCell firstVersion = + getTestSectionCell( + staticAuthorizedProject, + targetSectionId, + "Content V1 (Oldest)", + LocalDateTime.now().minusDays(3) // Oldest date + ); + sectionCellService.addNewSectionCell(firstVersion); + + Long sharedIdReference = firstVersion.getIdReference(); + assertNotNull( + sharedIdReference, + "idReference should be generated after saving the first version"); + System.out.println("Generated sharedIdReference: " + sharedIdReference); + + // Create subsequent versions and MANUALLY set the SAME idReference. + // These represent updates to the cell identified by sharedIdReference. + + SectionCell middleVersion = + getTestSectionCell( + staticAuthorizedProject, + targetSectionId, + "Content V2 (Middle)", + LocalDateTime.now().minusDays(2), // Middle date, before filter + sharedIdReference); + sectionCellService.addNewSectionCell(middleVersion); + + SectionCell latestBeforeFilter = + getTestSectionCell( + staticAuthorizedProject, + targetSectionId, + "Content V3 (Latest Before Filter)", + LocalDateTime.now().minusDays(1), // Latest date before filter + sharedIdReference); + sectionCellService.addNewSectionCell(latestBeforeFilter); + + SectionCell futureVersion = + getTestSectionCell( + staticAuthorizedProject, + targetSectionId, + "Content V4 (Future - Should Be Excluded)", + LocalDateTime.now().plusDays(1), // Date is AFTER the filter + sharedIdReference); + sectionCellService.addNewSectionCell(futureVersion); + + // --- Create other SectionCells that should NOT be included (different sectionId or + // project) --- + + // Cell in a different section ID + sectionCellService.addNewSectionCell( + getTestSectionCell( + staticAuthorizedProject, + 99L, // Different sectionId + "Content in Different Section", + LocalDateTime.now())); + + // Act + Iterable result = + sharedApiService.getSectionCells( + staticAuthorizedProject.getIdProject(), // Use static project ID + targetSectionId, + dateFilter.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")), + staticAuthorizedMail); // Use static authorized mail + + List resultList = TestUtils.toList(result); + + // Assert + + assertEquals( + latestBeforeFilter.getIdReference(), resultList.get(0).getIdReference(), "The 0"); + assertEquals( + latestBeforeFilter.getIdReference(), resultList.get(1).getIdReference(), "The 1"); + assertEquals( + latestBeforeFilter.getIdReference(), resultList.get(2).getIdReference(), "The 2"); + + assertEquals(1, resultList.size()); + // Verify that the returned cell is the 'latestBeforeFilter' cell + // Comparing by idSectionCell is a good way to verify the exact entity + assertEquals( + latestBeforeFilter.getIdSectionCell(), + resultList.get(0).getIdSectionCell(), + "The returned SectionCell should be the one with the latest modification date before the filter."); + + // Also assert the idReference and content + assertEquals( + sharedIdReference, + resultList.get(0).getIdReference(), + "The returned cell should have the shared idReference."); + assertEquals( + "Content V3 (Latest Before Filter)", + resultList.get(0).getContentSectionCell(), + "The returned cell should have the correct content."); + } + /* * _____ _ ____ _ ____ _ _ ____ * |_ _|__ ___| |_ / ___| ___| |_| _ \ _ __ ___ (_) ___ ___| |_| __ ) _ _ @@ -613,58 +719,6 @@ public class SharedApiServiceTest { * I pushed this half-human code. */ - // --- Test Methods (Use static data from @BeforeAll where possible) --- - - /* - * Tests retrieving section cells for a specific project and section ID before a given date - * when the user is authorized and matching cells exist. - * Verifies that only the correct cells are returned. - */ - /*@Test*/ - // Commenting out failing test - void testGetSectionCells_Authorized_Found() { - // Arrange: Create specific SectionCells for this test scenario - Long targetSectionId = 1L; - LocalDateTime dateFilter = LocalDateTime.now().plusDays(1); - - sectionCellService.addNewSectionCell( - getTestSectionCell( - staticAuthorizedProject, - targetSectionId, - "Old Content", - LocalDateTime.now().minusDays(2))); - SectionCell recentCell = - sectionCellService.addNewSectionCell( - getTestSectionCell( - staticAuthorizedProject, - targetSectionId, - "Recent Content", - LocalDateTime.now().minusDays(1))); - sectionCellService.addNewSectionCell( - getTestSectionCell( - staticAuthorizedProject, 2L, "Other Section", LocalDateTime.now())); - sectionCellService.addNewSectionCell( - getTestSectionCell( - staticAuthorizedProject, - targetSectionId, - "Future Content", - LocalDateTime.now().plusDays(2))); - - // Act - Iterable result = - sharedApiService.getSectionCells( - staticAuthorizedProject.getIdProject(), // Use static project ID - targetSectionId, - dateFilter.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")), - staticAuthorizedMail); // Use static authorized mail - - List resultList = TestUtils.toList(result); - - // Assert - assertEquals(1, resultList.size()); - assertEquals(recentCell.getIdSectionCell(), resultList.get(0).getIdSectionCell()); - } - /* * Tests retrieving the most recent section cell for each unique idReference * within a project when the user is authorized and cells exist. From 6f7fc70c4c4b48992f10b7e26c153724c70b36aa Mon Sep 17 00:00:00 2001 From: MAILLAL Anas Date: Tue, 29 Apr 2025 18:50:25 +0200 Subject: [PATCH 02/10] didn't use the correct function for seting ids, test fixed --- .../myinpulse/SharedApiServiceTest.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java b/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java index 3dc5b77..7eb2b60 100644 --- a/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java +++ b/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java @@ -309,7 +309,7 @@ public class SharedApiServiceTest { * when the user is authorized and matching cells exist. * Verifies that only the correct cells are returned. */ - /*@Test*/ + @Test // Commenting out failing test void testGetSectionCells_Authorized_Found() { Long targetSectionId = 1L; @@ -344,7 +344,9 @@ public class SharedApiServiceTest { "Content V2 (Middle)", LocalDateTime.now().minusDays(2), // Middle date, before filter sharedIdReference); - sectionCellService.addNewSectionCell(middleVersion); + middleVersion = sectionCellService.addNewSectionCell(middleVersion); + sectionCellService.updateSectionCellReferenceId( + middleVersion.getIdSectionCell(), sharedIdReference); SectionCell latestBeforeFilter = getTestSectionCell( @@ -353,7 +355,9 @@ public class SharedApiServiceTest { "Content V3 (Latest Before Filter)", LocalDateTime.now().minusDays(1), // Latest date before filter sharedIdReference); - sectionCellService.addNewSectionCell(latestBeforeFilter); + latestBeforeFilter = sectionCellService.addNewSectionCell(latestBeforeFilter); + sectionCellService.updateSectionCellReferenceId( + latestBeforeFilter.getIdSectionCell(), sharedIdReference); SectionCell futureVersion = getTestSectionCell( @@ -362,7 +366,9 @@ public class SharedApiServiceTest { "Content V4 (Future - Should Be Excluded)", LocalDateTime.now().plusDays(1), // Date is AFTER the filter sharedIdReference); - sectionCellService.addNewSectionCell(futureVersion); + futureVersion = sectionCellService.addNewSectionCell(futureVersion); + sectionCellService.updateSectionCellReferenceId( + futureVersion.getIdSectionCell(), sharedIdReference); // --- Create other SectionCells that should NOT be included (different sectionId or // project) --- @@ -386,13 +392,13 @@ public class SharedApiServiceTest { List resultList = TestUtils.toList(result); // Assert - + /* assertEquals( latestBeforeFilter.getIdReference(), resultList.get(0).getIdReference(), "The 0"); assertEquals( latestBeforeFilter.getIdReference(), resultList.get(1).getIdReference(), "The 1"); assertEquals( - latestBeforeFilter.getIdReference(), resultList.get(2).getIdReference(), "The 2"); + latestBeforeFilter.getIdReference(), resultList.get(2).getIdReference(), "The 2");*/ assertEquals(1, resultList.size()); // Verify that the returned cell is the 'latestBeforeFilter' cell From bbb4debcd8da17930fddb3b366df1e45ce48b3a8 Mon Sep 17 00:00:00 2001 From: MAILLAL Anas Date: Tue, 29 Apr 2025 19:18:12 +0200 Subject: [PATCH 03/10] fix: changed the data structure used in getAllSectionCells for processing the latest version of sectioneCells as it gave some exception (it doesn't like being iterated and modified at the same time) --- .../myinpulse/service/SharedApiService.java | 50 ++-- .../myinpulse/SharedApiServiceTest.java | 262 ++++++++++-------- 2 files changed, 178 insertions(+), 134 deletions(-) diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/SharedApiService.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/SharedApiService.java index c899534..8c0584a 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/SharedApiService.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/SharedApiService.java @@ -25,8 +25,9 @@ import java.nio.file.StandardCopyOption; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.Map; @Service public class SharedApiService { @@ -95,31 +96,36 @@ public class SharedApiService { } Project project = this.projectService.getProjectById(projectId); - List allSectionCells = new ArrayList(); - project.getListSectionCell() + + Map latestSectionCellsMap = + new HashMap<>(); // List for the intermediate result + + // Iterate through all SectionCells associated with the project + // This loop iterates over project.getListSectionCell() but does NOT modify it which causes + // ConcurrentModificationException. + // Modifications are done only on the latestSectionCellsMap (which is safe). + project.getListSectionCell() // <-- Iterating over the original list (read-only) .forEach( projectCell -> { - AtomicBoolean sameReferenceId = - new AtomicBoolean(false); // side effect lambdas - allSectionCells.forEach( - selectedCell -> { - if (projectCell - .getIdReference() - .equals(selectedCell.getIdReference())) { - sameReferenceId.set(true); - if (projectCell - .getModificationDate() - .isAfter(selectedCell.getModificationDate())) { - allSectionCells.remove(selectedCell); - allSectionCells.add(projectCell); - } - } - }); - if (!sameReferenceId.get()) { - allSectionCells.add(projectCell); + Long idReference = projectCell.getIdReference(); + // Check if we have already seen a SectionCell with this idReference in + // our map + if (latestSectionCellsMap.containsKey(idReference)) { + SectionCell existingCell = latestSectionCellsMap.get(idReference); + // Compare modification dates. If the current cell is newer, replace + // the one in the map. + if (projectCell + .getModificationDate() + .isAfter(existingCell.getModificationDate())) { + latestSectionCellsMap.put(idReference, projectCell); + } + } else { + // If this is the first time we encounter this idReference, add the + // cell to the map. + latestSectionCellsMap.put(idReference, projectCell); } }); - return allSectionCells; + return new ArrayList<>(latestSectionCellsMap.values()); } // TODO: test diff --git a/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java b/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java index 7eb2b60..54f3573 100644 --- a/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java +++ b/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java @@ -391,15 +391,6 @@ public class SharedApiServiceTest { List resultList = TestUtils.toList(result); - // Assert - /* - assertEquals( - latestBeforeFilter.getIdReference(), resultList.get(0).getIdReference(), "The 0"); - assertEquals( - latestBeforeFilter.getIdReference(), resultList.get(1).getIdReference(), "The 1"); - assertEquals( - latestBeforeFilter.getIdReference(), resultList.get(2).getIdReference(), "The 2");*/ - assertEquals(1, resultList.size()); // Verify that the returned cell is the 'latestBeforeFilter' cell // Comparing by idSectionCell is a good way to verify the exact entity @@ -419,6 +410,156 @@ public class SharedApiServiceTest { "The returned cell should have the correct content."); } + /* + * Tests retrieving the most recent section cell for each unique idReference + * within a project when the user is authorized and cells exist. + * Verifies that only the latest version of each referenced cell is returned. + */ + // Tests getAllSectionCells + @Test + // Commenting out failing test - Removed this comment as we are fixing it + void testGetAllSectionCells_Authorized_FoundLatest() { + // Arrange: Create specific SectionCells for this test + // Define the idReference values we will use for grouping + Long refIdGroup1 = 101L; + Long refIdGroup2 = 102L; + Long refIdOtherProject = 103L; + + // --- Create and Add Cells for Group 1 (refIdGroup1) --- + // Create the older cell for group 1 + SectionCell tempOldCell1 = + getTestSectionCell( + staticAuthorizedProject, // Project + 1L, // Section ID (assuming this groups by section within refId) + "Ref1 Old", // Name + LocalDateTime.now().minusDays(3), // Date (older) + null); // Pass null or let getTestSectionCell handle it, we'll set + // idReference later + final SectionCell oldCell1 = + sectionCellService.addNewSectionCell(tempOldCell1); // Add to DB + + // Create the newer cell for group 1 + SectionCell tempNewerCell1 = + getTestSectionCell( + staticAuthorizedProject, // Project + 1L, // Section ID + "Ref1 Newer", // Name + LocalDateTime.now().minusDays(2), // Date (newer than oldCell1) + null); // Pass null + final SectionCell newerCell1 = + sectionCellService.addNewSectionCell(tempNewerCell1); // Add to DB + + // Now, update the idReference for both cells in Group 1 to the desired value + sectionCellService.updateSectionCellReferenceId(oldCell1.getIdSectionCell(), refIdGroup1); + sectionCellService.updateSectionCellReferenceId(newerCell1.getIdSectionCell(), refIdGroup1); + + // --- Create and Add Cells for Group 2 (refIdGroup2) --- + // Create the older cell for group 2 + SectionCell tempOldCell2 = + getTestSectionCell( + staticAuthorizedProject, // Project + 2L, // Section ID (different section) + "Ref2 Old", // Name + LocalDateTime.now().minusDays(1), // Date (older than newerCell2) + null); // Pass null + final SectionCell oldCell2 = + sectionCellService.addNewSectionCell(tempOldCell2); // Add to DB + + // Create the newer cell for group 2 + SectionCell tempNewerCell2 = + getTestSectionCell( + staticAuthorizedProject, // Project + 2L, // Section ID + "Ref2 Newer", // Name + LocalDateTime.now(), // Date (latest) + null); // Pass null + final SectionCell newerCell2 = + sectionCellService.addNewSectionCell(tempNewerCell2); // Add to DB + + // Now, update the idReference for both cells in Group 2 to the desired value + sectionCellService.updateSectionCellReferenceId(oldCell2.getIdSectionCell(), refIdGroup2); + sectionCellService.updateSectionCellReferenceId(newerCell2.getIdSectionCell(), refIdGroup2); + + // --- Create and Add Cell for Other Project (refIdOtherProject) --- + Project otherProject = + projectService.addNewProject( + getTestProject( + "other_project_for_cell_test", + administratorService.addAdministrator( + getTestAdmin("other_admin_cell_test")))); + + SectionCell tempOtherProjectCell = + getTestSectionCell( + otherProject, // DIFFERENT Project + 1L, // Section ID + "Other Project Cell", // Name + LocalDateTime.now(), // Date + null); // Pass null + final SectionCell otherProjectCell = + sectionCellService.addNewSectionCell(tempOtherProjectCell); // Add to DB + + // Now, update the idReference for the Other Project cell + sectionCellService.updateSectionCellReferenceId( + otherProjectCell.getIdSectionCell(), refIdOtherProject); + + // Act + // Ensure the service call uses the correct project ID and mail + Iterable result = + sharedApiService.getAllSectionCells( + staticAuthorizedProject.getIdProject(), // Use static project ID + staticAuthorizedMail); // Use static authorized mail + + List resultList = TestUtils.toList(result); + + // Assert + // We expect 2 cells from the staticAuthorizedProject: + // - The latest one from refIdGroup1 (newerCell1) + // - The latest one from refIdGroup2 (newerCell2) + assertEquals(2, resultList.size()); + + // Assert that the result list contains the LATEST cell from each group within the correct + // project + assertTrue( + resultList.stream() + .anyMatch( + cell -> + cell.getIdSectionCell() + .equals(newerCell1.getIdSectionCell())), + "Should contain the latest cell for Group 1"); // Add assertion message + assertTrue( + resultList.stream() + .anyMatch( + cell -> + cell.getIdSectionCell() + .equals(newerCell2.getIdSectionCell())), + "Should contain the latest cell for Group 2"); // Add assertion message + + // Assert that the result list does NOT contain the OLDER cells from the correct project + assertFalse( + resultList.stream() + .anyMatch( + cell -> + cell.getIdSectionCell() + .equals(oldCell1.getIdSectionCell())), + "Should not contain the older cell for Group 1"); // Add assertion message + assertFalse( + resultList.stream() + .anyMatch( + cell -> + cell.getIdSectionCell() + .equals(oldCell2.getIdSectionCell())), + "Should not contain the older cell for Group 2"); // Add assertion message + + // Assert that the result list does NOT contain the cell from the other project + assertFalse( + resultList.stream() + .anyMatch( + cell -> + cell.getIdSectionCell() + .equals(otherProjectCell.getIdSectionCell())), + "Should not contain cells from other projects"); // Add assertion message + } + /* * _____ _ ____ _ ____ _ _ ____ * |_ _|__ ___| |_ / ___| ___| |_| _ \ _ __ ___ (_) ___ ___| |_| __ ) _ _ @@ -717,109 +858,6 @@ public class SharedApiServiceTest { */ - /* these tests fail because of the use of mockito's eq(), - * and since thee instances are technically not the same as - * as the classes used to turn them into persistant data - * (for e.g id are set by DB) so I have to add some equal functions - * probably and look at peer tests to see what they have done but for now - * I pushed this half-human code. - */ - - /* - * Tests retrieving the most recent section cell for each unique idReference - * within a project when the user is authorized and cells exist. - * Verifies that only the latest version of each referenced cell is returned. - */ - // Tests getAllSectionCells - /*@Test*/ - // Commenting out failing test - void testGetAllSectionCells_Authorized_FoundLatest() { - // Arrange: Create specific SectionCells for this test - Long refId1 = 101L; - Long refId2 = 102L; - - SectionCell tempOldCell1 = - getTestSectionCell( - staticAuthorizedProject, 1L, "Ref1 Old", LocalDateTime.now().minusDays(3)); - tempOldCell1.setIdReference(refId1); - final SectionCell oldCell1 = sectionCellService.addNewSectionCell(tempOldCell1); - - SectionCell tempNewerCell1 = - getTestSectionCell( - staticAuthorizedProject, - 1L, - "Ref1 Newer", - LocalDateTime.now().minusDays(2)); - tempNewerCell1.setIdReference(refId1); - final SectionCell newerCell1 = sectionCellService.addNewSectionCell(tempNewerCell1); - - SectionCell tempOldCell2 = - getTestSectionCell( - staticAuthorizedProject, 2L, "Ref2 Old", LocalDateTime.now().minusDays(1)); - tempOldCell2.setIdReference(refId2); - final SectionCell oldCell2 = sectionCellService.addNewSectionCell(tempOldCell2); - - SectionCell tempNewerCell2 = - getTestSectionCell(staticAuthorizedProject, 2L, "Ref2 Newer", LocalDateTime.now()); - tempNewerCell2.setIdReference(refId2); - final SectionCell newerCell2 = sectionCellService.addNewSectionCell(tempNewerCell2); - - Project otherProject = - projectService.addNewProject( - getTestProject( - "other_project_for_cell_test", - administratorService.addAdministrator( - getTestAdmin("other_admin_cell_test")))); - SectionCell tempOtherProjectCell = - getTestSectionCell(otherProject, 1L, "Other Project Cell", LocalDateTime.now()); - tempOtherProjectCell.setIdReference(103L); - final SectionCell otherProjectCell = - sectionCellService.addNewSectionCell(tempOtherProjectCell); - - // Act - Iterable result = - sharedApiService.getAllSectionCells( - staticAuthorizedProject.getIdProject(), // Use static project ID - staticAuthorizedMail); // Use static authorized mail - - List resultList = TestUtils.toList(result); - - // Assert - assertEquals(2, resultList.size()); // Expect 2 cells (one per idReference) - - assertTrue( - resultList.stream() - .anyMatch( - cell -> - cell.getIdSectionCell() - .equals(newerCell1.getIdSectionCell()))); - assertTrue( - resultList.stream() - .anyMatch( - cell -> - cell.getIdSectionCell() - .equals(newerCell2.getIdSectionCell()))); - - assertFalse( - resultList.stream() - .anyMatch( - cell -> - cell.getIdSectionCell() - .equals(oldCell1.getIdSectionCell()))); - assertFalse( - resultList.stream() - .anyMatch( - cell -> - cell.getIdSectionCell() - .equals(oldCell2.getIdSectionCell()))); - assertFalse( - resultList.stream() - .anyMatch( - cell -> - cell.getIdSectionCell() - .equals(otherProjectCell.getIdSectionCell()))); - } - /* * Tests retrieving entrepreneurs linked to a project when the user is authorized * and entrepreneurs are linked. From 3f18304028b6917fb1be467f50976f604f0bf7ac Mon Sep 17 00:00:00 2001 From: MAILLAL Anas Date: Tue, 29 Apr 2025 19:31:28 +0200 Subject: [PATCH 04/10] formatting works time in and time out, empty commit to fix --- .../src/test/java/enseirb/myinpulse/SharedApiServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java b/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java index 54f3573..933b6b0 100644 --- a/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java +++ b/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java @@ -434,7 +434,7 @@ public class SharedApiServiceTest { "Ref1 Old", // Name LocalDateTime.now().minusDays(3), // Date (older) null); // Pass null or let getTestSectionCell handle it, we'll set - // idReference later + // idReference later final SectionCell oldCell1 = sectionCellService.addNewSectionCell(tempOldCell1); // Add to DB From fcf4e1c01d48b0a5963d1c2c5da24219107b4dfb Mon Sep 17 00:00:00 2001 From: MAILLAL Anas Date: Tue, 29 Apr 2025 21:22:13 +0200 Subject: [PATCH 05/10] feat: added an endpoint for fething the project an entrepreneur is associated with --- .../myinpulse/controller/EntrepreneurApi.java | 15 +++++++++++ .../service/EntrepreneurApiService.java | 12 +++++++++ .../openapi/src/entrepreneurApi.yaml | 26 ++++++++++++++++++- documentation/openapi/src/main.yaml | 3 +++ 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/EntrepreneurApi.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/EntrepreneurApi.java index c0f3638..f5c23d1 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/EntrepreneurApi.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/EntrepreneurApi.java @@ -1,10 +1,12 @@ package enseirb.myinpulse.controller; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -42,6 +44,19 @@ public class EntrepreneurApi { sectionCellId, content, principal.getClaimAsString("email")); } + /** + * Endpoint used to update a LC section. + * + * @return status code + */ + @GetMapping("/entrepreneur/projects") + public Iterable getEntrepreneurProjectId( + @PathVariable Long sectionCellId, + @RequestBody String content, + @AuthenticationPrincipal Jwt principal) { + return entrepreneurApiService.getProjectIdViaClaim(principal.getClaimAsString("email")); + } + /** * TODO: checkReturn Type * diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/EntrepreneurApiService.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/EntrepreneurApiService.java index c89a35f..571b5ff 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/EntrepreneurApiService.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/EntrepreneurApiService.java @@ -14,7 +14,10 @@ import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; + import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; @Service public class EntrepreneurApiService { @@ -219,4 +222,13 @@ public class EntrepreneurApiService { } throw new ResponseStatusException(HttpStatus.CONFLICT, "User already exists in the system"); } + + public Iterable getProjectIdViaClaim(String email) { + Long UserId = this.userService.getUserByEmail(email).getIdUser(); + Entrepreneur entrepreneur = this.entrepreneurService.getEntrepreneurById(UserId); + List Project_List = new ArrayList<>(); + ; + Project_List.add(entrepreneur.getProjectParticipation()); + return Project_List; + } } diff --git a/documentation/openapi/src/entrepreneurApi.yaml b/documentation/openapi/src/entrepreneurApi.yaml index 3041742..6dd4a46 100644 --- a/documentation/openapi/src/entrepreneurApi.yaml +++ b/documentation/openapi/src/entrepreneurApi.yaml @@ -109,4 +109,28 @@ paths: "404": description: Bad Request - sectionCell not found. "401": - description: Unauthorized. \ No newline at end of file + description: Unauthorized. + + + /entrepreneur/projects: + get: + summary: gets the projectId of the project associated with the entrepreneur + description: returns a list of projectIds of the projects associated with the entrepreneur + tags: + - Entrepreneurs API + security: + - MyINPulse: [MyINPulse-entrepreneur] + parameters: + responses: + "200": + description: OK - Section cell updated successfully. Returns the updated cell. + content: + application/json: + schema: + type: array + items: + $ref: "./main.yaml#/components/schemas/project" + "404": + description: Bad Request - Invalid input or ID mismatch. + "401": + description: Unauthorized or identity not found \ No newline at end of file diff --git a/documentation/openapi/src/main.yaml b/documentation/openapi/src/main.yaml index 4c87d94..6cf5cb6 100644 --- a/documentation/openapi/src/main.yaml +++ b/documentation/openapi/src/main.yaml @@ -138,6 +138,9 @@ paths: # / ___ \| __/| | # /_/ \_\_| |___| # + + /entrepreneur/projects: + $ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects" /entrepreneur/projects/request: $ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects~1request" /entrepreneur/sectionCells: From b503cae2355d9189320befb63a68917b3ef431cd Mon Sep 17 00:00:00 2001 From: MAILLAL Anas Date: Tue, 29 Apr 2025 21:25:03 +0200 Subject: [PATCH 06/10] fix: styling --- .../java/enseirb/myinpulse/controller/EntrepreneurApi.java | 1 - .../java/enseirb/myinpulse/service/EntrepreneurApiService.java | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/EntrepreneurApi.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/EntrepreneurApi.java index f5c23d1..82f7e6e 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/EntrepreneurApi.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/EntrepreneurApi.java @@ -1,6 +1,5 @@ package enseirb.myinpulse.controller; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.core.annotation.AuthenticationPrincipal; diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/EntrepreneurApiService.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/EntrepreneurApiService.java index 571b5ff..1cf6ac5 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/EntrepreneurApiService.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/EntrepreneurApiService.java @@ -14,7 +14,6 @@ import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; - import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -227,7 +226,7 @@ public class EntrepreneurApiService { Long UserId = this.userService.getUserByEmail(email).getIdUser(); Entrepreneur entrepreneur = this.entrepreneurService.getEntrepreneurById(UserId); List Project_List = new ArrayList<>(); - ; + Project_List.add(entrepreneur.getProjectParticipation()); return Project_List; } From 5183a088e70431aa0b4e2f0d085e83bbb925f933 Mon Sep 17 00:00:00 2001 From: MAILLAL Anas Date: Tue, 29 Apr 2025 21:41:25 +0200 Subject: [PATCH 07/10] fix: forgot to remove a pram in an endpoint in docs --- documentation/openapi/src/adminApi.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/documentation/openapi/src/adminApi.yaml b/documentation/openapi/src/adminApi.yaml index 5f10715..958cf11 100644 --- a/documentation/openapi/src/adminApi.yaml +++ b/documentation/openapi/src/adminApi.yaml @@ -136,14 +136,6 @@ paths: If rejected (isAccepted=false), the pending project data might be archived or deleted based on business logic. security: - MyINPulse: [MyINPulse-admin] - parameters: - - in: path - name: pendingProjectId # Corrected typo and name change - required: true - schema: - type: integer - description: The ID of the pending project to decide upon. - example: 7 requestBody: required: true description: Decision payload. From 0730275e75a3222aea9b72c7176277d6520bca0b Mon Sep 17 00:00:00 2001 From: Pierre Tellier Date: Thu, 1 May 2025 20:50:59 +0200 Subject: [PATCH 08/10] feat: added create-account --- .../java/enseirb/myinpulse/controller/AdminApi.java | 12 ++++++++++++ .../enseirb/myinpulse/service/AdminApiService.java | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/AdminApi.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/AdminApi.java index 9bd93d9..85f8838 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/AdminApi.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/AdminApi.java @@ -115,4 +115,16 @@ public class AdminApi { public Iterable validateEntrepreneurAcc() { return this.adminApiService.getPendingUsers(); } + + @PostMapping("/admin/create-account") + public void createAccount(@AuthenticationPrincipal Jwt principal) { + String userSurname = principal.getClaimAsString("userSurname"); + String username = principal.getClaimAsString("preferred_username"); + String primaryMail = principal.getClaimAsString("email"); + String secondaryMail = principal.getClaimAsString("secondaryMail"); + String phoneNumber = principal.getClaimAsString("phoneNumber"); + String school = principal.getClaimAsString("school"); + this.adminApiService.createAccount( + userSurname, username, primaryMail, secondaryMail, phoneNumber, school); + } } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/AdminApiService.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/AdminApiService.java index 4aade61..6794b5c 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/AdminApiService.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/AdminApiService.java @@ -206,4 +206,16 @@ public class AdminApiService { public Iterable getPendingUsers() { return this.userService.getPendingAccounts(); } + + public void createAccount( + String username, + String userSurname, + String primaryMail, + String secondaryMail, + String phoneNumber, + String school) { + Administrator a = + new Administrator(username, userSurname, primaryMail, secondaryMail, phoneNumber); + this.administratorService.addAdministrator(a); + } } From f4589c63067668c776c77fefecb445eac863221b Mon Sep 17 00:00:00 2001 From: Pierre Tellier Date: Thu, 1 May 2025 21:03:23 +0200 Subject: [PATCH 09/10] fix: bug on CORS discussed before --- .../myinpulse/config/WebSecurityCustomConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/config/WebSecurityCustomConfiguration.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/config/WebSecurityCustomConfiguration.java index e83eaa7..2cbeccb 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/config/WebSecurityCustomConfiguration.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/config/WebSecurityCustomConfiguration.java @@ -31,7 +31,7 @@ public class WebSecurityCustomConfiguration { public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(List.of(frontendUrl)); - configuration.setAllowedMethods(Arrays.asList("GET", "OPTIONS")); + configuration.setAllowedMethods(Arrays.asList("GET", "OPTIONS", "POST", "PUT", "DELETE")); configuration.setAllowedHeaders( Arrays.asList("authorization", "content-type", "x-auth-token")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); From 13845394e3030d53e9b3600356b727eedcd36386 Mon Sep 17 00:00:00 2001 From: MAILLAL Anas Date: Sun, 4 May 2025 20:15:03 +0200 Subject: [PATCH 10/10] feat: added doc for endpoint make-admin --- .../myinpulse/controller/AdminApi.java | 3 +- .../myinpulse/service/AdminApiService.java | 3 +- documentation/openapi/src/adminApi.yaml | 29 +- documentation/openapi/src/bundled.yaml | 957 ++++++++++++++++++ .../openapi/src/entrepreneurApi.yaml | 6 +- documentation/openapi/src/main.yaml | 2 + documentation/openapi/src/sharedApi.yaml | 2 +- documentation/openapi/src/unauthApi.yaml | 6 +- 8 files changed, 990 insertions(+), 18 deletions(-) create mode 100644 documentation/openapi/src/bundled.yaml diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/AdminApi.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/AdminApi.java index 85f8838..41fe6dd 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/AdminApi.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/AdminApi.java @@ -123,8 +123,7 @@ public class AdminApi { String primaryMail = principal.getClaimAsString("email"); String secondaryMail = principal.getClaimAsString("secondaryMail"); String phoneNumber = principal.getClaimAsString("phoneNumber"); - String school = principal.getClaimAsString("school"); this.adminApiService.createAccount( - userSurname, username, primaryMail, secondaryMail, phoneNumber, school); + userSurname, username, primaryMail, secondaryMail, phoneNumber); } } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/AdminApiService.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/AdminApiService.java index 6794b5c..364415c 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/AdminApiService.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/AdminApiService.java @@ -212,8 +212,7 @@ public class AdminApiService { String userSurname, String primaryMail, String secondaryMail, - String phoneNumber, - String school) { + String phoneNumber) { Administrator a = new Administrator(username, userSurname, primaryMail, secondaryMail, phoneNumber); this.administratorService.addAdministrator(a); diff --git a/documentation/openapi/src/adminApi.yaml b/documentation/openapi/src/adminApi.yaml index 958cf11..3b66cd1 100644 --- a/documentation/openapi/src/adminApi.yaml +++ b/documentation/openapi/src/adminApi.yaml @@ -39,7 +39,7 @@ paths: schema: $ref: "./main.yaml#/components/schemas/project" responses: - "201": # Use 201 Created for successful creation + "200": # Use 200 Created for successful creation description: Created - Project added successfully. Returns the created project. content: application/json: @@ -144,7 +144,7 @@ paths: schema: $ref: './main.yaml#/components/schemas/projectDecision' responses: - "204": # Use 204 No Content for successful action with no body + "200": # Use 200 No Content for successful action with no body description: No Content - Decision processed successfully. "400": description: Bad Request - Invalid input (e.g., missing decision). @@ -191,7 +191,7 @@ paths: description: The ID of the user account to validate. example: 102 responses: - "204": + "200": description: No Content - Account validated successfully. "400": description: Bad Request - Invalid user ID format. @@ -247,7 +247,7 @@ paths: schema: $ref: "./main.yaml#/components/schemas/report" responses: - "201": + "200": description: Created - Report created and linked successfully. Returns the created report. content: application/json: @@ -310,7 +310,7 @@ paths: description: The ID of the project to remove. example: 12 responses: - "204": + "200": description: No Content - Project removed successfully. "400": description: Bad Request - Invalid project ID format. @@ -337,9 +337,24 @@ paths: description: The ID of the user to grant admin rights. example: 103 responses: - "204": # Use 204 No Content + "200": # Use 200 No Content description: No Content - Admin rights granted successfully. "400": description: Bad Request - Invalid user ID format or user is already an admin. "401": - description: Unauthorized. \ No newline at end of file + description: Unauthorized. + + /admin/create-account: + post: + summary: Creates Admin out Jwt Token + tags: + - Admin API + security: + - MyINPulse: [MyINPulse-admin] + description: Create an admin instance in the MyINPulse DB of the information provided from the authenticated user's keycloack token. + The information required in the token are `userSurname`, `username`, `primaryMail`, `secondaryMail`, `phoneNumber`. + responses: + "200": + description: No Content - Admin user created successfully. + "401": + description: Unauthorized. \ No newline at end of file diff --git a/documentation/openapi/src/bundled.yaml b/documentation/openapi/src/bundled.yaml new file mode 100644 index 0000000..d663aac --- /dev/null +++ b/documentation/openapi/src/bundled.yaml @@ -0,0 +1,957 @@ +openapi: 3.0.3 +info: + title: MyInpulse Backend API + description: 'This serves as an OpenAPI documentation for the MyInpulse backend service, covering operations for Entrepreneurs, Admins, and shared functionalities.' + version: 0.2.1 +tags: + - name: Entrepreneurs API + description: API endpoints primarily for Entrepreneur users. + - name: Admin API + description: API endpoints restricted to Admin users for management tasks. + - name: Shared API + description: API endpoints accessible by both Entrepreneurs and Admins. + - name: Unauth API + description: API endpoints related to user account management. +components: + schemas: + user: + type: object + properties: + idUser: + type: integer + description: Unique identifier for the user. + example: 101 + userSurname: + type: string + description: User's surname (last name). + example: Doe + userName: + type: string + description: User's given name (first name). + example: John + primaryMail: + type: string + format: email + description: User's primary email address. + example: john.doe@example.com + secondaryMail: + type: string + format: email + description: User's secondary email address (optional). + example: j.doe@personal.com + phoneNumber: + type: string + description: User's phone number. + example: '+33612345678' + user-entrepreneur: + allOf: + - $ref: '#/components/schemas/user' + - type: object + properties: + school: + type: string + description: The school the entrepreneur attends/attended. + example: ENSEIRB-MATMECA + course: + type: string + description: The specific course or program of study. + example: Electronics + sneeStatus: + type: boolean + description: Indicates if the user has SNEE status (Statut National d'Étudiant-Entrepreneur). + example: true + example: + idUser: 101 + userSurname: Doe + userName: John + primaryMail: john.doe@example.com + secondaryMail: j.doe@personal.com + phoneNumber: '+33612345678' + school: ENSEIRB-MATMECA + course: Electronics + sneeStatus: true + user-admin: + allOf: + - $ref: '#/components/schemas/user' + example: + idUser: 55 + userSurname: Admin + userName: Super + primaryMail: admin@myinpulse.com + phoneNumber: '+33512345678' + sectionCell: + type: object + description: Represents a cell (like a sticky note) within a specific section of a project's Lean Canvas. + properties: + idSectionCell: + type: integer + description: Unique identifier for the section cell. + example: 508 + sectionId: + type: integer + description: 'Identifier of the Lean Canvas section this cell belongs to (e.g., 1 for Problem, 2 for Solution).' + example: 1 + contentSectionCell: + type: string + description: The text content of the section cell. + example: Users find it hard to track project progress. + modificationDate: + type: string + format: date + description: The date when this cell was last modified. + example: 'yyyy-MM-dd HH:mm' + project: + type: object + description: Represents a project being managed or developed. + properties: + idProject: + type: integer + description: Unique identifier for the project. + example: 12 + projectName: + type: string + description: The name of the project. + example: MyInpulse Mobile App + creationDate: + type: string + format: date + description: The date when the project was created in the system. + example: 'yyyy-MM-dd HH:mm' + logo: + type: string + format: byte + description: Base64 encoded string representing the project logo image. + example: /*Base64 encoded string representing the project logo image*/ + status: + type: string + enum: + - PENDING + - ACTIVE + - ENDED + - ABORTED + - REJECTED + description: 'Corresponds to a status enum internal to the backend, it''s value in in requests incoming to the server should be ignored as the client shouldn''t be specifying them.' + example: NaN + report: + type: object + description: Represents a report associated with an appointment. + properties: + idReport: + type: integer + description: Unique identifier for the report. + example: 987 + reportContent: + type: string + description: The textual content of the report. Could be plain text or Markdown (specify if known). + example: Discussed roadmap milestones for Q3. Agreed on preliminary UI mockups. + appointment: + type: object + description: Represents a scheduled meeting or appointment. + properties: + idAppointment: + type: integer + description: Unique identifier for the appointment. + example: 303 + appointmentDate: + type: string + format: date + description: The date of the appointment. + example: '2025-05-10' + appointmentTime: + type: string + format: time + description: The time of the appointment (local time). + example: '14:30:00' + appointmentDuration: + type: string + description: 'Duration of the appointment in ISO 8601 duration format (e.g., PT1H30M for 1 hour 30 minutes).' + example: PT1H + appointmentPlace: + type: string + description: Location or meeting link for the appointment. + example: 'Meeting Room 3 / https://meet.example.com/abc-def-ghi' + appointmentSubject: + type: string + description: The main topic or subject of the appointment. + example: Q3 Roadmap Planning + joinRequest: + type: object + description: Represents a request from an entrepreneur to join an already existing project. + properties: + idProject: + type: integer + description: the ID of the project the entrepreneur wants to join. + example: 42 + entrepreneur: + $ref: '#/components/schemas/user-entrepreneur' + projectDecision: + type: object + description: Represents a decision from an admin to accept a pending project. + properties: + projectId: + type: integer + description: The ID of the project the entrepreneur wants to join. + example: 12 + adminId: + type: integer + description: The ID of the project the admin who will supervise the project in case of admission. + example: 2 + isAccepted: + type: boolean + description: The boolean value of the decision. + example: 'true' + joinRequestDecision: + type: object + description: Represents a decision from an admin to accept a pending project join request. + properties: + isAccepted: + type: boolean + description: The boolean value of the decision. + example: 'true' + securitySchemes: + MyINPulse: + type: oauth2 + description: OAuth2 authentication using Keycloak. + flows: + implicit: + authorizationUrl: '{keycloakBaseUrl}/realms/{keycloakRealm}/protocol/openid-connect/auth' + scopes: + MyINPulse-admin: Grants administrator access. + MyINPulse-entrepreneur: Grants standard entrepreneur user access. +servers: + - url: '{serverProtocol}://{serverHost}:{serverPort}' + description: API Server + variables: + serverProtocol: + enum: + - http + - https + default: http + serverHost: + default: localhost + serverPort: + enum: + - '8081' + default: '8081' + keycloakBaseUrl: + default: 'http://localhost:7080' + description: Base URL for the Keycloak server. + keycloakRealm: + default: MyInpulseRealm + description: Keycloak realm name. +paths: + /unauth/finalize: + post: + summary: Finalize account setup using authentication token + description: |- + Completes the user account creation/setup process in the MyInpulse system. + This endpoint requires the user to be authenticated via Keycloak (e.g., after initial login). + User details (name, email, etc.) are extracted from the authenticated user's token (e.g., Keycloak JWT). + No request body is needed. The account is marked as pending admin validation upon successful finalization. + tags: + - Unauth API + responses: + '200': + description: Created - Account finalized and pending admin validation. Returns the user profile. + '400': + description: Bad Request - Problem processing the token or user data derived from it. + '401': + description: Unauthorized - Valid authentication token required. + '/unauth/request-join/{projectId}': + post: + summary: Request to join an existing project + description: Submits a request for the authenticated user (keycloack authenticated) to join the project specified by projectId. Their role is then changed to entrepreneur in server and Keycloak. This requires approval from a project admin. + tags: + - Unauth API + parameters: + - in: path + name: projectId + required: true + schema: + type: integer + description: The ID of the project to request joining. + example: 15 + responses: + '200': + description: Accepted - Join request submitted and pending approval. + '400': + description: Bad Request - Invalid project ID format + '401': + description: Unauthorized. + '409': + description: Already member/request pending. + /admin/pending-accounts: + get: + operationId: getPendingAccounts + summary: Get accounts awaiting validation + description: Retrieves a list of entrepreneur user accounts that are pending admin validation. + tags: + - Admin API + security: + - MyINPulse: + - MyINPulse-admin + responses: + '200': + description: OK - List of pending accounts returned. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/user-entrepreneur' + '401': + description: Unauthorized. + '/admin/accounts/validate/{userId}': + post: + operationId: validateUserAccount + summary: Validate a pending user account + description: Marks the user account specified by userId as validated/active. + tags: + - Admin API + security: + - MyINPulse: + - MyINPulse-admin + parameters: + - in: path + name: userId + required: true + schema: + type: integer + description: The ID of the user account to validate. + example: 102 + responses: + '200': + description: No Content - Account validated successfully. + '400': + description: Bad Request - Invalid user ID format. + '401': + description: Unauthorized. + /admin/request-join: + get: + operationId: getPendingProjects + summary: Get entrepreneurs project join requests + tags: + - Admin API + security: + - MyINPulse: + - MyINPulse-admin + description: Retrieves a list of pending requests from entrepreneurs to join an existing project. + responses: + '200': + description: OK - List of pending project join requests. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/joinRequest' + '401': + description: Unauthorized. + '/admin/request-join/decision/{joinRequestId}': + post: + summary: Approve or reject a pending project join request + tags: + - Admin API + security: + - MyINPulse: + - MyINPulse-admin + parameters: + - in: path + name: joinRequestId + required: true + schema: + type: integer + description: The ID of the pending join request to decide upon. + description: |- + Allows an admin to make a decision on an ebtrepreneur's request to join an existing project awaiting validation. + If approved (isAccepted=true), the entrepreneur is linked to the project with ID joinRequestId. + If rejected (isAccepted=false), the pending request data might be archived or deleted based on business logic. + responses: + '200': + description: 'OK - No Content, decision processed successfully..' + content: + application/json: + $ref: '#/components/schemas/joinRequestDecision' + '400': + description: 'Bad Request - Invalid input (e.g., missing decision).' + '401': + description: Unauthorized. + /admin/projects: + get: + operationId: getAdminProjects + summary: Get projects associated with the admin + tags: + - Admin API + security: + - MyINPulse: + - MyINPulse-admin + description: 'Retrieves a list of projects managed by the requesting admin, including details for overview.' + responses: + '200': + description: OK - List of projects returned successfully. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/project' + '400': + description: 'Bad Request - Invalid project data provided (e.g., missing required fields).' + '401': + description: Unauthorized - Authentication required or invalid token. + post: + operationId: addProjectManually + summary: Manually add a new project + description: 'Creates a new project with the provided details. (NOTE that this meant for manually inserting projects, for example importing already existing projects).' + tags: + - Admin API + security: + - MyINPulse: + - MyINPulse-admin + requestBody: + required: true + description: Project details to create. `idProject` and `creationDate` will be ignored if sent and set by the server. + content: + application/json: + schema: + $ref: '#/components/schemas/project' + responses: + '200': + description: Created - Project added successfully. Returns the created project. + content: + application/json: + schema: + $ref: '#/components/schemas/project' + '401': + description: Unauthorized. + '409': + description: Bad Request - Project already exists. + /admin/projects/pending: + get: + operationId: getPendingProjects + summary: Get projects awaiting validation + tags: + - Admin API + security: + - MyINPulse: + - MyINPulse-admin + description: Retrieves a list of projects submitted by entrepreneurs that are pending admin approval. + responses: + '200': + description: OK - List of pending projects returned. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/project' + '401': + description: Unauthorized. + /admin/projects/pending/decision: + post: + operationId: decidePendingProject + summary: Approve or reject a pending project + tags: + - Admin API + description: |- + Allows an admin to make a decision on a project awaiting validation. + If approved (isAccepted=true), the project status changes, and it's linked to the involved users. + If rejected (isAccepted=false), the pending project data might be archived or deleted based on business logic. + security: + - MyINPulse: + - MyINPulse-admin + requestBody: + required: true + description: Decision payload. + content: + application/json: + schema: + $ref: '#/components/schemas/projectDecision' + responses: + '200': + description: No Content - Decision processed successfully. + '400': + description: 'Bad Request - Invalid input (e.g., missing decision).' + '401': + description: Unauthorized. + '/admin/appointments/report/{appointmentId}': + post: + operationId: createAppointmentReport + summary: Create a report for an appointment + description: 'Creates and links a new report (e.g., meeting minutes) to the specified appointment using the provided content.' + tags: + - Admin API + security: + - MyINPulse: + - MyINPulse-admin + parameters: + - in: path + name: appointmentId + required: true + schema: + type: integer + description: ID of the appointment to add a report to. + example: 303 + requestBody: + required: true + description: Report content. `idReport` will be ignored if sent. + content: + application/json: + schema: + $ref: '#/components/schemas/report' + responses: + '200': + description: Created - Report created and linked successfully. Returns the created report. + content: + application/json: + schema: + $ref: '#/components/schemas/report' + '400': + description: 'Bad Request - Invalid input (e.g., missing content, invalid appointment ID format).' + '401': + description: Unauthorized. + put: + operationId: updateAppointmentReport + summary: Update an existing appointment report + description: Updates the content of an existing report linked to the specified appointment. Replaces the entire report content. + tags: + - Admin API + security: + - MyINPulse: + - MyINPulse-admin + parameters: + - in: path + name: appointmentId + required: true + schema: + type: integer + description: ID of the appointment whose report needs updating. + example: 303 + requestBody: + required: true + description: New report content. `idReport` in the body should match the existing report's ID or will be ignored. + content: + application/json: + schema: + $ref: '#/components/schemas/report' + responses: + '200': + description: OK - Report updated successfully. Returns the updated report. + content: + application/json: + schema: + $ref: '#/components/schemas/report' + '400': + description: 'Bad Request - Invalid input (e.g., missing content).' + '401': + description: Unauthorized. + /admin/appointments/upcoming: + get: + operationId: getUpcomingAppointments + summary: Get upcoming appointments for an admin + tags: + - Admin API + security: + - MyINPulse: + - MyINPulse-admin + description: Retrieves a list of appointments scheduled for an admin in the future. + responses: + '200': + description: OK - List of upcoming appointments. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/appointment' + '401': + description: Unauthorized. + '404': + description: no appointments found. + '/admin/projects/{projectId}': + delete: + operationId: removeProject + summary: Remove a project + description: Permanently removes the project specified by projectId and potentially related data (use with caution). + tags: + - Admin API + security: + - MyINPulse: + - MyINPulse-admin + parameters: + - in: path + name: projectId + required: true + schema: + type: integer + description: The ID of the project to remove. + example: 12 + responses: + '200': + description: No Content - Project removed successfully. + '400': + description: Bad Request - Invalid project ID format. + '401': + description: Unauthorized. + '/admin/make-admin/{userId}': + post: + operationId: grantAdminRights + summary: Grant admin rights to a user + tags: + - Admin API + security: + - MyINPulse: + - MyINPulse-admin + description: Elevates the specified user to also have administrator privileges. Assumes the user already exists. + parameters: + - in: path + name: userId + required: true + schema: + type: integer + description: The ID of the user to grant admin rights. + example: 103 + responses: + '200': + description: No Content - Admin rights granted successfully. + '400': + description: Bad Request - Invalid user ID format or user is already an admin. + '401': + description: Unauthorized. + /admin/create-account: + post: + summary: Creates Admin out Jwt Token + tags: + - Admin API + security: + - MyINPulse: + - MyINPulse-admin + description: 'Create an admin instance in the MyINPulse DB of the information provided from the authenticated user''s keycloack token. The information required in the token are `userSurname`, `username`, `primaryMail`, `secondaryMail`, `phoneNumber`.' + responses: + '200': + description: No Content - Admin user created successfully. + '401': + description: Unauthorized. + '/shared/projects/sectionCells/{projectId}/{sectionId}/{date}': + get: + operationId: getSectionCellsByDate + summary: Get project section cells modified on a specific date + tags: + - Shared API + security: + - MyINPulse: + - MyINPulse-entrepreneur + - MyINPulse-admin + description: 'Retrieves section cells belonging to a specific section of a project, filtered by the last modification date. Requires user to have access to the project.' + parameters: + - in: path + name: projectId + required: true + schema: + type: integer + description: ID of the project. + - in: path + name: sectionId + required: true + schema: + type: integer + description: ID of the Lean Canvas section. + - in: path + name: date + required: true + schema: + type: string + format: date + description: 'The modification date to filter by (YYYY-MM-DD HH:mm).' + responses: + '200': + description: OK - List of section cells matching the criteria. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/sectionCell' + '400': + description: Bad Request - Invalid parameter format. + '401': + description: Unauthorized. + '/shared/projects/entrepreneurs/{projectId}': + get: + operationId: getProjectEntrepreneurs + summary: Get entrepreneurs associated with a project + tags: + - Shared API + security: + - MyINPulse: + - MyINPulse-entrepreneur + - MyINPulse-admin + description: Retrieves a list of entrepreneur users associated with the specified project. Requires access to the project. + parameters: + - in: path + name: projectId + required: true + schema: + type: integer + description: ID of the project. + responses: + '200': + description: OK - List of entrepreneurs. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/user-entrepreneur' + '401': + description: Unauthorized. + '403': + description: Forbidden - User does not have access to this project. + '404': + description: Not Found - Project not found. + '/shared/projects/admin/{projectId}': + get: + operationId: getProjectAdmin + summary: Get admin associated with a project + tags: + - Shared API + security: + - MyINPulse: + - MyINPulse-entrepreneur + - MyINPulse-admin + description: Retrieves a list of admin users associated with the specified project. Requires access to the project. + parameters: + - in: path + name: projectId + required: true + schema: + type: integer + description: ID of the project. + responses: + '200': + description: OK - admin. + content: + application/json: + schema: + $ref: '#/components/schemas/user-admin' + '401': + description: Unauthorized. + '403': + description: Forbidden - User does not have access to this project. + '404': + description: Not Found - Project not found. + '/shared/projects/appointments/{projectId}': + get: + operationId: getProjectAppointments + summary: Get appointments related to a project + tags: + - Shared API + security: + - MyINPulse: + - MyINPulse-entrepreneur + - MyINPulse-admin + description: Retrieves a list of appointments associated with the specified project. Requires access to the project. + parameters: + - in: path + name: projectId + required: true + schema: + type: integer + description: ID of the project. + responses: + '200': + description: OK - List of appointments. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/appointment' + '401': + description: Unauthorized. + '/shared/appointments/report/{appointmentId}': + get: + operationId: getAppointmentReport + summary: Get the report for an appointment + tags: + - Shared API + security: + - MyINPulse: + - MyINPulse-entrepreneur + - MyINPulse-admin + description: Retrieves the report associated with a specific appointment. Requires user to have access to the appointment/project. + parameters: + - in: path + name: appointmentId + required: true + schema: + type: integer + description: ID of the appointment. + responses: + '200': + description: OK - Report PDF returned. + content: + application/pdf: + schema: + schema: null + type: string + format: binary + '401': + description: Unauthorized. + /shared/appointments/request: + post: + operationId: requestAppointment + summary: Request a new appointment + tags: + - Shared API + security: + - MyINPulse: + - MyINPulse-entrepreneur + - MyINPulse-admin + description: 'Allows a user (entrepreneur or admin) to request a new appointment, potentially with another user or regarding a project. Details in the body. The request might need confirmation or create a pending appointment.' + requestBody: + required: true + description: Details of the appointment request. + content: + application/json: + schema: + $ref: '#/components/schemas/appointment' + responses: + '200': + description: Accepted - Appointment request submitted. + '400': + description: Bad Request - Invalid appointment details. + '401': + description: Unauthorized. + /entrepreneur/projects: + get: + summary: gets the projectId of the project associated with the entrepreneur + description: returns a list of projectIds of the projects associated with the entrepreneur + tags: + - Entrepreneurs API + security: + - MyINPulse: + - MyINPulse-entrepreneur + parameters: null + responses: + '200': + description: OK - Section cell updated successfully. Returns the updated cell. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/project' + '401': + description: Unauthorized or identity not found + '404': + description: Bad Request - Invalid input or ID mismatch. + /entrepreneur/projects/request: + post: + operationId: requestProjectCreation + summary: Request creation and validation of a new project + tags: + - Entrepreneurs API + description: |- + Submits a request for a new project. The project details are provided in the request body. + The requesting entrepreneur (identified by the token) will be associated to it. + The project is created with a 'pending' status, awaiting admin approval. + security: + - MyINPulse: + - MyINPulse-entrepreneur + requestBody: + required: true + description: 'Project details for the request. `status`, `creationDate` are required by the model when being sent but is ignored by the server; primarily expects a valid `projectId`, `name`, `logo`.' + content: + application/json: + schema: + $ref: '#/components/schemas/project' + responses: + '200': + description: Accepted - Project creation request received and is pending validation. + '400': + description: 'Bad Request - Invalid input (e.g., missing name).' + '401': + description: Unauthorized. + /entrepreneur/sectionCells: + post: + operationId: addSectionCell + summary: Add a cell to a Lean Canvas section + description: Adds a new cell (like a sticky note) with the provided content to a specific section of the entrepreneur's project's Lean Canvas. Assumes project context is known based on user's token. `idSectionCell` and `modificationDate` are server-generated so they're values in the request are ignored by the server. + tags: + - Entrepreneurs API + security: + - MyINPulse: + - MyINPulse-entrepreneur + requestBody: + required: true + description: Section cell details. `idSectionCell` and `modificationDate` will be ignored if sent. + content: + application/json: + schema: + $ref: '#/components/schemas/sectionCell' + responses: + '200': + description: Created - Section cell added successfully. Returns the created cell. + '400': + description: 'Bad Request - Invalid input (e.g., missing content or sectionId).' + '401': + description: Unauthorized. + '/entrepreneur/sectionCells/{sectionCellId}': + put: + operationId: modifySectionCell + summary: Modify data in a Lean Canvas section cell + description: Updates the content of an existing Lean Canvas section cell specified by `sectionCellId`. The server "updates" (it keeps a record of the previous version to keep a history of all the sectionCells and creates a new ones with the specified modifications) the `modificationDate`. + tags: + - Entrepreneurs API + security: + - MyINPulse: + - MyINPulse-entrepreneur + parameters: + - in: path + name: sectionCellId + required: true + schema: + type: integer + description: The ID of the section cell to modify. + example: 508 + requestBody: + required: true + description: Updated section cell details. `sectionCellId` "the path parameter" is the only id that's consideredn the `sectionCellId` id in the request body is ignored. `modificationDate` should be updated by the server. + content: + application/json: + schema: + $ref: '#/components/schemas/sectionCell' + responses: + '200': + description: OK - Section cell updated successfully. Returns the updated cell. + '401': + description: Unauthorized. + '404': + description: Bad Request - Invalid input or ID mismatch. + delete: + operationId: removeSectionCell + summary: Remove a Lean Canvas section cell + description: Deletes the Lean Canvas section cell specified by `sectionCellId`. + tags: + - Entrepreneurs API + security: + - MyINPulse: + - MyINPulse-entrepreneur + parameters: + - in: path + name: sectionCellId + required: true + schema: + type: integer + description: The ID of the section cell to remove. + example: 509 + responses: + '200': + description: No Content - Section cell removed successfully. + '400': + description: Bad Request - Invalid ID format. + '401': + description: Unauthorized. + '404': + description: Bad Request - sectionCell not found. diff --git a/documentation/openapi/src/entrepreneurApi.yaml b/documentation/openapi/src/entrepreneurApi.yaml index 6dd4a46..15ddc1f 100644 --- a/documentation/openapi/src/entrepreneurApi.yaml +++ b/documentation/openapi/src/entrepreneurApi.yaml @@ -21,7 +21,7 @@ paths: schema: $ref: "./main.yaml#/components/schemas/project" responses: - "202": + "200": description: Accepted - Project creation request received and is pending validation. "400": description: Bad Request - Invalid input (e.g., missing name). @@ -46,7 +46,7 @@ paths: schema: $ref: "./main.yaml#/components/schemas/sectionCell" responses: - "201": + "200": description: Created - Section cell added successfully. Returns the created cell. "400": description: Bad Request - Invalid input (e.g., missing content or sectionId). @@ -102,7 +102,7 @@ paths: description: The ID of the section cell to remove. example: 509 responses: - "204": + "200": description: No Content - Section cell removed successfully. "400": description: Bad Request - Invalid ID format. diff --git a/documentation/openapi/src/main.yaml b/documentation/openapi/src/main.yaml index 6cf5cb6..6239610 100644 --- a/documentation/openapi/src/main.yaml +++ b/documentation/openapi/src/main.yaml @@ -108,6 +108,8 @@ paths: $ref: "./adminApi.yaml#/paths/~1admin~1projects~1{projectId}" /admin/make-admin/{userId}: $ref: "./adminApi.yaml#/paths/~1admin~1make-admin~1{userId}" + /admin/create-account: + $ref: "./adminApi.yaml#/paths/~1admin~1create-account" # ____ _ _ _ ____ ___ # / ___|| |__ __ _ _ __ ___ __| | / \ | _ \_ _| diff --git a/documentation/openapi/src/sharedApi.yaml b/documentation/openapi/src/sharedApi.yaml index e7d31d3..15fa7bc 100644 --- a/documentation/openapi/src/sharedApi.yaml +++ b/documentation/openapi/src/sharedApi.yaml @@ -176,7 +176,7 @@ paths: $ref: "./main.yaml#/components/schemas/appointment" # Assuming request uses same model structure # Potentially add projectId or targetUserId here responses: - "202": # Accepted seems appropriate for a request + "200": # Accepted seems appropriate for a request description: Accepted - Appointment request submitted. "400": description: Bad Request - Invalid appointment details. diff --git a/documentation/openapi/src/unauthApi.yaml b/documentation/openapi/src/unauthApi.yaml index ad4709e..7c5545b 100644 --- a/documentation/openapi/src/unauthApi.yaml +++ b/documentation/openapi/src/unauthApi.yaml @@ -18,7 +18,7 @@ paths: tags: - Unauth API responses: - "201": + "200": description: Created - Account finalized and pending admin validation. Returns the user profile. "400": description: Bad Request - Problem processing the token or user data derived from it. @@ -39,7 +39,7 @@ paths: description: The ID of the project to request joining. example: 15 responses: # Moved responses block to correct level - "202": + "200": description: Accepted - Join request submitted and pending approval. "400": description: Bad Request - Invalid project ID format @@ -54,7 +54,7 @@ paths: tags: - Unauth API responses: - "202": + "200": description: Accepted - Become admin request submitted and pending approval. "400": description: Bad Request - Invalid project ID format or already member/request pending.