20 Commits

Author SHA1 Message Date
d0b615c59d fix: the endpoints reflect the db models and changes since the last version of the doc, notes.md indicate some changes that are needed to happen in the controller and questions to be discussed
All checks were successful
CI / build (push) Successful in 11s
2025-03-21 09:42:57 +01:00
eccf116f49 feat: added swagger-ui to read docs and test api, fix: minor changes to yaml file, more to come
All checks were successful
CI / build (push) Successful in 13s
2025-03-20 19:11:32 +01:00
8491c9b3cf added minor change 2025-03-19 22:02:28 +01:00
067eeb9494 docs: added examples and changed some weird character
All checks were successful
CI / build (push) Successful in 11s
2025-02-26 11:31:45 +01:00
b355463dd9 docs: added needed modications and reorganized yaml file
All checks were successful
CI / build (push) Successful in 11s
2025-02-25 22:34:33 +01:00
79e949bdd4 feat: configured gradle file to generate code for api interface from swagger specification
All checks were successful
CI / build (push) Successful in 11s
2025-02-19 01:36:52 +01:00
ef8c8e896d feat: added openapi documentation to the project
All checks were successful
CI / build (push) Successful in 11s
2025-02-12 20:59:13 +01:00
c32eea8a40 Merge pull request 'Mise en place d'un linter, de formatteur et d'actions afin de vérifier que le code du frontend compile bien' (#3) from linter into main
All checks were successful
CI / build (push) Successful in 12s
Reviewed-on: #3
Reviewed-by: Theo <tlelez@enseirb-matmeca.fr>
Vu que vous ne voulez pas faire avancer le projet, je vais abuser de mes droits d'administrateur et le faire.
2025-02-10 22:44:34 +01:00
30344a60b7 fix: removed import
All checks were successful
CI / build (push) Successful in 13s
2025-02-09 16:12:43 +01:00
2465545b6b fix: removed temp modal
Some checks failed
CI / build (push) Failing after 10s
2025-02-09 16:11:54 +01:00
83cbeb7a2e feat: single workflow that check prettier, linter and build
Some checks failed
CI / build (push) Failing after 11s
2025-02-09 16:07:40 +01:00
645a10477d feat: now respect codestyle
All checks were successful
CI / build (push) Successful in 4s
2025-02-09 15:59:30 +01:00
a859871265 fix: prettier now fail instead of doing nothing
Some checks failed
CI / build (push) Failing after 5s
2025-02-09 15:57:40 +01:00
0eab9a8063 feat: implemented prettier
All checks were successful
CI / build (push) Successful in 4s
2025-02-09 15:54:19 +01:00
1dff7573ff feat: now compliant with eslint
All checks were successful
CI / build (push) Successful in 10s
2025-02-09 15:28:09 +01:00
8af40bfe50 fix: linter action: installed required modules
Some checks failed
CI / build (push) Failing after 11s
2025-02-09 14:04:12 +01:00
afa4d34ec8 fix: linter action
Some checks failed
CI / build (push) Failing after 7s
2025-02-09 14:02:34 +01:00
c5fc5b600e fix: linter action
Some checks failed
CI / build (push) Failing after 12s
2025-02-09 14:00:49 +01:00
a4939737fe feat: trying to setup linter
Some checks failed
CI / build (push) Failing after 35s
2025-02-09 13:57:17 +01:00
e7ebcc0d3a feat: created eslint config 2025-02-09 12:36:43 +01:00
29 changed files with 3740 additions and 354 deletions

View File

@ -0,0 +1,24 @@
name: CI
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install everything
working-directory: ./front/MyINPulse-front
run: npm i
- name: Run ESLint
working-directory: ./front/MyINPulse-front
run: npx eslint
- name: Run prettier
working-directory: ./front/MyINPulse-front
run: npx prettier src --check
- name: Build frontend
working-directory: ./front/MyINPulse-front
run: npm run build

2
.gitignore vendored
View File

@ -2,3 +2,5 @@
.idea
keycloak/CAS/target
docker-compose.yaml
node_modules
.vscode

View File

@ -2,6 +2,7 @@ plugins {
id 'java'
id 'org.springframework.boot' version '3.4.2'
id 'io.spring.dependency-management' version '1.1.7'
id 'org.openapi.generator' version '7.11.0'
}
group = 'enseirb'
@ -20,8 +21,39 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
implementation 'org.springframework.boot:spring-boot-starter-web'
// swagger Codegen
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'
implementation 'jakarta.validation:jakarta.validation-api:3.0.2'
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
sourceSets {
main {
java {
srcDir "$buildDir/generated/src/main/java"
}
}
}
sourceSets {
main {
java.srcDir("$buildDir/generated/src/main/java")
}
}
openApiGenerate {
generatorName.set("spring")
inputSpec.set("$rootDir/src/main/resources/openapi.yaml")
outputDir.set("$buildDir/generated")
apiPackage.set("enseirb.myinpulse.api")
modelPackage.set("enseirb.myinpulse.model")
configOptions.put("dateLibrary", "java8")
configOptions.put("useSpringBoot3", "true")
//configOptions.put("interfaceOnly", "true")
configOptions.put("library", "spring-boot")
}
tasks.named('test') {

View File

@ -0,0 +1,173 @@
package enseirb.myinpulse;
import static enseirb.myinpulse.model.ProjectDecisionValue.*;
import static org.junit.jupiter.api.Assertions.*;
import enseirb.myinpulse.model.Administrator;
import enseirb.myinpulse.model.Project;
import enseirb.myinpulse.model.ProjectDecision;
import enseirb.myinpulse.service.AdminApiService;
import enseirb.myinpulse.service.database.AdministratorService;
import enseirb.myinpulse.service.database.ProjectService;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
@Transactional
public class AdminApiServiceTest {
private static long administratorid;
@Autowired private AdminApiService adminApiService;
@Autowired private ProjectService projectService;
@BeforeAll
static void setup(
@Autowired AdministratorService administratorService,
@Autowired ProjectService projectService) {
administratorService.addAdministrator(
new Administrator(
"admin",
"admin",
"testAdminEmpty@example.com",
"testAdmin@example.com",
""));
Administrator a =
administratorService.addAdministrator(
new Administrator(
"admin2",
"admin2",
"testAdminFull@example.com",
"testAdmin@example.com",
""));
administratorid = a.getIdUser();
projectService.addNewProject(
new Project(
"sampleProjectAdminApiService",
null,
LocalDate.now(),
ACTIVE,
administratorService.getAdministratorByPrimaryMain(
"testAdminFull@example.com")));
}
private <T> List<T> IterableToList(Iterable<T> iterable) {
List<T> l = new ArrayList<>();
iterable.forEach(l::add);
return l;
}
@Test
void getProjectOfAdminIsEmpty() {
Iterable<Project> projects =
adminApiService.getProjectsOfAdmin("testAdminEmpty@example.com");
assertEquals(0, IterableToList(projects).size());
}
@Test
void getProjectOfInexistantAdminFails() {
String nonExistentAdminEmail = "testInexistantAdmin@example.com";
assertThrows(
ResponseStatusException.class,
() -> {
adminApiService.getProjectsOfAdmin(nonExistentAdminEmail);
});
}
@Test
void getProjectOfAdminNotEmpty() {
Iterable<Project> projects =
adminApiService.getProjectsOfAdmin("testAdminFull@example.com");
List<Project> l = IterableToList(projects);
assertEquals(1, l.size());
Project p = l.getFirst();
assertEquals(p.getProjectName(), "sampleProjectAdminApiService");
}
@Test
void getPendingProjectsEmpty() {
assertEquals(0, IterableToList(this.adminApiService.getPendingProjects()).size());
}
@Test
void getPendingProjectsNotEmpty() {
this.projectService.addNewProject(
new Project(
"PendingProjectAdminApiService1", null, LocalDate.now(), PENDING, null));
this.projectService.addNewProject(
new Project(
"PendingProjectAdminApiService2", null, LocalDate.now(), PENDING, null));
Iterable<Project> pendingProjects = this.adminApiService.getPendingProjects();
List<Project> pendingProjectsList = IterableToList(pendingProjects);
assertEquals(2, pendingProjectsList.size());
assertTrue(
List.of("PendingProjectAdminApiService1", "PendingProjectAdminApiService2")
.contains(pendingProjectsList.getFirst().getProjectName()));
assertTrue(
List.of("PendingProjectAdminApiService1", "PendingProjectAdminApiService2")
.contains(pendingProjectsList.getLast().getProjectName()));
}
@Test
void validateInexistantProject() {
ProjectDecision d = new ProjectDecision(-1, 0, 1);
assertThrows(ResponseStatusException.class, () -> this.adminApiService.validateProject(d));
}
@Test
void validateExistantProject() {
Project p =
new Project("PendingProjectAdminApiService2", null, LocalDate.now(), PENDING, null);
this.projectService.addNewProject(p);
assertEquals(PENDING, p.getProjectStatus());
ProjectDecision d = new ProjectDecision(p.getIdProject(), administratorid, 1);
this.adminApiService.validateProject(d);
assertEquals(ACTIVE, p.getProjectStatus());
// Check if the project was really updated in the database
assertEquals(0, IterableToList(this.adminApiService.getPendingProjects()).size());
}
@Test
void refuseExistantProject() {
Project p =
new Project("PendingProjectAdminApiService2", null, LocalDate.now(), PENDING, null);
this.projectService.addNewProject(p);
assertEquals(PENDING, p.getProjectStatus());
ProjectDecision d = new ProjectDecision(p.getIdProject(), administratorid, 0);
this.adminApiService.validateProject(d);
assertEquals(REJECTED, p.getProjectStatus());
// Check if the project was really updated in the database
assertEquals(0, IterableToList(this.adminApiService.getPendingProjects()).size());
}
@Test
void addProject() {
assertEquals(0, IterableToList(this.adminApiService.getPendingProjects()).size());
Project p1 =
new Project("PendingProjectAdminApiService2", null, LocalDate.now(), PENDING, null);
this.adminApiService.addNewProject(p1);
assertEquals(1, IterableToList(this.adminApiService.getPendingProjects()).size());
}
@Test
void addDuplicateProject() {
Project p1 =
new Project("PendingProjectAdminApiService2", null, LocalDate.now(), PENDING, null);
Project p2 =
new Project("PendingProjectAdminApiService2", null, LocalDate.now(), PENDING, null);
this.adminApiService.addNewProject(p1);
assertThrows(ResponseStatusException.class, () -> this.adminApiService.addNewProject(p2));
}
}

View File

@ -0,0 +1,716 @@
openapi: 3.0.3
info:
title: MyInpulse Backend Api
description: this document servers as a documentation for the backend api.
version: 0.0.0
tags:
- name: Entrepreneurs API
description: La partie de l'api dédiée aux entrepreneurs
- name: Admin API
description: La partie de l'api dédiée aux entrepreneurs
- name: Shared API
description: La partie de l'api dédiée aux entrepreneurs et admins
components:
schemas:
user:
type: object
properties:
idUser:
type: integer
userSurname:
type: string
userName:
type: string
primaryMail:
type: string
example: "example@exmaple.com"
secondaryMail:
type: string
example: "example@exmaple.com"
phoneNumber:
type: string
example: "0612345678"
user-entrepreneur:
allOf:
- $ref: "#/components/schemas/user"
- type: object
properties:
school:
type: string
example: "enseirb"
course:
type: string
example: "info"
sneeStatus:
type: boolean
example: false
user-admin:
allOf:
- $ref: "#/components/schemas/user"
sectionCell:
type: object
properties:
idSectionCell:
type: integer
example: this the cell (postit id)
sectionId:
type: integer
contentSectionCell:
type: string
modificationDate:
type: string
project:
type: object
properties:
idProject:
type: integer
projectName:
type: string
creationDate:
type: string
logo:
example: to be discussed not yet fixed
type: string
format: binary
report:
type: object
properties:
idReport:
type: integer
reportContent:
type: string
appointement:
type: object
properties:
appointmentDate:
type: string
appointmentTime:
type: string
appointmentDuration:
type: string
appointmentPlace:
type: string
appointmentSubject:
type: string
securitySchemes:
MyINPulse:
type: oauth2
flows:
implicit:
authorizationUrl: https://petstore3.swagger.io/oauth/authorize
scopes:
MyINPulse-admin: Administrateur
MyINPulse-entrepreneur: Utilisateur
paths:
# _ ____ __ __ ___ _ _ _ ____ ___
# / \ | _ \| \/ |_ _| \ | | / \ | _ \_ _|
# / _ \ | | | | |\/| || || \| | / _ \ | |_) | |
# / ___ \| |_| | | | || || |\ | / ___ \| __/| |
# /_/ \_\____/|_| |_|___|_| \_| /_/ \_\_| |___|
#
/admin/projects:
get:
summary: Retourne la liste of projets associés à l'admin
tags:
- Admin API
security:
- MyINPulse:
- MyINPulse-admin
description:
JSON array of who's elements are objects containing necessary information for the view
(project name, entrepreneur names, etc..)
of the projects an admin is watching over.
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/project"
"400":
description: Bad request
"401":
description: Authorization information is missing or invalid
/admin/projects/pending/decision:
post:
summary: valider un projet en attente de validation
tags:
- Admin API
description:
if the request is accepted the status of the
project is changed to ongoing, entrepreneur
account is confirmed and the project is linked
to the admin accepting the request and the
entrepreneur requesting it. Else the pending
project and user info are deleted.
security:
- MyINPulse:
- MyINPulse-admin
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
pedingProjectId:
type: integer
decision:
type: boolean
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/admin/projects/add:
post:
summary: Ajout manuel d'un projet
description:
Adds a project with the
inputed details
tags:
- Admin API
security:
- MyINPulse:
- MyINPulse-admin
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/project"
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/admin/appointments/report/{appointmentId}:
put:
summary: enregistrer un rapport du rendez-vous
description:
Generate a PDF file formatted
from input text and links it
to the appointement.
tags:
- Admin API
security:
- MyINPulse:
- MyINPulse-admin
parameters:
- in: path
name: appointmentId
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/report"
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
post:
summary: modifier un rapport déja éxistant du rendez-vous
description:
Modifies the report file to input
text and links it to the appointement.
tags:
- Admin API
security:
- MyINPulse:
- MyINPulse-admin
parameters:
- in: path
name: appointmentId
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/report"
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/admin/projects/remove/{projectId}:
delete:
summary: supression d'un project
description:
Removes the project
with the inputed id projectId
tags:
- Admin API
security:
- MyINPulse:
- MyINPulse-admin
parameters:
- in: path
name: projectId
required: true
schema:
type: integer
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/admin/projects/pending:
get:
summary: Retourne la liste des projets en attente de validation
tags:
- Admin API
security:
- MyINPulse:
- MyINPulse-admin
description:
JSON array of who's elements are objects containing
necessary information for the view (project name, etc..)
of all pending projects.
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/project"
"400":
description: Bad request
"401":
description: Authorization information is missing or invalid
#
# ____ _ _ _ ____ ___
# / ___|| |__ __ _ _ __ ___ __| | / \ | _ \_ _|
# \___ \| '_ \ / _` | '__/ _ \/ _` | / _ \ | |_) | |
# ___) | | | | (_| | | | __/ (_| | / ___ \| __/| |
# |____/|_| |_|\__,_|_| \___|\__,_| /_/ \_\_| |___|
#
/shared/appointments/upcoming:
get:
summary: Retourne la list des prochains rendez-vous de l'utilisateur
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-admin
- MyINPulse-entrepreneur
description:
JSON array of upcoming appointment data (name, date, time etc..) for a user.
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/appointement"
"400":
description: Bad request
"401":
description: Authorization information is missing or invalid
/shared/projects/sectionCell/{projectId}/{sectionId}/{date}:
get:
summary: Retourne la liste de sections de LC avec un titre donné
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-admin
- MyINPulse-entrepreneur
description:
JSON array containing Lean Canvas
section data with a title for the
current date (or given date if the
date parameter is passed)
parameters:
- in: path
required: true
name: projectId
schema:
type: integer
- in: path
required: true
description: this number can be 1, 2,...,8. It is associated with the title of the sectionCell
name: sectionId
schema:
type: integer
enum: [1, 2, 3, 4, 5, 6, 7, 8]
- in: path
required: true
name: date
description: the date corresponding to the wanted version of lc section. "Nan" for the latest version
example: "NaN"
schema:
type: string
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/sectionCell"
"400":
description: Bad request
"401":
description: Authorization information is missing or invalid
/shared/projects/entrepreneurs/{projectId}:
get:
summary: Retourne la liste d'entrepreneurs associée à un projet donné
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-admin
- MyINPulse-entrepreneur
description:
JSON array of entrepreneur
names associated with a project
parameters:
- in: path
name: projectId
schema:
type: integer
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/user-entrepreneur"
"400":
description: Bad request
"401":
description: Authorization information is missing or invalid
/shared/projects/admin/{projectId}:
get:
summary: Retourne les informations de l'admin qui accompagne le projet
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-admin
- MyINPulse-entrepreneur
description:
JSON object containing information (name, gmail, tel, etc..)
the admin supervising the project with id projectID.
parameters:
- in: path
name: projectId
schema:
type: integer
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/user-admin"
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/shared/projects/appointments/{projectId}:
get:
summary: Retourne les rendez-vous du projet
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-admin
- MyINPulse-entrepreneur
description:
JSON array of upcoming and past appointment
data for the project with id projectID.
parameters:
- in: path
name: projectId
schema:
type: integer
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/appointement"
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/shared/projects/appointments/report/{appointmentId}:
get:
summary: Retourne le rapport pdf du rendez-vous
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-admin
- MyINPulse-entrepreneur
description:
PDF file containing the ap-
pointment report
parameters:
- in: path
name: apointementId
schema:
type: integer
required: true
responses:
"200":
description: OK
content:
application/pdf:
schema:
type: string
format: binary
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/shared/appointments/request:
post:
summary: demander un rendez-vous
description:
will add an appointement request request by the applicant
to have an appointment to be confirmed or denied by the
specified participants of the appointement.
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-entrepreneur
- MyINPulse-admin
requestBody:
description: \"participants\" property is an array containing userids of the participants in the appointement
required: true
content:
application/json:
schema:
type: object
properties:
title:
type: string
start_time:
type: string
end_time:
type: string
place:
type: string
applicantId:
type: integer
participants:
#/* */
type: array
items:
type: integer
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
# _____ _ _ _____ ____ _____ ____ ____ _____ _ _ _____ _ _ ____
# | ____| \ | |_ _| _ \| ____| _ \| _ \| ____| \ | | ____| | | | _ \
# | _| | \| | | | | |_) | _| | |_) | |_) | _| | \| | _| | | | | |_) |
# | |___| |\ | | | | _ <| |___| __/| _ <| |___| |\ | |___| |_| | _ <
# |_____|_|_\_| |_| |_| \_\_____|_| |_| \_\_____|_| \_|_____|\___/|_| \_\
# / \ | _ \_ _|
# / _ \ | |_) | |
# / ___ \| __/| |
# /_/ \_\_| |___|
#
/entrepreneur/projects/request:
post:
summary: demander la création et validation d'un projet
tags:
- Entrepreneurs API
description:
Adds project to pending projects
to then be accepted or rejected by
an admin
security:
- MyINPulse:
- MyINPulse-entrepreneur
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
founder:
$ref: "#/components/schemas/user-entrepreneur"
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/entrepreneur/sectionCell/add:
post:
summary: ajouter une sections au LC
description:
Adds input data to the user's LC
with a specified title.
tags:
- Entrepreneurs API
security:
- MyINPulse:
- MyINPulse-entrepreneur
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/sectionCell"
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/entrepreneur/sectionCell/modify:
put:
summary: modifier les données d'une section LC
description:
Modifies input Lean Canvas section by changing it to
the information in the request body and changes the
time stamp.
tags:
- Entrepreneurs API
security:
- MyINPulse:
- MyINPulse-entrepreneur
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/sectionCell"
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/entrepreneur/sectionCell/remove/{sectionCellId}:
delete:
summary: supprimer une section LC.
description:
Deletes section from Lean Canvas
tags:
- Entrepreneurs API
security:
- MyINPulse:
- MyINPulse-entrepreneur
parameters:
- in: path
name: sectionCellId
schema:
type: integer
required: true
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid

View File

@ -0,0 +1,47 @@
## API Endpoints notes
### EntrepreneurApi and SharedApi
#### Endpoint Name Changes
- `/entrepreneur/lcsection/modify/{sectionId}``/entrepreneur/sectionCell/modify/{sectionId}`
### Admin api
- `/admin/appointments/upcoming`: is shared not admin
- `/admin/projects/decision`: instanciates classes with `adminId` instead of taking the id from the token
- `/admin/project/add`:
- point 1: the doc has this `projects` everywhere this should be `/admin/projects/add` to avoid confusion I think
- point 2: this doesn't assiociate users with a project I need to add other endopint for that
- `/admin/appoitements/report/{appointmentId}`:
- typo: `appoitements``appointments`
- `/admin/projects/remove/{projectId}`, `/admin/project/add`, `/admin/projects/decision`, `/admin/projects/pending`:
- should need token to delete or add project
### Entrepreneur api
- `/entrepreneur/sectionCell/modify/{sectionId}`:
- the section-id because of the definition of `sectionCell` schema the `sectionId` is given twice possibly leading to inconsistency. Which is why the path var to be removed:
-`/entrepreneur/sectionCell/modify`
### Shared api
- `/shared/project/sectionCell/{projectId}/{sectionId}/{date}`:
- point 1:
same point for `project``projects`
- point 2:
have yet to read `sharedApiService` to see how dates are handled and to see if we agree on values of `date` to make it so it gets the version relative to current date
- `/shared/entrepreneurs/{projectId}`:
- maybe change to `/shared/projects/entrepreneurs/{projectId}` to match other similair endpoints like `/shared/projects/admin/{projectId}`
- `/shared/appointment/request`:
- creates the apointement but don't know how it associates other users, potentially multiple classes in one request body, is that possible ?
## TODOs for me
### list 1:
- add back-end server links (backend and auth) for interacting with api through swagger
- get config for that set up in the project
### list 2:
- see what to do about logo img
- see format for date and add it in examples
- ask the form of return of the json of iterables, for now I have put array
- add endpoint for adding users to a project
- update endpoint descriptions
- add examples for values in schemas

View File

@ -0,0 +1,14 @@
const express = require("express");
const swaggerUi = require("swagger-ui-express");
const yaml = require("js-yaml");
const fs = require("fs");
const app = express();
const swaggerDocument = yaml.load(fs.readFileSync("../main.yaml", "utf8"));
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
app.listen(3000, () => {
console.log("Swagger UI running at http://localhost:3000/api-docs");
});

View File

@ -0,0 +1,884 @@
{
"name": "swagger-ui",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "swagger-ui",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"express": "^4.21.2",
"js-yaml": "^4.1.0",
"swagger-ui-express": "^5.0.1"
}
},
"node_modules/@scarf/scarf": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
"integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
"hasInstallScript": true,
"license": "Apache-2.0"
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"license": "MIT",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
"node_modules/body-parser": {
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.13.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"license": "MIT",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"license": "MIT"
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
"license": "MIT",
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "4.21.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.7.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.3.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.7",
"qs": "6.13.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.19.0",
"serve-static": "1.16.2",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/finalhandler": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"statuses": "2.0.1",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"license": "MIT",
"dependencies": {
"depd": "2.0.0",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"toidentifier": "1.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/merge-descriptors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-to-regexp": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/send": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "2.4.1",
"range-parser": "~1.2.1",
"statuses": "2.0.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/serve-static": {
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"license": "MIT",
"dependencies": {
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.19.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/swagger-ui-dist": {
"version": "5.20.1",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.20.1.tgz",
"integrity": "sha512-qBPCis2w8nP4US7SvUxdJD3OwKcqiWeZmjN2VWhq2v+ESZEXOP/7n4DeiOiiZcGYTKMHAHUUrroHaTsjUWTEGw==",
"license": "Apache-2.0",
"dependencies": {
"@scarf/scarf": "=1.4.0"
}
},
"node_modules/swagger-ui-express": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz",
"integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==",
"license": "MIT",
"dependencies": {
"swagger-ui-dist": ">=5.0.0"
},
"engines": {
"node": ">= v0.10.32"
},
"peerDependencies": {
"express": ">=4.0.0 || >=5.0.0-beta"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"license": "MIT",
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"license": "MIT",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
}
}
}

View File

@ -0,0 +1,17 @@
{
"name": "swagger-ui",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"express": "^4.21.2",
"js-yaml": "^4.1.0",
"swagger-ui-express": "^5.0.1"
}
}

View File

@ -36,4 +36,4 @@ playwright-report/
# Custom
.installed
package-lock.json
./package-lock.json

View File

@ -0,0 +1,7 @@
{
"useTabs":false,
"semi":true,
"trailingComma":"es5",
"arrowParens":"always",
"tabWidth":4
}

View File

@ -0,0 +1,29 @@
import eslint from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier";
import eslintPluginVue from "eslint-plugin-vue";
import globals from "globals";
import typescriptEslint from "typescript-eslint";
export default typescriptEslint.config(
{ ignores: ["*.d.ts", "**/coverage", "**/dist"] },
{
extends: [
eslint.configs.recommended,
...typescriptEslint.configs.recommended,
...eslintPluginVue.configs["flat/recommended"],
],
files: ["**/*.{ts,vue}"],
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
globals: globals.browser,
parserOptions: {
parser: typescriptEslint.parser,
},
},
rules: {
// your rules
},
},
eslintConfigPrettier
);

File diff suppressed because it is too large Load Diff

View File

@ -26,8 +26,15 @@
"@types/node": "^22.10.7",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/tsconfig": "^0.7.0",
"eslint": "^9.20.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-vue": "^9.32.0",
"globals": "^15.14.0",
"jiti": "^2.4.2",
"npm-run-all2": "^7.0.2",
"prettier": "3.5.0",
"typescript": "~5.7.3",
"typescript-eslint": "^8.23.0",
"vite": "^6.0.11",
"vite-plugin-vue-devtools": "^7.7.0",
"vue-tsc": "^2.2.0"

View File

@ -1,50 +1,47 @@
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
import { RouterView } from "vue-router";
import ErrorWrapper from "@/views/errorWrapper.vue";
import ProjectComponent from "@/components/ProjectComponent.vue";
</script>
<template>
<Header />
<error-wrapper></error-wrapper>
<div id="main">
<ProjectComp
v-for="(project, index) in projects"
:key="index"
:projectName="project.name"
/>
<HeaderComponent />
<error-wrapper></error-wrapper>
<div id="main">
<ProjectComponent
v-for="(project, index) in projects"
:key="index"
:project-name="project.name"
/>
</div>
<RouterView />
<RouterView />
</template>
<style scoped>
</style>
<script lang="ts">
import Header from "@/components/Header.vue";
import ProjectComp from "@/components/Project-comp.vue";
import HeaderComponent from "@/components/HeaderComponent.vue";
export default {
name: 'App',
components: {
Header,
ProjectComp,
},
data() {
return {
projects: [
{
name: 'Projet Alpha',
//link: './project-alpha.html',
//members: ['Alice', 'Bob', 'Charlie'],
},
{
name: 'Projet Beta',
//link: './project-beta.html',
//members: ['David', 'Eve', 'Frank'],
},
],
};
},
name: "App",
components: {
HeaderComponent,
},
data() {
return {
projects: [
{
name: "Projet Alpha",
//link: './project-alpha.html',
//members: ['Alice', 'Bob', 'Charlie'],
},
{
name: "Projet Beta",
//link: './project-beta.html',
//members: ['David', 'Eve', 'Frank'],
},
],
};
},
};
</script>
</script>
<style scoped></style>

View File

@ -1,26 +1,26 @@
<template>
<header>
<img src="./icons/logo inpulse.png" alt="INPulse" />
<img src="./icons/logo inpulse.png" alt="INPulse" />
</header>
</template>
<script lang="ts">
export default {
name: 'Header',
};
</script>
<style scoped>
header img {
width: 100px;
}
</template>
header{
<script lang="ts">
export default {
name: "HeaderComponent",
};
</script>
<style scoped>
header img {
width: 100px;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
background-color: #fff;
border-bottom: 2px solid #ddd;
}
</style>
}
</style>

View File

@ -3,16 +3,19 @@
<div class="project-header">
<h2>{{ projectName }}</h2>
</div>
</div>
</template>
<script lang="ts">
import type { PropType } from "vue";
export default {
name: 'Project',
name: "ProjectComponent",
props: {
projectName: String,
}
}
</script>
projectName: {
type: Object as PropType<string>,
required: true,
},
},
};
</script>

View File

@ -1,17 +1,43 @@
<script setup lang="ts">
const props = defineProps(['data']);
import { color } from "@/services/popupDisplayer.ts";
import type { PropType } from "vue";
defineProps({
errorMessage: {
type: Object as PropType<string>,
required: true,
},
errorColor: {
type: Object as PropType<color>,
default: color.Red,
},
});
</script>
<template>
<div :class='["red", "yellow", "blue", "green"][data.type]' class="error-modal">
<p>{{["Erreur :(", "Warning :|", "Info :)", "Succes ;)"][data.type]}}</p>
<p>{{data.errorMessage}}</p>
<div class="loading" :class='["red-loader", "yellow-loader", "blue-loader", "green-loader"][data.type]'></div>
</div>
<div
:class="['red', 'yellow', 'blue', 'green'][errorColor]"
class="error-modal"
>
<p>
{{
["Erreur :(", "Warning :|", "Info :)", "Succes ;)"][errorColor]
}}
</p>
<p>{{ errorMessage }}</p>
<div
class="loading"
:class="
['red-loader', 'yellow-loader', 'blue-loader', 'green-loader'][
errorColor
]
"
></div>
</div>
</template>
<style scoped>
.error-modal{
.error-modal {
margin-bottom: 1em;
padding: 1em;
border-radius: 1em;
@ -19,41 +45,45 @@ const props = defineProps(['data']);
overflow: hidden;
position: relative;
animation: disappear 5s linear forwards;
}
}
.red{
.red {
background-color: #ee6055;
color: white;
}
.red-loader {
}
.red-loader {
background-color: #fa8383;
}
}
.yellow{
background-color: #FF9D23;
color: white;
}
.yellow-loader{
.yellow {
background-color: #ff9d23;
color: white;
}
.yellow-loader {
background-color: #ffbf81;
}
}
.blue {
.blue {
background-color: #809bce;
color: white;
}
.blue-loader{
background-color: #95b8d1;
}
}
.green {
.blue-loader {
background-color: #95b8d1;
}
.green {
background-color: green;
color: white;
}
.green-loader {
background-color: darkgreen;
}
}
.loading {
.green-loader {
background-color: darkgreen;
}
.loading {
box-sizing: border-box;
position: absolute;
padding: 0;
@ -62,33 +92,33 @@ const props = defineProps(['data']);
height: 1em;
width: 0;
animation: loading 4s linear forwards;
}
}
/* Animation for the loading bar */
@keyframes loading {
/* Animation for the loading bar */
@keyframes loading {
0% {
width: 100%;
width: 100%;
}
100% {
width: 0;
width: 0;
}
}
}
@keyframes disappear {
@keyframes disappear {
0% {
height: 100px;
padding: 1em;
margin: 1em;
height: 100px;
padding: 1em;
margin: 1em;
}
80% {
height: 100px;
padding: 1em;
margin: 1em;
height: 100px;
padding: 1em;
margin: 1em;
}
100% {
height: 0;
margin: 0;
padding: 0;
height: 0;
margin: 0;
padding: 0;
}
}
</style>
}
</style>

View File

@ -1,36 +1,31 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/router.ts'
import {createPinia} from "pinia";
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
import AuthStorePlugin from './plugins/authStore';
import keycloakService from './services/keycloak';
import {useAuthStore} from "@/stores/authStore.ts";
let store: any;
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router/router.ts";
import { createPinia } from "pinia";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
import keycloakService from "./services/keycloak";
import { type AuthStore, useAuthStore } from "@/stores/authStore.ts";
let store: AuthStore;
keycloakService.CallInit(() => {
try {
const app = createApp(App)
const app = createApp(App);
// Setup pinia store, allowing user to keep logged in status after refresh
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
app.use(pinia);
app.use(AuthStorePlugin, { pinia });
store = useAuthStore();
app.use(router)
keycloakService.CallInitStore(store);
app.use(router);
app.mount('#app');
app.mount("#app");
} catch (e) {
console.error("Error while initiating Keycloak.")
console.error(e)
createApp(App).mount('#app');
console.error("Error while initiating Keycloak.");
console.error(e);
createApp(App).mount("#app");
}
});
})
export {store};
export { store };

View File

@ -1,14 +0,0 @@
// file: src/plugins/authStore.js
import { useAuthStore } from "@/stores/authStore.ts";
import keycloakService from '@/services/keycloak';
// Setup auth store as a plugin so it can be accessed globally in our FE
const authStorePlugin = {
install(app: any, option: any) {
const store = useAuthStore(option.pinia);
app.config.globalProperties.$store = store;
keycloakService.CallInitStore(store);
}
}
export default authStorePlugin;

View File

@ -1,17 +1,17 @@
import { createRouter, createWebHistory } from 'vue-router'
import { createRouter, createWebHistory } from "vue-router";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/test',
name: 'test',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/test.vue'),
},
],
})
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/test",
name: "test",
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import("../views/testComponent.vue"),
},
],
});
export default router
export default router;

View File

@ -1,31 +1,36 @@
import axios from "axios";
import {store} from "@/main.ts";
import {addNewMessage, color} from "@/services/popupDisplayer.ts";
import axios, { type AxiosError, type AxiosResponse } from "axios";
import { store } from "@/main.ts";
import { addNewMessage, color } from "@/services/popupDisplayer.ts";
const axiosInstance = axios.create({
baseURL: import.meta.env.VITE_BACKEND_URL,
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
});
axiosInstance.interceptors.response.use(
response => response, // Directly return successful responses.
async error => {
(response) => response, // Directly return successful responses.
async (error) => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry && store.authenticated) {
if (
error.response.status === 401 &&
!originalRequest._retry &&
store.authenticated
) {
originalRequest._retry = true; // Mark the request as retried to avoid infinite loops.
try {
await store.refreshUserToken();
// Update the authorization header with the new access token.
axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${store.user.token}`;
axiosInstance.defaults.headers.common["Authorization"] =
`Bearer ${store.user.token}`;
return axiosInstance(originalRequest); // Retry the original request with the new access token.
} 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';
console.error("Token refresh failed:", refreshError);
localStorage.removeItem("accessToken");
localStorage.removeItem("refreshToken");
window.location.href = "/login";
return Promise.reject(refreshError);
}
}
@ -34,23 +39,29 @@ axiosInstance.interceptors.response.use(
);
// TODO: spawn a error modal
function defaultApiErrorHandler(err: string){
addNewMessage(err, color.Red);
function defaultApiErrorHandler(err: AxiosError) {
addNewMessage(err.message, color.Red);
}
function defaultApiSuccessHandler(response: any){
addNewMessage(response.data, color.green)
}
function callApi(endpoint: string, onSuccessHandler?: any, onErrorHandler?: any): void {
axiosInstance.get(endpoint).then(
onSuccessHandler == null ? defaultApiSuccessHandler : onSuccessHandler
).catch(
(err) => {
onErrorHandler == null ? defaultApiErrorHandler(err): onErrorHandler(err);
throw err;
}
)
function defaultApiSuccessHandler(response: AxiosResponse) {
addNewMessage(response.data, color.Green);
}
function callApi(
endpoint: string,
onSuccessHandler?: (response: AxiosResponse) => void,
onErrorHandler?: (error: AxiosError) => void
): void {
axiosInstance
.get(endpoint)
.then(
onSuccessHandler == null
? defaultApiSuccessHandler
: onSuccessHandler
)
.catch(
onErrorHandler == null ? defaultApiErrorHandler : onErrorHandler
);
}
export {callApi}
export { callApi };

View File

@ -1,33 +1,31 @@
import Keycloak from 'keycloak-js';
import Keycloak from "keycloak-js";
import type { AuthStore } from "@/stores/authStore.ts";
const options = {
url: import.meta.env.VITE_KEYCLOAK_URL,
clientId: import.meta.env.VITE_KEYCLOAK_CLIENT_ID,
realm: import.meta.env.VITE_KEYCLOAK_REALM
}
realm: import.meta.env.VITE_KEYCLOAK_REALM,
};
const keycloak = new Keycloak(options);
let authenticated: boolean | undefined;
let store = null;
async function login(){
async function login() {
try {
await keycloak.login() // https://www.keycloak.org/securing-apps/javascript-adapter#:~:text=when%20initialization%20completes.-,login(options),-Redirects%20to%20login
await keycloak.login(); // https://www.keycloak.org/securing-apps/javascript-adapter#:~:text=when%20initialization%20completes.-,login(options),-Redirects%20to%20login
return keycloak;
} catch (error) {
console.log(error)
console.log(error);
}
}
async function signup(){
async function signup() {
try {
await keycloak.login(
{action: "register"}
) // https://www.keycloak.org/securing-apps/javascript-adapter#:~:text=when%20initialization%20completes.-,login(options),-Redirects%20to%20login
await keycloak.login({ action: "register" }); // https://www.keycloak.org/securing-apps/javascript-adapter#:~:text=when%20initialization%20completes.-,login(options),-Redirects%20to%20login
return keycloak;
} catch (error) {
console.log(error)
console.log(error);
}
}
@ -42,31 +40,33 @@ async function init(onInitCallback: () => void) {
onLoad: "check-sso",
silentCheckSsoRedirectUri: `${location.origin}/silent-check-sso.htm`,
responseMode: "query",
})
onInitCallback()
});
onInitCallback();
} catch (error) {
console.error("Keycloak init failed")
console.error(error)
console.error("Keycloak init failed");
console.error(error);
}
};
}
/**
* Initializes store with Keycloak user data
*
*/
async function initStore(storeInstance: any) {
async function initStore(storeInstance: AuthStore) {
try {
store = storeInstance
console.log(keycloak)
await store.initOauth(keycloak)
store = storeInstance;
console.log(keycloak);
await store.initOauth(keycloak);
// Show alert if user is not authenticated
if (!authenticated) { console.warn("not authenticated") }
if (!authenticated) {
console.warn("not authenticated");
}
} catch (error) {
console.error("Keycloak init failed")
console.error(error)
console.error("Keycloak init failed");
console.error(error);
}
};
}
/**
* Logout user
@ -83,7 +83,7 @@ async function refreshToken() {
await keycloak.updateToken(480);
return keycloak;
} catch (error) {
console.error('Failed to refresh token');
console.error("Failed to refresh token");
console.error(error);
}
}
@ -97,4 +97,4 @@ const KeycloakService = {
callSignup: signup,
};
export default KeycloakService;
export default KeycloakService;

View File

@ -1,19 +1,43 @@
import {ref} from "vue";
enum color {Red, Yellow, Blue, green}
import { ref, type Ref } from "vue";
function addNewMessage(errorMessage: string, type?: color, timeout?: number){
if (timeout == null){
enum color {
Red,
Yellow,
Blue,
Green,
}
type ErrorMessageContent = {
message: string;
color: color;
id: number;
timeout: number;
};
let id: number = 0;
const getId = () => {
id = id + 1;
return id;
};
function addNewMessage(errorMessage: string, type?: color, timeout?: number) {
if (timeout == null) {
timeout = 5000;
}
if (type == null){
if (type == null) {
type = color.Red;
}
const data = {errorMessage: errorMessage, timeout: timeout, type: type, uid: Math.random()*100000};
errorList.value.push(data)
setTimeout(() => errorList.value.slice(0, 1), timeout)
const data: ErrorMessageContent = {
message: errorMessage,
timeout: timeout,
color: type,
id: getId(),
};
errorList.value.push(data);
setTimeout(() => errorList.value.slice(0, 1), timeout);
}
const errorList: any= ref([])
const errorList: Ref<ErrorMessageContent[]> = ref([]);
export {addNewMessage, errorList, color}
export { addNewMessage, errorList, color, type ErrorMessageContent };

View File

@ -1,54 +1,61 @@
import { defineStore } from "pinia";
import keycloakService from '@/services/keycloak';
import keycloakService from "@/services/keycloak";
import type Keycloak from "keycloak-js";
export const useAuthStore = defineStore("storeAuth", {
const useAuthStore = defineStore("storeAuth", {
state: () => {
return {
testv: true,
authenticated: false,
user: {
token: "",
refreshToken: "",
username: "",
},
}
};
},
persist: true,
getters: {},
actions: {
// Initialize Keycloak OAuth
async initOauth(keycloak: Keycloak, clearData = true) {
if(clearData) { await this.clearUserData(); }
if (clearData) {
await this.clearUserData();
}
this.authenticated = !!keycloak.authenticated; // the !! removes undefined
if (this.authenticated && keycloak.token && keycloak.idTokenParsed && keycloak.refreshToken){
if (
this.authenticated &&
keycloak.token &&
keycloak.idTokenParsed &&
keycloak.refreshToken
) {
this.user.username = keycloak.idTokenParsed.given_name;
this.user.token = keycloak.token;
this.user.refreshToken = keycloak.refreshToken;
}
},
async login(){
async login() {
try {
const keycloak = await keycloakService.callLogin();
if (keycloak)
await this.initOauth(keycloak);
if (keycloak) await this.initOauth(keycloak);
} catch (error) {
console.log(error)
console.log(error);
}
},
async signup() {
try {
const keycloak = await keycloakService.callSignup();
if (keycloak)
await this.initOauth(keycloak);
if (keycloak) await this.initOauth(keycloak);
} catch (error) {
console.log(error)
console.log(error);
}
},
// Logout user
async logout() {
try {
await keycloakService.CallLogout(import.meta.env.VITE_APP_URL + "/test");
await keycloakService.CallLogout(
import.meta.env.VITE_APP_URL + "/test"
);
await this.clearUserData();
} catch (error) {
console.error(error);
@ -58,23 +65,23 @@ export const useAuthStore = defineStore("storeAuth", {
async refreshUserToken() {
try {
const keycloak = await keycloakService.CallTokenRefresh();
if (keycloak)
await this.initOauth(keycloak, false);
if (keycloak) await this.initOauth(keycloak, false);
} catch (error) {
console.error(error);
}
},
test() {
this.testv = !this.testv;
},
// Clear user's store data
clearUserData() {
this.authenticated = false;
this.user = {
token: "",
refreshToken: "",
username: "",
refreshToken: "",
username: "",
};
}
}
});
},
},
});
type AuthStore = ReturnType<typeof useAuthStore>;
export { useAuthStore, type AuthStore };

View File

@ -1,15 +0,0 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
}
</style>

View File

@ -1,22 +1,25 @@
<script setup lang="ts">
import {errorList} from "@/services/popupDisplayer.ts";
import { errorList } from "@/services/popupDisplayer.ts";
import ErrorModal from "@/components/errorModal.vue";
</script>
<template>
<div class="error-wrapper">
<error-modal v-for="elm in errorList" :data=elm></error-modal>
</div>
<div class="error-wrapper">
<error-modal
v-for="elm in errorList"
:key="elm.id"
:error-message="elm.message"
:error-color="elm.color"
/>
</div>
</template>
<style scoped>
.error-wrapper{
position: absolute;
left: 70%;
//background-color: blue;
height: 100%;
width: 30%;
.error-wrapper {
position: absolute;
left: 70%;
//background-color: blue;
height: 100%;
width: 30%;
}
</style>
</style>

View File

@ -1,75 +0,0 @@
<script setup lang="ts">
import {store} from "../main.ts";
import {callApi} from "@/services/api.ts";
import ErrorModal from "@/components/errorModal.vue";
import {errorList} from "@/services/popupDisplayer.ts";
import TempModal from "@/components/temp-modal.vue";
import ErrorWrapper from "@/App.vue";
function addResToTable(id: any){
return (req: any) => {
console.log(req)
}
}
</script>
<template>
<h1>Test page</h1>
<table class="test" style="width:100%">
<tbody>
<tr>
<td>Is Currently Authenticated ? </td>
<td>{{store.authenticated}}</td>
<td><button @click="store.login">Login</button></td>
<td><button @click="store.logout">Logout</button></td>
<td><button @click="store.signup">Signup</button></td>
</tr>
<tr>
<td>current token</td>
<td>{{store.user.token}}</td>
<td><button @click="store.refreshUserToken">Refresh</button></td>
</tr>
<tr>
<td>Current refresh token</td>
<td>{{store.user.refreshToken}}</td>
</tr>
<tr>
<td>Entrepreneur API call</td>
<td><button @click="callApi('random')">call</button></td>
<td>res</td>
<td></td>
</tr>
<tr>
<td>Admin API call</td>
<td><button @click="callApi('random2')">call</button></td>
<td>res</td>
<td></td>
</tr>
<tr>
<td>Unauth API call</td>
<td><button @click="callApi('random3')">call</button></td>
<td>res</td>
<td id="3"></td>
</tr>
</tbody>
</table>
<temp-modal></temp-modal>
</template>
<style scoped>
table {
width: 100px;
table-layout: fixed
}
tr {
width: 100%;
height: 100%;
}
td {
border: solid 1px black;
width: 20%;
height: 100%;
overflow: hidden;
}
</style>

View File

@ -0,0 +1,79 @@
<script lang="ts" setup>
import { store } from "../main.ts";
import { callApi } from "@/services/api.ts";
</script>
<template>
<h1>Test page</h1>
<table class="test" style="width: 100%">
<tbody>
<tr>
<td>Is Currently Authenticated ?</td>
<td>{{ store.authenticated }}</td>
<td>
<button @click="store.login">Login</button>
</td>
<td>
<button @click="store.logout">Logout</button>
</td>
<td>
<button @click="store.signup">Signup</button>
</td>
</tr>
<tr>
<td>current token</td>
<td>{{ store.user.token }}</td>
<td>
<button @click="store.refreshUserToken">Refresh</button>
</td>
</tr>
<tr>
<td>Current refresh token</td>
<td>{{ store.user.refreshToken }}</td>
</tr>
<tr>
<td>Entrepreneur API call</td>
<td>
<button @click="callApi('random')">call</button>
</td>
<td>res</td>
<td></td>
</tr>
<tr>
<td>Admin API call</td>
<td>
<button @click="callApi('random2')">call</button>
</td>
<td>res</td>
<td></td>
</tr>
<tr>
<td>Unauth API call</td>
<td>
<button @click="callApi('random3')">call</button>
</td>
<td>res</td>
<td id="3"></td>
</tr>
</tbody>
</table>
</template>
<style scoped>
table {
width: 100px;
table-layout: fixed;
}
tr {
width: 100%;
height: 100%;
}
td {
border: solid 1px black;
width: 20%;
height: 100%;
overflow: hidden;
}
</style>