39 Commits

Author SHA1 Message Date
47be7d340b fix: formatter
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 13s
Format / formatting (pull_request) Successful in 7s
2025-05-14 09:06:52 +02:00
232d10b164 fix: formatter
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 43s
CI / build (push) Successful in 13s
Format / formatting (pull_request) Failing after 6s
2025-05-14 09:04:35 +02:00
bc7ce888ad fix: build
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 44s
CI / build (push) Successful in 12s
Format / formatting (pull_request) Failing after 5s
2025-05-13 20:00:51 +02:00
ed67a3734a fix: fix build
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 43s
CI / build (push) Failing after 12s
2025-05-13 19:56:03 +02:00
95eb154556 fix: eslint
Some checks failed
Format / formatting (push) Failing after 7s
Build / build (push) Successful in 43s
CI / build (push) Failing after 11s
2025-05-13 19:52:31 +02:00
19fef63b0e fix: merge
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 45s
CI / build (push) Failing after 11s
2025-05-11 21:18:38 +02:00
1fd95265ea fix: this is not gonna work in time, switching to mock mod for canvas so we can show what we supposed to have atleast (local canvas) and we can explain what is the problem (you can disable the mock by putting the var USE_MOCK to false in CanvasItem.vue but dont do it) 2025-05-11 21:17:09 +02:00
3ef2d8a198 fix: join or create
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 43s
CI / build (push) Failing after 10s
2025-05-11 20:37:57 +02:00
6b49bbbe57 fix: handlers weren't used properly in the unauth code
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 10s
2025-05-10 23:35:30 +02:00
4c15cab607 fix: still working on the pending
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 12s
2025-05-10 23:16:08 +02:00
abfe92bc87 Merge: branch 'backend-test' into front_foundation
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 9s
2025-05-10 21:54:20 +02:00
85b4fe6a4c fix: finalize logic
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 10s
2025-05-10 21:48:41 +02:00
f2448a029f added two endpoints necessary for routing in project request phase
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-05-10 20:39:45 +02:00
cef4daef15 feat: finalize2
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 13s
2025-05-10 18:10:39 +02:00
f5aba70017 feat: add finalise logic
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 12s
2025-05-10 17:59:05 +02:00
27adc81ddc fix: minor change
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 9s
2025-05-10 01:16:00 +02:00
48f14e8a04 Merge branch 'backend-test' into front_foundation
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 10s
2025-05-09 22:59:23 +02:00
d4533ea725 added an endpoint to see if useraccuont is pending or not
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 42s
CI / build (push) Successful in 11s
2025-05-09 21:23:35 +02:00
7fc06035c7 fix: unauth users redirection
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 43s
CI / build (push) Failing after 10s
2025-05-09 18:47:37 +02:00
1b559f29b7 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation 2025-05-09 02:24:33 +02:00
63327bc312 fix: config stuff, do make keycloak first
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 43s
CI / build (push) Failing after 10s
2025-05-09 00:40:41 +02:00
f0cef41e2b Merge branch 'main' of https://gitea.piair.dev/piair/MyINPulse into front_foundation 2025-05-08 18:46:27 +02:00
7f16cdc86f fix: commit before merge 2025-05-08 18:11:50 +02:00
72d6f49995 Merge remote-tracking branch 'origin/main' into front_foundation 2025-05-08 18:08:18 +02:00
695ec5d9b8 fix: merge depuis backend-test
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 9s
Format / formatting (pull_request) Successful in 7s
2025-05-08 16:32:06 +02:00
0abafb4f7f Merge remote-tracking branch 'origin/backend-test' into front_foundation 2025-05-08 16:30:44 +02:00
3cd63e78e9 fix: changement de makefile 2025-05-08 16:30:42 +02:00
255af7ee7f feat: final test in sharedApi passing, it took a while to find where the bug is getAppointments by project
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-05-07 20:57:03 +02:00
3b308cfa6d fix: my bad 403 error codes are never thrown by src code, now is up to date
All checks were successful
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 39s
CI / build (push) Successful in 10s
2025-05-07 11:44:09 +02:00
d039105f0a Merge remote-tracking branch 'origin/backend-test' into front_foundation 2025-05-07 11:16:15 +02:00
0a15dbbf2d fix: merge errors 2025-05-07 11:16:12 +02:00
d1fce63ac5 Merge remote-tracking branch 'origin/main' into front_foundation 2025-05-07 11:09:51 +02:00
d9aaa225aa Merge pull request 'Fix 403 errors' (#12) from backend-test into main
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 11s
Reviewed-on: #12
Reviewed-by: adnane <adnane.alami@bordeaux-inp.fr>
Reviewed-by: omar <omar.el_alaoui_el_ismaili@bordeaux-inp.fr>
Reviewed-by: Theo <tlelez@enseirb-matmeca.fr>
Reviewed-by: anas <anas.maillal@bordeaux-inp.fr>
2025-05-07 11:08:57 +02:00
d31bf259dd Merge branch 'main' into backend-test
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 11s
Format / formatting (pull_request) Successful in 6s
2025-05-07 11:06:30 +02:00
43b40c9432 feat: just added 403 response
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 10s
Format / formatting (pull_request) Successful in 5s
2025-05-07 11:04:24 +02:00
e84f69c21a fix: unused imports
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-05-07 11:02:08 +02:00
c76e83f2bf feat: changed endpoints
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 8s
2025-05-07 11:00:15 +02:00
1f0f9196c4 feat: fixed 403 errors
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 11s
2025-05-07 10:45:38 +02:00
40e577ef07 Merge pull request 'backend-test' (#10) from backend-test into main
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
Reviewed-on: #10
Reviewed-by: adnane <adnane.alami@bordeaux-inp.fr>
Reviewed-by: anas <anas.maillal@bordeaux-inp.fr>
Reviewed-by: omar <omar.el_alaoui_el_ismaili@bordeaux-inp.fr>
2025-05-07 10:43:30 +02:00
29 changed files with 3329 additions and 1320 deletions

View File

@ -33,8 +33,6 @@ dev-front: clean vite keycloak
@cp config/frontdev.docker-compose.yaml docker-compose.yaml
@docker compose up -d --build
@cd ./front/MyINPulse-front/ && npm run dev
@echo "cd MyINPulse-back" && echo 'export $$(cat .env | xargs)'
@echo "./gradlew bootRun --args='--server.port=8081'"
prod: clean keycloak
@cp config/prod.env front/MyINPulse-front/.env
@ -42,7 +40,6 @@ prod: clean keycloak
@cp config/prod.env .env
@cp config/prod.docker-compose.yaml docker-compose.yaml
@docker compose up -d --build

View File

@ -56,12 +56,18 @@ public class WebSecurityCustomConfiguration {
http.authorizeHttpRequests(
authorize ->
authorize
.requestMatchers("/entrepreneur/**", "/shared/**")
.requestMatchers("/entrepreneur/**")
.access(hasRole("REALM_MyINPulse-entrepreneur"))
.requestMatchers("/admin/**", "/shared/**")
.requestMatchers("/admin/**")
.access(hasRole("REALM_MyINPulse-admin"))
.requestMatchers("/shared/**")
.hasAnyRole(
"REALM_MyINPulse-admin",
"REALM_MyINPulse-entrepreneur")
.requestMatchers("/unauth/**")
.authenticated())
.authenticated()
.anyRequest()
.denyAll())
.oauth2ResourceServer(
oauth2 ->
oauth2.jwt(

View File

@ -95,4 +95,22 @@ public class EntrepreneurApi {
@RequestBody Project project, @AuthenticationPrincipal Jwt principal) {
entrepreneurApiService.requestNewProject(project, principal.getClaimAsString("email"));
}
/*
* <p>Endpoint to check if project is has already been validated by an admin
*/
@GetMapping("/entrepreneur/projects/project-is-active")
public Boolean checkIfProjectValidated(@AuthenticationPrincipal Jwt principal) {
return entrepreneurApiService.checkIfEntrepreneurProjectActive(
principal.getClaimAsString("email"));
}
/*
* <p>Endpoint to check if a user requested a project (used when project is pending)
*/
@GetMapping("/entrepreneur/projects/has-pending-request")
public Boolean checkIfHasRequested(@AuthenticationPrincipal Jwt principal) {
return entrepreneurApiService.entrepreneurHasPendingRequestedProject(
principal.getClaimAsString("email"));
}
}

View File

@ -1,9 +1,8 @@
package enseirb.myinpulse.controller;
import enseirb.myinpulse.model.Administrator;
import enseirb.myinpulse.model.Entrepreneur;
import enseirb.myinpulse.service.AdminApiService;
import enseirb.myinpulse.service.EntrepreneurApiService;
import enseirb.myinpulse.service.UtilsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@ -16,15 +15,15 @@ import org.springframework.web.bind.annotation.*;
public class UnauthApi {
private final EntrepreneurApiService entrepreneurApiService;
private final AdminApiService adminApiService;
private final UtilsService utilsService;
@Autowired
UnauthApi(EntrepreneurApiService entrepreneurApiService, AdminApiService administratorService) {
UnauthApi(EntrepreneurApiService entrepreneurApiService, UtilsService utilsService) {
this.entrepreneurApiService = entrepreneurApiService;
this.adminApiService = administratorService;
this.utilsService = utilsService;
}
@GetMapping("/unauth/finalize")
@PostMapping("/unauth/finalize")
public void createAccount(@AuthenticationPrincipal Jwt principal) {
boolean sneeStatus;
if (principal.getClaimAsString("sneeStatus") != null) {
@ -50,21 +49,13 @@ public class UnauthApi {
course,
sneeStatus,
true);
entrepreneurApiService.createAccount(e);
}
/*
* These bottom endpoints are meant for testing only
* and should not py merged to main
*
*/
@GetMapping("/unauth/getAllAdmins")
public Iterable<Administrator> getEveryAdmin() {
return this.adminApiService.getAllAdmins();
}
@GetMapping("/unauth/getAllEntrepreneurs")
public Iterable<Entrepreneur> getEveryEntrepreneur() {
return this.entrepreneurApiService.getAllEntrepreneurs();
@GetMapping("/unauth/check-if-not-pending")
public Boolean checkAccountStatus(@AuthenticationPrincipal Jwt principal) {
// Throws 404 if user not found
return utilsService.checkEntrepreneurNotPending(principal.getClaimAsString("email"));
}
}

View File

@ -15,4 +15,6 @@ public interface SectionCellRepository extends JpaRepository<SectionCell, Long>
Iterable<SectionCell> findByProjectSectionCellAndSectionIdAndModificationDateBefore(
Project project, long sectionId, LocalDateTime date);
Iterable<SectionCell> findByProjectSectionCell(Project project);
}

View File

@ -1,10 +1,12 @@
package enseirb.myinpulse.service;
import static enseirb.myinpulse.model.ProjectDecisionValue.PENDING;
import static enseirb.myinpulse.model.ProjectDecisionValue.ACTIVE;
import enseirb.myinpulse.model.Entrepreneur;
import enseirb.myinpulse.model.Project;
import enseirb.myinpulse.model.SectionCell;
import enseirb.myinpulse.model.User;
import enseirb.myinpulse.service.database.*;
import org.apache.logging.log4j.LogManager;
@ -234,4 +236,49 @@ public class EntrepreneurApiService {
public Iterable<Entrepreneur> getAllEntrepreneurs() {
return entrepreneurService.getAllEntrepreneurs();
}
/**
* Checks if an entrepreneur with the given email has a project that is ACTIVE.
*
* @param email The email of the entrepreneur.
* @return true if the entrepreneur has an active project, false otherwise.
*/
public Boolean checkIfEntrepreneurProjectActive(String email) {
User user = this.userService.getUserByEmail(email);
if (user == null) {
return false;
}
Long userId = user.getIdUser();
Entrepreneur entrepreneur = this.entrepreneurService.getEntrepreneurById(userId);
if (entrepreneur == null) {
return false;
}
Project proposedProject = entrepreneur.getProjectProposed();
return proposedProject != null && proposedProject.getProjectStatus() == ACTIVE;
}
/**
* Checks if an entrepreneur with the given email has proposed a project.
*
* @param email The email of the entrepreneur.
* @return true if the entrepreneur has a proposed project, false otherwise.
*/
public Boolean entrepreneurHasPendingRequestedProject(String email) {
User user = this.userService.getUserByEmail(email);
if (user == null) {
return false;
}
Long userId = user.getIdUser();
Entrepreneur entrepreneur = this.entrepreneurService.getEntrepreneurById(userId);
if (entrepreneur == null) {
return false;
}
Project proposedProject = entrepreneur.getProjectProposed();
if (entrepreneur.getProjectProposed() == null) {
return false;
}
return proposedProject.getProjectStatus() == PENDING;
}
}

View File

@ -26,8 +26,10 @@ import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Service
public class SharedApiService {
@ -169,18 +171,26 @@ public class SharedApiService {
"User {} tried to check the appointments related to the project {}",
mail,
projectId);
Iterable<SectionCell> sectionCells =
this.sectionCellService.getSectionCellsByProject(
projectService.getProjectById(projectId),
2L); // sectionId useless in this function ?
List<Appointment> appointments = new ArrayList<Appointment>();
sectionCells.forEach(
Project project = projectService.getProjectById(projectId);
Iterable<SectionCell> sectionCellsIterable =
this.sectionCellService.getSectionCellsByProject(project);
// Use a Set to collect unique appointments
Set<Appointment> uniqueAppointments = new HashSet<>();
sectionCellsIterable.forEach(
sectionCell -> {
appointments.addAll(
List<Appointment> sectionAppointments =
this.sectionCellService.getAppointmentsBySectionCellId(
sectionCell.getIdSectionCell()));
sectionCell.getIdSectionCell());
// Add all appointments from this section cell to the Set
uniqueAppointments.addAll(sectionAppointments);
});
return appointments;
// Convert the Set back to a List for the return value
return new ArrayList<>(uniqueAppointments);
}
public void getPDFReport(long appointmentId, String mail)

View File

@ -72,4 +72,10 @@ public class UtilsService {
return false;
}
}
public Boolean checkEntrepreneurNotPending(String email) {
// Throws 404 if user not found
User user = userService.getUserByEmail(email);
return !user.isPending();
}
}

View File

@ -14,6 +14,7 @@ import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -118,6 +119,18 @@ public class SectionCellService {
return this.sectionCellRepository.findByProjectSectionCellAndSectionId(project, sectionId);
}
public Iterable<SectionCell> getSectionCellsByProject(Project project) {
logger.info("Fetching SectionCells for Project ID: {}", project.getIdProject());
Iterable<SectionCell> sectionCells =
this.sectionCellRepository.findByProjectSectionCell(project);
List<SectionCell> sectionCellList = new ArrayList<>();
sectionCells.forEach(
cell -> {
sectionCellList.add(cell);
});
return sectionCellList;
}
public Long getProjectId(Long sectionCellId) {
SectionCell sectionCell = getSectionCellById(sectionCellId);
Project sectionProject = sectionCell.getProjectSectionCell();

View File

@ -1,6 +1,6 @@
spring.application.name=myinpulse
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:7080/realms/test/protocol/openid-connect/certs
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/test
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:7080/realms/${VITE_KEYCLOAK_REALM}/protocol/openid-connect/certs
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/${VITE_KEYCLOAK_REALM}
spring.datasource.url=jdbc:postgresql://${DATABASE_URL}/${BACKEND_DB}
spring.datasource.username=${BACKEND_USER}
spring.datasource.password=${BACKEND_PASSWORD}

View File

@ -8,9 +8,10 @@ import static org.mockito.Mockito.when;
import enseirb.myinpulse.model.*;
import enseirb.myinpulse.service.SharedApiService;
import enseirb.myinpulse.service.database.*;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import enseirb.myinpulse.service.UtilsService;
import com.itextpdf.text.DocumentException;
import org.junit.jupiter.api.BeforeAll; // Use BeforeAll for static setup
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; // Keep this import
@ -22,8 +23,6 @@ import org.springframework.web.server.ResponseStatusException;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import java.io.IOException;
import java.net.URISyntaxException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
@ -712,6 +711,129 @@ public class SharedApiServiceTest {
assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatusCode());
}
@PersistenceContext // Inject EntityManager
private EntityManager entityManager;
// Assume these static variables are defined elsewhere in your test class
// private static Project staticAuthorizedProject;
// private static String staticAuthorizedMail;
// private static Administrator staticAuthorizedAdmin;
// Assume getTestSectionCell, getTestProject, getTestAdmin, getTestAppointment, TestUtils.toList
// are defined elsewhere
@Test
void testGetAppointmentsByProjectId_Authorized_Found() {
// Arrange: Create specific SectionCells and Appointments for this test
SectionCell cell1 =
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject, 1L, "Cell 1 Test", LocalDateTime.now()));
SectionCell cell2 =
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject, 2L, "Cell 2 Test", LocalDateTime.now()));
Project otherProject =
projectService.addNewProject(
getTestProject(
"other_project_app_test",
administratorService.addAdministrator(
getTestAdmin("other_admin_app_test"))));
SectionCell otherProjectCell =
sectionCellService.addNewSectionCell(
getTestSectionCell(
otherProject,
1L,
"Other Project Cell App Test",
LocalDateTime.now()));
// Create Appointments with SectionCells lists (Owning side)
Appointment app1 =
getTestAppointment(
LocalDate.now().plusDays(10),
LocalTime.NOON,
LocalTime.of(0, 30),
"Place 1 App Test",
"Subject 1 App Test",
List.of(cell1), // This links Appointment to SectionCell
null);
Appointment savedApp1 = appointmentService.addNewAppointment(app1);
Appointment app2 =
getTestAppointment(
LocalDate.now().plusDays(11),
LocalTime.NOON.plusHours(1),
LocalTime.of(1, 0),
"Place 2 App Test",
"Subject 2 App Test",
List.of(cell1, cell2), // This links Appointment to SectionCells
null);
Appointment savedApp2 = appointmentService.addNewAppointment(app2);
Appointment otherApp =
getTestAppointment(
LocalDate.now().plusDays(12),
LocalTime.MIDNIGHT,
LocalTime.of(0, 15),
"Other Place App Test",
"Other Subject App Test",
List.of(otherProjectCell), // This links Appointment to SectionCell
null);
Appointment savedOtherApp =
appointmentService.addNewAppointment(otherApp); // Capture saved entity
// --- IMPORTANT DEBUGGING STEPS ---
// Flush pending changes to the database (including join table inserts)
entityManager.flush();
// Clear the persistence context cache to ensure entities are loaded fresh from the database
entityManager.clear();
// --- END IMPORTANT DEBUGGING STEPS ---
// --- Add Debug Logging Here ---
// Re-fetch cells to see their state after saving Appointments and flushing/clearing cache
// These fetches should load from the database due to entityManager.clear()
SectionCell fetchedCell1_postPersist =
sectionCellService.getSectionCellById(cell1.getIdSectionCell());
SectionCell fetchedCell2_postPersist =
sectionCellService.getSectionCellById(cell2.getIdSectionCell());
SectionCell fetchedOtherCell_postPersist =
sectionCellService.getSectionCellById(otherProjectCell.getIdSectionCell());
// Access the lazy collections to see if they are populated from the DB
// This access should trigger lazy loading if the data is in the DB
List<Appointment> cell1Apps_postPersist =
fetchedCell1_postPersist.getAppointmentSectionCell();
List<Appointment> cell2Apps_postPersist =
fetchedCell2_postPersist.getAppointmentSectionCell();
List<Appointment> otherCellApps_postPersist =
fetchedOtherCell_postPersist.getAppointmentSectionCell();
// Ensure logging is enabled in SharedApiService and SectionCellService methods called below
Iterable<Appointment> result =
sharedApiService.getAppointmentsByProjectId(
staticAuthorizedProject.getIdProject(), // Use static project ID
staticAuthorizedMail); // Use static authorized mail
List<Appointment> resultList = TestUtils.toList(result);
// Assert
assertEquals(2, resultList.size());
assertTrue(
resultList.stream()
.anyMatch(a -> a.getIdAppointment().equals(savedApp1.getIdAppointment())));
assertTrue(
resultList.stream()
.anyMatch(a -> a.getIdAppointment().equals(savedApp2.getIdAppointment())));
assertFalse(
resultList.stream()
.anyMatch(
a ->
a.getIdAppointment()
.equals(savedOtherApp.getIdAppointment())));
}
/*
* Tests creating a new appointment request when the user is authorized
* for the project linked to the appointment's section cell.
@ -797,288 +919,4 @@ public class SharedApiServiceTest {
a.getIdAppointment()
.equals(createdAppointment.getIdAppointment())));
}
/*
* Tests creating a new appointment request when the user is not authorized
* for the project linked to the appointment's section cell.
* Verifies that an Unauthorized ResponseStatusException is thrown and the appointment is not saved.
*/
@Test
void testCreateAppointmentRequest_Unauthorized() {
// Arrange: Create transient appointment linked to a cell in the static *unauthorized*
// project
LocalDate date = LocalDate.parse("2026-01-01");
LocalTime time = LocalTime.parse("10:00:00");
LocalTime duration = LocalTime.parse("00:30:00");
String place = "Meeting Room";
String subject = "Discuss Project";
String reportContent = "Initial Report";
SectionCell linkedCell =
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticUnauthorizedProject,
1L,
"Related Section Content",
LocalDateTime.now()));
Report newReport = getTestReport(reportContent);
Appointment newAppointment =
getTestAppointment(
date, time, duration, place, subject, List.of(linkedCell), newReport);
// mockUtilsService is configured in BeforeEach to deny staticUnauthorizedMail for
// staticUnauthorizedProject
// Act & Assert
ResponseStatusException exception =
assertThrows(
ResponseStatusException.class,
() -> {
sharedApiService.createAppointmentRequest(
newAppointment,
staticUnauthorizedMail); // Unauthorized user mail
});
assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatusCode());
}
/*
_____ _ _ _
| ___|_ _(_) | ___ __| |
| |_ / _` | | |/ _ \/ _` |
| _| (_| | | | __/ (_| |
|_| \__,_|_|_|\___|\__,_|
_____ _____ ____ _____
|_ _| ____/ ___|_ _|
| | | _| \___ \ | |
| | | |___ ___) || |
|_| |_____|____/ |_|
*/
/*
* Tests retrieving entrepreneurs linked to a project when the user is authorized
* and entrepreneurs are linked.
* Verifies that the correct entrepreneurs are returned.
*/
// Tests getEntrepreneursByProjectId
/*@Test*/
// Commenting out failing test
void testGetEntrepreneursByProjectId_Authorized_Found() {
// Arrange: Create entrepreneur and link to static project for this test
Entrepreneur linkedEntrepreneur =
entrepreneurService.addEntrepreneur(
getTestEntrepreneur("linked_entrepreneur_test"));
// Fetch the static project to update its list
Project projectToUpdate =
projectService.getProjectById(staticAuthorizedProject.getIdProject());
projectToUpdate.updateListEntrepreneurParticipation(linkedEntrepreneur);
projectService.addNewProject(projectToUpdate); // Save the updated project
Entrepreneur otherEntrepreneur =
entrepreneurService.addEntrepreneur(getTestEntrepreneur("other_entrepreneur_test"));
// Act
Iterable<Entrepreneur> result =
sharedApiService.getEntrepreneursByProjectId(
staticAuthorizedProject.getIdProject(), staticAuthorizedMail);
List<Entrepreneur> resultList = TestUtils.toList(result);
// Assert
assertEquals(1, resultList.size());
assertTrue(
resultList.stream()
.anyMatch(e -> e.getIdUser().equals(linkedEntrepreneur.getIdUser())));
assertFalse(
resultList.stream()
.anyMatch(e -> e.getIdUser().equals(otherEntrepreneur.getIdUser())));
}
/*
* Tests retrieving appointments linked to a project's section cells when the user is authorized
* and such appointments exist.
* Verifies that the correct appointments are returned.
*/
// Tests getAppointmentsByProjectId
/*@Test*/
// Commenting out failing test
void testGetAppointmentsByProjectId_Authorized_Found() {
// Arrange: Create specific SectionCells and Appointments for this test
SectionCell cell1 =
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject, 1L, "Cell 1 Test", LocalDateTime.now()));
SectionCell cell2 =
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject, 2L, "Cell 2 Test", LocalDateTime.now()));
Project otherProject =
projectService.addNewProject(
getTestProject(
"other_project_app_test",
administratorService.addAdministrator(
getTestAdmin("other_admin_app_test"))));
SectionCell otherProjectCell =
sectionCellService.addNewSectionCell(
getTestSectionCell(
otherProject,
1L,
"Other Project Cell App Test",
LocalDateTime.now()));
Appointment app1 =
getTestAppointment(
LocalDate.now().plusDays(10),
LocalTime.NOON,
LocalTime.of(0, 30),
"Place 1 App Test",
"Subject 1 App Test",
List.of(cell1),
null);
Appointment savedApp1 = appointmentService.addNewAppointment(app1);
Appointment app2 =
getTestAppointment(
LocalDate.now().plusDays(11),
LocalTime.NOON.plusHours(1),
LocalTime.of(1, 0),
"Place 2 App Test",
"Subject 2 App Test",
List.of(cell1, cell2),
null);
Appointment savedApp2 = appointmentService.addNewAppointment(app2);
Appointment otherApp =
getTestAppointment(
LocalDate.now().plusDays(12),
LocalTime.MIDNIGHT,
LocalTime.of(0, 15),
"Other Place App Test",
"Other Subject App Test",
List.of(otherProjectCell),
null);
appointmentService.addNewAppointment(otherApp);
// Act
Iterable<Appointment> result =
sharedApiService.getAppointmentsByProjectId(
staticAuthorizedProject.getIdProject(), // Use static project ID
staticAuthorizedMail); // Use static authorized mail
List<Appointment> resultList = TestUtils.toList(result);
// Assert
assertEquals(2, resultList.size());
assertTrue(
resultList.stream()
.anyMatch(a -> a.getIdAppointment().equals(savedApp1.getIdAppointment())));
assertTrue(
resultList.stream()
.anyMatch(a -> a.getIdAppointment().equals(savedApp2.getIdAppointment())));
assertFalse(
resultList.stream()
.anyMatch(
a ->
a.getIdAppointment()
.equals(otherApp.getIdAppointment()))); // Ensure
// appointment from other project is not included
}
/*
* Tests generating a PDF report for an appointment when the user is authorized
* for the project linked to the appointment's section cell.
* Verifies that no authorization exception is thrown. (Note: File I/O is mocked).
*/
// Tests getPDFReport (Focus on authorization and data retrieval flow)
/*@Test*/
// Commenting out failing test
void testGetPDFReport_Authorized() throws DocumentException, URISyntaxException, IOException {
// Arrange: Create a specific appointment linked to the static authorized project
SectionCell cell =
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject,
1L,
"Cell for PDF Test",
LocalDateTime.now()));
Report report =
new Report(null, "PDF Report Content // Point 2 PDF Content"); // ID set by DB
Appointment appointment =
getTestAppointment(
LocalDate.now().plusDays(20),
LocalTime.of(14, 0),
LocalTime.of(0, 45),
"Salle PDF",
"PDF Subject",
List.of(cell),
report);
Appointment savedAppointment = appointmentService.addNewAppointment(appointment);
// Mock getAppointmentById to return the saved appointment for the service to use
when(appointmentService.getAppointmentById(eq(savedAppointment.getIdAppointment())))
.thenReturn(savedAppointment);
// mockUtilsService is configured in BeforeEach to allow staticAuthorizedMail for
// staticAuthorizedProject
// Act & Assert (Just assert no authorization exception is thrown)
assertDoesNotThrow(
() ->
sharedApiService.getPDFReport(
savedAppointment.getIdAppointment(), staticAuthorizedMail));
// Note: Actual PDF generation and file operations are not tested here,
// as that requires mocking external libraries and file system operations.
}
/*
* Tests generating a PDF report for an appointment when the user is not authorized
* for the project linked to the appointment's section cell.
* Verifies that an Unauthorized ResponseStatusException is thrown.
*/
/*@Test*/
// Commenting out failing test
void testGetPDFReport_Unauthorized() {
// Arrange: Create a specific appointment linked to the static *unauthorized* project
SectionCell cell =
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticUnauthorizedProject,
1L,
"Cell for Unauthorized PDF Test",
LocalDateTime.now()));
Report report = new Report(null, "Unauthorized PDF Report Content");
Appointment appointment =
getTestAppointment(
LocalDate.now().plusDays(21),
LocalTime.of(15, 0),
LocalTime.of(0, 30),
"Salle Unauthorized PDF",
"Unauthorized PDF Subject",
List.of(cell),
report);
Appointment savedAppointment = appointmentService.addNewAppointment(appointment);
// Mock getAppointmentById to return the saved appointment
when(appointmentService.getAppointmentById(eq(savedAppointment.getIdAppointment())))
.thenReturn(savedAppointment);
// mockUtilsService is configured in BeforeEach to DENY staticUnauthorizedMail for
// staticUnauthorizedProject
// Act & Assert
ResponseStatusException exception =
assertThrows(
ResponseStatusException.class,
() -> {
sharedApiService.getPDFReport(
savedAppointment.getIdAppointment(),
staticUnauthorizedMail); // Unauthorized user mail
});
assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatusCode());
}
}

View File

@ -16,7 +16,7 @@ BACKEND_PASSWORD=backend_db_user_password
DATABASE_URL=localhost:5433
VITE_KEYCLOAK_URL=http://localhost:7080
VITE_KEYCLOAK_CLIENT_ID=myinpulse-dev
VITE_KEYCLOAK_REALM=test
VITE_KEYCLOAK_CLIENT_ID=MyINPulse-vite
VITE_KEYCLOAK_REALM=MyINPulse
VITE_APP_URL=http://localhost:5173
VITE_BACKEND_URL=http://localhost:8081/

View File

@ -22,6 +22,8 @@ paths:
description: Bad Request - Invalid project data provided (e.g., missing required fields).
"401":
description: Unauthorized - Authentication required or invalid token.
"403":
description: Bad Token - Invalid Keycloack configuration.
post:
operationId: addProjectManually
@ -49,6 +51,8 @@ paths:
description: Bad Request - Project already exists.
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/projects/pending:
@ -70,7 +74,9 @@ paths:
items:
$ref: "./main.yaml#/components/schemas/project" # Assuming pending projects use the same schema
"401":
description: Unauthorized.
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/request-join:
get:
@ -92,6 +98,8 @@ paths:
$ref: "./main.yaml#/components/schemas/joinRequest"
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/request-join/decision/{joinRequestId}:
post:
@ -121,7 +129,9 @@ paths:
"400":
description: Bad Request - Invalid input (e.g., missing decision).
"401":
description: Unauthorized.
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/projects/pending/decision:
@ -150,6 +160,8 @@ paths:
description: Bad Request - Invalid input (e.g., missing decision).
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/pending-accounts: # Path updated
@ -172,6 +184,8 @@ paths:
$ref: "./main.yaml#/components/schemas/user-entrepreneur"
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/accounts/validate/{userId}:
post: # Changed to POST as it changes state
@ -195,7 +209,8 @@ paths:
description: No Content - Account validated successfully.
"400":
description: Bad Request - Invalid user ID format.
"403":
description: Bad Token - Invalid Keycloack configuration.
"401":
description: Unauthorized.
@ -217,6 +232,8 @@ paths:
type: array
items:
$ref: "./main.yaml#/components/schemas/appointment"
"403":
description: Bad Token - Invalid Keycloack configuration.
"404":
description: no appointments found.
"401":
@ -254,6 +271,8 @@ paths:
schema: { $ref: "./main.yaml#/components/schemas/report" }
"400":
description: Bad Request - Invalid input (e.g., missing content, invalid appointment ID format).
"403":
description: Bad Token - Invalid Keycloack configuration.
"401":
description: Unauthorized.
@ -288,6 +307,8 @@ paths:
schema: { $ref: "./main.yaml#/components/schemas/report" }
"400":
description: Bad Request - Invalid input (e.g., missing content).
"403":
description: Bad Token - Invalid Keycloack configuration.
"401":
description: Unauthorized.
@ -314,6 +335,8 @@ paths:
description: No Content - Project removed successfully.
"400":
description: Bad Request - Invalid project ID format.
"403":
description: Bad Token - Invalid Keycloack configuration.
"401":
description: Unauthorized.
@ -340,7 +363,9 @@ paths:
"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.
description: Bad Request - Invalid user ID format or user is already an admin.
"403":
description: Bad Token - Invalid Keycloack configuration.
"401":
description: Unauthorized.
@ -357,4 +382,6 @@ paths:
"200":
description: No Content - Admin user created successfully.
"401":
description: Unauthorized.
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.

View File

@ -1,957 +0,0 @@
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.

View File

@ -27,6 +27,8 @@ paths:
description: Bad Request - Invalid input (e.g., missing name).
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/sectionCells: # Base path
post:
@ -52,6 +54,8 @@ paths:
description: Bad Request - Invalid input (e.g., missing content or sectionId).
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/sectionCells/{sectionCellId}:
put:
@ -84,6 +88,8 @@ paths:
description: Bad Request - Invalid input or ID mismatch.
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
delete:
operationId: removeSectionCell
@ -110,6 +116,8 @@ paths:
description: Bad Request - sectionCell not found.
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/projects:
@ -133,4 +141,57 @@ paths:
"404":
description: Bad Request - Invalid input or ID mismatch.
"401":
description: Unauthorized or identity not found
description: Unauthorized or identity not found
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/projects/project-is-active:
get:
summary: checks if the project associated with an entrepreneur is active
description: returns a boolean if the project associated with an entrepreneur has an active status
(i.e has been validated by an admin). The user should be routed to LeanCanvas. any other response code
should be treated as false
tags:
- Entrepreneurs API
security:
- MyINPulse: [MyINPulse-entrepreneur]
parameters:
responses:
"200":
description: OK - got the value successfully any other response code should be treated as false.
content:
application/json:
schema:
type: boolean
"404":
description: Bad Request - Invalid input or ID mismatch.
"401":
description: Unauthorized or identity not found
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/projects/has-pending-request:
get:
summary: checks if the user has a pending projectRequest
description: returns a boolean if the project associated with an entrepreneur has a pending status
(i.e has not yet been validated by an admin). The user should be routed to a page telling him that he should
wait for admin validation. any other response code should be treated as false.
tags:
- Entrepreneurs API
security:
- MyINPulse: [MyINPulse-entrepreneur]
parameters:
responses:
"200":
description: OK - got the value successfully any other response code should be treated as false.
content:
application/json:
schema:
type: boolean
"404":
description: Bad Request - Invalid input or ID mismatch.
"401":
description: Unauthorized or identity not found
"403":
description: Bad Token - Invalid Keycloack configuration.

View File

@ -79,6 +79,10 @@ paths:
$ref: "./unauthApi.yaml#/paths/~1unauth~1finalize"
/unauth/request-join/{projectId}:
$ref: "./unauthApi.yaml#/paths/~1unauth~1request-join~1{projectId}"
/unauth/request-admin-role:
$ref: "./unauthApi.yaml#/paths/~1unauth~1request-admin-role"
/unauth/check-if-not-pending:
$ref: "./unauthApi.yaml#/paths/~1unauth~1check-if-not-pending"
# _ ____ __ __ ___ _ _ _ ____ ___
# / \ | _ \| \/ |_ _| \ | | / \ | _ \_ _|
@ -148,4 +152,8 @@ paths:
/entrepreneur/sectionCells:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells"
/entrepreneur/sectionCells/{sectionCellId}:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells~1{sectionCellId}"
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells~1{sectionCellId}"
/entrepreneur/projects/project-is-active:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects~1project-is-active"
/entrepreneur/projects/has-pending-request:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects~1has-pending-request"

View File

@ -36,7 +36,9 @@ paths:
items:
$ref: "./main.yaml#/components/schemas/sectionCell"
"400":
description: Bad Request - Invalid parameter format.
description: Bad Request - Invalid parameter format.
"403":
description: Bad Token - Invalid Keycloack configuration.
"401":
description: Unauthorized.
@ -68,7 +70,7 @@ paths:
"401":
description: Unauthorized.
"403":
description: Forbidden - User does not have access to this project.
description: Bad Token - Invalid Keycloack configuration.
"404":
description: Not Found - Project not found.
@ -97,7 +99,7 @@ paths:
"401":
description: Unauthorized.
"403":
description: Forbidden - User does not have access to this project.
description: Bad Token - Invalid Keycloack configuration.
"404":
description: Not Found - Project not found.
@ -126,6 +128,8 @@ paths:
type: array
items:
$ref: "./main.yaml#/components/schemas/appointment"
"403":
description: Bad Token - Invalid Keycloack configuration.
"401":
description: Unauthorized.
@ -156,6 +160,8 @@ paths:
format: binary
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/shared/appointments/request:
@ -180,7 +186,8 @@ paths:
description: Accepted - Appointment request submitted.
"400":
description: Bad Request - Invalid appointment details.
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.

View File

@ -24,6 +24,8 @@ paths:
description: Bad Request - Problem processing the token or user data derived from it.
"401":
description: Unauthorized - Valid authentication token required.
"403":
description: Bad Token - Invalid Keycloack configuration.
/unauth/request-join/{projectId}:
post:
summary: Request to join an existing project
@ -47,9 +49,11 @@ paths:
description: Already member/request pending.
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/unauth/request-admin-role:
post:
summary: Request to join an existing project
summary: Request to become an admin
description: Submits a request for the authenticated user (keycloack authenticated) to become an admin. Their role is then changed to admin in server and Keycloak. This requires approval from a project admin.
tags:
- Unauth API
@ -59,4 +63,28 @@ paths:
"400":
description: Bad Request - Invalid project ID format or already member/request pending.
"401":
description: Unauthorized.
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/unauth/check-if-not-pending:
get:
summary: Returns a boolean of whether the user's account is not pending
description: Returns a boolean with value `true` if the user's account is not pending and `false` if it is.
tags:
- Unauth API
responses:
"200":
description: Accepted - Become admin request submitted and pending approval.
content:
application/json:
schema:
type: boolean
"400":
description: Bad Request - Invalid project ID format or already member/request pending.
"401":
description: Unauthorized.
"404":
description: Bad Request - User not found in database.
"403":
description: Bad Token - Invalid Keycloack configuration.

View File

@ -1,9 +1,9 @@
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import { onMounted } from "vue";
import { useRouter } from "vue-router";
import { jwtDecode } from "jwt-decode"; // i hope this doesn't break the code later
import { store } from "../main.ts";
import { callApi } from "@/services/api.ts";
import { checkPending } from "@/services/Apis/Unauth";
import Header from "@/components/HeaderComponent.vue";
const router = useRouter();
@ -13,7 +13,7 @@ type TokenPayload = {
};
};
const customRequest = ref("");
//const customRequest = ref("");
onMounted(() => {
if (store.authenticated && store.user.token) {
@ -23,23 +23,44 @@ onMounted(() => {
if (roles.includes("MyINPulse-admin")) {
router.push("/admin");
} else if (roles.includes("MyINPulse-entrepreneur")) {
router.push("/canvas");
return;
}
if (roles.includes("MyINPulse-entrepreneur")) {
router.push("/canvas");
return;
}
checkPending(
(response) => {
const isValidated = response.data === true;
if (
isValidated &&
roles.includes("MyINPulse-entrepreneur")
) {
router.push("/canvas");
//router.push("/JorCproject");
} else {
router.push("/JorCproject");
//router.push("/finalize");
}
},
(error) => {
if (error.response?.status === 403) {
router.push("/finalize");
} else {
console.error(
"Unexpected error during checkPending",
error
);
}
}
);
} catch (err) {
console.error("Failed to decode token", err);
}
}
});
/*
const loading = ref(false);
const callApiWithLoading = async (path: string) => {
loading.value = true;
await callApi(path);
loading.value = false;
};
*/
</script>
<template>
@ -47,7 +68,7 @@ const callApiWithLoading = async (path: string) => {
<error-wrapper></error-wrapper>
<div class="auth-container">
<div class="auth-card">
<h1>Bienvenue</h1>
<h1>Bienvenue à MyINPulse</h1>
<div
class="status"
@ -65,11 +86,12 @@ const callApiWithLoading = async (path: string) => {
<div class="actions">
<button @click="store.login">Login</button>
<button @click="store.logout">Logout</button>
<button @click="store.signup">Signup-admin</button>
<!--<button @click="store.signup">Signup-admin</button>
<button @click="store.signup">Signup-Entrepreneur</button>
<button @click="store.refreshUserToken">Refresh Token</button>
<button @click="store.refreshUserToken">Refresh Token</button>-->
</div>
<!--
<div v-if="store.authenticated" class="token-section">
<p><strong>Access Token:</strong></p>
<pre>{{ store.user.token }}</pre>
@ -93,7 +115,7 @@ const callApiWithLoading = async (path: string) => {
/>
<button @click="callApi(customRequest)">Call</button>
</div>
</div>
</div>-->
</div>
</div>
</template>

View File

@ -110,7 +110,7 @@ const props = defineProps<{
isAdmin: boolean;
}>();
const IS_MOCK_MODE = false;
const IS_MOCK_MODE = true;
const IS_ADMIN = props.isAdmin;
const expanded = ref(false);

View File

@ -40,6 +40,18 @@ const router = createRouter({
name: "JorCproject",
component: () => import("../views/JoinOrCreatProjectForEntrep.vue"),
},
{
path: "/finalize",
name: "finalize",
component: () => import("../views/FinalizeAccount.vue"),
},
{
path: "/pending-approval",
name: "PendingApproval",
component: () => import("@/views/PendingApproval.vue"),
},
],
});

View File

@ -123,10 +123,58 @@ function removeSectionCell(
});
}
// Checks if the entrepreneur has a pending project request
function checkPendingProjectRequest(
onSuccessHandler?: (response: AxiosResponse<boolean>) => void,
onErrorHandler?: (error: AxiosError) => void
): void {
axiosInstance
.get("/entrepreneur/projects/has-pending-request")
.then((response) => {
if (onSuccessHandler) {
onSuccessHandler(response);
} else {
defaultApiSuccessHandler(response);
}
})
.catch((error: AxiosError) => {
if (onErrorHandler) {
onErrorHandler(error);
} else {
defaultApiErrorHandler(error);
}
});
}
// Checks if the entrepreneur has an active project
function checkIfProjectIsActive(
onSuccessHandler?: (response: AxiosResponse<boolean>) => void,
onErrorHandler?: (error: AxiosError) => void
): void {
axiosInstance
.get("/entrepreneur/projects/project-is-active")
.then((response) => {
if (onSuccessHandler) {
onSuccessHandler(response);
} else {
defaultApiSuccessHandler(response);
}
})
.catch((error: AxiosError) => {
if (onErrorHandler) {
onErrorHandler(error);
} else {
defaultApiErrorHandler(error);
}
});
}
export {
getEntrepreneurProjectId,
requestProjectCreation,
addSectionCell,
modifySectionCell,
removeSectionCell,
checkPendingProjectRequest,
checkIfProjectIsActive,
};

View File

@ -74,8 +74,30 @@ function getAllEntrepreneurs(
});
}
function checkPending(
onSuccessHandler?: (response: AxiosResponse<boolean>) => void,
onErrorHandler?: (error: AxiosError) => void
): void {
axiosInstance
.get("/unauth/check-if-not-pending")
.then((response) => {
if (onSuccessHandler) {
onSuccessHandler(response);
} else {
defaultApiSuccessHandler(response);
}
})
.catch((error: AxiosError) => {
if (onErrorHandler) {
onErrorHandler(error);
} else {
defaultApiErrorHandler(error);
}
});
}
export {
finalizeAccount,
getAllEntrepreneurs,
checkPending,
// requestJoinProject, // Not yet implemented [cite: 4]
};

View File

@ -83,6 +83,7 @@ const createFirstAdmin = () => {
}
);
};
onMounted(createFirstAdmin);
const fetchProjects = () => {

View File

@ -0,0 +1,81 @@
<template>
<Header />
<div class="finalize-page">
<div class="loader-container">
<button class="return-button" @click="store.logout">Logout</button>
<div class="spinner"></div>
<p>Finalisation du compte en cours...</p>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted } from "vue";
//import { useRouter } from "vue-router";
import { finalizeAccount } from "@/services/Apis/Unauth";
import Header from "@/components/HeaderComponent.vue";
import { store } from "@/main.ts";
//const router = useRouter();
onMounted(() => {
finalizeAccount(
() => {
console.log("finalize sended");
},
(error) => {
console.error("Erreur lors de la finalisation :", error);
}
);
});
</script>
<style scoped>
.finalize-page {
display: flex;
justify-content: center;
align-items: center;
min-height: 80vh;
background-color: #f9fbfd;
}
.loader-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
color: #333;
font-size: 1.1rem;
font-style: italic;
}
.spinner {
width: 50px;
height: 50px;
border: 5px solid #cfd8dc;
border-top: 5px solid #3498db;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
.return-button {
background-color: #009cde;
color: white;
border: none;
padding: 10px 15px;
cursor: pointer;
font-size: 14px;
border-radius: 5px;
text-decoration: none;
transition: background-color 0.2s ease;
font-family: Arial, sans-serif;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@ -1,10 +1,12 @@
<template>
<Header />
<header class="header">
<img
src="@/components/icons/logo inpulse.png"
alt="INPulse Logo"
class="logo"
/>
<button class="return-button" @click="store.logout">Logout</button>
</header>
<div class="choix-projet">
@ -39,10 +41,18 @@
</template>
<script setup lang="ts">
import { ref } from "vue";
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";
import Project from "@/ApiClasses/Project";
import { requestProjectCreation } from "@/services/Apis/Entrepreneurs.ts";
import Header from "../components/HeaderComponent.vue";
import { store } from "@/main.ts";
import {
requestProjectCreation,
checkIfProjectIsActive,
checkPendingProjectRequest,
} from "@/services/Apis/Entrepreneurs";
const router = useRouter();
const choix = ref<string | null>(null);
const nomProjet = ref("");
@ -56,21 +66,18 @@ const validerCreation = () => {
return;
}
// Obtenir la date actuelle au format YYYY-MM-DD
const today = new Date();
const yyyy = today.getFullYear();
const mm = String(today.getMonth() + 1).padStart(2, "0");
const dd = String(today.getDate()).padStart(2, "0");
const formattedDate = `${yyyy}-${mm}-${dd}`;
// Créer une instance de Project
const nouveauProjet = new Project({
projectName: nomProjet.value.trim(),
creationDate: formattedDate,
status: "PENDING",
});
// Appeler lAPI
requestProjectCreation(
nouveauProjet,
(response) => {
@ -83,6 +90,28 @@ const validerCreation = () => {
}
);
};
onMounted(() => {
checkIfProjectIsActive(
(response) => {
if (response.data === true) {
router.push("/canvas");
}
},
() => {
checkPendingProjectRequest(
(response) => {
if (response.data === true) {
router.push("/pending-approval");
}
},
(error) => {
console.warn("No active or pending project:", error);
}
);
}
);
});
</script>
<style scoped>
@ -140,4 +169,17 @@ input {
.logo {
height: 50px;
}
.return-button {
background-color: #009cde;
color: white;
border: none;
padding: 10px 15px;
cursor: pointer;
font-size: 14px;
border-radius: 5px;
text-decoration: none;
transition: background-color 0.2s ease;
font-family: Arial, sans-serif;
}
</style>

View File

@ -0,0 +1,41 @@
<template>
<Header />
<div class="pending-container">
<h1>Projet en attente de validation</h1>
<p>
Votre demande de création de projet a bien été reçue.<br />
Un administrateur doit valider votre projet avant que vous puissiez
continuer.
</p>
</div>
</template>
<script setup lang="ts">
import Header from "@/components/HeaderComponent.vue";
</script>
<style scoped>
.pending-container {
max-width: 600px;
margin: 100px auto;
padding: 2rem;
text-align: center;
background-color: #fffdf8;
border: 1px solid #ececec;
border-radius: 12px;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
font-family: "Inter", sans-serif;
}
h1 {
color: #f57c00;
font-size: 1.8rem;
margin-bottom: 1rem;
}
p {
font-size: 1.1rem;
color: #333;
margin-bottom: 1.5rem;
}
</style>

View File

@ -58,7 +58,7 @@ const USERID = ref("");
<tr>
<td>Get Pending Accounts</td>
<td>
<button @click="callApi('admin/get_pending_accounts')">
<button @click="callApi('/admin/pending-accounts')">
call
</button>
</td>

2638
keycloak/realm.json Normal file

File diff suppressed because it is too large Load Diff