66 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
cc1fc9b45b fix: eslint
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 13s
2025-05-07 11:01:57 +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
0d0ec255a5 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 8s
2025-05-07 10:53:09 +02:00
e0c43a5c95 fix: merge 2025-05-07 10:53:02 +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
60302c44d2 "reverting the fix, last ditch was no good: last ditch effort with .env variables, using default variable to set db connection values"
Some checks failed
Format / formatting (push) Successful in 10s
Build / build (push) Successful in 49s
CI / build (push) Failing after 12s
This reverts commit a6e4f80a01.
2025-05-07 00:04:26 +02:00
a6e4f80a01 fix: last ditch effort with .env variables, using default variable to set db connection values
Some checks failed
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 51s
CI / build (push) Failing after 13s
2025-05-06 23:57:03 +02:00
02bff19de0 fix: il ya encors un problem de config! des erreurs de type (Connection to localhost:5433 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections)
Some checks failed
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 56s
CI / build (push) Failing after 25s
2025-05-06 23:31:01 +02:00
ae36549de9 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation 2025-05-06 22:38:28 +02:00
b1a4c874ec trying out config to see if this is where 403 responses are comming from
All checks were successful
Format / formatting (push) Successful in 15s
Build / build (push) Successful in 1m4s
CI / build (push) Successful in 39s
2025-05-06 21:17:52 +02:00
829baac85e apply prettier to adminappointment
All checks were successful
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 40s
CI / build (push) Successful in 13s
2025-05-05 20:36:09 +02:00
2e9d841709 utilisation des fonctions Apis/admin.ts pour mettre un raport a un appointmentid
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 43s
CI / build (push) Failing after 9s
2025-05-05 20:32:09 +02:00
25235f418a fix: merging from the main 2025-05-04 20:23:19 +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
6004bce4e8 Merge branch 'main' into backend-test
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 42s
CI / build (push) Successful in 11s
2025-05-01 20:53:18 +02:00
0730275e75 feat: added create-account
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 44s
CI / build (push) Successful in 11s
2025-05-01 20:51:10 +02:00
5183a088e7 fix: forgot to remove a pram in an endpoint in docs
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 45s
CI / build (push) Successful in 13s
2025-04-29 21:41:25 +02:00
b503cae235 fix: styling
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 1m33s
CI / build (push) Successful in 11s
2025-04-29 21:25:03 +02:00
fcf4e1c01d feat: added an endpoint for fething the project an entrepreneur is associated with
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 1m28s
CI / build (push) Successful in 11s
2025-04-29 21:22:13 +02:00
3f18304028 formatting works time in and time out, empty commit to fix
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 42s
CI / build (push) Successful in 11s
2025-04-29 19:31:28 +02:00
bbb4debcd8 fix: changed the data structure used in getAllSectionCells for processing the latest version of sectioneCells as it gave some exception (it doesn't like being iterated and modified at the same time)
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-04-29 19:18:12 +02:00
6f7fc70c4c didn't use the correct function for seting ids, test fixed
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 16s
2025-04-29 18:50:25 +02:00
3d57ecb01a fix: fixed some sectionCell fetching logic (previously wasn't grouping by idReference), found out that idRefrence in sectionCell is always being incremented at inserion still looking for a fix
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-04-29 13:59:05 +02:00
b1df7421dc fix: commiting some minor changes before switching to front_foundation branch 2025-02-10 15:32:28 +01:00
7a03146bf8 fix: commiting minor changes before switching to front_foundation branch 2025-02-10 15:21:07 +01:00
f0a371dc52 Merge pull request 'main-revert' (#2) from main-revert into main
Reviewed-on: #2
2025-02-09 13:11:01 +01:00
ac19d33bdb Reapply "fix: fixed TS errors"
This reverts commit 3d4d5b90d1.
2025-02-09 13:05:28 +01:00
3d4d5b90d1 Revert "fix: fixed TS errors"
This reverts commit b5c9b40672.
2025-02-09 13:03:26 +01:00
35 changed files with 3854 additions and 606 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
@ -45,7 +43,6 @@ prod: clean keycloak
dev-back: keycloak
@cp config/backdev.env front/MyINPulse-front/.env
@cp config/backdev.env .env

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

@ -115,4 +115,15 @@ public class AdminApi {
public Iterable<User> validateEntrepreneurAcc() {
return this.adminApiService.getPendingUsers();
}
@PostMapping("/admin/create-account")
public void createAccount(@AuthenticationPrincipal Jwt principal) {
String userSurname = principal.getClaimAsString("userSurname");
String username = principal.getClaimAsString("preferred_username");
String primaryMail = principal.getClaimAsString("email");
String secondaryMail = principal.getClaimAsString("secondaryMail");
String phoneNumber = principal.getClaimAsString("phoneNumber");
this.adminApiService.createAccount(
userSurname, username, primaryMail, secondaryMail, phoneNumber);
}
}

View File

@ -5,6 +5,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
@ -42,6 +43,19 @@ public class EntrepreneurApi {
sectionCellId, content, principal.getClaimAsString("email"));
}
/**
* Endpoint used to update a LC section.
*
* @return status code
*/
@GetMapping("/entrepreneur/projects")
public Iterable<Project> getEntrepreneurProjectId(
@PathVariable Long sectionCellId,
@RequestBody String content,
@AuthenticationPrincipal Jwt principal) {
return entrepreneurApiService.getProjectIdViaClaim(principal.getClaimAsString("email"));
}
/**
* TODO: checkReturn Type
*
@ -81,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

@ -207,6 +207,17 @@ public class AdminApiService {
return this.userService.getPendingAccounts();
}
public void createAccount(
String username,
String userSurname,
String primaryMail,
String secondaryMail,
String phoneNumber) {
Administrator a =
new Administrator(username, userSurname, primaryMail, secondaryMail, phoneNumber);
this.administratorService.addAdministrator(a);
}
public Iterable<Administrator> getAllAdmins() {
return this.administratorService.allAdministrators();
}

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;
@ -15,6 +17,8 @@ import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Service
public class EntrepreneurApiService {
@ -220,7 +224,61 @@ public class EntrepreneurApiService {
throw new ResponseStatusException(HttpStatus.CONFLICT, "User already exists in the system");
}
public Iterable<Project> getProjectIdViaClaim(String email) {
Long UserId = this.userService.getUserByEmail(email).getIdUser();
Entrepreneur entrepreneur = this.entrepreneurService.getEntrepreneurById(UserId);
List<Project> Project_List = new ArrayList<>();
Project_List.add(entrepreneur.getProjectParticipation());
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

@ -25,8 +25,11 @@ import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.Map;
import java.util.Set;
@Service
public class SharedApiService {
@ -79,7 +82,7 @@ public class SharedApiService {
LocalDateTime dateTime = LocalDateTime.parse(date, formatter);
Project project = this.projectService.getProjectById(projectId);
return this.sectionCellService.getSectionCellsByProjectAndSectionIdBeforeDate(
return this.sectionCellService.getLatestSectionCellsByIdReferenceBeforeDate(
project, sectionId, dateTime);
}
@ -95,31 +98,36 @@ public class SharedApiService {
}
Project project = this.projectService.getProjectById(projectId);
List<SectionCell> allSectionCells = new ArrayList<SectionCell>();
project.getListSectionCell()
Map<Long, SectionCell> latestSectionCellsMap =
new HashMap<>(); // List for the intermediate result
// Iterate through all SectionCells associated with the project
// This loop iterates over project.getListSectionCell() but does NOT modify it which causes
// ConcurrentModificationException.
// Modifications are done only on the latestSectionCellsMap (which is safe).
project.getListSectionCell() // <-- Iterating over the original list (read-only)
.forEach(
projectCell -> {
AtomicBoolean sameReferenceId =
new AtomicBoolean(false); // side effect lambdas
allSectionCells.forEach(
selectedCell -> {
if (projectCell
.getIdReference()
.equals(selectedCell.getIdReference())) {
sameReferenceId.set(true);
Long idReference = projectCell.getIdReference();
// Check if we have already seen a SectionCell with this idReference in
// our map
if (latestSectionCellsMap.containsKey(idReference)) {
SectionCell existingCell = latestSectionCellsMap.get(idReference);
// Compare modification dates. If the current cell is newer, replace
// the one in the map.
if (projectCell
.getModificationDate()
.isAfter(selectedCell.getModificationDate())) {
allSectionCells.remove(selectedCell);
allSectionCells.add(projectCell);
.isAfter(existingCell.getModificationDate())) {
latestSectionCellsMap.put(idReference, projectCell);
}
} else {
// If this is the first time we encounter this idReference, add the
// cell to the map.
latestSectionCellsMap.put(idReference, projectCell);
}
});
if (!sameReferenceId.get()) {
allSectionCells.add(projectCell);
}
});
return allSectionCells;
return new ArrayList<>(latestSectionCellsMap.values());
}
// TODO: test
@ -163,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,7 +14,10 @@ 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;
import java.util.Optional;
@Service
@ -116,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();
@ -132,4 +147,37 @@ public class SectionCellService {
return sectionCellRepository.findByProjectSectionCellAndSectionIdAndModificationDateBefore(
project, sectionId, date);
}
public Iterable<SectionCell> getLatestSectionCellsByIdReferenceBeforeDate(
Project project, long sectionId, LocalDateTime date) {
// 1. Fetch ALL relevant SectionCells modified before the date
Iterable<SectionCell> allMatchingCells =
sectionCellRepository.findByProjectSectionCellAndSectionIdAndModificationDateBefore(
project, sectionId, date);
// 2. Find the latest for each idReference
Map<Long, SectionCell> latestCellsByIdReference = new HashMap<>();
for (SectionCell cell : allMatchingCells) {
Long idReference = cell.getIdReference();
// Check if we've seen this idReference before
if (latestCellsByIdReference.containsKey(idReference)) {
// If yes, compare modification dates
SectionCell existingLatest = latestCellsByIdReference.get(idReference);
// If the current cell is more recent, update the map
if (cell.getModificationDate().isAfter(existingLatest.getModificationDate())) {
latestCellsByIdReference.put(idReference, cell);
}
} else {
// If this is the first time we see this idReference, add it to the map
latestCellsByIdReference.put(idReference, cell);
}
}
// 3. Return the collection of the latest cells (the values from the map)
return latestCellsByIdReference.values();
}
}

View File

@ -1,13 +1,8 @@
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}
spring.jpa.hibernate.ddl-auto=update
logging.pattern.console=%d{yyyy-MMM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create

View File

@ -0,0 +1,13 @@
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
logging.pattern.console=%d{yyyy-MMM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create

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;
@ -123,20 +122,6 @@ public class SharedApiServiceTest {
when(mockUtilsService.isAllowedToCheckProject(eq(staticUnauthorizedMail), anyLong()))
.thenReturn(false); // Unauthorized entrepreneur NOT allowed for ANY project ID by
// default
// Add more specific mock setups here if needed for entrepreneur tests
// E.g., If you have a test specifically for an entrepreneur accessing THEIR project:
// Entrepreneur testEntrepreneur =
// entrepreneurService.addEntrepreneur(getTestEntrepreneur("specific_linked_entrepreneur"));
// Project linkedProject =
// projectService.addNewProject(getTestProject("specific_linked_project",
// staticAuthorizedAdmin));
// // Link testEntrepreneur to linkedProject in the database setup...
// when(mockUtilsService.isAllowedToCheckProject(eq(testEntrepreneur.getPrimaryMail()),
// eq(linkedProject.getIdProject()))).thenReturn(true);
// when(mockUtilsService.isAllowedToCheckProject(eq(testEntrepreneur.getPrimaryMail()),
// anyLong())).thenReturn(false); // Deny for other projects
}
// --- Helper Methods (Can remain non-static or static as needed) ---
@ -176,6 +161,17 @@ public class SharedApiServiceTest {
return sectionCell;
}
private static SectionCell getTestSectionCell(
Project project, Long sectionId, String content, LocalDateTime date, Long refrenceId) {
SectionCell sectionCell = new SectionCell();
sectionCell.setProjectSectionCell(project);
sectionCell.setSectionId(sectionId);
sectionCell.setContentSectionCell(content);
sectionCell.setModificationDate(date);
sectionCell.setIdReference(refrenceId);
return sectionCell;
}
private static Appointment getTestAppointment(
LocalDate date,
LocalTime time,
@ -307,6 +303,262 @@ public class SharedApiServiceTest {
assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatusCode());
}
/*
* Tests retrieving section cells for a specific project and section ID before a given date
* when the user is authorized and matching cells exist.
* Verifies that only the correct cells are returned.
*/
@Test
// Commenting out failing test
void testGetSectionCells_Authorized_Found() {
Long targetSectionId = 1L;
// Set a date filter slightly in the future so our "latest before" cell is included
LocalDateTime dateFilter = LocalDateTime.now().plusMinutes(5);
// Creating versions of the SAME SectionCell (share the same idReference)
// the first version. This will get a GENERATED idReference.
SectionCell firstVersion =
getTestSectionCell(
staticAuthorizedProject,
targetSectionId,
"Content V1 (Oldest)",
LocalDateTime.now().minusDays(3) // Oldest date
);
sectionCellService.addNewSectionCell(firstVersion);
Long sharedIdReference = firstVersion.getIdReference();
assertNotNull(
sharedIdReference,
"idReference should be generated after saving the first version");
System.out.println("Generated sharedIdReference: " + sharedIdReference);
// Create subsequent versions and MANUALLY set the SAME idReference.
// These represent updates to the cell identified by sharedIdReference.
SectionCell middleVersion =
getTestSectionCell(
staticAuthorizedProject,
targetSectionId,
"Content V2 (Middle)",
LocalDateTime.now().minusDays(2), // Middle date, before filter
sharedIdReference);
middleVersion = sectionCellService.addNewSectionCell(middleVersion);
sectionCellService.updateSectionCellReferenceId(
middleVersion.getIdSectionCell(), sharedIdReference);
SectionCell latestBeforeFilter =
getTestSectionCell(
staticAuthorizedProject,
targetSectionId,
"Content V3 (Latest Before Filter)",
LocalDateTime.now().minusDays(1), // Latest date before filter
sharedIdReference);
latestBeforeFilter = sectionCellService.addNewSectionCell(latestBeforeFilter);
sectionCellService.updateSectionCellReferenceId(
latestBeforeFilter.getIdSectionCell(), sharedIdReference);
SectionCell futureVersion =
getTestSectionCell(
staticAuthorizedProject,
targetSectionId,
"Content V4 (Future - Should Be Excluded)",
LocalDateTime.now().plusDays(1), // Date is AFTER the filter
sharedIdReference);
futureVersion = sectionCellService.addNewSectionCell(futureVersion);
sectionCellService.updateSectionCellReferenceId(
futureVersion.getIdSectionCell(), sharedIdReference);
// --- Create other SectionCells that should NOT be included (different sectionId or
// project) ---
// Cell in a different section ID
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject,
99L, // Different sectionId
"Content in Different Section",
LocalDateTime.now()));
// Act
Iterable<SectionCell> result =
sharedApiService.getSectionCells(
staticAuthorizedProject.getIdProject(), // Use static project ID
targetSectionId,
dateFilter.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")),
staticAuthorizedMail); // Use static authorized mail
List<SectionCell> resultList = TestUtils.toList(result);
assertEquals(1, resultList.size());
// Verify that the returned cell is the 'latestBeforeFilter' cell
// Comparing by idSectionCell is a good way to verify the exact entity
assertEquals(
latestBeforeFilter.getIdSectionCell(),
resultList.get(0).getIdSectionCell(),
"The returned SectionCell should be the one with the latest modification date before the filter.");
// Also assert the idReference and content
assertEquals(
sharedIdReference,
resultList.get(0).getIdReference(),
"The returned cell should have the shared idReference.");
assertEquals(
"Content V3 (Latest Before Filter)",
resultList.get(0).getContentSectionCell(),
"The returned cell should have the correct content.");
}
/*
* Tests retrieving the most recent section cell for each unique idReference
* within a project when the user is authorized and cells exist.
* Verifies that only the latest version of each referenced cell is returned.
*/
// Tests getAllSectionCells
@Test
// Commenting out failing test - Removed this comment as we are fixing it
void testGetAllSectionCells_Authorized_FoundLatest() {
// Arrange: Create specific SectionCells for this test
// Define the idReference values we will use for grouping
Long refIdGroup1 = 101L;
Long refIdGroup2 = 102L;
Long refIdOtherProject = 103L;
// --- Create and Add Cells for Group 1 (refIdGroup1) ---
// Create the older cell for group 1
SectionCell tempOldCell1 =
getTestSectionCell(
staticAuthorizedProject, // Project
1L, // Section ID (assuming this groups by section within refId)
"Ref1 Old", // Name
LocalDateTime.now().minusDays(3), // Date (older)
null); // Pass null or let getTestSectionCell handle it, we'll set
// idReference later
final SectionCell oldCell1 =
sectionCellService.addNewSectionCell(tempOldCell1); // Add to DB
// Create the newer cell for group 1
SectionCell tempNewerCell1 =
getTestSectionCell(
staticAuthorizedProject, // Project
1L, // Section ID
"Ref1 Newer", // Name
LocalDateTime.now().minusDays(2), // Date (newer than oldCell1)
null); // Pass null
final SectionCell newerCell1 =
sectionCellService.addNewSectionCell(tempNewerCell1); // Add to DB
// Now, update the idReference for both cells in Group 1 to the desired value
sectionCellService.updateSectionCellReferenceId(oldCell1.getIdSectionCell(), refIdGroup1);
sectionCellService.updateSectionCellReferenceId(newerCell1.getIdSectionCell(), refIdGroup1);
// --- Create and Add Cells for Group 2 (refIdGroup2) ---
// Create the older cell for group 2
SectionCell tempOldCell2 =
getTestSectionCell(
staticAuthorizedProject, // Project
2L, // Section ID (different section)
"Ref2 Old", // Name
LocalDateTime.now().minusDays(1), // Date (older than newerCell2)
null); // Pass null
final SectionCell oldCell2 =
sectionCellService.addNewSectionCell(tempOldCell2); // Add to DB
// Create the newer cell for group 2
SectionCell tempNewerCell2 =
getTestSectionCell(
staticAuthorizedProject, // Project
2L, // Section ID
"Ref2 Newer", // Name
LocalDateTime.now(), // Date (latest)
null); // Pass null
final SectionCell newerCell2 =
sectionCellService.addNewSectionCell(tempNewerCell2); // Add to DB
// Now, update the idReference for both cells in Group 2 to the desired value
sectionCellService.updateSectionCellReferenceId(oldCell2.getIdSectionCell(), refIdGroup2);
sectionCellService.updateSectionCellReferenceId(newerCell2.getIdSectionCell(), refIdGroup2);
// --- Create and Add Cell for Other Project (refIdOtherProject) ---
Project otherProject =
projectService.addNewProject(
getTestProject(
"other_project_for_cell_test",
administratorService.addAdministrator(
getTestAdmin("other_admin_cell_test"))));
SectionCell tempOtherProjectCell =
getTestSectionCell(
otherProject, // DIFFERENT Project
1L, // Section ID
"Other Project Cell", // Name
LocalDateTime.now(), // Date
null); // Pass null
final SectionCell otherProjectCell =
sectionCellService.addNewSectionCell(tempOtherProjectCell); // Add to DB
// Now, update the idReference for the Other Project cell
sectionCellService.updateSectionCellReferenceId(
otherProjectCell.getIdSectionCell(), refIdOtherProject);
// Act
// Ensure the service call uses the correct project ID and mail
Iterable<SectionCell> result =
sharedApiService.getAllSectionCells(
staticAuthorizedProject.getIdProject(), // Use static project ID
staticAuthorizedMail); // Use static authorized mail
List<SectionCell> resultList = TestUtils.toList(result);
// Assert
// We expect 2 cells from the staticAuthorizedProject:
// - The latest one from refIdGroup1 (newerCell1)
// - The latest one from refIdGroup2 (newerCell2)
assertEquals(2, resultList.size());
// Assert that the result list contains the LATEST cell from each group within the correct
// project
assertTrue(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(newerCell1.getIdSectionCell())),
"Should contain the latest cell for Group 1"); // Add assertion message
assertTrue(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(newerCell2.getIdSectionCell())),
"Should contain the latest cell for Group 2"); // Add assertion message
// Assert that the result list does NOT contain the OLDER cells from the correct project
assertFalse(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(oldCell1.getIdSectionCell())),
"Should not contain the older cell for Group 1"); // Add assertion message
assertFalse(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(oldCell2.getIdSectionCell())),
"Should not contain the older cell for Group 2"); // Add assertion message
// Assert that the result list does NOT contain the cell from the other project
assertFalse(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(otherProjectCell.getIdSectionCell())),
"Should not contain cells from other projects"); // Add assertion message
}
/*
* _____ _ ____ _ ____ _ _ ____
* |_ _|__ ___| |_ / ___| ___| |_| _ \ _ __ ___ (_) ___ ___| |_| __ ) _ _
@ -459,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.
@ -544,443 +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());
}
/*
_____ _ _ _
| ___|_ _(_) | ___ __| |
| |_ / _` | | |/ _ \/ _` |
| _| (_| | | | __/ (_| |
|_| \__,_|_|_|\___|\__,_|
_____ _____ ____ _____
|_ _| ____/ ___|_ _|
| | | _| \___ \ | |
| | | |___ ___) || |
|_| |_____|____/ |_|
*/
/* these tests fail because of the use of mockito's eq(),
* and since thee instances are technically not the same as
* as the classes used to turn them into persistant data
* (for e.g id are set by DB) so I have to add some equal functions
* probably and look at peer tests to see what they have done but for now
* I pushed this half-human code.
*/
// --- Test Methods (Use static data from @BeforeAll where possible) ---
/*
* Tests retrieving section cells for a specific project and section ID before a given date
* when the user is authorized and matching cells exist.
* Verifies that only the correct cells are returned.
*/
/*@Test*/
// Commenting out failing test
void testGetSectionCells_Authorized_Found() {
// Arrange: Create specific SectionCells for this test scenario
Long targetSectionId = 1L;
LocalDateTime dateFilter = LocalDateTime.now().plusDays(1);
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject,
targetSectionId,
"Old Content",
LocalDateTime.now().minusDays(2)));
SectionCell recentCell =
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject,
targetSectionId,
"Recent Content",
LocalDateTime.now().minusDays(1)));
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject, 2L, "Other Section", LocalDateTime.now()));
sectionCellService.addNewSectionCell(
getTestSectionCell(
staticAuthorizedProject,
targetSectionId,
"Future Content",
LocalDateTime.now().plusDays(2)));
// Act
Iterable<SectionCell> result =
sharedApiService.getSectionCells(
staticAuthorizedProject.getIdProject(), // Use static project ID
targetSectionId,
dateFilter.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")),
staticAuthorizedMail); // Use static authorized mail
List<SectionCell> resultList = TestUtils.toList(result);
// Assert
assertEquals(1, resultList.size());
assertEquals(recentCell.getIdSectionCell(), resultList.get(0).getIdSectionCell());
}
/*
* Tests retrieving the most recent section cell for each unique idReference
* within a project when the user is authorized and cells exist.
* Verifies that only the latest version of each referenced cell is returned.
*/
// Tests getAllSectionCells
/*@Test*/
// Commenting out failing test
void testGetAllSectionCells_Authorized_FoundLatest() {
// Arrange: Create specific SectionCells for this test
Long refId1 = 101L;
Long refId2 = 102L;
SectionCell tempOldCell1 =
getTestSectionCell(
staticAuthorizedProject, 1L, "Ref1 Old", LocalDateTime.now().minusDays(3));
tempOldCell1.setIdReference(refId1);
final SectionCell oldCell1 = sectionCellService.addNewSectionCell(tempOldCell1);
SectionCell tempNewerCell1 =
getTestSectionCell(
staticAuthorizedProject,
1L,
"Ref1 Newer",
LocalDateTime.now().minusDays(2));
tempNewerCell1.setIdReference(refId1);
final SectionCell newerCell1 = sectionCellService.addNewSectionCell(tempNewerCell1);
SectionCell tempOldCell2 =
getTestSectionCell(
staticAuthorizedProject, 2L, "Ref2 Old", LocalDateTime.now().minusDays(1));
tempOldCell2.setIdReference(refId2);
final SectionCell oldCell2 = sectionCellService.addNewSectionCell(tempOldCell2);
SectionCell tempNewerCell2 =
getTestSectionCell(staticAuthorizedProject, 2L, "Ref2 Newer", LocalDateTime.now());
tempNewerCell2.setIdReference(refId2);
final SectionCell newerCell2 = sectionCellService.addNewSectionCell(tempNewerCell2);
Project otherProject =
projectService.addNewProject(
getTestProject(
"other_project_for_cell_test",
administratorService.addAdministrator(
getTestAdmin("other_admin_cell_test"))));
SectionCell tempOtherProjectCell =
getTestSectionCell(otherProject, 1L, "Other Project Cell", LocalDateTime.now());
tempOtherProjectCell.setIdReference(103L);
final SectionCell otherProjectCell =
sectionCellService.addNewSectionCell(tempOtherProjectCell);
// Act
Iterable<SectionCell> result =
sharedApiService.getAllSectionCells(
staticAuthorizedProject.getIdProject(), // Use static project ID
staticAuthorizedMail); // Use static authorized mail
List<SectionCell> resultList = TestUtils.toList(result);
// Assert
assertEquals(2, resultList.size()); // Expect 2 cells (one per idReference)
assertTrue(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(newerCell1.getIdSectionCell())));
assertTrue(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(newerCell2.getIdSectionCell())));
assertFalse(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(oldCell1.getIdSectionCell())));
assertFalse(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(oldCell2.getIdSectionCell())));
assertFalse(
resultList.stream()
.anyMatch(
cell ->
cell.getIdSectionCell()
.equals(otherProjectCell.getIdSectionCell())));
}
/*
* Tests retrieving entrepreneurs linked to a project when the user is authorized
* and entrepreneurs are linked.
* 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
@ -39,7 +41,7 @@ paths:
schema:
$ref: "./main.yaml#/components/schemas/project"
responses:
"201": # Use 201 Created for successful creation
"200": # Use 200 Created for successful creation
description: Created - Project added successfully. Returns the created project.
content:
application/json:
@ -49,6 +51,8 @@ paths:
description: Bad Request - Project already exists.
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/projects/pending:
@ -71,6 +75,8 @@ paths:
$ref: "./main.yaml#/components/schemas/project" # Assuming pending projects use the same schema
"401":
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:
@ -122,6 +130,8 @@ paths:
description: Bad Request - Invalid input (e.g., missing decision).
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/projects/pending/decision:
@ -136,14 +146,6 @@ paths:
If rejected (isAccepted=false), the pending project data might be archived or deleted based on business logic.
security:
- MyINPulse: [MyINPulse-admin]
parameters:
- in: path
name: pendingProjectId # Corrected typo and name change
required: true
schema:
type: integer
description: The ID of the pending project to decide upon.
example: 7
requestBody:
required: true
description: Decision payload.
@ -152,12 +154,14 @@ paths:
schema:
$ref: './main.yaml#/components/schemas/projectDecision'
responses:
"204": # Use 204 No Content for successful action with no body
"200": # Use 200 No Content for successful action with no body
description: No Content - Decision processed successfully.
"400":
description: Bad Request - Invalid input (e.g., missing decision).
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/admin/pending-accounts: # Path updated
@ -180,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
@ -199,11 +205,12 @@ paths:
description: The ID of the user account to validate.
example: 102
responses:
"204":
"200":
description: No Content - Account validated successfully.
"400":
description: Bad Request - Invalid user ID format.
"403":
description: Bad Token - Invalid Keycloack configuration.
"401":
description: Unauthorized.
@ -225,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":
@ -255,13 +264,15 @@ paths:
schema:
$ref: "./main.yaml#/components/schemas/report"
responses:
"201":
"200":
description: Created - Report created and linked successfully. Returns the created report.
content:
application/json:
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.
@ -296,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.
@ -318,10 +331,12 @@ paths:
description: The ID of the project to remove.
example: 12
responses:
"204":
"200":
description: No Content - Project removed successfully.
"400":
description: Bad Request - Invalid project ID format.
"403":
description: Bad Token - Invalid Keycloack configuration.
"401":
description: Unauthorized.
@ -345,9 +360,28 @@ paths:
description: The ID of the user to grant admin rights.
example: 103
responses:
"204": # Use 204 No Content
"200": # Use 200 No Content
description: No Content - Admin rights granted successfully.
"400":
description: Bad Request - Invalid user ID format or user is already an admin.
"403":
description: Bad Token - Invalid Keycloack configuration.
"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.
"403":
description: Bad Token - Invalid Keycloack configuration.

View File

@ -21,12 +21,14 @@ paths:
schema:
$ref: "./main.yaml#/components/schemas/project"
responses:
"202":
"200":
description: Accepted - Project creation request received and is pending validation.
"400":
description: Bad Request - Invalid input (e.g., missing name).
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/sectionCells: # Base path
post:
@ -46,12 +48,14 @@ paths:
schema:
$ref: "./main.yaml#/components/schemas/sectionCell"
responses:
"201":
"200":
description: Created - Section cell added successfully. Returns the created cell.
"400":
description: Bad Request - Invalid input (e.g., missing content or sectionId).
"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
@ -102,7 +108,7 @@ paths:
description: The ID of the section cell to remove.
example: 509
responses:
"204":
"200":
description: No Content - Section cell removed successfully.
"400":
description: Bad Request - Invalid ID format.
@ -110,3 +116,82 @@ paths:
description: Bad Request - sectionCell not found.
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/projects:
get:
summary: gets the projectId of the project associated with the entrepreneur
description: returns a list of projectIds of the projects associated with the entrepreneur
tags:
- Entrepreneurs API
security:
- MyINPulse: [MyINPulse-entrepreneur]
parameters:
responses:
"200":
description: OK - Section cell updated successfully. Returns the updated cell.
content:
application/json:
schema:
type: array
items:
$ref: "./main.yaml#/components/schemas/project"
"404":
description: Bad Request - Invalid input or ID mismatch.
"401":
description: Unauthorized or identity not found
"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"
# _ ____ __ __ ___ _ _ _ ____ ___
# / \ | _ \| \/ |_ _| \ | | / \ | _ \_ _|
@ -108,6 +112,8 @@ paths:
$ref: "./adminApi.yaml#/paths/~1admin~1projects~1{projectId}"
/admin/make-admin/{userId}:
$ref: "./adminApi.yaml#/paths/~1admin~1make-admin~1{userId}"
/admin/create-account:
$ref: "./adminApi.yaml#/paths/~1admin~1create-account"
# ____ _ _ _ ____ ___
# / ___|| |__ __ _ _ __ ___ __| | / \ | _ \_ _|
@ -138,9 +144,16 @@ paths:
# / ___ \| __/| |
# /_/ \_\_| |___|
#
/entrepreneur/projects:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects"
/entrepreneur/projects/request:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects~1request"
/entrepreneur/sectionCells:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells"
/entrepreneur/sectionCells/{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

@ -37,6 +37,8 @@ paths:
$ref: "./main.yaml#/components/schemas/sectionCell"
"400":
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:
@ -176,11 +182,12 @@ paths:
$ref: "./main.yaml#/components/schemas/appointment" # Assuming request uses same model structure
# Potentially add projectId or targetUserId here
responses:
"202": # Accepted seems appropriate for a request
"200": # Accepted seems appropriate for a request
description: Accepted - Appointment request submitted.
"400":
description: Bad Request - Invalid appointment details.
"401":
description: Unauthorized.
"403":
description: Bad Token - Invalid Keycloack configuration.

View File

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

@ -37,6 +37,11 @@
</template>
<script>
import {
createAppointmentReport,
updateAppointmentReport,
} from "@/services/Apis/Admin";
export default {
name: "AdminAppointmentsComponent",
data() {
@ -63,28 +68,21 @@ export default {
this.appointmentId--;
}
},
async submitReport() {
const reportData = {
reportContent: this.reportContent,
};
const url = `/admin/appointments/report/${this.appointmentId}`;
const method = this.isUpdate ? "PUT" : "POST";
try {
const response = await this.$axios({
method,
url,
data: reportData,
});
submitReport() {
const reportData = { content: this.reportContent };
const onSuccess = (response) => {
if (response.status === 201 || response.status === 200) {
this.responseMessage =
"Rapport " +
(this.isUpdate ? "mis à jour" : "créé") +
" avec succès.";
}
} catch (error) {
};
const onError = (error) => {
console.error(
"Erreur lors de l'envoi du rapport :",
error.response || error.message
);
if (error.response && error.response.status === 400) {
this.responseMessage =
"Requête invalide. Vérifiez les informations.";
@ -95,6 +93,22 @@ export default {
this.responseMessage =
"Une erreur est survenue. Veuillez réessayer.";
}
};
if (this.isUpdate) {
updateAppointmentReport(
this.appointmentId,
reportData,
onSuccess,
onError
);
} else {
createAppointmentReport(
this.appointmentId,
reportData,
onSuccess,
onError
);
}
},
},

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

@ -35,7 +35,6 @@
</button>
</div>
</div>
<RouterLink to="/" class="return-button">Retour</RouterLink>
</div>
</div>
</header>

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

@ -1,6 +1,7 @@
import { type AxiosError, type AxiosResponse } from "axios";
import Report from "@/ApiClasses/Repport";
import ProjectDecision from "@/ApiClasses/ProjectDecision";
//import UserAdmin from "@/ApiClasses/UserAdmin";
import {
axiosInstance,
defaultApiErrorHandler,
@ -297,6 +298,28 @@ function grantAdminRights(
});
}
function createAdmin(
onSuccessHandler?: (response: AxiosResponse) => void,
onErrorHandler?: (error: AxiosError) => void
): void {
axiosInstance
.post("/admin/create-account")
.then((response) => {
if (onSuccessHandler) {
onSuccessHandler(response);
} else {
defaultApiSuccessHandler(response);
}
})
.catch((error: AxiosError) => {
if (onErrorHandler) {
onErrorHandler(error);
} else {
defaultApiErrorHandler(error);
}
});
}
export {
axiosInstance,
//requestJoinProject, // Not yet implemented [cite: 4]
@ -313,4 +336,5 @@ export {
getUpcomingAppointments,
removeProject,
grantAdminRights,
createAdmin,
};

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

@ -44,9 +44,10 @@ import ProjectComp from "../components/ProjectComponent.vue";
import PendingProjectComponent from "@/components/PendingProjectComponent.vue";
import AddProjectForm from "@/components/AddProjectForm.vue";
import PendingRequestsManager from "@/components/PendingRequestsManager.vue";
import { createAdmin } from "@/services/Apis/Admin";
import Project from "@/ApiClasses/Project";
import UserEntrepreneur from "@/ApiClasses/UserEntrepreneur";
//import UserAdmin from "@/ApiClasses/UserAdmin";
//import AllEntrep from "../components/AllEntrep.vue";
const projects = ref<
{
@ -69,6 +70,22 @@ const fallbackProjects = [
},
];
const createFirstAdmin = () => {
createAdmin(
(response) => {
console.log("Admin créé avec succès :", response.data);
},
(error) => {
console.error(
"Erreur lors de la création de l'admin :",
error.message
);
}
);
};
onMounted(createFirstAdmin);
const fetchProjects = () => {
getAdminProjects(
(response: AxiosResponse) => {
@ -121,8 +138,6 @@ const fetchProjects = () => {
);
};
onMounted(fetchProjects);
const pendingProjects = ref<Project[]>([]);
const mockPendingProjects = [
@ -130,6 +145,7 @@ const mockPendingProjects = [
new Project({ projectName: "l'air", creationDate: "09-03-2023" }),
];
onMounted(fetchProjects);
onMounted(() => {
getPendingProjects(
(response) => {

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