13 Commits

Author SHA1 Message Date
f3c5401b07 added annoying bundle.yaml to gitignore
All checks were successful
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 43s
CI / build (push) Successful in 11s
2025-05-10 22:51:59 +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
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
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
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
13845394e3 feat: added doc for endpoint make-admin
All checks were successful
Format / formatting (push) Successful in 25s
Build / build (push) Successful in 44s
CI / build (push) Successful in 13s
Format / formatting (pull_request) Successful in 6s
2025-05-04 20:15:03 +02:00
f4589c6306 fix: bug on CORS discussed before
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 46s
CI / build (push) Successful in 13s
Format / formatting (pull_request) Successful in 6s
2025-05-01 21:03:23 +02:00
22 changed files with 3077 additions and 337 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ docker-compose.yaml
node_modules node_modules
.vscode .vscode
postgres/data postgres/data
bundled.yaml

View File

@ -2,6 +2,7 @@ help:
@echo "make [clean dev-front prod dev-back dev]" @echo "make [clean dev-front prod dev-back dev]"
clean: clean:
pkill -9 node
@cp config/frontdev.env front/MyINPulse-front/.env @cp config/frontdev.env front/MyINPulse-front/.env
@cp config/frontdev.env .env @cp config/frontdev.env .env
@cp config/frontdev.env MyINPulse-back/.env @cp config/frontdev.env MyINPulse-back/.env

View File

@ -31,7 +31,7 @@ public class WebSecurityCustomConfiguration {
public CorsConfigurationSource corsConfigurationSource() { public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration(); CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of(frontendUrl)); configuration.setAllowedOrigins(List.of(frontendUrl));
configuration.setAllowedMethods(Arrays.asList("GET", "OPTIONS")); configuration.setAllowedMethods(Arrays.asList("GET", "OPTIONS", "POST", "PUT", "DELETE"));
configuration.setAllowedHeaders( configuration.setAllowedHeaders(
Arrays.asList("authorization", "content-type", "x-auth-token")); Arrays.asList("authorization", "content-type", "x-auth-token"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
@ -56,12 +56,18 @@ public class WebSecurityCustomConfiguration {
http.authorizeHttpRequests( http.authorizeHttpRequests(
authorize -> authorize ->
authorize authorize
.requestMatchers("/entrepreneur/**", "/shared/**") .requestMatchers("/entrepreneur/**")
.access(hasRole("REALM_MyINPulse-entrepreneur")) .access(hasRole("REALM_MyINPulse-entrepreneur"))
.requestMatchers("/admin/**", "/shared/**") .requestMatchers("/admin/**")
.access(hasRole("REALM_MyINPulse-admin")) .access(hasRole("REALM_MyINPulse-admin"))
.requestMatchers("/shared/**")
.hasAnyRole(
"REALM_MyINPulse-admin",
"REALM_MyINPulse-entrepreneur")
.requestMatchers("/unauth/**") .requestMatchers("/unauth/**")
.authenticated()) .authenticated()
.anyRequest()
.denyAll())
.oauth2ResourceServer( .oauth2ResourceServer(
oauth2 -> oauth2 ->
oauth2.jwt( oauth2.jwt(

View File

@ -123,8 +123,7 @@ public class AdminApi {
String primaryMail = principal.getClaimAsString("email"); String primaryMail = principal.getClaimAsString("email");
String secondaryMail = principal.getClaimAsString("secondaryMail"); String secondaryMail = principal.getClaimAsString("secondaryMail");
String phoneNumber = principal.getClaimAsString("phoneNumber"); String phoneNumber = principal.getClaimAsString("phoneNumber");
String school = principal.getClaimAsString("school");
this.adminApiService.createAccount( this.adminApiService.createAccount(
userSurname, username, primaryMail, secondaryMail, phoneNumber, school); userSurname, username, primaryMail, secondaryMail, phoneNumber);
} }
} }

View File

@ -95,4 +95,22 @@ public class EntrepreneurApi {
@RequestBody Project project, @AuthenticationPrincipal Jwt principal) { @RequestBody Project project, @AuthenticationPrincipal Jwt principal) {
entrepreneurApiService.requestNewProject(project, principal.getClaimAsString("email")); 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

@ -2,6 +2,7 @@ package enseirb.myinpulse.controller;
import enseirb.myinpulse.model.Entrepreneur; import enseirb.myinpulse.model.Entrepreneur;
import enseirb.myinpulse.service.EntrepreneurApiService; import enseirb.myinpulse.service.EntrepreneurApiService;
import enseirb.myinpulse.service.UtilsService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@ -14,13 +15,15 @@ import org.springframework.web.bind.annotation.*;
public class UnauthApi { public class UnauthApi {
private final EntrepreneurApiService entrepreneurApiService; private final EntrepreneurApiService entrepreneurApiService;
private final UtilsService utilsService;
@Autowired @Autowired
UnauthApi(EntrepreneurApiService entrepreneurApiService) { UnauthApi(EntrepreneurApiService entrepreneurApiService, UtilsService utilsService) {
this.entrepreneurApiService = entrepreneurApiService; this.entrepreneurApiService = entrepreneurApiService;
this.utilsService = utilsService;
} }
@GetMapping("/unauth/finalize") @PostMapping("/unauth/finalize")
public void createAccount(@AuthenticationPrincipal Jwt principal) { public void createAccount(@AuthenticationPrincipal Jwt principal) {
boolean sneeStatus; boolean sneeStatus;
if (principal.getClaimAsString("sneeStatus") != null) { if (principal.getClaimAsString("sneeStatus") != null) {
@ -46,6 +49,13 @@ public class UnauthApi {
course, course,
sneeStatus, sneeStatus,
true); true);
entrepreneurApiService.createAccount(e); entrepreneurApiService.createAccount(e);
} }
@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( Iterable<SectionCell> findByProjectSectionCellAndSectionIdAndModificationDateBefore(
Project project, long sectionId, LocalDateTime date); Project project, long sectionId, LocalDateTime date);
Iterable<SectionCell> findByProjectSectionCell(Project project);
} }

View File

@ -212,8 +212,7 @@ public class AdminApiService {
String userSurname, String userSurname,
String primaryMail, String primaryMail,
String secondaryMail, String secondaryMail,
String phoneNumber, String phoneNumber) {
String school) {
Administrator a = Administrator a =
new Administrator(username, userSurname, primaryMail, secondaryMail, phoneNumber); new Administrator(username, userSurname, primaryMail, secondaryMail, phoneNumber);
this.administratorService.addAdministrator(a); this.administratorService.addAdministrator(a);

View File

@ -1,10 +1,12 @@
package enseirb.myinpulse.service; package enseirb.myinpulse.service;
import static enseirb.myinpulse.model.ProjectDecisionValue.PENDING; import static enseirb.myinpulse.model.ProjectDecisionValue.PENDING;
import static enseirb.myinpulse.model.ProjectDecisionValue.ACTIVE;
import enseirb.myinpulse.model.Entrepreneur; import enseirb.myinpulse.model.Entrepreneur;
import enseirb.myinpulse.model.Project; import enseirb.myinpulse.model.Project;
import enseirb.myinpulse.model.SectionCell; import enseirb.myinpulse.model.SectionCell;
import enseirb.myinpulse.model.User;
import enseirb.myinpulse.service.database.*; import enseirb.myinpulse.service.database.*;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -230,4 +232,53 @@ public class EntrepreneurApiService {
Project_List.add(entrepreneur.getProjectParticipation()); Project_List.add(entrepreneur.getProjectParticipation());
return Project_List; return Project_List;
} }
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.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
@Service @Service
public class SharedApiService { public class SharedApiService {
@ -169,18 +171,26 @@ public class SharedApiService {
"User {} tried to check the appointments related to the project {}", "User {} tried to check the appointments related to the project {}",
mail, mail,
projectId); projectId);
Iterable<SectionCell> sectionCells =
this.sectionCellService.getSectionCellsByProject( Project project = projectService.getProjectById(projectId);
projectService.getProjectById(projectId),
2L); // sectionId useless in this function ? Iterable<SectionCell> sectionCellsIterable =
List<Appointment> appointments = new ArrayList<Appointment>(); this.sectionCellService.getSectionCellsByProject(project);
sectionCells.forEach(
// Use a Set to collect unique appointments
Set<Appointment> uniqueAppointments = new HashSet<>();
sectionCellsIterable.forEach(
sectionCell -> { sectionCell -> {
appointments.addAll( List<Appointment> sectionAppointments =
this.sectionCellService.getAppointmentsBySectionCellId( 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) public void getPDFReport(long appointmentId, String mail)

View File

@ -72,4 +72,10 @@ public class UtilsService {
return false; 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 org.springframework.web.server.ResponseStatusException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -118,6 +119,18 @@ public class SectionCellService {
return this.sectionCellRepository.findByProjectSectionCellAndSectionId(project, sectionId); 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) { public Long getProjectId(Long sectionCellId) {
SectionCell sectionCell = getSectionCellById(sectionCellId); SectionCell sectionCell = getSectionCellById(sectionCellId);
Project sectionProject = sectionCell.getProjectSectionCell(); Project sectionProject = sectionCell.getProjectSectionCell();

View File

@ -1,6 +1,6 @@
spring.application.name=myinpulse 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.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/test 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.url=jdbc:postgresql://${DATABASE_URL}/${BACKEND_DB}
spring.datasource.username=${BACKEND_USER} spring.datasource.username=${BACKEND_USER}
spring.datasource.password=${BACKEND_PASSWORD} spring.datasource.password=${BACKEND_PASSWORD}

View File

@ -8,9 +8,10 @@ import static org.mockito.Mockito.when;
import enseirb.myinpulse.model.*; import enseirb.myinpulse.model.*;
import enseirb.myinpulse.service.SharedApiService; import enseirb.myinpulse.service.SharedApiService;
import enseirb.myinpulse.service.database.*; import enseirb.myinpulse.service.database.*;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import enseirb.myinpulse.service.UtilsService; 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.BeforeAll; // Use BeforeAll for static setup
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; // Keep this import 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.http.HttpStatus;
import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.context.bean.override.mockito.MockitoBean;
import java.io.IOException;
import java.net.URISyntaxException;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
@ -712,6 +711,129 @@ public class SharedApiServiceTest {
assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatusCode()); 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 * Tests creating a new appointment request when the user is authorized
* for the project linked to the appointment's section cell. * for the project linked to the appointment's section cell.
@ -797,288 +919,4 @@ public class SharedApiServiceTest {
a.getIdAppointment() a.getIdAppointment()
.equals(createdAppointment.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 DATABASE_URL=localhost:5433
VITE_KEYCLOAK_URL=http://localhost:7080 VITE_KEYCLOAK_URL=http://localhost:7080
VITE_KEYCLOAK_CLIENT_ID=myinpulse-dev VITE_KEYCLOAK_CLIENT_ID=MyINPulse-vite
VITE_KEYCLOAK_REALM=test VITE_KEYCLOAK_REALM=MyINPulse
VITE_APP_URL=http://localhost:5173 VITE_APP_URL=http://localhost:5173
VITE_BACKEND_URL=http://localhost:8081/ 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). description: Bad Request - Invalid project data provided (e.g., missing required fields).
"401": "401":
description: Unauthorized - Authentication required or invalid token. description: Unauthorized - Authentication required or invalid token.
"403":
description: Bad Token - Invalid Keycloack configuration.
post: post:
operationId: addProjectManually operationId: addProjectManually
@ -39,7 +41,7 @@ paths:
schema: schema:
$ref: "./main.yaml#/components/schemas/project" $ref: "./main.yaml#/components/schemas/project"
responses: responses:
"201": # Use 201 Created for successful creation "200": # Use 200 Created for successful creation
description: Created - Project added successfully. Returns the created project. description: Created - Project added successfully. Returns the created project.
content: content:
application/json: application/json:
@ -49,6 +51,8 @@ paths:
description: Bad Request - Project already exists. description: Bad Request - Project already exists.
"401": "401":
description: Unauthorized. description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/projects/pending: /admin/projects/pending:
@ -70,7 +74,9 @@ paths:
items: items:
$ref: "./main.yaml#/components/schemas/project" # Assuming pending projects use the same schema $ref: "./main.yaml#/components/schemas/project" # Assuming pending projects use the same schema
"401": "401":
description: Unauthorized. description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/request-join: /admin/request-join:
get: get:
@ -92,6 +98,8 @@ paths:
$ref: "./main.yaml#/components/schemas/joinRequest" $ref: "./main.yaml#/components/schemas/joinRequest"
"401": "401":
description: Unauthorized. description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/request-join/decision/{joinRequestId}: /admin/request-join/decision/{joinRequestId}:
post: post:
@ -121,7 +129,9 @@ paths:
"400": "400":
description: Bad Request - Invalid input (e.g., missing decision). description: Bad Request - Invalid input (e.g., missing decision).
"401": "401":
description: Unauthorized. description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/projects/pending/decision: /admin/projects/pending/decision:
@ -144,12 +154,14 @@ paths:
schema: schema:
$ref: './main.yaml#/components/schemas/projectDecision' $ref: './main.yaml#/components/schemas/projectDecision'
responses: 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. description: No Content - Decision processed successfully.
"400": "400":
description: Bad Request - Invalid input (e.g., missing decision). description: Bad Request - Invalid input (e.g., missing decision).
"401": "401":
description: Unauthorized. description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/pending-accounts: # Path updated /admin/pending-accounts: # Path updated
@ -172,6 +184,8 @@ paths:
$ref: "./main.yaml#/components/schemas/user-entrepreneur" $ref: "./main.yaml#/components/schemas/user-entrepreneur"
"401": "401":
description: Unauthorized. description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/accounts/validate/{userId}: /admin/accounts/validate/{userId}:
post: # Changed to POST as it changes state post: # Changed to POST as it changes state
@ -191,11 +205,12 @@ paths:
description: The ID of the user account to validate. description: The ID of the user account to validate.
example: 102 example: 102
responses: responses:
"204": "200":
description: No Content - Account validated successfully. description: No Content - Account validated successfully.
"400": "400":
description: Bad Request - Invalid user ID format. description: Bad Request - Invalid user ID format.
"403":
description: Bad Token - Invalid Keycloack configuration.
"401": "401":
description: Unauthorized. description: Unauthorized.
@ -217,6 +232,8 @@ paths:
type: array type: array
items: items:
$ref: "./main.yaml#/components/schemas/appointment" $ref: "./main.yaml#/components/schemas/appointment"
"403":
description: Bad Token - Invalid Keycloack configuration.
"404": "404":
description: no appointments found. description: no appointments found.
"401": "401":
@ -247,13 +264,15 @@ paths:
schema: schema:
$ref: "./main.yaml#/components/schemas/report" $ref: "./main.yaml#/components/schemas/report"
responses: responses:
"201": "200":
description: Created - Report created and linked successfully. Returns the created report. description: Created - Report created and linked successfully. Returns the created report.
content: content:
application/json: application/json:
schema: { $ref: "./main.yaml#/components/schemas/report" } schema: { $ref: "./main.yaml#/components/schemas/report" }
"400": "400":
description: Bad Request - Invalid input (e.g., missing content, invalid appointment ID format). description: Bad Request - Invalid input (e.g., missing content, invalid appointment ID format).
"403":
description: Bad Token - Invalid Keycloack configuration.
"401": "401":
description: Unauthorized. description: Unauthorized.
@ -288,6 +307,8 @@ paths:
schema: { $ref: "./main.yaml#/components/schemas/report" } schema: { $ref: "./main.yaml#/components/schemas/report" }
"400": "400":
description: Bad Request - Invalid input (e.g., missing content). description: Bad Request - Invalid input (e.g., missing content).
"403":
description: Bad Token - Invalid Keycloack configuration.
"401": "401":
description: Unauthorized. description: Unauthorized.
@ -310,10 +331,12 @@ paths:
description: The ID of the project to remove. description: The ID of the project to remove.
example: 12 example: 12
responses: responses:
"204": "200":
description: No Content - Project removed successfully. description: No Content - Project removed successfully.
"400": "400":
description: Bad Request - Invalid project ID format. description: Bad Request - Invalid project ID format.
"403":
description: Bad Token - Invalid Keycloack configuration.
"401": "401":
description: Unauthorized. description: Unauthorized.
@ -337,9 +360,28 @@ paths:
description: The ID of the user to grant admin rights. description: The ID of the user to grant admin rights.
example: 103 example: 103
responses: responses:
"204": # Use 204 No Content "200": # Use 200 No Content
description: No Content - Admin rights granted successfully. description: No Content - Admin rights granted successfully.
"400": "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": "401":
description: Unauthorized. 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.
"403":
description: Bad Token - Invalid Keycloack configuration.

View File

@ -21,12 +21,14 @@ paths:
schema: schema:
$ref: "./main.yaml#/components/schemas/project" $ref: "./main.yaml#/components/schemas/project"
responses: responses:
"202": "200":
description: Accepted - Project creation request received and is pending validation. description: Accepted - Project creation request received and is pending validation.
"400": "400":
description: Bad Request - Invalid input (e.g., missing name). description: Bad Request - Invalid input (e.g., missing name).
"401": "401":
description: Unauthorized. description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/sectionCells: # Base path /entrepreneur/sectionCells: # Base path
post: post:
@ -46,12 +48,14 @@ paths:
schema: schema:
$ref: "./main.yaml#/components/schemas/sectionCell" $ref: "./main.yaml#/components/schemas/sectionCell"
responses: responses:
"201": "200":
description: Created - Section cell added successfully. Returns the created cell. description: Created - Section cell added successfully. Returns the created cell.
"400": "400":
description: Bad Request - Invalid input (e.g., missing content or sectionId). description: Bad Request - Invalid input (e.g., missing content or sectionId).
"401": "401":
description: Unauthorized. description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/sectionCells/{sectionCellId}: /entrepreneur/sectionCells/{sectionCellId}:
put: put:
@ -84,6 +88,8 @@ paths:
description: Bad Request - Invalid input or ID mismatch. description: Bad Request - Invalid input or ID mismatch.
"401": "401":
description: Unauthorized. description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
delete: delete:
operationId: removeSectionCell operationId: removeSectionCell
@ -102,7 +108,7 @@ paths:
description: The ID of the section cell to remove. description: The ID of the section cell to remove.
example: 509 example: 509
responses: responses:
"204": "200":
description: No Content - Section cell removed successfully. description: No Content - Section cell removed successfully.
"400": "400":
description: Bad Request - Invalid ID format. description: Bad Request - Invalid ID format.
@ -110,6 +116,8 @@ paths:
description: Bad Request - sectionCell not found. description: Bad Request - sectionCell not found.
"401": "401":
description: Unauthorized. description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/projects: /entrepreneur/projects:
@ -133,4 +141,57 @@ paths:
"404": "404":
description: Bad Request - Invalid input or ID mismatch. description: Bad Request - Invalid input or ID mismatch.
"401": "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" $ref: "./unauthApi.yaml#/paths/~1unauth~1finalize"
/unauth/request-join/{projectId}: /unauth/request-join/{projectId}:
$ref: "./unauthApi.yaml#/paths/~1unauth~1request-join~1{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"
# _ ____ __ __ ___ _ _ _ ____ ___ # _ ____ __ __ ___ _ _ _ ____ ___
# / \ | _ \| \/ |_ _| \ | | / \ | _ \_ _| # / \ | _ \| \/ |_ _| \ | | / \ | _ \_ _|
@ -108,6 +112,8 @@ paths:
$ref: "./adminApi.yaml#/paths/~1admin~1projects~1{projectId}" $ref: "./adminApi.yaml#/paths/~1admin~1projects~1{projectId}"
/admin/make-admin/{userId}: /admin/make-admin/{userId}:
$ref: "./adminApi.yaml#/paths/~1admin~1make-admin~1{userId}" $ref: "./adminApi.yaml#/paths/~1admin~1make-admin~1{userId}"
/admin/create-account:
$ref: "./adminApi.yaml#/paths/~1admin~1create-account"
# ____ _ _ _ ____ ___ # ____ _ _ _ ____ ___
# / ___|| |__ __ _ _ __ ___ __| | / \ | _ \_ _| # / ___|| |__ __ _ _ __ ___ __| | / \ | _ \_ _|
@ -146,4 +152,8 @@ paths:
/entrepreneur/sectionCells: /entrepreneur/sectionCells:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells" $ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells"
/entrepreneur/sectionCells/{sectionCellId}: /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: items:
$ref: "./main.yaml#/components/schemas/sectionCell" $ref: "./main.yaml#/components/schemas/sectionCell"
"400": "400":
description: Bad Request - Invalid parameter format. description: Bad Request - Invalid parameter format.
"403":
description: Bad Token - Invalid Keycloack configuration.
"401": "401":
description: Unauthorized. description: Unauthorized.
@ -68,7 +70,7 @@ paths:
"401": "401":
description: Unauthorized. description: Unauthorized.
"403": "403":
description: Forbidden - User does not have access to this project. description: Bad Token - Invalid Keycloack configuration.
"404": "404":
description: Not Found - Project not found. description: Not Found - Project not found.
@ -97,7 +99,7 @@ paths:
"401": "401":
description: Unauthorized. description: Unauthorized.
"403": "403":
description: Forbidden - User does not have access to this project. description: Bad Token - Invalid Keycloack configuration.
"404": "404":
description: Not Found - Project not found. description: Not Found - Project not found.
@ -126,6 +128,8 @@ paths:
type: array type: array
items: items:
$ref: "./main.yaml#/components/schemas/appointment" $ref: "./main.yaml#/components/schemas/appointment"
"403":
description: Bad Token - Invalid Keycloack configuration.
"401": "401":
description: Unauthorized. description: Unauthorized.
@ -156,6 +160,8 @@ paths:
format: binary format: binary
"401": "401":
description: Unauthorized. description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/shared/appointments/request: /shared/appointments/request:
@ -176,11 +182,12 @@ paths:
$ref: "./main.yaml#/components/schemas/appointment" # Assuming request uses same model structure $ref: "./main.yaml#/components/schemas/appointment" # Assuming request uses same model structure
# Potentially add projectId or targetUserId here # Potentially add projectId or targetUserId here
responses: responses:
"202": # Accepted seems appropriate for a request "200": # Accepted seems appropriate for a request
description: Accepted - Appointment request submitted. description: Accepted - Appointment request submitted.
"400": "400":
description: Bad Request - Invalid appointment details. description: Bad Request - Invalid appointment details.
"401": "401":
description: Unauthorized. description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.

View File

@ -18,12 +18,14 @@ paths:
tags: tags:
- Unauth API - Unauth API
responses: responses:
"201": "200":
description: Created - Account finalized and pending admin validation. Returns the user profile. description: Created - Account finalized and pending admin validation. Returns the user profile.
"400": "400":
description: Bad Request - Problem processing the token or user data derived from it. description: Bad Request - Problem processing the token or user data derived from it.
"401": "401":
description: Unauthorized - Valid authentication token required. description: Unauthorized - Valid authentication token required.
"403":
description: Bad Token - Invalid Keycloack configuration.
/unauth/request-join/{projectId}: /unauth/request-join/{projectId}:
post: post:
summary: Request to join an existing project summary: Request to join an existing project
@ -39,7 +41,7 @@ paths:
description: The ID of the project to request joining. description: The ID of the project to request joining.
example: 15 example: 15
responses: # Moved responses block to correct level responses: # Moved responses block to correct level
"202": "200":
description: Accepted - Join request submitted and pending approval. description: Accepted - Join request submitted and pending approval.
"400": "400":
description: Bad Request - Invalid project ID format description: Bad Request - Invalid project ID format
@ -47,16 +49,42 @@ paths:
description: Already member/request pending. description: Already member/request pending.
"401": "401":
description: Unauthorized. description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/unauth/request-admin-role: /unauth/request-admin-role:
post: 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. 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: tags:
- Unauth API - Unauth API
responses: responses:
"202": "200":
description: Accepted - Become admin request submitted and pending approval. description: Accepted - Become admin request submitted and pending approval.
"400": "400":
description: Bad Request - Invalid project ID format or already member/request pending. description: Bad Request - Invalid project ID format or already member/request pending.
"401": "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

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

2638
keycloak/realm.json Normal file

File diff suppressed because it is too large Load Diff