diff --git a/Makefile b/Makefile index 676cf0d..6f4e820 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,6 @@ help: @echo "make [clean dev-front prod dev-back dev]" clean: - pkill -9 node @cp config/frontdev.env front/MyINPulse-front/.env @cp config/frontdev.env .env @cp config/frontdev.env MyINPulse-back/.env diff --git a/MyINPulse-back/build.gradle b/MyINPulse-back/build.gradle index 47b0dc2..326537b 100644 --- a/MyINPulse-back/build.gradle +++ b/MyINPulse-back/build.gradle @@ -29,6 +29,8 @@ dependencies { implementation 'org.postgresql:postgresql' implementation group: 'com.itextpdf', name: 'itextpdf', version: '5.5.13.3' + runtimeOnly 'com.h2database:h2' + testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'com.h2database:h2' diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/EntrepreneurApi.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/EntrepreneurApi.java index 82f7e6e..206e3f9 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/EntrepreneurApi.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/EntrepreneurApi.java @@ -95,4 +95,22 @@ public class EntrepreneurApi { @RequestBody Project project, @AuthenticationPrincipal Jwt principal) { entrepreneurApiService.requestNewProject(project, principal.getClaimAsString("email")); } + + /* + *

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")); + } + + /* + *

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")); + } } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/UnauthApi.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/UnauthApi.java index 52ddd41..ede6d1b 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/UnauthApi.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/controller/UnauthApi.java @@ -2,6 +2,7 @@ package enseirb.myinpulse.controller; import enseirb.myinpulse.model.Entrepreneur; import enseirb.myinpulse.service.EntrepreneurApiService; +import enseirb.myinpulse.service.UtilsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -14,13 +15,15 @@ import org.springframework.web.bind.annotation.*; public class UnauthApi { private final EntrepreneurApiService entrepreneurApiService; + private final UtilsService utilsService; @Autowired - UnauthApi(EntrepreneurApiService entrepreneurApiService) { + UnauthApi(EntrepreneurApiService entrepreneurApiService, UtilsService utilsService) { this.entrepreneurApiService = entrepreneurApiService; + this.utilsService = utilsService; } - @GetMapping("/unauth/finalize") + @PostMapping("/unauth/finalize") public void createAccount(@AuthenticationPrincipal Jwt principal) { boolean sneeStatus; if (principal.getClaimAsString("sneeStatus") != null) { @@ -46,6 +49,13 @@ public class UnauthApi { course, sneeStatus, true); + 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")); + } } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/repository/SectionCellRepository.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/repository/SectionCellRepository.java index 4ad5b51..ecbd66f 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/repository/SectionCellRepository.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/repository/SectionCellRepository.java @@ -15,4 +15,6 @@ public interface SectionCellRepository extends JpaRepository Iterable findByProjectSectionCellAndSectionIdAndModificationDateBefore( Project project, long sectionId, LocalDateTime date); + + Iterable findByProjectSectionCell(Project project); } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/AdminApiService.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/AdminApiService.java index 364415c..2a7c0da 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/AdminApiService.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/AdminApiService.java @@ -217,4 +217,8 @@ public class AdminApiService { new Administrator(username, userSurname, primaryMail, secondaryMail, phoneNumber); this.administratorService.addAdministrator(a); } + + public Iterable getAllAdmins() { + return this.administratorService.allAdministrators(); + } } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/EntrepreneurApiService.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/EntrepreneurApiService.java index 1cf6ac5..8bfbdcb 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/EntrepreneurApiService.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/EntrepreneurApiService.java @@ -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; @@ -230,4 +232,53 @@ public class EntrepreneurApiService { Project_List.add(entrepreneur.getProjectParticipation()); return Project_List; } + + public Iterable 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; + } } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/SharedApiService.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/SharedApiService.java index 8c0584a..2f315d7 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/SharedApiService.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/SharedApiService.java @@ -26,8 +26,10 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; @Service public class SharedApiService { @@ -169,18 +171,26 @@ public class SharedApiService { "User {} tried to check the appointments related to the project {}", mail, projectId); - Iterable sectionCells = - this.sectionCellService.getSectionCellsByProject( - projectService.getProjectById(projectId), - 2L); // sectionId useless in this function ? - List appointments = new ArrayList(); - sectionCells.forEach( + + Project project = projectService.getProjectById(projectId); + + Iterable sectionCellsIterable = + this.sectionCellService.getSectionCellsByProject(project); + + // Use a Set to collect unique appointments + Set uniqueAppointments = new HashSet<>(); + + sectionCellsIterable.forEach( sectionCell -> { - appointments.addAll( + List 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) diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/UtilsService.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/UtilsService.java index f7412de..b8822e5 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/UtilsService.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/UtilsService.java @@ -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(); + } } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/database/SectionCellService.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/database/SectionCellService.java index d1a444d..f222349 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/service/database/SectionCellService.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/service/database/SectionCellService.java @@ -14,6 +14,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -118,6 +119,18 @@ public class SectionCellService { return this.sectionCellRepository.findByProjectSectionCellAndSectionId(project, sectionId); } + public Iterable getSectionCellsByProject(Project project) { + logger.info("Fetching SectionCells for Project ID: {}", project.getIdProject()); + Iterable sectionCells = + this.sectionCellRepository.findByProjectSectionCell(project); + List sectionCellList = new ArrayList<>(); + sectionCells.forEach( + cell -> { + sectionCellList.add(cell); + }); + return sectionCellList; + } + public Long getProjectId(Long sectionCellId) { SectionCell sectionCell = getSectionCellById(sectionCellId); Project sectionProject = sectionCell.getProjectSectionCell(); diff --git a/MyINPulse-back/src/main/resources/application.properties_dbtest b/MyINPulse-back/src/main/resources/application.properties_dbtest new file mode 100644 index 0000000..37c209e --- /dev/null +++ b/MyINPulse-back/src/main/resources/application.properties_dbtest @@ -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 \ No newline at end of file diff --git a/MyINPulse-back/src/main/resources/data.sql b/MyINPulse-back/src/main/resources/data.sql deleted file mode 100644 index 444f8da..0000000 --- a/MyINPulse-back/src/main/resources/data.sql +++ /dev/null @@ -1,99 +0,0 @@ -TRUNCATE project, user_inpulse, entrepreneur, administrator, section_cell, appointment, report, annotation CASCADE; - -SELECT setval('annotation_id_annotation_seq', 1, false); -SELECT setval('appointment_id_appointment_seq', 1, false); -SELECT setval('make_appointment_id_make_appointment_seq', 1, false); -SELECT setval('project_id_project_seq', 1, false); -SELECT setval('report_id_report_seq', 1, false); -SELECT setval('section_cell_id_section_cell_seq', 1, false); -SELECT setval('user_inpulse_id_user_seq', 1, false); - -INSERT INTO user_inpulse (user_surname, user_name, primary_mail, secondary_mail, phone_number) -VALUES ('Dupont', 'Dupond', 'super@mail.fr', 'super2@mail.fr', '06 45 72 45 98'), - ('Martin', 'Matin', 'genial@mail.fr', 'genial2@mail.fr', '06 52 14 58 73'), - ('Charvet', 'Lautre', 'mieux@tmail.fr', 'mieux2@tmail.fr', '07 49 82 16 35'), - ('Leguez', 'Theo', 'bof@mesmails.fr', 'bof2@mesmails.fr', '+33 6 78 14 25 29'), - ('Kia', 'Bi', 'special@mail.fr', 'special2@mail.fr', '07 65 31 38 95'), - ('Ducaillou', 'Pierre', 'maildefou@xyz.fr', 'maildefou2@xyz.fr', '06 54 78 12 62'), - ('Janine', 'Dave', 'janine@labri.fr', 'janine2@labri.fr', '06 87 12 45 95'); - -INSERT INTO administrator (id_administrator) -VALUES (7); - -INSERT INTO project (project_name, logo, creation_date, project_status, id_administrator) -VALUES ('Eau du robinet', decode('013d7d16d7ad4fefb61bd95b765c8ceb', 'hex'), TO_DATE('01-OCT-2023', 'DD-MON-YYYY'), - 'En cours', 7), - ('Air oxygéné', decode('150647a0984e8f228cd14b54', 'hex'), TO_DATE('04-APR-2024', 'DD-MON-YYYY'), 'En cours', 7), - ('Débat concours', decode('022024abd5486e245c145dda65116f', 'hex'), TO_DATE('22-NOV-2023', 'DD-MON-YYYY'), - 'Suspendu', 7), - ('HDeirbMI', decode('ab548d6c1d595a2975e6476f544d14c55a', 'hex'), TO_DATE('07-DEC-2024', 'DD-MON-YYYY'), - 'Lancement', 7); - - -INSERT INTO entrepreneur (school, course, snee_status, id_entrepreneur, id_project_participation, id_project_proposed) -VALUES ('ENSEIRB-MATMECA', 'INFO', TRUE, 1, 4, 4), - ('ENSC', 'Cognitique', TRUE, 2, 2, null), - ('ENSEIRB-MATMECA', 'MATMECA', FALSE, 3, 3, 3), - ('SupOptique', 'Classique', TRUE, 4, 1, 1), - ('ENSEGID', 'Géoscience', FALSE, 5, 1, null), - ('ENSMAC', 'Matériaux composites - Mécanique', FALSE, 6, 2, 2); - - -INSERT INTO section_cell (title, content_section_cell, modification_date, id_project) -VALUES ('Problème', 'les problèmes...', TO_TIMESTAMP('15-JAN-2025 09:30:20', 'DD-MON-YYYY, HH24:MI:SS'), 2), - ('Segment de client', 'Le segment AB passant le client n°8 est de longueur 32mm. - Le segment BC a quant à lui un longueur de 28mm. Quelle la longueur du segment AC ?', - TO_TIMESTAMP('12-OCT-2022 17:47:38', 'DD-MON-YYYY, HH24:MI:SS'), 3), - ('Proposition de valeur unique', '''Son prix est de 2594€'' ''Ah oui c''est unique en effet', - TO_TIMESTAMP('25-MAY-2024 11:12:04', 'DD-MON-YYYY, HH24:MI:SS'), 2), - ('Solution', 'Un problème ? Une solution', TO_TIMESTAMP('08-FEB-2024 10:17:53', 'DD-MON-YYYY, HH24:MI:SS'), 1), - ('Canaux', 'Ici nous avons la Seine, là-bas le Rhin, oh et plus loin le canal de Suez', - TO_TIMESTAMP('19-JUL-2023 19:22:45', 'DD-MON-YYYY, HH24:MI:SS'), 4), - ('Sources de revenus', 'Y''en n''a pas on est pas payé. Enfin y''a du café quoi', - TO_TIMESTAMP('12-JAN-2025 11:40:26', 'DD-MON-YYYY, HH24:MI:SS'), 1), - ('Structure des coûts', '''Ah oui là ça va faire au moins 1000€ par mois'', Eirbware', - TO_TIMESTAMP('06-FEB-2025 13:04:06', 'DD-MON-YYYY, HH24:MI:SS'), 3), - ('Indicateurs clés', 'On apprend les clés comme des badges, ça se fait', - TO_TIMESTAMP('05-FEB-2025 12:42:38', 'DD-MON-YYYY, HH24:MI:SS'), 4), - ('Avantages concurrentiel', 'On est meilleur', TO_TIMESTAMP('23-APR-2024 16:24:02', 'DD-MON-YYYY, HH24:MI:SS'), - 2); - -INSERT INTO appointment (appointment_date, appointment_time, appointment_duration, appointment_place, - appointment_subject) -VALUES (TO_DATE('24-DEC-2023', 'DD-MON-YYYY'), '00:00:00', '00:37:53', 'À la maison', 'Ouvrir les cadeaux'), - (TO_DATE('15-AUG-2024', 'DD-MON-YYYY'), '22:35:00', '00:12:36', 'Sur les quais ou dans un champ probablement', - 'BOUM BOUM les feux d''artifices (on fête quoi déjà ?)'), - (TO_DATE('28-FEB-2023', 'DD-MON-YYYY'), '14:20:00', '00:20:00', 'Salle TD 15', - 'Ah mince c''est pas une année bissextile !'), - (TO_DATE('23-JAN-2024', 'DD-MON-YYYY'), '12:56:27', '11:03:33', 'Là où le vent nous porte', - 'Journée la plus importante de l''année'), - (TO_DATE('25-AUG-2025', 'DD-MON-YYYY'), '00:09:00', '01:00:00', 'Euh c''est par où l''amphi 56 ?', - 'Rentrée scolaire (il fait trop froid c''est quoi ça on est en août)'); - -INSERT INTO report (report_content, id_appointment) -VALUES ('Ah oui ça c''est super, ah ouais j''aime bien, bien vu de penser à ça', 1), - ('Bonne réunion', 3), - ('Ouais, j''ai rien compris mais niquel on fait comme vous avez dit', 3), - ('Non non ça va pas du tout ce que tu me proposes, faut tout refaire', 4), - ('Réponse de la DSI : non', 2), - ('Trop dommage qu''Apple ait sorti leur logiciel avant nous, on avait la même idée et tout on aurait tellement pu leur faire de la concurrence', - 5); - -INSERT INTO annotation (comment, id_administrator, id_section_cell) -VALUES ('faut changer ça hein', 7, 5), - ('??? sérieusement, vous pensez que c''est une bonne idée ?', 7, 7), - ('ok donc ça c''est votre business plan, bah glhf la team', 7, 2); - - - - - - - - - - - - - - diff --git a/MyINPulse-back/src/main/resources/delete.sql b/MyINPulse-back/src/main/resources/delete.sql deleted file mode 100644 index 701b1f3..0000000 --- a/MyINPulse-back/src/main/resources/delete.sql +++ /dev/null @@ -1,2 +0,0 @@ -DROP TABLE IF EXISTS administrateurs, projets, utilisateurs, entrepreneurs, sections, rendez_vous, comptes_rendus, concerner CASCADE; -DROP TABLE IF EXISTS administrator, project, user_inpulse, entrepreneur, section_cell, appointment, make_appointment, report, annotation, concern CASCADE; \ No newline at end of file diff --git a/MyINPulse-back/src/main/resources/import.sql b/MyINPulse-back/src/main/resources/import.sql new file mode 100644 index 0000000..376e5fa --- /dev/null +++ b/MyINPulse-back/src/main/resources/import.sql @@ -0,0 +1,92 @@ +-- Initial Database State Script + +-- Insert Administrators +INSERT INTO administrator (idAdministrator) VALUES +(1), +(2), +(3); -- Added more administrators + +-- Insert User Inpulse (some pending) +INSERT INTO user_inpulse (idUser, userSurname, userName, primaryMail, secondaryMail, phoneNumber, pending) VALUES +(1, 'Doe', 'John', 'john.doe@example.com', NULL, '123-456-7890', FALSE), +(2, 'Smith', 'Jane', 'jane.smith@example.com', 'jane.s@altmail.com', '987-654-3210', FALSE), +(3, 'Williams', 'Peter', 'peter.w@example.com', NULL, NULL, TRUE), -- Pending user +(4, 'Jones', 'Mary', 'mary.j@example.com', NULL, '555-123-4567', FALSE), +(5, 'Brown', 'Michael', 'michael.b@example.com', 'mike.brown@work.com', '111-222-3333', FALSE), +(6, 'Garcia', 'Maria', 'maria.g@example.com', NULL, '444-555-6666', TRUE), -- Another pending user +(7, 'Miller', 'David', 'david.m@example.com', NULL, '777-888-9999', FALSE); + +-- Insert Entrepreneurs +INSERT INTO entrepreneur (idEntrepreneur, school, course, sneeStatus, idProjectParticipation, idProjectProposed, idMakeAppointment) VALUES +(1, 'Business School A', 'MBA', TRUE, NULL, NULL, NULL), +(2, 'Tech University B', 'Computer Science', FALSE, NULL, NULL, NULL), +(3, 'Art Institute C', 'Graphic Design', TRUE, NULL, NULL, NULL), +(4, 'Science College D', 'Biology', FALSE, NULL, NULL, NULL), +(5, 'Engineering School E', 'Mechanical Engineering', TRUE, NULL, NULL, NULL); -- Added more entrepreneurs + +-- Insert Projects +-- Main project +INSERT INTO project (IdProject, projectName, loga, creationDate, projectStatus, pending, idAdministrator, entrepreneurProposed) VALUES +(101, 'Innovative Startup Idea', NULL, '2023-10-26', 'In Progress', FALSE, 1, NULL), +(102, 'Pending Project Alpha', NULL, '2024-01-15', 'Planning', TRUE, 1, NULL), -- Pending project +(103, 'Pending Project Beta', NULL, '2024-02-20', 'Idea Stage', TRUE, NULL, 1), -- Another pending project, proposed by entrepreneur 1 +(104, 'E-commerce Platform Development', NULL, '2024-03-10', 'Completed', FALSE, 2, NULL), -- Completed project +(105, 'Mobile App for Education', NULL, '2024-04-01', 'In Progress', FALSE, 3, NULL), +(106, 'Pending Research Proposal', NULL, '2024-04-25', 'Drafting', TRUE, NULL, 4); -- Pending project proposed by entrepreneur 4 + +-- Link Entrepreneurs to projects (Project Participation and Proposed) +-- Based on the current schema, we'll update the entrepreneur table directly. +-- This might need adjustment based on actual application logic if it's a many-to-many. +UPDATE entrepreneur SET idProjectParticipation = 101 WHERE idEntrepreneur IN (1, 2); -- Entrepreneurs 1 and 2 participate in Project 101 +UPDATE entrepreneur SET idProjectParticipation = 104 WHERE idEntrepreneur = 3; -- Entrepreneur 3 participates in Project 104 +UPDATE entrepreneur SET idProjectProposed = 103 WHERE idEntrepreneur = 1; -- Entrepreneur 1 proposed Project 103 +UPDATE entrepreneur SET idProjectProposed = 106 WHERE idEntrepreneur = 4; -- Entrepreneur 4 proposed Project 106 + +-- Insert Section Cells for the main project (Project 101) and other projects +INSERT INTO section_cell (idSectionCell, IdReference, sectionid, contentSectionCell, modificationDate, idProject) VALUES +(1001, NULL, 1, 'Initial project description for Project 101.', '2023-10-26 10:00:00', 101), +(1002, 1001, 2, 'Market analysis summary for Project 101.', '2023-10-27 14:30:00', 101), +(1003, NULL, 3, 'Team member profiles for Project 101.', '2023-10-28 09:00:00', 101), +(1004, NULL, 1, 'Project brief for Project 104.', '2024-03-10 11:00:00', 104), +(1005, 1004, 2, 'Technical specifications for Project 104.', '2024-03-15 16:00:00', 104), +(1006, NULL, 1, 'Initial concept for Project 105.', '2024-04-01 09:30:00', 105); + +-- Insert Appointments +INSERT INTO appointment (idAppointment, appointmentDate, appointmentTime, appointmentDuration, appointmentPlace, appointmentSubject) VALUES +(2001, '2023-11-05', '11:00:00', '01:00:00', 'Meeting Room A', 'Project 101 Kick-off Meeting'), +(2002, '2023-11-10', '14:00:00', '00:30:00', 'Online', 'Project 101 Follow-up Discussion'), +(2003, '2024-03-20', '10:00:00', '01:30:00', 'Client Office', 'Project 104 Final Review'), +(2004, '2024-04-10', '15:00:00', '00:45:00', 'Video Call', 'Project 105 Initial Sync'); -- Added more appointments + +-- Insert Concerns (linking Appointments and Section Cells) +INSERT INTO concern (IdAppointment, idSectionCell) VALUES +(2001, 1001), -- Kick-off meeting concerns section 1001 (Project 101) +(2001, 1002), -- Kick-off meeting concerns section 1002 (Project 101) +(2002, 1002), -- Follow-up concerns section 1002 (Project 101) +(2003, 1004), -- Project 104 review concerns section 1004 +(2003, 1005), -- Project 104 review concerns section 1005 +(2004, 1006); -- Project 105 sync concerns section 1006 + +-- Insert Make Appointments (linking Appointments, Administrators, and Entrepreneurs) +INSERT INTO make_appointment (idMakeAppointment, idAppointment, idAdministrator, idEntrepreneur) VALUES +(3001, 2001, 1, 1), -- Admin 1 scheduled appointment 2001 with Entrepreneur 1 +(3002, 2001, 1, 2), -- Admin 1 scheduled appointment 2001 with Entrepreneur 2 +(3003, 2002, 1, 1), -- Admin 1 scheduled appointment 2002 with Entrepreneur 1 +(3004, 2003, 2, 3), -- Admin 2 scheduled appointment 2003 with Entrepreneur 3 +(3005, 2004, 3, 5); -- Admin 3 scheduled appointment 2004 with Entrepreneur 5 + +-- Insert Annotations (linking to Section Cells and Administrators) +INSERT INTO annotation (IdAnnotation, comment, idSectionCell, idAdministrator) VALUES +(4001, 'Needs more detail on market size.', 1002, 1), +(4002, 'Looks good.', 1001, 1), +(4003, 'Confirm technical requirements.', 1005, 2), +(4004, 'Initial thoughts on UI/UX.', 1006, 3); -- Added more annotations + +-- Insert Reports (linking to Make Appointments) +INSERT INTO report (IdReport, reportContent, idMakeAppointment) VALUES +(5001, 'Discussed project scope and timelines for Project 101.', 3001), +(5002, 'Reviewed market analysis feedback for Project 101.', 3003), +(5003, 'Final sign-off on Project 104 deliverables.', 3004), +(5004, 'Discussed initial concepts for Project 105.', 3005); -- Added more reports + +-- The project ID for the main project is 101. diff --git a/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java b/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java index 933b6b0..a2ab7c5 100644 --- a/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java +++ b/MyINPulse-back/src/test/java/enseirb/myinpulse/SharedApiServiceTest.java @@ -8,9 +8,10 @@ import static org.mockito.Mockito.when; import enseirb.myinpulse.model.*; import enseirb.myinpulse.service.SharedApiService; import enseirb.myinpulse.service.database.*; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import enseirb.myinpulse.service.UtilsService; -import com.itextpdf.text.DocumentException; import org.junit.jupiter.api.BeforeAll; // Use BeforeAll for static setup import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; // Keep this import @@ -22,8 +23,6 @@ import org.springframework.web.server.ResponseStatusException; import org.springframework.http.HttpStatus; import org.springframework.test.context.bean.override.mockito.MockitoBean; -import java.io.IOException; -import java.net.URISyntaxException; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -712,6 +711,129 @@ public class SharedApiServiceTest { assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatusCode()); } + @PersistenceContext // Inject EntityManager + private EntityManager entityManager; + + // Assume these static variables are defined elsewhere in your test class + // private static Project staticAuthorizedProject; + // private static String staticAuthorizedMail; + // private static Administrator staticAuthorizedAdmin; + + // Assume getTestSectionCell, getTestProject, getTestAdmin, getTestAppointment, TestUtils.toList + // are defined elsewhere + + @Test + void testGetAppointmentsByProjectId_Authorized_Found() { + // Arrange: Create specific SectionCells and Appointments for this test + SectionCell cell1 = + sectionCellService.addNewSectionCell( + getTestSectionCell( + staticAuthorizedProject, 1L, "Cell 1 Test", LocalDateTime.now())); + SectionCell cell2 = + sectionCellService.addNewSectionCell( + getTestSectionCell( + staticAuthorizedProject, 2L, "Cell 2 Test", LocalDateTime.now())); + Project otherProject = + projectService.addNewProject( + getTestProject( + "other_project_app_test", + administratorService.addAdministrator( + getTestAdmin("other_admin_app_test")))); + SectionCell otherProjectCell = + sectionCellService.addNewSectionCell( + getTestSectionCell( + otherProject, + 1L, + "Other Project Cell App Test", + LocalDateTime.now())); + + // Create Appointments with SectionCells lists (Owning side) + Appointment app1 = + getTestAppointment( + LocalDate.now().plusDays(10), + LocalTime.NOON, + LocalTime.of(0, 30), + "Place 1 App Test", + "Subject 1 App Test", + List.of(cell1), // This links Appointment to SectionCell + null); + Appointment savedApp1 = appointmentService.addNewAppointment(app1); + + Appointment app2 = + getTestAppointment( + LocalDate.now().plusDays(11), + LocalTime.NOON.plusHours(1), + LocalTime.of(1, 0), + "Place 2 App Test", + "Subject 2 App Test", + List.of(cell1, cell2), // This links Appointment to SectionCells + null); + Appointment savedApp2 = appointmentService.addNewAppointment(app2); + + Appointment otherApp = + getTestAppointment( + LocalDate.now().plusDays(12), + LocalTime.MIDNIGHT, + LocalTime.of(0, 15), + "Other Place App Test", + "Other Subject App Test", + List.of(otherProjectCell), // This links Appointment to SectionCell + null); + Appointment savedOtherApp = + appointmentService.addNewAppointment(otherApp); // Capture saved entity + + // --- IMPORTANT DEBUGGING STEPS --- + // Flush pending changes to the database (including join table inserts) + entityManager.flush(); + // Clear the persistence context cache to ensure entities are loaded fresh from the database + entityManager.clear(); + // --- END IMPORTANT DEBUGGING STEPS --- + + // --- Add Debug Logging Here --- + // Re-fetch cells to see their state after saving Appointments and flushing/clearing cache + // These fetches should load from the database due to entityManager.clear() + SectionCell fetchedCell1_postPersist = + sectionCellService.getSectionCellById(cell1.getIdSectionCell()); + SectionCell fetchedCell2_postPersist = + sectionCellService.getSectionCellById(cell2.getIdSectionCell()); + SectionCell fetchedOtherCell_postPersist = + sectionCellService.getSectionCellById(otherProjectCell.getIdSectionCell()); + + // Access the lazy collections to see if they are populated from the DB + // This access should trigger lazy loading if the data is in the DB + List cell1Apps_postPersist = + fetchedCell1_postPersist.getAppointmentSectionCell(); + List cell2Apps_postPersist = + fetchedCell2_postPersist.getAppointmentSectionCell(); + List otherCellApps_postPersist = + fetchedOtherCell_postPersist.getAppointmentSectionCell(); + + // Ensure logging is enabled in SharedApiService and SectionCellService methods called below + Iterable result = + sharedApiService.getAppointmentsByProjectId( + staticAuthorizedProject.getIdProject(), // Use static project ID + staticAuthorizedMail); // Use static authorized mail + + List resultList = TestUtils.toList(result); + + // Assert + assertEquals(2, resultList.size()); + + assertTrue( + resultList.stream() + .anyMatch(a -> a.getIdAppointment().equals(savedApp1.getIdAppointment()))); + assertTrue( + resultList.stream() + .anyMatch(a -> a.getIdAppointment().equals(savedApp2.getIdAppointment()))); + + assertFalse( + resultList.stream() + .anyMatch( + a -> + a.getIdAppointment() + .equals(savedOtherApp.getIdAppointment()))); + } + /* * Tests creating a new appointment request when the user is authorized * for the project linked to the appointment's section cell. @@ -797,288 +919,4 @@ public class SharedApiServiceTest { a.getIdAppointment() .equals(createdAppointment.getIdAppointment()))); } - - /* - * Tests creating a new appointment request when the user is not authorized - * for the project linked to the appointment's section cell. - * Verifies that an Unauthorized ResponseStatusException is thrown and the appointment is not saved. - */ - @Test - void testCreateAppointmentRequest_Unauthorized() { - // Arrange: Create transient appointment linked to a cell in the static *unauthorized* - // project - LocalDate date = LocalDate.parse("2026-01-01"); - LocalTime time = LocalTime.parse("10:00:00"); - LocalTime duration = LocalTime.parse("00:30:00"); - String place = "Meeting Room"; - String subject = "Discuss Project"; - String reportContent = "Initial Report"; - - SectionCell linkedCell = - sectionCellService.addNewSectionCell( - getTestSectionCell( - staticUnauthorizedProject, - 1L, - "Related Section Content", - LocalDateTime.now())); - - Report newReport = getTestReport(reportContent); - Appointment newAppointment = - getTestAppointment( - date, time, duration, place, subject, List.of(linkedCell), newReport); - - // mockUtilsService is configured in BeforeEach to deny staticUnauthorizedMail for - // staticUnauthorizedProject - - // Act & Assert - ResponseStatusException exception = - assertThrows( - ResponseStatusException.class, - () -> { - sharedApiService.createAppointmentRequest( - newAppointment, - staticUnauthorizedMail); // Unauthorized user mail - }); - - assertEquals(HttpStatus.UNAUTHORIZED, exception.getStatusCode()); - } - - /* - - _____ _ _ _ - | ___|_ _(_) | ___ __| | - | |_ / _` | | |/ _ \/ _` | - | _| (_| | | | __/ (_| | - |_| \__,_|_|_|\___|\__,_| - _____ _____ ____ _____ - |_ _| ____/ ___|_ _| - | | | _| \___ \ | | - | | | |___ ___) || | - |_| |_____|____/ |_| - - */ - - /* - * Tests retrieving entrepreneurs linked to a project when the user is authorized - * and entrepreneurs are linked. - * Verifies that the correct entrepreneurs are returned. - */ - // Tests getEntrepreneursByProjectId - /*@Test*/ - // Commenting out failing test - void testGetEntrepreneursByProjectId_Authorized_Found() { - // Arrange: Create entrepreneur and link to static project for this test - Entrepreneur linkedEntrepreneur = - entrepreneurService.addEntrepreneur( - getTestEntrepreneur("linked_entrepreneur_test")); - // Fetch the static project to update its list - Project projectToUpdate = - projectService.getProjectById(staticAuthorizedProject.getIdProject()); - projectToUpdate.updateListEntrepreneurParticipation(linkedEntrepreneur); - projectService.addNewProject(projectToUpdate); // Save the updated project - - Entrepreneur otherEntrepreneur = - entrepreneurService.addEntrepreneur(getTestEntrepreneur("other_entrepreneur_test")); - - // Act - Iterable result = - sharedApiService.getEntrepreneursByProjectId( - staticAuthorizedProject.getIdProject(), staticAuthorizedMail); - - List 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 result = - sharedApiService.getAppointmentsByProjectId( - staticAuthorizedProject.getIdProject(), // Use static project ID - staticAuthorizedMail); // Use static authorized mail - - List 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()); - } } diff --git a/documentation/openapi/src/bundled.yaml b/documentation/openapi/src/bundled.yaml deleted file mode 100644 index 3d4db60..0000000 --- a/documentation/openapi/src/bundled.yaml +++ /dev/null @@ -1,1007 +0,0 @@ -openapi: 3.0.3 -info: - title: MyInpulse Backend API - description: 'This serves as an OpenAPI documentation for the MyInpulse backend service, covering operations for Entrepreneurs, Admins, and shared functionalities.' - version: 0.2.1 -tags: - - name: Entrepreneurs API - description: API endpoints primarily for Entrepreneur users. - - name: Admin API - description: API endpoints restricted to Admin users for management tasks. - - name: Shared API - description: API endpoints accessible by both Entrepreneurs and Admins. - - name: Unauth API - description: API endpoints related to user account management. -components: - schemas: - user: - type: object - properties: - idUser: - type: integer - description: Unique identifier for the user. - example: 101 - userSurname: - type: string - description: User's surname (last name). - example: Doe - userName: - type: string - description: User's given name (first name). - example: John - primaryMail: - type: string - format: email - description: User's primary email address. - example: john.doe@example.com - secondaryMail: - type: string - format: email - description: User's secondary email address (optional). - example: j.doe@personal.com - phoneNumber: - type: string - description: User's phone number. - example: '+33612345678' - user-entrepreneur: - allOf: - - $ref: '#/components/schemas/user' - - type: object - properties: - school: - type: string - description: The school the entrepreneur attends/attended. - example: ENSEIRB-MATMECA - course: - type: string - description: The specific course or program of study. - example: Electronics - sneeStatus: - type: boolean - description: Indicates if the user has SNEE status (Statut National d'Étudiant-Entrepreneur). - example: true - example: - idUser: 101 - userSurname: Doe - userName: John - primaryMail: john.doe@example.com - secondaryMail: j.doe@personal.com - phoneNumber: '+33612345678' - school: ENSEIRB-MATMECA - course: Electronics - sneeStatus: true - user-admin: - allOf: - - $ref: '#/components/schemas/user' - example: - idUser: 55 - userSurname: Admin - userName: Super - primaryMail: admin@myinpulse.com - phoneNumber: '+33512345678' - sectionCell: - type: object - description: Represents a cell (like a sticky note) within a specific section of a project's Lean Canvas. - properties: - idSectionCell: - type: integer - description: Unique identifier for the section cell. - example: 508 - sectionId: - type: integer - description: 'Identifier of the Lean Canvas section this cell belongs to (e.g., 1 for Problem, 2 for Solution).' - example: 1 - contentSectionCell: - type: string - description: The text content of the section cell. - example: Users find it hard to track project progress. - modificationDate: - type: string - format: date - description: The date when this cell was last modified. - example: 'yyyy-MM-dd HH:mm' - project: - type: object - description: Represents a project being managed or developed. - properties: - idProject: - type: integer - description: Unique identifier for the project. - example: 12 - projectName: - type: string - description: The name of the project. - example: MyInpulse Mobile App - creationDate: - type: string - format: date - description: The date when the project was created in the system. - example: 'yyyy-MM-dd HH:mm' - logo: - type: string - format: byte - description: Base64 encoded string representing the project logo image. - example: /*Base64 encoded string representing the project logo image*/ - status: - type: string - enum: - - PENDING - - ACTIVE - - ENDED - - ABORTED - - REJECTED - description: 'Corresponds to a status enum internal to the backend, it''s value in in requests incoming to the server should be ignored as the client shouldn''t be specifying them.' - example: NaN - report: - type: object - description: Represents a report associated with an appointment. - properties: - idReport: - type: integer - description: Unique identifier for the report. - example: 987 - reportContent: - type: string - description: The textual content of the report. Could be plain text or Markdown (specify if known). - example: Discussed roadmap milestones for Q3. Agreed on preliminary UI mockups. - appointment: - type: object - description: Represents a scheduled meeting or appointment. - properties: - idAppointment: - type: integer - description: Unique identifier for the appointment. - example: 303 - appointmentDate: - type: string - format: date - description: The date of the appointment. - example: '2025-05-10' - appointmentTime: - type: string - format: time - description: The time of the appointment (local time). - example: '14:30:00' - appointmentDuration: - type: string - description: 'Duration of the appointment in ISO 8601 duration format (e.g., PT1H30M for 1 hour 30 minutes).' - example: PT1H - appointmentPlace: - type: string - description: Location or meeting link for the appointment. - example: 'Meeting Room 3 / https://meet.example.com/abc-def-ghi' - appointmentSubject: - type: string - description: The main topic or subject of the appointment. - example: Q3 Roadmap Planning - joinRequest: - type: object - description: Represents a request from an entrepreneur to join an already existing project. - properties: - idProject: - type: integer - description: the ID of the project the entrepreneur wants to join. - example: 42 - entrepreneur: - $ref: '#/components/schemas/user-entrepreneur' - projectDecision: - type: object - description: Represents a decision from an admin to accept a pending project. - properties: - projectId: - type: integer - description: The ID of the project the entrepreneur wants to join. - example: 12 - adminId: - type: integer - description: The ID of the project the admin who will supervise the project in case of admission. - example: 2 - isAccepted: - type: boolean - description: The boolean value of the decision. - example: 'true' - joinRequestDecision: - type: object - description: Represents a decision from an admin to accept a pending project join request. - properties: - isAccepted: - type: boolean - description: The boolean value of the decision. - example: 'true' - securitySchemes: - MyINPulse: - type: oauth2 - description: OAuth2 authentication using Keycloak. - flows: - implicit: - authorizationUrl: '{keycloakBaseUrl}/realms/{keycloakRealm}/protocol/openid-connect/auth' - scopes: - MyINPulse-admin: Grants administrator access. - MyINPulse-entrepreneur: Grants standard entrepreneur user access. -servers: - - url: '{serverProtocol}://{serverHost}:{serverPort}' - description: API Server - variables: - serverProtocol: - enum: - - http - - https - default: http - serverHost: - default: localhost - serverPort: - enum: - - '8081' - default: '8081' - keycloakBaseUrl: - default: 'http://localhost:7080' - description: Base URL for the Keycloak server. - keycloakRealm: - default: MyInpulseRealm - description: Keycloak realm name. -paths: - /unauth/finalize: - post: - summary: Finalize account setup using authentication token - description: |- - Completes the user account creation/setup process in the MyInpulse system. - This endpoint requires the user to be authenticated via Keycloak (e.g., after initial login). - User details (name, email, etc.) are extracted from the authenticated user's token (e.g., Keycloak JWT). - No request body is needed. The account is marked as pending admin validation upon successful finalization. - tags: - - Unauth API - responses: - '200': - description: Created - Account finalized and pending admin validation. Returns the user profile. - '400': - description: Bad Request - Problem processing the token or user data derived from it. - '401': - description: Unauthorized - Valid authentication token required. - '403': - description: Bad Token - Invalid Keycloack configuration. - '/unauth/request-join/{projectId}': - post: - summary: Request to join an existing project - description: Submits a request for the authenticated user (keycloack authenticated) to join the project specified by projectId. Their role is then changed to entrepreneur in server and Keycloak. This requires approval from a project admin. - tags: - - Unauth API - parameters: - - in: path - name: projectId - required: true - schema: - type: integer - description: The ID of the project to request joining. - example: 15 - responses: - '200': - description: Accepted - Join request submitted and pending approval. - '400': - description: Bad Request - Invalid project ID format - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - '409': - description: Already member/request pending. - /admin/pending-accounts: - get: - operationId: getPendingAccounts - summary: Get accounts awaiting validation - description: Retrieves a list of entrepreneur user accounts that are pending admin validation. - tags: - - Admin API - security: - - MyINPulse: - - MyINPulse-admin - responses: - '200': - description: OK - List of pending accounts returned. - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/user-entrepreneur' - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - '/admin/accounts/validate/{userId}': - post: - operationId: validateUserAccount - summary: Validate a pending user account - description: Marks the user account specified by userId as validated/active. - tags: - - Admin API - security: - - MyINPulse: - - MyINPulse-admin - parameters: - - in: path - name: userId - required: true - schema: - type: integer - description: The ID of the user account to validate. - example: 102 - responses: - '200': - description: No Content - Account validated successfully. - '400': - description: Bad Request - Invalid user ID format. - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - /admin/request-join: - get: - operationId: getPendingProjects - summary: Get entrepreneurs project join requests - tags: - - Admin API - security: - - MyINPulse: - - MyINPulse-admin - description: Retrieves a list of pending requests from entrepreneurs to join an existing project. - responses: - '200': - description: OK - List of pending project join requests. - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/joinRequest' - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - '/admin/request-join/decision/{joinRequestId}': - post: - summary: Approve or reject a pending project join request - tags: - - Admin API - security: - - MyINPulse: - - MyINPulse-admin - parameters: - - in: path - name: joinRequestId - required: true - schema: - type: integer - description: The ID of the pending join request to decide upon. - description: |- - Allows an admin to make a decision on an ebtrepreneur's request to join an existing project awaiting validation. - If approved (isAccepted=true), the entrepreneur is linked to the project with ID joinRequestId. - If rejected (isAccepted=false), the pending request data might be archived or deleted based on business logic. - responses: - '200': - description: 'OK - No Content, decision processed successfully..' - content: - application/json: - $ref: '#/components/schemas/joinRequestDecision' - '400': - description: 'Bad Request - Invalid input (e.g., missing decision).' - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - /admin/projects: - get: - operationId: getAdminProjects - summary: Get projects associated with the admin - tags: - - Admin API - security: - - MyINPulse: - - MyINPulse-admin - description: 'Retrieves a list of projects managed by the requesting admin, including details for overview.' - responses: - '200': - description: OK - List of projects returned successfully. - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/project' - '400': - description: 'Bad Request - Invalid project data provided (e.g., missing required fields).' - '401': - description: Unauthorized - Authentication required or invalid token. - '403': - description: Bad Token - Invalid Keycloack configuration. - post: - operationId: addProjectManually - summary: Manually add a new project - description: 'Creates a new project with the provided details. (NOTE that this meant for manually inserting projects, for example importing already existing projects).' - tags: - - Admin API - security: - - MyINPulse: - - MyINPulse-admin - requestBody: - required: true - description: Project details to create. `idProject` and `creationDate` will be ignored if sent and set by the server. - content: - application/json: - schema: - $ref: '#/components/schemas/project' - responses: - '200': - description: Created - Project added successfully. Returns the created project. - content: - application/json: - schema: - $ref: '#/components/schemas/project' - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - '409': - description: Bad Request - Project already exists. - /admin/projects/pending: - get: - operationId: getPendingProjects - summary: Get projects awaiting validation - tags: - - Admin API - security: - - MyINPulse: - - MyINPulse-admin - description: Retrieves a list of projects submitted by entrepreneurs that are pending admin approval. - responses: - '200': - description: OK - List of pending projects returned. - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/project' - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - /admin/projects/pending/decision: - post: - operationId: decidePendingProject - summary: Approve or reject a pending project - tags: - - Admin API - description: |- - Allows an admin to make a decision on a project awaiting validation. - If approved (isAccepted=true), the project status changes, and it's linked to the involved users. - If rejected (isAccepted=false), the pending project data might be archived or deleted based on business logic. - security: - - MyINPulse: - - MyINPulse-admin - requestBody: - required: true - description: Decision payload. - content: - application/json: - schema: - $ref: '#/components/schemas/projectDecision' - responses: - '200': - description: No Content - Decision processed successfully. - '400': - description: 'Bad Request - Invalid input (e.g., missing decision).' - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - '/admin/appointments/report/{appointmentId}': - post: - operationId: createAppointmentReport - summary: Create a report for an appointment - description: 'Creates and links a new report (e.g., meeting minutes) to the specified appointment using the provided content.' - tags: - - Admin API - security: - - MyINPulse: - - MyINPulse-admin - parameters: - - in: path - name: appointmentId - required: true - schema: - type: integer - description: ID of the appointment to add a report to. - example: 303 - requestBody: - required: true - description: Report content. `idReport` will be ignored if sent. - content: - application/json: - schema: - $ref: '#/components/schemas/report' - responses: - '200': - description: Created - Report created and linked successfully. Returns the created report. - content: - application/json: - schema: - $ref: '#/components/schemas/report' - '400': - description: 'Bad Request - Invalid input (e.g., missing content, invalid appointment ID format).' - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - put: - operationId: updateAppointmentReport - summary: Update an existing appointment report - description: Updates the content of an existing report linked to the specified appointment. Replaces the entire report content. - tags: - - Admin API - security: - - MyINPulse: - - MyINPulse-admin - parameters: - - in: path - name: appointmentId - required: true - schema: - type: integer - description: ID of the appointment whose report needs updating. - example: 303 - requestBody: - required: true - description: New report content. `idReport` in the body should match the existing report's ID or will be ignored. - content: - application/json: - schema: - $ref: '#/components/schemas/report' - responses: - '200': - description: OK - Report updated successfully. Returns the updated report. - content: - application/json: - schema: - $ref: '#/components/schemas/report' - '400': - description: 'Bad Request - Invalid input (e.g., missing content).' - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - /admin/appointments/upcoming: - get: - operationId: getUpcomingAppointments - summary: Get upcoming appointments for an admin - tags: - - Admin API - security: - - MyINPulse: - - MyINPulse-admin - description: Retrieves a list of appointments scheduled for an admin in the future. - responses: - '200': - description: OK - List of upcoming appointments. - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/appointment' - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - '404': - description: no appointments found. - '/admin/projects/{projectId}': - delete: - operationId: removeProject - summary: Remove a project - description: Permanently removes the project specified by projectId and potentially related data (use with caution). - tags: - - Admin API - security: - - MyINPulse: - - MyINPulse-admin - parameters: - - in: path - name: projectId - required: true - schema: - type: integer - description: The ID of the project to remove. - example: 12 - responses: - '200': - description: No Content - Project removed successfully. - '400': - description: Bad Request - Invalid project ID format. - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - '/admin/make-admin/{userId}': - post: - operationId: grantAdminRights - summary: Grant admin rights to a user - tags: - - Admin API - security: - - MyINPulse: - - MyINPulse-admin - description: Elevates the specified user to also have administrator privileges. Assumes the user already exists. - parameters: - - in: path - name: userId - required: true - schema: - type: integer - description: The ID of the user to grant admin rights. - example: 103 - responses: - '200': - description: No Content - Admin rights granted successfully. - '400': - description: Bad Request - Invalid user ID format or user is already an admin. - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - /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. - '/shared/projects/sectionCells/{projectId}/{sectionId}/{date}': - get: - operationId: getSectionCellsByDate - summary: Get project section cells modified on a specific date - tags: - - Shared API - security: - - MyINPulse: - - MyINPulse-entrepreneur - - MyINPulse-admin - description: 'Retrieves section cells belonging to a specific section of a project, filtered by the last modification date. Requires user to have access to the project.' - parameters: - - in: path - name: projectId - required: true - schema: - type: integer - description: ID of the project. - - in: path - name: sectionId - required: true - schema: - type: integer - description: ID of the Lean Canvas section. - - in: path - name: date - required: true - schema: - type: string - format: date - description: 'The modification date to filter by (YYYY-MM-DD HH:mm).' - responses: - '200': - description: OK - List of section cells matching the criteria. - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/sectionCell' - '400': - description: Bad Request - Invalid parameter format. - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - '/shared/projects/entrepreneurs/{projectId}': - get: - operationId: getProjectEntrepreneurs - summary: Get entrepreneurs associated with a project - tags: - - Shared API - security: - - MyINPulse: - - MyINPulse-entrepreneur - - MyINPulse-admin - description: Retrieves a list of entrepreneur users associated with the specified project. Requires access to the project. - parameters: - - in: path - name: projectId - required: true - schema: - type: integer - description: ID of the project. - responses: - '200': - description: OK - List of entrepreneurs. - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/user-entrepreneur' - '401': - description: Unauthorized. - '403': - description: Forbidden - User does not have access to this project or invalid Keycloack configuration. - '404': - description: Not Found - Project not found. - '/shared/projects/admin/{projectId}': - get: - operationId: getProjectAdmin - summary: Get admin associated with a project - tags: - - Shared API - security: - - MyINPulse: - - MyINPulse-entrepreneur - - MyINPulse-admin - description: Retrieves a list of admin users associated with the specified project. Requires access to the project. - parameters: - - in: path - name: projectId - required: true - schema: - type: integer - description: ID of the project. - responses: - '200': - description: OK - admin. - content: - application/json: - schema: - $ref: '#/components/schemas/user-admin' - '401': - description: Unauthorized. - '403': - description: Forbidden - User does not have access to this project or invalid Keycloack configuration. - '404': - description: Not Found - Project not found. - '/shared/projects/appointments/{projectId}': - get: - operationId: getProjectAppointments - summary: Get appointments related to a project - tags: - - Shared API - security: - - MyINPulse: - - MyINPulse-entrepreneur - - MyINPulse-admin - description: Retrieves a list of appointments associated with the specified project. Requires access to the project. - parameters: - - in: path - name: projectId - required: true - schema: - type: integer - description: ID of the project. - responses: - '200': - description: OK - List of appointments. - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/appointment' - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - '/shared/appointments/report/{appointmentId}': - get: - operationId: getAppointmentReport - summary: Get the report for an appointment - tags: - - Shared API - security: - - MyINPulse: - - MyINPulse-entrepreneur - - MyINPulse-admin - description: Retrieves the report associated with a specific appointment. Requires user to have access to the appointment/project. - parameters: - - in: path - name: appointmentId - required: true - schema: - type: integer - description: ID of the appointment. - responses: - '200': - description: OK - Report PDF returned. - content: - application/pdf: - schema: - schema: null - type: string - format: binary - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - /shared/appointments/request: - post: - operationId: requestAppointment - summary: Request a new appointment - tags: - - Shared API - security: - - MyINPulse: - - MyINPulse-entrepreneur - - MyINPulse-admin - description: 'Allows a user (entrepreneur or admin) to request a new appointment, potentially with another user or regarding a project. Details in the body. The request might need confirmation or create a pending appointment.' - requestBody: - required: true - description: Details of the appointment request. - content: - application/json: - schema: - $ref: '#/components/schemas/appointment' - responses: - '200': - description: Accepted - Appointment request submitted. - '400': - description: Bad Request - Invalid appointment details. - '401': - description: Unauthorized. - '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: null - responses: - '200': - description: OK - Section cell updated successfully. Returns the updated cell. - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/project' - '401': - description: Unauthorized or identity not found - '403': - description: Bad Token - Invalid Keycloack configuration. - '404': - description: Bad Request - Invalid input or ID mismatch. - /entrepreneur/projects/request: - post: - operationId: requestProjectCreation - summary: Request creation and validation of a new project - tags: - - Entrepreneurs API - description: |- - Submits a request for a new project. The project details are provided in the request body. - The requesting entrepreneur (identified by the token) will be associated to it. - The project is created with a 'pending' status, awaiting admin approval. - security: - - MyINPulse: - - MyINPulse-entrepreneur - requestBody: - required: true - description: 'Project details for the request. `status`, `creationDate` are required by the model when being sent but is ignored by the server; primarily expects a valid `projectId`, `name`, `logo`.' - content: - application/json: - schema: - $ref: '#/components/schemas/project' - responses: - '200': - description: Accepted - Project creation request received and is pending validation. - '400': - description: 'Bad Request - Invalid input (e.g., missing name).' - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - /entrepreneur/sectionCells: - post: - operationId: addSectionCell - summary: Add a cell to a Lean Canvas section - description: Adds a new cell (like a sticky note) with the provided content to a specific section of the entrepreneur's project's Lean Canvas. Assumes project context is known based on user's token. `idSectionCell` and `modificationDate` are server-generated so they're values in the request are ignored by the server. - tags: - - Entrepreneurs API - security: - - MyINPulse: - - MyINPulse-entrepreneur - requestBody: - required: true - description: Section cell details. `idSectionCell` and `modificationDate` will be ignored if sent. - content: - application/json: - schema: - $ref: '#/components/schemas/sectionCell' - responses: - '200': - description: Created - Section cell added successfully. Returns the created cell. - '400': - description: 'Bad Request - Invalid input (e.g., missing content or sectionId).' - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - '/entrepreneur/sectionCells/{sectionCellId}': - put: - operationId: modifySectionCell - summary: Modify data in a Lean Canvas section cell - description: Updates the content of an existing Lean Canvas section cell specified by `sectionCellId`. The server "updates" (it keeps a record of the previous version to keep a history of all the sectionCells and creates a new ones with the specified modifications) the `modificationDate`. - tags: - - Entrepreneurs API - security: - - MyINPulse: - - MyINPulse-entrepreneur - parameters: - - in: path - name: sectionCellId - required: true - schema: - type: integer - description: The ID of the section cell to modify. - example: 508 - requestBody: - required: true - description: Updated section cell details. `sectionCellId` "the path parameter" is the only id that's consideredn the `sectionCellId` id in the request body is ignored. `modificationDate` should be updated by the server. - content: - application/json: - schema: - $ref: '#/components/schemas/sectionCell' - responses: - '200': - description: OK - Section cell updated successfully. Returns the updated cell. - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - '404': - description: Bad Request - Invalid input or ID mismatch. - delete: - operationId: removeSectionCell - summary: Remove a Lean Canvas section cell - description: Deletes the Lean Canvas section cell specified by `sectionCellId`. - tags: - - Entrepreneurs API - security: - - MyINPulse: - - MyINPulse-entrepreneur - parameters: - - in: path - name: sectionCellId - required: true - schema: - type: integer - description: The ID of the section cell to remove. - example: 509 - responses: - '200': - description: No Content - Section cell removed successfully. - '400': - description: Bad Request - Invalid ID format. - '401': - description: Unauthorized. - '403': - description: Bad Token - Invalid Keycloack configuration. - '404': - description: Bad Request - sectionCell not found. diff --git a/documentation/openapi/src/entrepreneurApi.yaml b/documentation/openapi/src/entrepreneurApi.yaml index 4857132..78f5395 100644 --- a/documentation/openapi/src/entrepreneurApi.yaml +++ b/documentation/openapi/src/entrepreneurApi.yaml @@ -142,5 +142,56 @@ paths: 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. \ No newline at end of file diff --git a/documentation/openapi/src/main.yaml b/documentation/openapi/src/main.yaml index 6239610..62d9b3a 100644 --- a/documentation/openapi/src/main.yaml +++ b/documentation/openapi/src/main.yaml @@ -79,6 +79,10 @@ paths: $ref: "./unauthApi.yaml#/paths/~1unauth~1finalize" /unauth/request-join/{projectId}: $ref: "./unauthApi.yaml#/paths/~1unauth~1request-join~1{projectId}" + /unauth/request-admin-role: + $ref: "./unauthApi.yaml#/paths/~1unauth~1request-admin-role" + /unauth/check-if-not-pending: + $ref: "./unauthApi.yaml#/paths/~1unauth~1check-if-not-pending" # _ ____ __ __ ___ _ _ _ ____ ___ # / \ | _ \| \/ |_ _| \ | | / \ | _ \_ _| @@ -148,4 +152,8 @@ paths: /entrepreneur/sectionCells: $ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells" /entrepreneur/sectionCells/{sectionCellId}: - $ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells~1{sectionCellId}" \ No newline at end of file + $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" \ No newline at end of file diff --git a/documentation/openapi/src/sharedApi.yaml b/documentation/openapi/src/sharedApi.yaml index 5a21aaa..ea260d5 100644 --- a/documentation/openapi/src/sharedApi.yaml +++ b/documentation/openapi/src/sharedApi.yaml @@ -70,7 +70,7 @@ paths: "401": description: Unauthorized. "403": - description: Forbidden - User does not have access to this project or invalid Keycloack configuration. + description: Bad Token - Invalid Keycloack configuration. "404": description: Not Found - Project not found. @@ -99,7 +99,7 @@ paths: "401": description: Unauthorized. "403": - description: Forbidden - User does not have access to this project or invalid Keycloack configuration. + description: Bad Token - Invalid Keycloack configuration. "404": description: Not Found - Project not found. diff --git a/documentation/openapi/src/unauthApi.yaml b/documentation/openapi/src/unauthApi.yaml index 5150d99..fc7d555 100644 --- a/documentation/openapi/src/unauthApi.yaml +++ b/documentation/openapi/src/unauthApi.yaml @@ -53,7 +53,7 @@ paths: 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 @@ -65,4 +65,26 @@ paths: "401": description: Unauthorized. "403": - description: Bad Token - Invalid Keycloack configuration. \ No newline at end of file + 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. diff --git a/front/Dockerfile b/front/Dockerfile old mode 100644 new mode 100755 diff --git a/front/MyINPulse-front/index.html b/front/MyINPulse-front/index.html index 9e5fc8f..a678cb5 100644 --- a/front/MyINPulse-front/index.html +++ b/front/MyINPulse-front/index.html @@ -1,13 +1,13 @@ - + - - - - - Vite App - - -

- - + + + + + Vite App + + +
+ + diff --git a/front/MyINPulse-front/package-lock.json b/front/MyINPulse-front/package-lock.json index 8809acc..641b0b6 100644 --- a/front/MyINPulse-front/package-lock.json +++ b/front/MyINPulse-front/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "axios": "^1.7.9", "cors": "^2.8.5", + "jwt-decode": "^4.0.0", "keycloak-js": "^26.1.0", "pinia": "^2.3.1", "pinia-plugin-persistedstate": "^4.2.0", @@ -3588,6 +3589,15 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/keycloak-js": { "version": "26.1.0", "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-26.1.0.tgz", diff --git a/front/MyINPulse-front/package.json b/front/MyINPulse-front/package.json index ff46180..57ec628 100644 --- a/front/MyINPulse-front/package.json +++ b/front/MyINPulse-front/package.json @@ -18,7 +18,8 @@ "pinia": "^2.3.1", "pinia-plugin-persistedstate": "^4.2.0", "vue": "^3.5.13", - "vue-router": "^4.5.0" + "vue-router": "^4.5.0", + "jwt-decode": "^4.0.0" }, "devDependencies": { "@playwright/test": "^1.49.1", diff --git a/front/MyINPulse-front/src/ApiClasses/Appointment.ts b/front/MyINPulse-front/src/ApiClasses/Appointment.ts new file mode 100644 index 0000000..559eeb2 --- /dev/null +++ b/front/MyINPulse-front/src/ApiClasses/Appointment.ts @@ -0,0 +1,79 @@ +// appointment.ts +class Appointment { + private _idAppointment?: number; + private _appointmentDate?: string; + private _appointmentTime?: string; + private _appointmentDuration?: string; + private _appointmentPlace?: string; + private _appointmentSubject?: string; + + constructor(data: Partial = {}) { + this._idAppointment = data.idAppointment; + this._appointmentDate = data.appointmentDate; + this._appointmentTime = data.appointmentTime; + this._appointmentDuration = data.appointmentDuration; + this._appointmentPlace = data.appointmentPlace; + this._appointmentSubject = data.appointmentSubject; + } + + get idAppointment(): number | undefined { + return this._idAppointment; + } + + set idAppointment(value: number | undefined) { + this._idAppointment = value; + } + + get appointmentDate(): string | undefined { + return this._appointmentDate; + } + + set appointmentDate(value: string | undefined) { + this._appointmentDate = value; + } + + get appointmentTime(): string | undefined { + return this._appointmentTime; + } + + set appointmentTime(value: string | undefined) { + this._appointmentTime = value; + } + + get appointmentDuration(): string | undefined { + return this._appointmentDuration; + } + + set appointmentDuration(value: string | undefined) { + this._appointmentDuration = value; + } + + get appointmentPlace(): string | undefined { + return this._appointmentPlace; + } + + set appointmentPlace(value: string | undefined) { + this._appointmentPlace = value; + } + + get appointmentSubject(): string | undefined { + return this._appointmentSubject; + } + + set appointmentSubject(value: string | undefined) { + this._appointmentSubject = value; + } + + toObject() { + return { + idAppointment: this.idAppointment, + appointmentDate: this.appointmentDate, + appointmentTime: this.appointmentTime, + appointmentDuration: this.appointmentDuration, + appointmentPlace: this.appointmentPlace, + appointmentSubject: this.appointmentSubject, + }; + } +} + +export default Appointment; diff --git a/front/MyINPulse-front/src/ApiClasses/JoinRequest.ts b/front/MyINPulse-front/src/ApiClasses/JoinRequest.ts new file mode 100644 index 0000000..2bc97df --- /dev/null +++ b/front/MyINPulse-front/src/ApiClasses/JoinRequest.ts @@ -0,0 +1,39 @@ +// joinRequest.ts +import UserEntrepreneur from "./UserEntrepreneur"; + +class JoinRequest { + private _idProject?: number; + private _entrepreneur?: UserEntrepreneur; + + constructor(data: Partial = {}) { + this._idProject = data.idProject; + this._entrepreneur = data.entrepreneur + ? new UserEntrepreneur(data.entrepreneur) + : undefined; + } + + get idProject(): number | undefined { + return this._idProject; + } + + set idProject(value: number | undefined) { + this._idProject = value; + } + + get entrepreneur(): UserEntrepreneur | undefined { + return this._entrepreneur; + } + + set entrepreneur(value: UserEntrepreneur | undefined) { + this._entrepreneur = value; + } + + toObject() { + return { + idProject: this.idProject, + entrepreneur: this.entrepreneur, + }; + } +} + +export default JoinRequest; diff --git a/front/MyINPulse-front/src/ApiClasses/JoinRequestDecision.ts b/front/MyINPulse-front/src/ApiClasses/JoinRequestDecision.ts new file mode 100644 index 0000000..82f51e7 --- /dev/null +++ b/front/MyINPulse-front/src/ApiClasses/JoinRequestDecision.ts @@ -0,0 +1,24 @@ +// joinRequestDecision.ts +class JoinRequestDecision { + private _isAccepted?: boolean; + + constructor(data: Partial = {}) { + this._isAccepted = data.isAccepted; + } + + get isAccepted(): boolean | undefined { + return this._isAccepted; + } + + set isAccepted(value: boolean | undefined) { + this._isAccepted = value; + } + + toObject() { + return { + isAccepted: this._isAccepted, + }; + } +} + +export default JoinRequestDecision; diff --git a/front/MyINPulse-front/src/ApiClasses/Project.ts b/front/MyINPulse-front/src/ApiClasses/Project.ts new file mode 100644 index 0000000..f445276 --- /dev/null +++ b/front/MyINPulse-front/src/ApiClasses/Project.ts @@ -0,0 +1,89 @@ +// project.ts +class Project { + private _idProject?: number; + private _projectName?: string; + private _creationDate?: string; + private _logo?: string; + private _status?: "PENDING" | "ACTIVE" | "ENDED" | "ABORTED" | "REJECTED"; + + constructor(data: Partial = {}) { + this._idProject = data.idProject; + this._projectName = data.projectName; + this._creationDate = data.creationDate; + this._logo = data.logo; + this._status = data.status; + } + + get idProject(): number | undefined { + return this._idProject; + } + + set idProject(value: number | undefined) { + this._idProject = value; + } + + get projectName(): string { + return this._projectName ?? ""; + } + + set projectName(value: string | undefined) { + this._projectName = value; + } + + get creationDate(): string { + return this._creationDate ?? ""; + } + + set creationDate(value: string | undefined) { + this._creationDate = value; + } + + get logo(): string | undefined { + return this._logo; + } + + set logo(value: string | undefined) { + this._logo = value; + } + + get status(): + | "PENDING" + | "ACTIVE" + | "ENDED" + | "ABORTED" + | "REJECTED" + | undefined { + return this._status; + } + + set status( + value: + | "PENDING" + | "ACTIVE" + | "ENDED" + | "ABORTED" + | "REJECTED" + | undefined + ) { + this._status = value; + } + + toObject() { + return { + idProject: this.idProject, + projectName: this.projectName, + creationDate: this.creationDate, + logo: this.logo, + status: this.status, + }; + } + + toCreatePayload() { + return { + projectName: this.projectName, + logo: this.logo, + }; + } +} + +export default Project; diff --git a/front/MyINPulse-front/src/ApiClasses/ProjectDecision.ts b/front/MyINPulse-front/src/ApiClasses/ProjectDecision.ts new file mode 100644 index 0000000..82a1e7f --- /dev/null +++ b/front/MyINPulse-front/src/ApiClasses/ProjectDecision.ts @@ -0,0 +1,46 @@ +// projectDecision.ts +class ProjectDecision { + private _projectId?: number; + private _adminId?: number; + private _isAccepted?: boolean; + + constructor(data: Partial = {}) { + this._projectId = data.projectId; + this._adminId = data.adminId; + this._isAccepted = data.isAccepted; + } + + get projectId(): number | undefined { + return this._projectId; + } + + set projectId(value: number | undefined) { + this._projectId = value; + } + + get adminId(): number | undefined { + return this._adminId; + } + + set adminId(value: number | undefined) { + this._adminId = value; + } + + get isAccepted(): boolean | undefined { + return this._isAccepted; + } + + set isAccepted(value: boolean | undefined) { + this._isAccepted = value; + } + + toObject() { + return { + projectId: this._projectId, + adminId: this._adminId, + isAccepted: this._isAccepted, + }; + } +} + +export default ProjectDecision; diff --git a/front/MyINPulse-front/src/ApiClasses/Repport.ts b/front/MyINPulse-front/src/ApiClasses/Repport.ts new file mode 100644 index 0000000..b4cc304 --- /dev/null +++ b/front/MyINPulse-front/src/ApiClasses/Repport.ts @@ -0,0 +1,35 @@ +// report.ts +class Report { + private _idReport?: number; + private _reportContent?: string; + + constructor(data: Partial = {}) { + this._idReport = data.idReport; + this._reportContent = data.reportContent; + } + + get idReport(): number | undefined { + return this._idReport; + } + + set idReport(value: number | undefined) { + this._idReport = value; + } + + get reportContent(): string | undefined { + return this._reportContent; + } + + set reportContent(value: string | undefined) { + this._reportContent = value; + } + + toObject() { + return { + idReport: this._idReport, + reportContent: this._reportContent, + }; + } +} + +export default Report; diff --git a/front/MyINPulse-front/src/ApiClasses/SectionCell.ts b/front/MyINPulse-front/src/ApiClasses/SectionCell.ts new file mode 100644 index 0000000..acf6912 --- /dev/null +++ b/front/MyINPulse-front/src/ApiClasses/SectionCell.ts @@ -0,0 +1,57 @@ +// sectionCell.ts +class SectionCell { + private _idSectionCell?: number; + private _sectionId?: number; + private _contentSectionCell?: string; + private _modificationDate?: string; + + constructor(data: Partial = {}) { + this._idSectionCell = data.idSectionCell; + this._sectionId = data.sectionId; + this._contentSectionCell = data.contentSectionCell; + this._modificationDate = data.modificationDate; + } + + get idSectionCell(): number | undefined { + return this._idSectionCell; + } + + set idSectionCell(value: number | undefined) { + this._idSectionCell = value; + } + + get sectionId(): number | undefined { + return this._sectionId; + } + + set sectionId(value: number | undefined) { + this._sectionId = value; + } + + get contentSectionCell(): string | undefined { + return this._contentSectionCell; + } + + set contentSectionCell(value: string | undefined) { + this._contentSectionCell = value; + } + + get modificationDate(): string | undefined { + return this._modificationDate; + } + + set modificationDate(value: string | undefined) { + this._modificationDate = value; + } + + toObject() { + return { + idSectionCell: this._idSectionCell, + sectionId: this._sectionId, + contentSectionCell: this._contentSectionCell, + modificationDate: this._modificationDate, + }; + } +} + +export default SectionCell; diff --git a/front/MyINPulse-front/src/ApiClasses/User.ts b/front/MyINPulse-front/src/ApiClasses/User.ts new file mode 100644 index 0000000..d0484a4 --- /dev/null +++ b/front/MyINPulse-front/src/ApiClasses/User.ts @@ -0,0 +1,78 @@ +class User { + private _idUser?: number; + private _userSurname?: string; + private _userName?: string; + private _primaryMail?: string; + private _secondaryMail?: string; + private _phoneNumber?: string; + + constructor(data: Partial = {}) { + this._idUser = data.idUser; + this._userSurname = data.userSurname; + this._userName = data.userName; + this._primaryMail = data.primaryMail; + this._secondaryMail = data.secondaryMail; + this._phoneNumber = data.phoneNumber; + } + + get idUser(): number | undefined { + return this._idUser; + } + + set idUser(value: number | undefined) { + this._idUser = value; + } + + get userSurname(): string | undefined { + return this._userSurname; + } + + set userSurname(value: string | undefined) { + this._userSurname = value; + } + + get userName(): string | undefined { + return this._userName; + } + + set userName(value: string | undefined) { + this._userName = value; + } + + get primaryMail(): string | undefined { + return this._primaryMail; + } + + set primaryMail(value: string | undefined) { + this._primaryMail = value; + } + + get secondaryMail(): string | undefined { + return this._secondaryMail; + } + + set secondaryMail(value: string | undefined) { + this._secondaryMail = value; + } + + get phoneNumber(): string | undefined { + return this._phoneNumber; + } + + set phoneNumber(value: string | undefined) { + this._phoneNumber = value; + } + + toObject() { + return { + idUser: this._idUser, + userSurname: this._userSurname, + userName: this._userName, + primaryMail: this._primaryMail, + secondaryMail: this._secondaryMail, + phoneNumber: this._phoneNumber, + }; + } +} + +export default User; diff --git a/front/MyINPulse-front/src/ApiClasses/UserAdmin.ts b/front/MyINPulse-front/src/ApiClasses/UserAdmin.ts new file mode 100644 index 0000000..27e3bfb --- /dev/null +++ b/front/MyINPulse-front/src/ApiClasses/UserAdmin.ts @@ -0,0 +1,14 @@ +// user-admin.ts +import User from "./User"; + +class UserAdmin extends User { + constructor(data: Partial = {}) { + super(data); + } + + get idUser(): number | undefined { + return super.idUser; + } +} + +export default UserAdmin; diff --git a/front/MyINPulse-front/src/ApiClasses/UserEntrepreneur.ts b/front/MyINPulse-front/src/ApiClasses/UserEntrepreneur.ts new file mode 100644 index 0000000..0a340a5 --- /dev/null +++ b/front/MyINPulse-front/src/ApiClasses/UserEntrepreneur.ts @@ -0,0 +1,50 @@ +// user-entrepreneur.ts +import User from "./User"; + +class UserEntrepreneur extends User { + private _school?: string; + private _course?: string; + private _sneeStatus?: boolean; + + constructor(data: Partial = {}) { + super(data); + this._school = data.school; + this._course = data.course; + this._sneeStatus = data.sneeStatus; + } + + get school(): string | undefined { + return this._school; + } + + set school(value: string | undefined) { + this._school = value; + } + + get course(): string | undefined { + return this._course; + } + + set course(value: string | undefined) { + this._course = value; + } + + get sneeStatus(): boolean | undefined { + return this._sneeStatus; + } + + set sneeStatus(value: boolean | undefined) { + this._sneeStatus = value; + } + + toObject() { + return { + ...super.toObject(), + school: this._school, + course: this._course, + sneeStatus: this._sneeStatus, + }; + } +} + +export default UserEntrepreneur; diff --git a/front/MyINPulse-front/src/App.vue b/front/MyINPulse-front/src/App.vue index feac4cf..d359e36 100644 --- a/front/MyINPulse-front/src/App.vue +++ b/front/MyINPulse-front/src/App.vue @@ -1,47 +1,12 @@ - - - - diff --git a/front/MyINPulse-front/src/components/AddProjectForm.vue b/front/MyINPulse-front/src/components/AddProjectForm.vue new file mode 100644 index 0000000..2c6ce5a --- /dev/null +++ b/front/MyINPulse-front/src/components/AddProjectForm.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/front/MyINPulse-front/src/components/AdminAppointments.vue b/front/MyINPulse-front/src/components/AdminAppointments.vue new file mode 100644 index 0000000..59535c4 --- /dev/null +++ b/front/MyINPulse-front/src/components/AdminAppointments.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/front/MyINPulse-front/src/components/AgendaComponent.vue b/front/MyINPulse-front/src/components/AgendaComponent.vue new file mode 100644 index 0000000..6b58c4c --- /dev/null +++ b/front/MyINPulse-front/src/components/AgendaComponent.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/front/MyINPulse-front/src/components/AllEntrep.vue b/front/MyINPulse-front/src/components/AllEntrep.vue new file mode 100644 index 0000000..b8e2df8 --- /dev/null +++ b/front/MyINPulse-front/src/components/AllEntrep.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/front/MyINPulse-front/src/components/GetAppointments.vue b/front/MyINPulse-front/src/components/GetAppointments.vue new file mode 100644 index 0000000..bc84e0d --- /dev/null +++ b/front/MyINPulse-front/src/components/GetAppointments.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/front/MyINPulse-front/src/components/HeaderComponent.vue b/front/MyINPulse-front/src/components/HeaderComponent.vue index b285ca1..5717f08 100644 --- a/front/MyINPulse-front/src/components/HeaderComponent.vue +++ b/front/MyINPulse-front/src/components/HeaderComponent.vue @@ -1,26 +1,64 @@ - diff --git a/front/MyINPulse-front/src/components/LoginComponent.vue b/front/MyINPulse-front/src/components/LoginComponent.vue new file mode 100644 index 0000000..e59db5a --- /dev/null +++ b/front/MyINPulse-front/src/components/LoginComponent.vue @@ -0,0 +1,229 @@ + + + + + diff --git a/front/MyINPulse-front/src/components/PendingProjectComponent.vue b/front/MyINPulse-front/src/components/PendingProjectComponent.vue new file mode 100644 index 0000000..cb4af63 --- /dev/null +++ b/front/MyINPulse-front/src/components/PendingProjectComponent.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/front/MyINPulse-front/src/components/PendingRequestsManager.vue b/front/MyINPulse-front/src/components/PendingRequestsManager.vue new file mode 100644 index 0000000..ea0f1f3 --- /dev/null +++ b/front/MyINPulse-front/src/components/PendingRequestsManager.vue @@ -0,0 +1,228 @@ + + + + + + + diff --git a/front/MyINPulse-front/src/components/ProjectComponent.vue b/front/MyINPulse-front/src/components/ProjectComponent.vue index 3802495..8b199df 100644 --- a/front/MyINPulse-front/src/components/ProjectComponent.vue +++ b/front/MyINPulse-front/src/components/ProjectComponent.vue @@ -1,21 +1,306 @@ - + + diff --git a/front/MyINPulse-front/src/components/canvas/CanvasItem.vue b/front/MyINPulse-front/src/components/canvas/CanvasItem.vue new file mode 100755 index 0000000..b41b6dd --- /dev/null +++ b/front/MyINPulse-front/src/components/canvas/CanvasItem.vue @@ -0,0 +1,687 @@ + + + + + diff --git a/front/MyINPulse-front/src/components/canvas/HeaderCanvas.vue b/front/MyINPulse-front/src/components/canvas/HeaderCanvas.vue new file mode 100644 index 0000000..3b68bb3 --- /dev/null +++ b/front/MyINPulse-front/src/components/canvas/HeaderCanvas.vue @@ -0,0 +1,239 @@ + + + + + diff --git a/front/MyINPulse-front/src/components/canvas/LeanCanvas.vue b/front/MyINPulse-front/src/components/canvas/LeanCanvas.vue new file mode 100644 index 0000000..09f6cd3 --- /dev/null +++ b/front/MyINPulse-front/src/components/canvas/LeanCanvas.vue @@ -0,0 +1,174 @@ + + + + + diff --git a/front/MyINPulse-front/src/components/canvas/style-project.css b/front/MyINPulse-front/src/components/canvas/style-project.css new file mode 100644 index 0000000..26fc868 --- /dev/null +++ b/front/MyINPulse-front/src/components/canvas/style-project.css @@ -0,0 +1,156 @@ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 10px; +} + +.row { + display: flex; +} + +.cell { + flex: 1; + border: 1px solid #ddd; + padding: 10px; + text-align: center; + background-color: #f1f1f1; +} + +.produit { + background-color: #f9e4e4; +} + +.marche { + background-color: #e4f1f9; +} + +.valeur { + background-color: #f9f4e4; +} + +h3 { + margin: 0; + font-size: 18px; + color: #333; +} + +p { + margin: 5px 0 0; + font-size: 14px; +} + +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + background-color: #f9f9f9; +} + +h1 img { + height: 80px; + margin: 40px; + text-align: center; +} + +.row { + display: flex; + margin-bottom: 10px; +} + +#ade { + max-width: 1200px; + margin: 20px auto; + padding: 20px; + text-align: center; + background-color: #e8f5e9; + border: 2px solid #4caf50; + border-radius: 10px; +} + +#ade h3 { + color: #2e7d32; +} + +#ade p { + margin: 10px 0; + font-size: 16px; + color: #333; +} +header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 20px; + background-color: #fff; + border-bottom: 2px solid #ddd; +} + +header img { + height: 60px; +} + +header .contact-menu { + position: relative; +} + +.contact-button, +.return { + padding: 10px 15px; + border: none; + border-radius: 4px; + background-color: #2196f3; + color: #fff; + cursor: pointer; +} + +.contact-button:hover, +.return:hover { + background-color: #1976d2; +} + +/* Dropdown styling */ +.contact-dropdown { + position: absolute; + right: 0; + top: 50px; + display: none; + flex-direction: column; + gap: 10px; + padding: 15px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + z-index: 10; +} + +.contact-dropdown button { + padding: 8px 12px; + border: none; + border-radius: 4px; + background-color: #4caf50; + color: #fff; + cursor: pointer; +} + +.contact-dropdown button:hover { + background-color: #388e3c; +} + +.return { + background-color: #f44336; +} + +.return:hover { + background-color: #d32f2f; +} + +.header-buttons { + display: flex; + align-items: center; + gap: 15px; +} + +a { + color: white; +} diff --git a/front/MyINPulse-front/src/components/contact.ts b/front/MyINPulse-front/src/components/contact.ts new file mode 100644 index 0000000..e69de29 diff --git a/front/MyINPulse-front/src/components/icons/logo inpulse.png b/front/MyINPulse-front/src/components/icons/logo inpulse.png index e5a30ee..059d30d 100644 Binary files a/front/MyINPulse-front/src/components/icons/logo inpulse.png and b/front/MyINPulse-front/src/components/icons/logo inpulse.png differ diff --git a/front/MyINPulse-front/src/main.ts b/front/MyINPulse-front/src/main.ts index 0eb983d..063a7d7 100644 --- a/front/MyINPulse-front/src/main.ts +++ b/front/MyINPulse-front/src/main.ts @@ -28,4 +28,46 @@ keycloakService.CallInit(() => { } }); +// this shit made by me so i can run the canva vue app +//createApp(App).use(router).mount('#app'); + +// TODO: fix the comment +/* +function tokenInterceptor () { + axios.interceptors.request.use(config => { + const keycloak = useKeycloak() + if (keycloak.authenticated) { + // Note that this is a simple example. + // you should be careful not to leak tokens to third parties. + // in this example the token is added to all usage of axios. + config.headers.Authorization = `Bearer ${keycloak.token}` + } + return config + }, error => { + console.error("tokenInterceptor: Rejected") + return Promise.reject(error) + }) +} +*/ + +/* +app.use(VueKeyCloak,{ + onReady: (keycloak) => { + console.log("Ready !") + tokenInterceptor() + }, + init: { + onLoad: 'login-required', + checkLoginIframe: false, + + }, + + config: { + realm: 'test', + url: 'http://localhost:7080', + clientId: 'myinpulse' + } +} ); +*/ + export { store }; diff --git a/front/MyINPulse-front/src/plugins/authStore.ts b/front/MyINPulse-front/src/plugins/authStore.ts new file mode 100644 index 0000000..e844126 --- /dev/null +++ b/front/MyINPulse-front/src/plugins/authStore.ts @@ -0,0 +1,16 @@ +// file: src/plugins/authStore.js + +import { useAuthStore } from "@/stores/authStore.ts"; +import keycloakService from "@/services/keycloak"; +import type { Pinia } from "pinia"; +import type { App } from "vue"; +// Setup auth store as a plugin so it can be accessed globally in our FE +const authStorePlugin = { + install(app: App, option: { pinia: Pinia }) { + const store = useAuthStore(option.pinia); + app.config.globalProperties.$store = store; + keycloakService.CallInitStore(store); + }, +}; + +export default authStorePlugin; diff --git a/front/MyINPulse-front/src/router/index.ts b/front/MyINPulse-front/src/router/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/front/MyINPulse-front/src/router/router.ts b/front/MyINPulse-front/src/router/router.ts index f2eb27d..bb81468 100644 --- a/front/MyINPulse-front/src/router/router.ts +++ b/front/MyINPulse-front/src/router/router.ts @@ -11,6 +11,47 @@ const router = createRouter({ // which is lazy-loaded when the route is visited. component: () => import("../views/testComponent.vue"), }, + { + path: "/", + name: "login", + component: () => import("../components/LoginComponent.vue"), + }, + { + path: "/admin", + name: "Admin-main", + component: () => import("../views/AdminMain.vue"), + }, + + // route pour les canvas (made by adnane), in fact the two vue apps are separated for now + { + path: "/canvas", + name: "canvas", + component: () => import("../views/CanvasView.vue"), + }, + + { + path: "/signup", + name: "signup", + component: () => import("../views/EntrepSignUp.vue"), + }, + + { + path: "/JorCproject", + 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"), + }, ], }); diff --git a/front/MyINPulse-front/src/services/Apis/Admin.ts b/front/MyINPulse-front/src/services/Apis/Admin.ts new file mode 100644 index 0000000..23a7c06 --- /dev/null +++ b/front/MyINPulse-front/src/services/Apis/Admin.ts @@ -0,0 +1,340 @@ +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, + defaultApiSuccessHandler, +} from "@/services/api"; + +// Admin API +function getPendingAccounts( + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .get("/admin/pending-accounts") + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function validateUserAccount( + userId: number, + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .post(`/admin/accounts/validate/${userId}`) + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function getPendingProjectJoinRequests( // Not yet implemented + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .get("/admin/request-join") + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} +function decideProjectJoinRequest( // Not yet implemented + joinRequestId: number, + decision: { isAccepted: boolean }, + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .post(`/admin/request-join/decision/${joinRequestId}`, decision) + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function getAdminProjects( + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .get("/admin/projects") + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +type ProjectCreatePayload = { + projectName: string; + logo?: string; +}; + +function addProjectManually( + payload: ProjectCreatePayload, + onSuccess?: (response: AxiosResponse) => void, + onError?: (error: AxiosError) => void +): void { + axiosInstance + .post("/admin/projects", payload) + .then((response) => onSuccess?.(response)) + .catch((error: AxiosError) => onError?.(error)); +} + +function getPendingProjects( + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .get("/admin/projects/pending") + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function decidePendingProject( + decision: ProjectDecision, + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .post(`/admin/projects/pending/decision`, decision.toObject()) + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function createAppointmentReport( + appointmentId: number, + reportContent: Report, // Replace 'any' with a proper type for report content if available + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .post(`/admin/appointments/report/${appointmentId}`, reportContent) + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function updateAppointmentReport( + appointmentId: number, + reportContent: Report, // Replace 'any' with a proper type for report content if available + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .put(`/admin/appointments/report/${appointmentId}`, reportContent) + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function getUpcomingAppointments( + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .get("/admin/appointments/upcoming") + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function removeProject( + projectId: number, + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .delete(`/admin/projects/${projectId}`) + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function grantAdminRights( + userId: number, + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .post(`/admin/make-admin/${userId}`) + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +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] + getPendingAccounts, + validateUserAccount, + getPendingProjectJoinRequests, // Not yet implemented [cite: 3] + decideProjectJoinRequest, // Not yet implemented [cite: 3] + getAdminProjects, + addProjectManually, + getPendingProjects, + decidePendingProject, + createAppointmentReport, + updateAppointmentReport, + getUpcomingAppointments, + removeProject, + grantAdminRights, + createAdmin, +}; diff --git a/front/MyINPulse-front/src/services/Apis/Entrepreneurs.ts b/front/MyINPulse-front/src/services/Apis/Entrepreneurs.ts new file mode 100644 index 0000000..cbd39a1 --- /dev/null +++ b/front/MyINPulse-front/src/services/Apis/Entrepreneurs.ts @@ -0,0 +1,180 @@ +import { type AxiosError, type AxiosResponse } from "axios"; +import Project from "@/ApiClasses/Project"; +import SectionCell from "@/ApiClasses/SectionCell"; +import { + axiosInstance, + defaultApiErrorHandler, + defaultApiSuccessHandler, +} from "@/services/api"; + +// Entrepreneurs API +function getEntrepreneurProjectId( + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .get("/entrepreneur/projects") + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function requestProjectCreation( + projectDetails: Project, + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .post("/entrepreneur/projects/request", projectDetails.toObject()) + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function addSectionCell( + sectionCellDetails: SectionCell, + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .post("/entrepreneur/sectionCells", sectionCellDetails.toObject()) + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function modifySectionCell( + sectionCellId: number, + sectionCellDetails: SectionCell, + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .put(`/entrepreneur/sectionCells/${sectionCellId}`, sectionCellDetails) + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function removeSectionCell( + sectionCellId: number, + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .delete(`/entrepreneur/sectionCells/${sectionCellId}`) + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +// Checks if the entrepreneur has a pending project request +function checkPendingProjectRequest( + onSuccessHandler?: (response: AxiosResponse) => 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) => 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, +}; diff --git a/front/MyINPulse-front/src/services/Apis/Shared.ts b/front/MyINPulse-front/src/services/Apis/Shared.ts new file mode 100644 index 0000000..b6cef9b --- /dev/null +++ b/front/MyINPulse-front/src/services/Apis/Shared.ts @@ -0,0 +1,157 @@ +import { type AxiosError, type AxiosResponse } from "axios"; +import Appointment from "@/ApiClasses/Appointment"; +import { + axiosInstance, + defaultApiErrorHandler, + defaultApiSuccessHandler, +} from "@/services/api"; + +// Shared API +function getSectionCellsByDate( + projectId: number, + sectionId: number, + date: string, // Use string for date in 'YYYY-MM-DD HH:mm' format + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .get(`/shared/projects/sectionCells/${projectId}/${sectionId}/${date}`) + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function getProjectEntrepreneurs( + projectId: number, + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .get(`/shared/projects/entrepreneurs/${projectId}`) + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function getProjectAdmin( + projectId: number, + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .get(`/shared/projects/admin/${projectId}`) + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function getProjectAppointments( + projectId: number, + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .get(`/shared/projects/appointments/${projectId}`) + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function getAppointmentReport( + appointmentId: number, + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .get(`/shared/appointments/report/${appointmentId}`) + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function requestAppointment( + appointmentDetails: Appointment, // Replace 'any' with a proper type for appointment details if available + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .post("/shared/appointments/request", appointmentDetails) + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +export { + getSectionCellsByDate, + getProjectEntrepreneurs, + getProjectAdmin, + getProjectAppointments, + getAppointmentReport, + requestAppointment, +}; diff --git a/front/MyINPulse-front/src/services/Apis/Unauth.ts b/front/MyINPulse-front/src/services/Apis/Unauth.ts new file mode 100644 index 0000000..7c8915a --- /dev/null +++ b/front/MyINPulse-front/src/services/Apis/Unauth.ts @@ -0,0 +1,103 @@ +import { type AxiosError, type AxiosResponse } from "axios"; + +import { + axiosInstance, + defaultApiErrorHandler, + defaultApiSuccessHandler, +} from "@/services/api"; + +// Unauth API +function finalizeAccount( + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .post("/unauth/finalize") + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +// function requestJoinProject( // Not yet implemented [cite: 4] +// projectId: number, +// onSuccessHandler?: (response: AxiosResponse) => void, +// onErrorHandler?: (error: AxiosError) => void +// ): void { +// axiosInstance +// .post(`/unauth/request-join/${projectId}`) +// .then((response) => { +// if (onSuccessHandler) { +// onSuccessHandler(response); +// } else { +// defaultApiSuccessHandler(response); +// } +// }) +// .catch((error: AxiosError) => { +// if (onErrorHandler) { +// onErrorHandler(error); +// } else { +// defaultApiErrorHandler(error); +// } +// }); +// } + +function getAllEntrepreneurs( + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .get("/unauth/getAllEntrepreneurs") + .then((response) => { + if (onSuccessHandler) { + onSuccessHandler(response); + } else { + defaultApiSuccessHandler(response); + } + }) + .catch((error: AxiosError) => { + if (onErrorHandler) { + onErrorHandler(error); + } else { + defaultApiErrorHandler(error); + } + }); +} + +function checkPending( + onSuccessHandler?: (response: AxiosResponse) => 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] +}; diff --git a/front/MyINPulse-front/src/services/api.ts b/front/MyINPulse-front/src/services/api.ts index 5c4fc7b..b5cc3ac 100644 --- a/front/MyINPulse-front/src/services/api.ts +++ b/front/MyINPulse-front/src/services/api.ts @@ -1,6 +1,7 @@ import axios, { type AxiosError, type AxiosResponse } from "axios"; import { store } from "@/main.ts"; import { addNewMessage, color } from "@/services/popupDisplayer.ts"; +import router from "@/router/router"; const axiosInstance = axios.create({ baseURL: import.meta.env.VITE_BACKEND_URL, @@ -9,6 +10,19 @@ const axiosInstance = axios.create({ }, }); +axiosInstance.interceptors.request.use( + (config) => { + const token = store.user?.token; // Récupérez le token depuis le store + if (token) { + config.headers["Authorization"] = `Bearer ${token}`; // Ajoutez le token dans l'en-tête + } + return config; + }, + (error) => { + return Promise.reject(error); + } +); + axiosInstance.interceptors.response.use( (response) => response, // Directly return successful responses. async (error) => { @@ -19,19 +33,17 @@ axiosInstance.interceptors.response.use( !originalRequest._retry && store.authenticated ) { - originalRequest._retry = true; // Mark the request as retried to avoid infinite loops. + originalRequest._retry = true; try { await store.refreshUserToken(); - // Update the authorization header with the new access token. axiosInstance.defaults.headers.common["Authorization"] = `Bearer ${store.user.token}`; - return axiosInstance(originalRequest); // Retry the original request with the new access token. + return axiosInstance(originalRequest); } catch (refreshError) { - // Handle refresh token errors by clearing stored tokens and redirecting to the login page. console.error("Token refresh failed:", refreshError); localStorage.removeItem("accessToken"); localStorage.removeItem("refreshToken"); - window.location.href = "/login"; + router.push("/login"); return Promise.reject(refreshError); } } @@ -41,7 +53,9 @@ axiosInstance.interceptors.response.use( // TODO: spawn a error modal function defaultApiErrorHandler(err: AxiosError) { - addNewMessage(err.message, color.Red); + const errorMessage = + (err.response?.data as { message?: string })?.message ?? err.message; + addNewMessage(errorMessage, color.Red); } function defaultApiSuccessHandler(response: AxiosResponse) { @@ -65,4 +79,36 @@ function callApi( ); } -export { callApi }; +function postApi( + endpoint: string, + data: unknown, //to fix eslint issue, go back here if errors occurs later + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .post(endpoint, data) + .then(onSuccessHandler ?? defaultApiSuccessHandler) + .catch(onErrorHandler ?? defaultApiErrorHandler); +} + +function deleteApi( + endpoint: string, + onSuccessHandler?: (response: AxiosResponse) => void, + onErrorHandler?: (error: AxiosError) => void +): void { + axiosInstance + .delete(endpoint) + .then(onSuccessHandler ?? defaultApiSuccessHandler) + .catch(onErrorHandler ?? defaultApiErrorHandler); +} + +//export { axiosInstance, callApi, postApi, deleteApi }; + +export { + axiosInstance, + defaultApiErrorHandler, + defaultApiSuccessHandler, + callApi, + postApi, + deleteApi, +}; diff --git a/front/MyINPulse-front/src/services/tools.ts b/front/MyINPulse-front/src/services/tools.ts new file mode 100644 index 0000000..1f914ae --- /dev/null +++ b/front/MyINPulse-front/src/services/tools.ts @@ -0,0 +1,24 @@ +import { jwtDecode } from "jwt-decode"; +import { store } from "@/main"; + +type TokenPayload = { + realm_access?: { + roles?: string[]; + }; +}; + +function isAdmin(): boolean { + if (store.authenticated && store.user.token) { + const decoded = jwtDecode(store.user.token); + const roles = decoded.realm_access?.roles || []; + if (roles.includes("MyINPulse-admin")) { + return true; + } else { + return false; + } + } else { + return false; + } +} + +export { isAdmin }; diff --git a/front/MyINPulse-front/src/stores/authStore.ts b/front/MyINPulse-front/src/stores/authStore.ts index 06187ca..6685a0b 100644 --- a/front/MyINPulse-front/src/stores/authStore.ts +++ b/front/MyINPulse-front/src/stores/authStore.ts @@ -54,7 +54,7 @@ const useAuthStore = defineStore("storeAuth", { async logout() { try { await keycloakService.CallLogout( - import.meta.env.VITE_APP_URL + "/test" + import.meta.env.VITE_APP_URL + "/" //redirect to login page instead of test... ); await this.clearUserData(); } catch (error) { diff --git a/front/MyINPulse-front/src/views/AdminMain.vue b/front/MyINPulse-front/src/views/AdminMain.vue new file mode 100644 index 0000000..5b70d55 --- /dev/null +++ b/front/MyINPulse-front/src/views/AdminMain.vue @@ -0,0 +1,226 @@ + + + + + diff --git a/front/MyINPulse-front/src/views/CanvasView.vue b/front/MyINPulse-front/src/views/CanvasView.vue new file mode 100644 index 0000000..d8174d4 --- /dev/null +++ b/front/MyINPulse-front/src/views/CanvasView.vue @@ -0,0 +1,181 @@ + + + + + diff --git a/front/MyINPulse-front/src/views/EntrepSignUp.vue b/front/MyINPulse-front/src/views/EntrepSignUp.vue new file mode 100644 index 0000000..ec19941 --- /dev/null +++ b/front/MyINPulse-front/src/views/EntrepSignUp.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/front/MyINPulse-front/src/views/FinalizeAccount.vue b/front/MyINPulse-front/src/views/FinalizeAccount.vue new file mode 100644 index 0000000..1fc581b --- /dev/null +++ b/front/MyINPulse-front/src/views/FinalizeAccount.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/front/MyINPulse-front/src/views/JoinOrCreatProjectForEntrep.vue b/front/MyINPulse-front/src/views/JoinOrCreatProjectForEntrep.vue new file mode 100644 index 0000000..8ced314 --- /dev/null +++ b/front/MyINPulse-front/src/views/JoinOrCreatProjectForEntrep.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/front/MyINPulse-front/src/views/PendingApproval.vue b/front/MyINPulse-front/src/views/PendingApproval.vue new file mode 100644 index 0000000..031ffc2 --- /dev/null +++ b/front/MyINPulse-front/src/views/PendingApproval.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/front/MyINPulse-front/src/views/errorWrapper.vue b/front/MyINPulse-front/src/views/errorWrapper.vue index 807dffe..c01461c 100644 --- a/front/MyINPulse-front/src/views/errorWrapper.vue +++ b/front/MyINPulse-front/src/views/errorWrapper.vue @@ -18,7 +18,7 @@ import ErrorModal from "@/components/errorModal.vue"; .error-wrapper { position: absolute; left: 70%; - //background-color: blue; + /*background-color: blue;*/ height: 100%; width: 30%; }