13 Commits

Author SHA1 Message Date
d60cb8b48d fix: update doc to encompose all response codes hopefully
All checks were successful
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 38s
CI / build (push) Successful in 11s
2025-04-22 00:23:38 +02:00
50d35beb63 final version of endpoint name, response code still need to be fully chacked
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 40s
CI / build (push) Successful in 10s
2025-04-21 23:58:11 +02:00
20bca79472 feat: added doc for upcoming endpoints to finish up entrepreneur join, response codes remaining to update
All checks were successful
Format / formatting (push) Successful in 21s
Build / build (push) Successful in 39s
CI / build (push) Successful in 11s
2025-04-21 10:51:15 +02:00
095f34581d fix: aadded changes, doc is ssomewhat coherent still need to change some endpoint names in controller and some minor changes
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 38s
CI / build (push) Successful in 10s
2025-04-16 12:33:12 +02:00
6da9d762df feat: updated doc, yet to be finished
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 38s
CI / build (push) Successful in 12s
2025-04-16 10:16:51 +02:00
81ce4fdb4c fix: commented docker back lines in order to push after trying to fix the error in tests
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 39s
CI / build (push) Successful in 11s
2025-03-30 19:35:14 +02:00
ebd76a30ee feat: updated date format next is descriptions and setting up http request tests 2025-03-27 17:34:41 +01:00
6ff6ce5052 fix: just fixed duplicate test files due to bad merge
All checks were successful
Format / formatting (push) Successful in 9s
Build / build (push) Successful in 43s
CI / build (push) Successful in 11s
2025-03-26 10:04:46 +01:00
60ec920cff fix: split openapi src files and made bash file to run
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Failing after 32s
CI / build (push) Successful in 11s
2025-03-23 17:12:31 +01:00
900a4c5bdc merged backend-api onto openapi to avoid working with worktree
Some checks failed
Format / formatting (push) Successful in 7s
Build / build (push) Failing after 30s
CI / build (push) Successful in 11s
2025-03-23 15:41:44 +01:00
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
44 changed files with 3393 additions and 3362 deletions

2
.gitignore vendored
View File

@ -2,4 +2,6 @@
.idea .idea
keycloak/CAS/target keycloak/CAS/target
docker-compose.yaml docker-compose.yaml
node_modules
.vscode
postgres/data postgres/data

View File

@ -1,741 +0,0 @@
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:
nom:
type: string
prenom:
type: string
email:
type: string
example: "example@exmaple.com"
secondaryEmail:
type: string
example: "example@exmaple.com"
tel:
type: string
example: "0612345678"
user-entrepreneur:
type: object
properties:
user:
$ref: "#/components/schemas/user"
entrepreneur:
type: object
properties:
ecole:
type: string
example: "enseirb"
filiere:
type: string
example: "info"
status:
type: boolean
example: false
user-admin:
type: object
properties:
admin:
$ref: "#/components/schemas/user"
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:
type: object
properties:
name:
type: string
E_names:
type: string
description: entrepreneur names
"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:
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
/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:
type: object
properties:
title:
type: string
body:
type: string
conclusion:
type: string
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:
type: object
properties:
title:
type: string
body:
type: string
conclusion:
type: string
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,
entrepreneur names, etc..) of all pending projects.
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
type: object
properties:
name:
type: string
founder:
$ref: "#/components/schemas/user-entrepreneur"
"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:
type: object
properties:
name:
type: string
date:
type: string
time:
type: string
"400":
description: Bad request
"401":
description: Authorization information is missing or invalid
/shared/projects/lcsection/{projectId}/{title}/{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 lcsection
name: title
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:
type: object
properties:
section:
type: string
txt:
type: string
time:
type: string
"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:
type: object
properties:
appointementId:
type: integer
name:
type: string
date:
type: string
time:
type: string
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/shared/projects/appointments/report/{apointementId}:
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/lcsection/add/{projectId}:
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
parameters:
- in: path
name: projectId
schema:
type: integer
required: true
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
title:
type: string
txt:
type: string
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/entrepreneur/lcsection/modify/{sectionId}:
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
parameters:
- in: path
name: sectionId
schema:
type: integer
required: true
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
title:
type: string
txt:
type: string
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/entrepreneur/lcsection/remove/{sectionId}:
delete:
summary: supprimer une section LC.
description:
Deletes section from Lean Canvas
tags:
- Entrepreneurs API
security:
- MyINPulse:
- MyINPulse-entrepreneur
parameters:
- in: path
name: sectionId
schema:
type: integer
required: true
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid

View File

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

View File

@ -48,4 +48,4 @@ services:
# container_name: MyINPulse-back # container_name: MyINPulse-back
# ports: # ports:
# - "8081:8080" # - "8081:8080"
#

View File

@ -42,11 +42,11 @@ services:
# ports: # ports:
# - "8080:80" # - "8080:80"
back: #back:
build: # build:
context: ./MyINPulse-back/ # context: ./MyINPulse-back/
dockerfile: Dockerfile # dockerfile: Dockerfile
container_name: MyINPulse-back # container_name: MyINPulse-back
ports: # ports:
- "8081:8080" # - "8081:8080"

View File

@ -45,11 +45,11 @@ services:
ports: ports:
- "8080:80" - "8080:80"
back: #back:
build: # build:
context: ./MyINPulse-back/ # context: ./MyINPulse-back/
dockerfile: Dockerfile # dockerfile: Dockerfile
container_name: MyINPulse-back # container_name: MyINPulse-back
#ports: # #ports:
# - "8081:8080" # # - "8081:8080"

View File

@ -0,0 +1,13 @@
## API Endpoints notes
### EntrepreneurApi and SharedApi
#### Endpoint Name Changes
- `/entrepreneur/lcsection/modify/{sectionId}``/entrepreneur/sectionCell/modify/{sectionId}`
### Admin api
- `/admin/appointments/report/{appointmentId}` has no PUT and DELETE
- `/admin/request-join` and `/admin/request-join/decision/{joinRequestId}` have not yet been implemented
### Unauth api
- `/unauth/request-join/{projectId}` has not yet been implemented

View File

@ -0,0 +1,11 @@
#!/bin/bash
cd ./swagger-ui
if [ ! -d "./node_modules/" ]
then
npm install
npm install swagger-cli
fi
npm start

View File

@ -0,0 +1,353 @@
# Admin API Endpoints
paths:
/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: "./main.yaml#/components/schemas/project"
"400":
description: Bad Request - Invalid project data provided (e.g., missing required fields).
"401":
description: Unauthorized - Authentication required or invalid token.
post:
operationId: addProjectManually
summary: Manually add a new project
description: Creates a new project with the provided details. (NOTE that this meant for manually inserting projects, for example importing already existing projects).
tags:
- Admin API
security:
- MyINPulse: [MyINPulse-admin]
requestBody:
required: true
description: Project details to create. `idProject` and `creationDate` will be ignored if sent and set by the server.
content:
application/json:
schema:
$ref: "./main.yaml#/components/schemas/project"
responses:
"201": # Use 201 Created for successful creation
description: Created - Project added successfully. Returns the created project.
content:
application/json:
schema:
$ref: "./main.yaml#/components/schemas/project"
"409":
description: Bad Request - Project already exists.
"401":
description: Unauthorized.
/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: "./main.yaml#/components/schemas/project" # Assuming pending projects use the same schema
"401":
description: Unauthorized.
/admin/request-join:
get:
operationId: getPendingProjects
summary: Get entrepreneurs project join requests
tags:
- Admin API
security:
- MyINPulse: [MyINPulse-admin]
description: Retrieves a list of pending requests from entrepreneurs to join an existing project.
responses:
"200":
description: OK - List of pending project join requests.
content:
application/json:
schema:
type: array
items:
$ref: "./main.yaml#/components/schemas/joinRequest"
"401":
description: Unauthorized.
/admin/request-join/decision/{joinRequestId}:
post:
summary: Approve or reject a pending project join request
tags:
- Admin API
security:
- MyINPulse: [MyINPulse-admin]
parameters:
- in: path
name: joinRequestId
required: true
schema:
type: integer
description: The ID of the pending join request to decide upon.
description: |-
Allows an admin to make a decision on an ebtrepreneur's request to join an existing project awaiting validation.
If approved (isAccepted=true), the entrepreneur is linked to the project with ID joinRequestId.
If rejected (isAccepted=false), the pending request data might be archived or deleted based on business logic.
responses:
"200":
description: OK - No Content, decision processed successfully..
content:
application/json:
$ref: "./main.yaml#/components/schemas/joinRequestDecision"
"400":
description: Bad Request - Invalid input (e.g., missing decision).
"401":
description: Unauthorized.
/admin/projects/pending/decision:
post:
operationId: decidePendingProject
summary: Approve or reject a pending project
tags:
- Admin API
description: |-
Allows an admin to make a decision on a project awaiting validation.
If approved (isAccepted=true), the project status changes, and it's linked to the involved users.
If rejected (isAccepted=false), the pending project data might be archived or deleted based on business logic.
security:
- MyINPulse: [MyINPulse-admin]
parameters:
- in: path
name: pendingProjectId # Corrected typo and name change
required: true
schema:
type: integer
description: The ID of the pending project to decide upon.
example: 7
requestBody:
required: true
description: Decision payload.
content:
application/json:
schema:
$ref: './main.yaml#/components/schemas/projectDecision'
responses:
"204": # Use 204 No Content for successful action with no body
description: No Content - Decision processed successfully.
"400":
description: Bad Request - Invalid input (e.g., missing decision).
"401":
description: Unauthorized.
/admin/pending-accounts: # Path updated
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: "./main.yaml#/components/schemas/user-entrepreneur"
"401":
description: Unauthorized.
/admin/accounts/validate/{userId}:
post: # Changed to POST as it changes state
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:
"204":
description: No Content - Account validated successfully.
"400":
description: Bad Request - Invalid user ID format.
"401":
description: Unauthorized.
/admin/appointments/upcoming:
get:
operationId: getUpcomingAppointments
summary: Get upcoming appointments for an admin
tags:
- Admin API
security:
- MyINPulse: [MyINPulse-admin]
description: Retrieves a list of appointments scheduled for an admin in the future.
responses:
"200":
description: OK - List of upcoming appointments.
content:
application/json:
schema:
type: array
items:
$ref: "./main.yaml#/components/schemas/appointment"
"404":
description: no appointments found.
"401":
description: Unauthorized.
/admin/appointments/report/{appointmentId}:
post:
operationId: createAppointmentReport
summary: Create a report for an appointment
description: Creates and links a new report (e.g., meeting minutes) to the specified appointment using the provided content.
tags:
- Admin API
security:
- MyINPulse: [MyINPulse-admin]
parameters:
- in: path
name: appointmentId
required: true
schema:
type: integer
description: ID of the appointment to add a report to.
example: 303
requestBody:
required: true
description: Report content. `idReport` will be ignored if sent.
content:
application/json:
schema:
$ref: "./main.yaml#/components/schemas/report"
responses:
"201":
description: Created - Report created and linked successfully. Returns the created report.
content:
application/json:
schema: { $ref: "./main.yaml#/components/schemas/report" }
"400":
description: Bad Request - Invalid input (e.g., missing content, invalid appointment ID format).
"401":
description: Unauthorized.
put: # Changed to PUT for update/replacement
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: "./main.yaml#/components/schemas/report"
responses:
"200":
description: OK - Report updated successfully. Returns the updated report.
content:
application/json:
schema: { $ref: "./main.yaml#/components/schemas/report" }
"400":
description: Bad Request - Invalid input (e.g., missing content).
"401":
description: Unauthorized.
/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:
"204":
description: No Content - Project removed successfully.
"400":
description: Bad Request - Invalid project ID format.
"401":
description: Unauthorized.
/admin/make-admin/{userId}:
post:
operationId: grantAdminRights
summary: Grant admin rights to a user
tags:
- Admin API
security:
- MyINPulse: [MyINPulse-admin]
description: Elevates the specified user to also have administrator privileges. Assumes the user already exists.
parameters:
- in: path
name: userId
required: true
schema:
type: integer
description: The ID of the user to grant admin rights.
example: 103
responses:
"204": # Use 204 No Content
description: No Content - Admin rights granted successfully.
"400":
description: Bad Request - Invalid user ID format or user is already an admin.
"401":
description: Unauthorized.

View File

@ -0,0 +1,112 @@
# Entrepreneur API Endpoints
paths:
/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: "./main.yaml#/components/schemas/project"
responses:
"202":
description: Accepted - Project creation request received and is pending validation.
"400":
description: Bad Request - Invalid input (e.g., missing name).
"401":
description: Unauthorized.
/entrepreneur/sectionCells: # Base path
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: "./main.yaml#/components/schemas/sectionCell"
responses:
"201":
description: Created - Section cell added successfully. Returns the created cell.
"400":
description: Bad Request - Invalid input (e.g., missing content or sectionId).
"401":
description: Unauthorized.
/entrepreneur/sectionCells/{sectionCellId}:
put:
operationId: modifySectionCell
summary: Modify data in a Lean Canvas section cell
description: Updates the content of an existing Lean Canvas section cell specified by `sectionCellId`. The server "updates" (it keeps a record of the previous version to keep a history of all the sectionCells and creates a new ones with the specified modifications) the `modificationDate`.
tags:
- Entrepreneurs API
security:
- MyINPulse: [MyINPulse-entrepreneur]
parameters:
- in: path
name: sectionCellId
required: true
schema:
type: integer
description: The ID of the section cell to modify.
example: 508
requestBody:
required: true
description: Updated section cell details. `sectionCellId` "the path parameter" is the only id that's consideredn the `sectionCellId` id in the request body is ignored. `modificationDate` should be updated by the server.
content:
application/json:
schema:
$ref: "./main.yaml#/components/schemas/sectionCell"
responses:
"200":
description: OK - Section cell updated successfully. Returns the updated cell.
"404":
description: Bad Request - Invalid input or ID mismatch.
"401":
description: Unauthorized.
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:
"204":
description: No Content - Section cell removed successfully.
"400":
description: Bad Request - Invalid ID format.
"404":
description: Bad Request - sectionCell not found.
"401":
description: Unauthorized.

View File

@ -0,0 +1,146 @@
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:
$ref: "models.yaml#/user"
user-entrepreneur:
$ref: "models.yaml#/user-entrepreneur"
user-admin:
$ref: "models.yaml#/user-admin"
sectionCell:
$ref: "models.yaml#/sectionCell"
project:
$ref: "models.yaml#/project"
report:
$ref: "models.yaml#/report"
appointment:
$ref: "models.yaml#/appointment"
joinRequest:
$ref: "models.yaml#/joinRequest"
projectDecision:
$ref: "models.yaml#/projectDecision"
joinRequestDecision:
$ref: "models.yaml#/joinRequestDecision"
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:
$ref: "./unauthApi.yaml#/paths/~1unauth~1finalize"
/unauth/request-join/{projectId}:
$ref: "./unauthApi.yaml#/paths/~1unauth~1request-join~1{projectId}"
# _ ____ __ __ ___ _ _ _ ____ ___
# / \ | _ \| \/ |_ _| \ | | / \ | _ \_ _|
# / _ \ | | | | |\/| || || \| | / _ \ | |_) | |
# / ___ \| |_| | | | || || |\ | / ___ \| __/| |
# /_/ \_\____/|_| |_|___|_| \_| /_/ \_\_| |___|
#
/admin/pending-accounts:
$ref: "./adminApi.yaml#/paths/~1admin~1pending-accounts"
/admin/accounts/validate/{userId}:
$ref: "./adminApi.yaml#/paths/~1admin~1accounts~1validate~1{userId}"
/admin/request-join:
$ref: "./adminApi.yaml#/paths/~1admin~1request-join"
/admin/request-join/decision/{joinRequestId}:
$ref: "./adminApi.yaml#/paths/~1admin~1request-join~1decision~1{joinRequestId}"
/admin/projects:
$ref: "./adminApi.yaml#/paths/~1admin~1projects"
/admin/projects/pending:
$ref: "./adminApi.yaml#/paths/~1admin~1projects~1pending"
/admin/projects/pending/decision:
$ref: "./adminApi.yaml#/paths/~1admin~1projects~1pending~1decision"
/admin/appointments/report/{appointmentId}:
$ref: "./adminApi.yaml#/paths/~1admin~1appointments~1report~1{appointmentId}"
/admin/appointments/upcoming:
$ref: "./adminApi.yaml#/paths/~1admin~1appointments~1upcoming"
/admin/projects/{projectId}:
$ref: "./adminApi.yaml#/paths/~1admin~1projects~1{projectId}"
/admin/make-admin/{userId}:
$ref: "./adminApi.yaml#/paths/~1admin~1make-admin~1{userId}"
# ____ _ _ _ ____ ___
# / ___|| |__ __ _ _ __ ___ __| | / \ | _ \_ _|
# \___ \| '_ \ / _` | '__/ _ \/ _` | / _ \ | |_) | |
# ___) | | | | (_| | | | __/ (_| | / ___ \| __/| |
# |____/|_| |_|\__,_|_| \___|\__,_| /_/ \_\_| |___|
#
/shared/projects/sectionCells/{projectId}/{sectionId}/{date}:
$ref: "./sharedApi.yaml#/paths/~1shared~1projects~1sectionCells~1{projectId}~1{sectionId}~1{date}"
/shared/projects/entrepreneurs/{projectId}:
$ref: "./sharedApi.yaml#/paths/~1shared~1projects~1entrepreneurs~1{projectId}"
/shared/projects/admin/{projectId}:
$ref: "./sharedApi.yaml#/paths/~1shared~1projects~1admin~1{projectId}"
/shared/projects/appointments/{projectId}:
$ref: "./sharedApi.yaml#/paths/~1shared~1projects~1appointments~1{projectId}"
/shared/appointments/report/{appointmentId}:
$ref: "./sharedApi.yaml#/paths/~1shared~1appointments~1report~1{appointmentId}"
/shared/appointments/request:
$ref: "./sharedApi.yaml#/paths/~1shared~1appointments~1request"
# _____ _ _ _____ ____ _____ ____ ____ _____ _ _ _____ _ _ ____
# | ____| \ | |_ _| _ \| ____| _ \| _ \| ____| \ | | ____| | | | _ \
# | _| | \| | | | | |_) | _| | |_) | |_) | _| | \| | _| | | | | |_) |
# | |___| |\ | | | | _ <| |___| __/| _ <| |___| |\ | |___| |_| | _ <
# |_____|_|_\_| |_| |_| \_\_____|_| |_| \_\_____|_| \_|_____|\___/|_| \_\
# / \ | _ \_ _|
# / _ \ | |_) | |
# / ___ \| __/| |
# /_/ \_\_| |___|
#
/entrepreneur/projects/request:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects~1request"
/entrepreneur/sectionCells:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells"
/entrepreneur/sectionCells/{sectionCellId}:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells~1{sectionCellId}"

View File

@ -0,0 +1,210 @@
# models.yaml
user:
type: object
properties:
idUser:
type: integer
description: Unique identifier for the user.
#readOnly: true # Typically generated by the server
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" # Example using international format
user-entrepreneur:
allOf:
- $ref: "#/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: # Added full object 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: "#/user"
# No additional properties needed for this example
example: # Added full object 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.
#readOnly: true # Generated by server
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 # Using Java LocalDate -> YYYY-MM-DD
description: The date when this cell was last modified.
#readOnly: true # Typically updated by the server on modification
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.
#readOnly: true # Generated by server
example: 12
projectName:
type: string
description: The name of the project.
example: "MyInpulse Mobile App"
creationDate:
type: string
format: date # Using Java LocalDate -> YYYY-MM-DD
description: The date when the project was created in the system.
#readOnly: true # Set by server
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"
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: "#/user-entrepreneur"
report:
type: object
description: Represents a report associated with an appointment.
properties:
idReport:
type: integer
description: Unique identifier for the report.
#readOnly: true # Generated by server
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: # Corrected typo
type: object
description: Represents a scheduled meeting or appointment.
properties:
idAppointment: # Assuming there's an ID
type: integer
description: Unique identifier for the appointment.
#readOnly: true
example: 303
appointmentDate:
type: string
format: date # Using Java LocalDate -> YYYY-MM-DD
description: The date of the appointment.
example: "2025-05-10"
appointmentTime:
type: string
format: time # Using Java LocalTime -> HH:mm:ss
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" # Example for 1 hour
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"
# Consider adding project ID or user IDs if relevant association exists
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"

View File

@ -0,0 +1,197 @@
# Shared API Endpoints
paths:
/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 } # Expect YYYY-MM-DD
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: "./main.yaml#/components/schemas/sectionCell"
"400":
description: Bad Request - Invalid parameter format.
"401":
description: Unauthorized.
/shared/projects/entrepreneurs/{projectId}:
get:
operationId: getProjectEntrepreneurs
summary: Get entrepreneurs associated with a project
tags:
- Shared API
security:
- MyINPulse: [MyINPulse-entrepreneur, MyINPulse-admin]
description: Retrieves a list of entrepreneur users associated with the specified project. Requires access to the project.
parameters:
- in: path
name: projectId
required: true
schema: { type: integer }
description: ID of the project.
responses:
"200":
description: OK - List of entrepreneurs.
content:
application/json:
schema:
type: array
items:
$ref: "./main.yaml#/components/schemas/user-entrepreneur"
"401":
description: Unauthorized.
"403":
description: Forbidden - User does not have access to this project.
"404":
description: Not Found - Project not found.
/shared/projects/admin/{projectId}: # Path updated
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: "./main.yaml#/components/schemas/user-admin"
"401":
description: Unauthorized.
"403":
description: Forbidden - User does not have access to this project.
"404":
description: Not Found - Project not found.
/shared/projects/appointments/{projectId}:
get:
operationId: getProjectAppointments
summary: Get appointments related to a project
tags:
- Shared API
security:
- MyINPulse: [MyINPulse-entrepreneur, MyINPulse-admin]
description: Retrieves a list of appointments associated with the specified project. Requires access to the project.
parameters:
- in: path
name: projectId
required: true
schema: { type: integer }
description: ID of the project.
responses:
"200":
description: OK - List of appointments.
content:
application/json:
schema:
type: array
items:
$ref: "./main.yaml#/components/schemas/appointment"
"401":
description: Unauthorized.
/shared/appointments/report/{appointmentId}: # Path updated
get:
operationId: getAppointmentReport # Shared endpoint implies read-only access might be possible
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:
type: string
format: binary
"401":
description: Unauthorized.
/shared/appointments/request:
post:
operationId: requestAppointment
summary: Request a new appointment
tags:
- Shared API
security:
- MyINPulse: [MyINPulse-entrepreneur, MyINPulse-admin]
description: Allows a user (entrepreneur or admin) to request a new appointment, potentially with another user or regarding a project. Details in the body. The request might need confirmation or create a pending appointment.
requestBody:
required: true
description: Details of the appointment request.
content:
application/json:
schema:
$ref: "./main.yaml#/components/schemas/appointment" # Assuming request uses same model structure
example:
value:
appointmentDate: "2025-06-01"
appointmentTime: "10:00:00"
appointmentDuration: "PT1H"
appointmentPlace: "Online"
appointmentSubject: "Follow-up on prototype"
# Potentially add projectId or targetUserId here
responses:
"202": # Accepted seems appropriate for a request
description: Accepted - Appointment request submitted.
content:
application/json: # Optionally return the pending appointment data
schema:
$ref: "./main.yaml#/components/schemas/appointment"
"400":
description: Bad Request - Invalid appointment details.
"401":
description: Unauthorized.

View File

@ -0,0 +1,62 @@
# _ _ _ _ _ _
# | | | |_ __ __ _ _ _| |_| |__ / \ _ __ (_)
# | | | | '_ \ / _` | | | | __| '_ \ / _ \ | '_ \| |
# | |_| | | | | (_| | |_| | |_| | | |/ ___ \| |_) | |
# \___/|_| |_|\__,_|\__,_|\__|_| |_/_/ \_\ .__/|_|
# |_|
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:
"201":
description: Created - Account finalized and pending admin validation. Returns the user profile.
"400":
description: Bad Request - Problem processing the token or user data derived from it.
"401":
description: Unauthorized - Valid authentication token required.
/unauth/request-join/{projectId}:
post:
summary: Request to join an existing project
description: Submits a request for the authenticated user (keycloack authenticated) to join the project specified by projectId. Their role is then changed to entrepreneur in server and Keycloak. This requires approval from a project admin.
tags:
- Unauth API
parameters:
- in: path
name: projectId
required: true
schema:
type: integer
description: The ID of the project to request joining.
example: 15
responses: # Moved responses block to correct level
"202":
description: Accepted - Join request submitted and pending approval.
"400":
description: Bad Request - Invalid project ID format
"409":
description: Already member/request pending.
"401":
description: Unauthorized.
/unauth/request-admin-role:
post:
summary: Request to join an existing project
description: Submits a request for the authenticated user (keycloack authenticated) to become an admin. Their role is then changed to admin in server and Keycloak. This requires approval from a project admin.
tags:
- Unauth API
responses:
"202":
description: Accepted - Become admin request submitted and pending approval.
"400":
description: Bad Request - Invalid project ID format or already member/request pending.
"401":
description: Unauthorized.

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("../src/bundled.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");
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
{
"name": "swagger-ui",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"bundle": "swagger-cli bundle -o ../src/bundled.yaml -t yaml ../src/main.yaml",
"start": "npm run bundle; node main.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"express": "^4.21.2",
"js-yaml": "^4.1.0",
"package.json": "^2.0.1",
"swagger-cli": "^4.0.4",
"swagger-ui-express": "^5.0.1"
}
}

0
front/Dockerfile Executable file → Normal file
View File

View File

@ -1,63 +0,0 @@
{
"entrepreneurs": [
{ "id": 1, "name": "Alice", "email": "alice@example.com" },
{ "id": 2, "name": "Bob", "email": "bob@example.com" },
{ "id": 3, "name": "Charlie", "email": "charlie@example.com" }
],
"data": [
{
"projectId": 1,
"title": 1,
"title_text": "1. Problème",
"description": "3 problèmes essentiels à résoudre pour le client"
},
{
"projectId": 1,
"title": 2,
"title_text": "2. Segments",
"description": "Les segments de clientèle visés"
},
{
"projectId": 1,
"title": 3,
"title_text": "3. Valeur",
"description": "La proposition de valeur"
},
{
"projectId": 1,
"title": 4,
"title_text": "4. Solution",
"description": "Les solutions proposées"
},
{
"projectId": 1,
"title": 5,
"title_text": "5. Avantage",
"description": "Les avantages concurrentiels"
},
{
"projectId": 1,
"title": 6,
"title_text": "6. Canaux",
"description": "Les canaux de distribution"
},
{
"projectId": 1,
"title": 7,
"title_text": "7. Indicateurs",
"description": "Les indicateurs clés de performance"
},
{
"projectId": 1,
"title": 8,
"title_text": "8. Coûts",
"description": "Les coûts associés"
},
{
"projectId": 1,
"title": 9,
"title_text": "9. Revenus",
"description": "Les sources de revenus"
}
]
}

View File

@ -1,2 +0,0 @@
#!/usr/bin/bash
json-server --watch db.json --port 5000

View File

@ -10,7 +10,6 @@
"dependencies": { "dependencies": {
"axios": "^1.7.9", "axios": "^1.7.9",
"cors": "^2.8.5", "cors": "^2.8.5",
"jwt-decode": "^4.0.0",
"keycloak-js": "^26.1.0", "keycloak-js": "^26.1.0",
"pinia": "^2.3.1", "pinia": "^2.3.1",
"pinia-plugin-persistedstate": "^4.2.0", "pinia-plugin-persistedstate": "^4.2.0",
@ -3589,15 +3588,6 @@
"graceful-fs": "^4.1.6" "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": { "node_modules/keycloak-js": {
"version": "26.1.0", "version": "26.1.0",
"resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-26.1.0.tgz", "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-26.1.0.tgz",

View File

@ -18,8 +18,7 @@
"pinia": "^2.3.1", "pinia": "^2.3.1",
"pinia-plugin-persistedstate": "^4.2.0", "pinia-plugin-persistedstate": "^4.2.0",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "^4.5.0", "vue-router": "^4.5.0"
"jwt-decode": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.49.1", "@playwright/test": "^1.49.1",

View File

@ -1,12 +1,47 @@
<script setup lang="ts"> <script setup lang="ts">
import { /*RouterLink,*/ RouterView } from "vue-router"; import { RouterView } from "vue-router";
import ErrorWrapper from "@/views/errorWrapper.vue"; import ErrorWrapper from "@/views/errorWrapper.vue";
import ProjectComponent from "@/components/ProjectComponent.vue";
</script> </script>
<template> <template>
<Header /> <HeaderComponent />
<ErrorWrapper /> <error-wrapper></error-wrapper>
<!--<RouterLink to="/">Home</RouterLink> | --> <div id="main">
<!--<RouterLink to="/canvas">Canvas</RouterLink> --> <ProjectComponent
v-for="(project, index) in projects"
:key="index"
:project-name="project.name"
/>
</div>
<RouterView /> <RouterView />
</template> </template>
<script lang="ts">
import HeaderComponent from "@/components/HeaderComponent.vue";
export default {
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>
<style scoped></style>

View File

@ -1,108 +0,0 @@
<template>
<form class="add-project-form" @submit.prevent="submitProject">
<h2>Ajouter un projet</h2>
<div class="form-group">
<label for="projectName">Nom du projet</label>
<input
id="projectName"
v-model="project.projectName"
type="text"
required
/>
</div>
<div class="form-group">
<label for="creationDate">Date de création</label>
<input
id="creationDate"
v-model="project.creationDate"
type="text"
placeholder="JJ-MM-AAAA"
required
/>
</div>
<div class="form-group">
<label for="logo">Logo</label>
<input
id="logo"
v-model="project.logo"
type="text"
placeholder="(à discuter)"
/>
</div>
<button type="submit">Ajouter</button>
</form>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { postApi } from "@/services/api.ts";
const project = ref({
projectName: "",
creationDate: "",
logo: "to be discussed not yet fixed",
});
function submitProject() {
postApi("/admin/projects/add", project.value);
}
</script>
<style scoped>
h2 {
font-size: 1.5rem;
color: #333;
margin-bottom: 1.2rem;
border-bottom: 2px solid #ddd;
padding-bottom: 0.5rem;
}
.add-project-form {
max-width: 500px;
margin: 0 auto;
padding: 20px;
background: #fff;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
h2 {
margin-bottom: 20px;
font-size: 24px;
color: #333;
}
.form-group {
margin-bottom: 15px;
display: flex;
flex-direction: column;
}
label {
font-weight: bold;
margin-bottom: 5px;
}
input {
padding: 8px;
border-radius: 5px;
border: 1px solid #ccc;
}
button {
background-color: #4caf50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
</style>

View File

@ -1,93 +0,0 @@
<template>
<div id="agenda">
<h3>Rendez-vous</h3>
<table>
<thead>
<tr>
<th>Projet</th>
<th>Date</th>
<th>Lieu</th>
</tr>
</thead>
<tbody>
<tr v-for="(p, index) in projectRDV" :key="index">
<td>{{ p.projectName }}</td>
<td>{{ p.date }}</td>
<td>{{ p.lieu }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup lang="ts">
import { defineProps } from "vue";
interface rendezVous {
projectName: string;
date: string;
lieu: string;
}
defineProps<{
projectRDV: rendezVous[];
}>();
</script>
<style scoped>
h3 {
font-size: 1.5rem;
color: #333;
margin-bottom: 1.2rem;
border-bottom: 2px solid #ddd;
padding-bottom: 0.5rem;
}
#agenda {
padding: 20px;
background-color: white;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}
/* Table Styling */
table {
width: 100%;
border-collapse: collapse;
font-family: Arial, sans-serif;
text-align: left;
margin-top: 20px;
border: 1px solid #ccc;
}
th {
background-color: #f0f2f5;
padding: 12px;
font-weight: 600;
color: #333;
}
/* Table Body Rows */
tbody tr {
border-bottom: 1px solid #ddd;
transition: background-color 0.2s ease; /* Smooth hover effect */
}
tbody tr:hover {
background-color: #f9f9f9; /* Highlight row on hover */
}
/* Cells Styling */
td {
padding: 10px;
border: 1px solid #eee;
font-size: 14px;
vertical-align: middle; /* Align text to middle */
}
/* First Column Styling */
td:first-child {
text-align: center;
width: 50px; /* Adjust width as needed */
}
</style>

View File

@ -1,206 +0,0 @@
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import { jwtDecode } from "jwt-decode"; // i hope this doesn't break the code later
import { store } from "../main.ts";
import { callApi } from "@/services/api.ts";
const router = useRouter();
type TokenPayload = {
realm_access?: {
roles?: string[];
};
};
const customRequest = ref("");
onMounted(() => {
if (store.authenticated && store.user.token) {
try {
const decoded = jwtDecode<TokenPayload>(store.user.token);
const roles = decoded.realm_access?.roles || [];
if (roles.includes("MyINPulse-admin")) {
router.push("/");
} else if (roles.includes("MyINPulse-entrepreneur")) {
router.push("/leanCanva");
}
} catch (err) {
console.error("Failed to decode token", err);
}
}
});
/*
const loading = ref(false);
const callApiWithLoading = async (path: string) => {
loading.value = true;
await callApi(path);
loading.value = false;
};
*/
</script>
<template>
<error-wrapper></error-wrapper>
<div class="auth-container">
<div class="auth-card">
<h1>Bienvenue</h1>
<div
class="status"
:class="store.authenticated ? 'success' : 'error'"
>
<p>
{{
store.authenticated
? "✅ Authenticated"
: "❌ Not Authenticated"
}}
</p>
</div>
<div class="actions">
<button @click="store.login">Login</button>
<button @click="store.logout">Logout</button>
<button @click="store.signup">Signup-admin</button>
<button @click="store.signup">Signup-Entrepreneur</button>
<button @click="store.refreshUserToken">Refresh Token</button>
</div>
<div v-if="store.authenticated" class="token-section">
<p><strong>Access Token:</strong></p>
<pre>{{ store.user.token }}</pre>
<p><strong>Refresh Token:</strong></p>
<pre>{{ store.user.refreshToken }}</pre>
</div>
<div class="api-calls">
<h2>Test API Calls</h2>
<button @click="callApi('random')">
Call Entrepreneur API
</button>
<button @click="callApi('random2')">Call Admin API</button>
<button @click="callApi('unauth/dev')">Call Unauth API</button>
<div class="custom-call">
<input
v-model="customRequest"
placeholder="Custom endpoint"
/>
<button @click="callApi(customRequest)">Call</button>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.auth-container {
display: flex;
justify-content: center;
align-items: center;
padding: 3rem 1rem;
min-height: 100vh;
background-color: #eef1f5;
font-family: Arial, sans-serif;
}
.auth-card {
background: white;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 600px;
}
h1 {
text-align: center;
margin-bottom: 1rem;
color: #333;
}
.status {
text-align: center;
margin-bottom: 1.5rem;
font-weight: bold;
}
.success {
color: green;
}
.error {
color: red;
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
margin-bottom: 1.5rem;
}
.actions button {
padding: 0.6rem 1rem;
background-color: #4a90e2;
border: none;
color: white;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.2s;
}
.actions button:hover {
background-color: #357abd;
}
.token-section pre {
background: #f6f8fa;
padding: 0.5rem;
overflow-x: auto;
border: 1px solid #ddd;
border-radius: 6px;
margin-bottom: 1rem;
font-size: 0.85rem;
}
.api-calls {
margin-top: 2rem;
}
.api-calls h2 {
margin-bottom: 1rem;
color: #444;
font-size: 1.1rem;
}
.api-calls button {
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
.custom-call {
margin-top: 1rem;
display: flex;
gap: 0.5rem;
}
.custom-call input {
flex: 1;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 6px;
}
/*
.status {
padding: 0.5rem 1rem;
border-radius: 8px;
display: inline-block;
background-color: #e0f7e9;
color: #2e7d32;
}
*/
.status.error {
background-color: #ffe2e2;
color: #c62828;
}
</style>

View File

@ -1,129 +0,0 @@
<template>
<div class="project">
<div class="project-header">
<div class="project-title">
<h2>{{ projectName }}</h2>
<p>Projet mis le: {{ creationDate }}</p>
</div>
<div class="project-buttons">
<button id="accept" @click="acceptProject">Accepter</button>
<button id="refus" @click="refuseProject">Refuser</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineProps } from "vue";
import { postApi } from "@/services/api";
import { addNewMessage, color } from "@/services/popupDisplayer";
const props = defineProps<{
projectName: string;
creationDate: string;
}>();
const URI = "/admin/projects/pending/decision";
const sendDecision = (decision: "true" | "false") => {
postApi(
URI,
{
projectName: props.projectName,
decision,
},
() => {
addNewMessage(
`Projet ${props.projectName} ${decision === "true" ? "accepté" : "refusé"}`,
color.Green
);
},
(err) => {
addNewMessage(`Erreur lors de la décision`, color.Red);
console.error(err);
}
);
};
const acceptProject = () => sendDecision("true");
const refuseProject = () => sendDecision("false");
</script>
<style scoped>
.project {
background: linear-gradient(to right, #f8f9fb, #ffffff);
border: 1px solid #e0e0e0;
border-radius: 16px;
padding: 1.5rem;
margin: 1.5rem 0;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
font-family: Arial, sans-serif;
transition: box-shadow 0.3s ease;
}
.project:hover {
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
}
.project-header {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
}
.project-title {
display: flex;
flex-direction: column;
}
.project-title h2 {
font-size: 1.25rem;
color: #222;
margin: 0;
font-weight: 600;
}
.project-title p {
font-size: 0.9rem;
color: #666;
margin-top: 0.25rem;
}
.project-buttons {
display: flex;
gap: 0.75rem;
}
button {
padding: 0.5rem 1.1rem;
color: white;
border: none;
border-radius: 8px;
font-size: 0.9rem;
font-weight: 500;
cursor: pointer;
transition:
background-color 0.2s ease,
transform 0.2s ease;
}
#accept {
background-color: #4caf50;
}
#accept:hover {
background-color: #3e8e41;
transform: translateY(-2px);
}
#refus {
background-color: #e74c3c;
}
#refus:hover {
background-color: #c0392b;
transform: translateY(-2px);
}
</style>

View File

@ -1,278 +1,21 @@
<template> <template>
<div class="project"> <div class="project">
<div class="project-header"> <div class="project-header">
<h2 @click="goToLink">{{ projectName }}</h2> <h2>{{ projectName }}</h2>
<div class="header-actions">
<div class="dropdown-wrapper">
<!-- Empêche la propagation du clic vers le parent -->
<button class="contact-button" @click.stop="toggleDropdown">
Contact
</button>
<div class="dropdown-menu" v-if="isDropdownOpen">
<button @click.stop="contactAll">Contacter tous</button>
<button
v-for="(email, index) in entrepreneurEmails"
:key="index"
@click.stop="contactSingle(email)"
>
{{ email }}
</button>
</div>
</div>
</div>
</div>
<!-- Toute cette partie est cliquable -->
<div class="project-body" @click="goToLink">
<ul>
<li v-for="(name, index) in listName" :key="index">
{{ name }}
</li>
</ul>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts">
import type { PropType } from "vue";
<script setup lang="ts"> export default {
import { defineProps, ref, onMounted, onBeforeUnmount } from "vue"; name: "ProjectComponent",
import { useRouter } from "vue-router"; props: {
import axios from "axios"; projectName: {
type: Object as PropType<string>,
const IS_MOCK_MODE = true; required: true,
const props = defineProps<{
projectName: string;
listName: string[];
projectLink: string;
projectId: number;
}>();
const router = useRouter();
const isDropdownOpen = ref(false);
const entrepreneurEmails = ref<string[]>([]);
// Pour fermer le dropdown si on clique ailleurs
const handleClickOutside = (event: MouseEvent) => {
const dropdown = document.querySelector(".dropdown-wrapper");
if (dropdown && !dropdown.contains(event.target as Node)) {
isDropdownOpen.value = false;
}
};
onMounted(() => {
fetchEntrepreneurs(props.projectId, IS_MOCK_MODE);
document.addEventListener("click", handleClickOutside);
});
onBeforeUnmount(() => {
document.removeEventListener("click", handleClickOutside);
});
const toggleDropdown = () => {
isDropdownOpen.value = !isDropdownOpen.value;
};
const goToLink = () => {
if (props.projectLink) {
router.push(props.projectLink);
}
};
const fetchEntrepreneurs = async (
projectId: number,
useMock = IS_MOCK_MODE
) => {
try {
const responseData: Entrepreneur[] = useMock
? await mockFetchEntrepreneurs(projectId)
: (
await axios.get(
`http://localhost:5000/shared/projects/entrepreneurs/${projectId}`
)
).data;
entrepreneurEmails.value = responseData.map(
(item: Entrepreneur) => item.primaryMail
);
} catch (error) {
console.error("Erreur lors de la récupération des entrepreneurs :", error);
}
};
type Entrepreneur = {
idUser: number;
userSurname: string;
userName: string;
primaryMail: string;
secondaryMail: string;
phoneNumber: string;
school: string;
course: string;
sneeStatus: boolean;
};
const mockFetchEntrepreneurs = async (projectId: number) => {
return new Promise<Entrepreneur[]>((resolve) => {
setTimeout(() => {
resolve([
{
idUser: 1,
userSurname: "Doe",
userName: "John",
primaryMail: "john.doe@example.com",
secondaryMail: "johndoe@backup.com",
phoneNumber: "612345678",
school: "ENSEIRB",
course: "Info",
sneeStatus: false,
}, },
{
idUser: 2,
userSurname: "Smith",
userName: "Jane",
primaryMail: "jane.smith@example.com",
secondaryMail: "janesmith@backup.com",
phoneNumber: "698765432",
school: "ENSEIRB",
course: "Info",
sneeStatus: true,
}, },
]);
}, 500);
});
};
const contactAll = () => {
const allEmails = entrepreneurEmails.value.join(", ");
navigator.clipboard.writeText(allEmails).then(() => {
alert("Tous les emails copiés dans le presse-papiers !");
window.open("https://partage.bordeaux-inp.fr/", "_blank");
});
};
const contactSingle = (email: string) => {
navigator.clipboard.writeText(email).then(() => {
alert(`Adresse copiée : ${email}`);
window.open("https://partage.bordeaux-inp.fr/", "_blank");
});
}; };
</script> </script>
<style scoped>
.project {
background: linear-gradient(to right, #f8f9fb, #ffffff);
border: 1px solid #e0e0e0;
border-radius: 16px;
padding: 1.5rem;
margin: 1.5rem 0;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
transition: box-shadow 0.3s ease;
cursor: pointer;
}
.project:hover {
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
}
.project-header {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 0.5rem;
}
.project-header h2 {
font-size: 1.25rem;
color: #222;
margin: 0;
font-weight: 600;
}
.project-buttons {
display: flex;
gap: 0.5rem;
}
.contact-btn {
background-color: #007bff;
color: #fff;
padding: 0.5rem 1rem;
border: none;
border-radius: 8px;
font-size: 0.9rem;
font-weight: 500;
transition:
background-color 0.2s ease,
transform 0.2s ease;
}
.contact-btn:hover {
background-color: #0056b3;
transform: translateY(-2px);
}
.project-body {
margin-top: 1rem;
}
.project-body ul {
list-style-type: disc;
padding-left: 1.25rem;
margin: 0;
}
.project-body ul li {
font-size: 0.95rem;
color: #555;
line-height: 1.6;
}
button {
padding: 10px 15px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
border-radius: 5px;
}
button:hover {
background-color: #0056b3;
}
.dropdown-wrapper {
position: relative;
}
.dropdown-menu {
position: absolute;
top: 100%; /* juste en dessous du bouton */
right: 0;
background-color: white;
border: 1px solid #ccc;
padding: 0.5rem;
z-index: 1000;
display: flex;
flex-direction: column;
gap: 0.5rem;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 0.25rem;
min-width: 150px;
}
.dropdown-menu button {
text-align: left;
padding: 0.3rem 0.5rem;
background: none;
border: none;
cursor: pointer;
transition: background-color 0.2s;
}
.dropdown-menu button:hover {
background-color: #f0f0f0;
}
</style>

View File

@ -1,420 +0,0 @@
<template>
<div :class="['cell', { expanded }]" @click="handleClick">
<h3 class="fs-5 fw-medium">{{ titleText }}</h3>
<div
v-for="(desc, index) in currentDescriptions"
:key="index"
class="section-bloc"
>
<!-- ADMIN -------------------------------------------------------------------------------------------->
<template v-if="IS_ADMIN">
<div class="description">
<p class="m-0">{{ desc }}</p>
</div>
</template>
<!-- ENTREP ------------------------------------------------------------------------------------------->
<template v-if="!IS_ADMIN">
<!-- Mode affichage -->
<template v-if="!isEditing[index]">
<div class="description">
<p class="m-0">{{ desc }}</p>
</div>
<div class="button-container">
<button
v-if="expanded"
class="edit-button"
@click.stop="startEditing(index)"
>
Éditer
</button>
</div>
</template>
<!-- Mode édition -->
<template v-else>
<textarea
v-model="editedDescriptions[index]"
class="edit-input"
></textarea>
<div class="button-container">
<button
class="save-button"
@click.stop="saveEdit(index)"
>
Enregistrer
</button>
<button
class="cancel-button"
@click.stop="cancelEdit(index)"
>
Annuler
</button>
</div>
</template>
</template>
</div>
<!---------------------------------------------------------------------------------------------------->
<template v-if="expanded">
<div class="canvas-exit-hint">
Cliquez n'importe où pour quitter le canvas
</div>
</template>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps, onMounted } from "vue";
import axios from "axios";
import { axiosInstance } from "@/services/api.ts";
const IS_MOCK_MODE = true;
const props = defineProps<{
projectId: number;
title: number;
titleText: string;
description: string;
isAdmin: number;
}>();
const IS_ADMIN = props.isAdmin;
const expanded = ref(false);
const currentDescriptions = ref<string[]>([]);
currentDescriptions.value[0] = props.description;
const editedDescriptions = ref<string[]>([]);
const isEditing = ref<boolean[]>([]);
onMounted(() => {
fetchData(props.projectId, props.title, "NaN", IS_MOCK_MODE);
});
/* FOR LOCAL DATABASE
const fetchData = async () => {
try {
const response = await axios.get("http://localhost:5000/data"); // Met à jour l'URL
if (response.data.length > 0) {
currentDescription.value = response.data[0].canva_data;
editedDescription.value = response.data[0].canva_data;
} else {
console.warn("Aucune donnée reçue.");
}
} catch (error) {
console.error("Erreur lors de la récupération des données :", error);
}
};
*/
// Fonction fetchData avec possibilité d'utiliser le mock
/* FOR FETCHING WITH AXIOS DIRECTLY
const fetchData = async (projectId: number, title: number, date: string, useMock = false) => {
try {
const responseData = useMock
? await mockFetch(projectId, title, date)
: (await axios.get<{ txt: string }[]>(
`http://localhost:5000/shared/projects/lcsection/${projectId}/${title}/${date}`
)).data;
if (responseData.length > 0) {
currentDescriptions.value = responseData.map((item) => item.txt);
editedDescriptions.value = [...currentDescriptions.value];
isEditing.value = Array(responseData.length).fill(false);
} else {
console.warn("Aucune donnée reçue.");
}
} catch (error) {
console.error("Erreur lors de la récupération des données :", error);
}
};
*/
// Fonction fetchData avec possibilité d'utiliser le mock
const fetchData = async (
projectId: number,
title: number,
date: string,
useMock = false
) => {
try {
const responseData = useMock
? await mockFetch(projectId, title, date)
: (
await axiosInstance.get<{ txt: string }[]>(
`/shared/projects/lcsection/${projectId}/${title}/${date}`
)
).data;
if (responseData.length > 0) {
currentDescriptions.value = responseData.map((item) => item.txt);
editedDescriptions.value = [...currentDescriptions.value];
isEditing.value = Array(responseData.length).fill(false);
} else {
console.warn("Aucune donnée reçue.");
}
} catch (error) {
console.error("Erreur lors de la récupération des données :", error);
}
};
// Fonction de simulation de l'API
const mockFetch = async (projectId: number, title: number, date: string) => {
console.log(
`Mock fetch pour projectId: ${projectId}, title: ${title}, date: ${date}`
);
return new Promise<{ txt: string }[]>((resolve) => {
setTimeout(() => {
resolve([
{ txt: "Ceci est une description 1 pour tester le front." },
{ txt: "Deuxième description." },
{ txt: "Troisième description." },
]);
}, 500); // Simule un délai réseau de 500ms
});
};
// Utilisation du mock dans handleClick pour tester sans serveur
const handleClick = async () => {
if (!expanded.value) {
await fetchData(props.projectId, props.title, "NaN", IS_MOCK_MODE);
} else if (!isEditing.value.includes(true)) {
// Réinitialiser les descriptions si aucune édition n'est en cours
currentDescriptions.value = [props.description];
editedDescriptions.value = [props.description];
}
if (!isEditing.value.includes(true)) {
expanded.value = !expanded.value;
}
};
const startEditing = (index: number) => {
isEditing.value[index] = true;
};
/*
const saveEdit = async (index: number) => {
try {
const id = index + 1; // À adapter selon l'ID réel des données
await axios.put(`http://localhost:5000/data/${id}`, {
canva_data: editedDescriptions.value[index]
});
// Mettre à jour l'affichage local après la mise à jour réussie
currentDescriptions.value[index] = editedDescriptions.value[index];
isEditing.value[index] = false;
} catch (error) {
console.error("Erreur lors de la mise à jour des données :", error);
}
};
*/
const saveEdit = async (index: number) => {
if (IS_MOCK_MODE) {
await mockSaveEdit(index);
} else {
try {
const id = index + 1;
await axios.put(`http://localhost:5000/data/${id}`, {
canva_data: editedDescriptions.value[index],
});
// Mettre à jour l'affichage local après la mise à jour réussie
currentDescriptions.value[index] = editedDescriptions.value[index];
isEditing.value[index] = false;
} catch (error) {
console.error("Erreur lors de la mise à jour des données :", error);
}
}
};
// Fonction de mock pour l'enregistrement
const mockSaveEdit = async (index: number) => {
try {
const id = index + 1;
console.log(
`Mock save pour l'ID ${id} avec la description : ${editedDescriptions.value[index]}`
);
await new Promise((resolve) => setTimeout(resolve, 500)); // Simulation de délai réseau
// Mettre à jour l'affichage local après la mise à jour réussie
currentDescriptions.value[index] = editedDescriptions.value[index];
isEditing.value[index] = false;
} catch (error) {
console.error(
"Erreur lors de la mise à jour des données mockées :",
error
);
}
};
const cancelEdit = (index: number) => {
editedDescriptions.value[index] = currentDescriptions.value[index];
isEditing.value[index] = false;
};
</script>
<style scoped>
@import "@/components/canvas/style-project.css";
.cell {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.1);
}
.expanded-content {
justify-content: flex-start !important;
}
.cell:not(.expanded):hover {
transform: scale(1.05);
box-shadow: 0 8px 9px rgba(0, 0, 0, 0.2);
}
.cell h3 {
font-size: 15px;
font-weight: 500;
font-family: "Arial", sans-serif;
}
.p {
font-size: 10px;
color: #666;
font-family: "Arial", sans-serif;
}
.expanded {
padding-top: 10%;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: white;
z-index: 10;
display: flex;
align-items: center;
justify-content: flex-start;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
.description {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
width: 100%;
height: 100%;
font-size: 16px;
margin-top: 10px;
margin-left: 2%;
margin-right: 4%;
}
.description + .p {
align-items: center;
justify-content: center;
text-align: center;
}
.edit-input {
width: 100%;
height: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
margin-top: 10px;
box-sizing: border-box;
margin-left: 2%;
}
.button-container {
display: block;
margin-top: 20px;
justify-content: center;
align-items: center;
gap: 10px;
padding-right: 1%;
}
.section-bloc,
.editing-section-bloc {
width: 100%;
justify-content: center;
align-items: center;
display: flex;
margin-right: 10%;
margin: 10px;
}
.edit-button {
width: 100px;
height: 40px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s ease;
font-size: 12px;
margin-right: 20px;
}
.save-button,
.cancel-button {
width: 100px;
height: 40px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s ease;
font-size: 12px;
margin-bottom: 5px;
}
.edit-button {
background-color: #007bff;
color: white;
}
.save-button {
background-color: #28a745;
color: white;
}
.cancel-button {
background-color: #dc3545;
color: white;
}
.edit-button:hover {
background-color: #0056b3;
}
.save-button:hover {
background-color: #218838;
}
.cancel-button:hover {
background-color: #c82333;
}
.canvas-exit-hint {
font-size: 0.75rem;
color: #666;
position: fixed;
bottom: 10px;
left: 0;
width: 100%;
text-align: center;
z-index: 1000;
}
</style>

View File

@ -1,256 +0,0 @@
<template>
<header class="header">
<img src="../icons/logo inpulse.png" alt="INPulse Logo" class="logo" />
<div class="header-actions">
<div class="dropdown-wrapper" ref="dropdownRef">
<button class="contact-button" @click.stop="toggleDropdown">
Contact
</button>
<div
class="contact-dropdown"
:class="{ 'dropdown-visible': isDropdownOpen }"
>
<button @click="contactAll">Contacter tous</button>
<button
v-for="(email, index) in entrepreneurEmails"
:key="index"
@click="contactSingle(email)"
>
{{ email }}
</button>
</div>
</div>
<RouterLink to="/" class="return-button">Retour</RouterLink>
</div>
</header>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from "vue";
import axios from "axios";
const IS_MOCK_MODE = true;
const dropdownRef = ref<HTMLElement | null>(null); // ref pour le dropdown
const props = defineProps<{
projectId: number;
}>();
type Entrepreneur = {
idUser: number;
userSurname: string;
userName: string;
primaryMail: string;
secondaryMail: string;
phoneNumber: string;
school: string;
course: string;
sneeStatus: boolean;
};
const isDropdownOpen = ref(false);
const entrepreneurEmails = ref<string[]>([]);
const toggleDropdown = () => {
isDropdownOpen.value = !isDropdownOpen.value;
console.log("Dropdown toggled:", isDropdownOpen.value);
};
const fetchEntrepreneurs = async (
projectId: number,
useMock = IS_MOCK_MODE
) => {
try {
const responseData: Entrepreneur[] = useMock
? await mockFetchEntrepreneurs(projectId)
: (
await axios.get(
`http://localhost:5000/shared/projects/entrepreneurs/${projectId}`
)
).data;
if (responseData.length > 0) {
entrepreneurEmails.value = responseData.map(
(item: Entrepreneur) => item.primaryMail
);
} else {
console.warn("Aucun entrepreneur trouvé.");
}
} catch (error) {
console.error(
"Erreur lors de la récupération des entrepreneurs :",
error
);
}
};
// Fonction de simulation de l'API
const mockFetchEntrepreneurs = async (projectId: number) => {
console.log(`Mock fetch pour projectId: ${projectId}`);
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{
idUser: 1,
userSurname: "Doe",
userName: "John",
primaryMail: "john.doe@example.com",
secondaryMail: "johndoe@backup.com",
phoneNumber: "612345678",
school: "ENSEIRB",
course: "Info",
sneeStatus: false,
},
{
idUser: 2,
userSurname: "Smith",
userName: "Jane",
primaryMail: "jane.smith@example.com",
secondaryMail: "janesmith@backup.com",
phoneNumber: "698765432",
school: "ENSEIRB",
course: "Info",
sneeStatus: true,
},
]);
}, 500);
});
};
const contactAll = () => {
const allEmails = entrepreneurEmails.value.join(", ");
navigator.clipboard
.writeText(allEmails)
.then(() => {
alert("Tous les emails copiés dans le presse-papiers !");
window.open("https://partage.bordeaux-inp.fr/", "_blank");
})
.catch((err) => {
console.error("Erreur lors de la copie :", err);
});
};
const contactSingle = (email: string) => {
navigator.clipboard
.writeText(email)
.then(() => {
alert(`Adresse copiée : ${email}`);
window.open("https://partage.bordeaux-inp.fr/", "_blank");
})
.catch((err) => {
console.error("Erreur lors de la copie :", err);
});
};
/*
const copyToClipboard = (email: string) => {
navigator.clipboard.writeText(email).then(() => {
alert(`Adresse copiée : ${email}`);
}).catch(err => {
console.error("Erreur lors de la copie :", err);
});
};
*/
// Cacher le menu si on clique en dehors
const handleClickOutside = (event: MouseEvent) => {
if (
isDropdownOpen.value &&
dropdownRef.value &&
!dropdownRef.value.contains(event.target as Node)
) {
isDropdownOpen.value = false;
}
};
onMounted(() => {
fetchEntrepreneurs(props.projectId, IS_MOCK_MODE);
document.addEventListener("click", handleClickOutside);
});
onBeforeUnmount(() => {
document.removeEventListener("click", handleClickOutside);
});
</script>
<style scoped>
@import "@/components/canvas/style-project.css";
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 30px;
background-color: #f9f9f9;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.logo {
height: 50px;
}
.header-actions {
display: flex;
align-items: center;
gap: 20px;
position: relative;
}
.contact-button,
.return-button {
background-color: #009cde;
color: white;
border: none;
padding: 10px 15px;
cursor: pointer;
font-size: 14px;
border-radius: 5px;
text-decoration: none;
transition: background-color 0.2s ease;
font-family: Arial, sans-serif;
}
.return-button:hover,
.contact-button:hover {
background-color: #007bad;
}
.contact-dropdown {
position: absolute;
top: 100%;
left: 0;
background-color: #000;
color: white;
box-shadow: 0px 4px 8px rgba(255, 255, 255, 0.2);
border-radius: 8px;
padding: 10px;
margin-top: 5px;
z-index: 1000;
min-width: 200px;
display: none;
}
.contact-dropdown button {
display: block;
width: 100%;
padding: 5px;
text-align: left;
border: none;
background: none;
cursor: pointer;
color: white;
}
.contact-dropdown button:hover {
background-color: #009cde;
}
.contact-dropdown.dropdown-visible {
display: block;
}
</style>

View File

@ -1,167 +0,0 @@
<template>
<div class="canvas container-fluid">
<CanvasItem
v-for="(item, index) in items"
:key="index"
:title="item.title"
:title-text="item.title_text"
:description="item.description"
:project-id="item.projectId"
:class="['canvas-item', item.class, 'card', 'shadow', 'p-3']"
:is-admin="props.isAdmin"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import CanvasItem from "@/components/canvas/CanvasItem.vue";
const props = defineProps<{
isAdmin: number;
}>();
const items = ref([
{
projectId: 1,
title: 1,
title_text: "1. Problème",
description: "3 problèmes essentiels à résoudre pour le client",
class: "Probleme",
},
{
projectId: 1,
title: 2,
title_text: "2. Segments",
description: "Les segments de clientèle visés",
class: "Segments",
},
{
projectId: 1,
title: 3,
title_text: "3. Valeur",
description: "La proposition de valeur",
class: "Valeur",
},
{
projectId: 1,
title: 4,
title_text: "4. Solution",
description: "Les solutions proposées",
class: "Solution",
},
{
projectId: 1,
title: 5,
title_text: "5. Avantage",
description: "Les avantages concurrentiels",
class: "Avantage",
},
{
projectId: 1,
title: 6,
title_text: "6. Canaux",
description: "Les canaux de distribution",
class: "Canaux",
},
{
projectId: 1,
title: 7,
title_text: "7. Indicateurs",
description: "Les indicateurs clés de performance",
class: "Indicateurs",
},
{
projectId: 1,
title: 8,
title_text: "8. Coûts",
description: "Les coûts associés",
class: "Couts",
},
{
projectId: 1,
title: 9,
title_text: "9. Revenus",
description: "Les sources de revenus",
class: "Revenus",
},
]);
onMounted(() => {
const bootstrapCss = document.createElement("link");
bootstrapCss.rel = "stylesheet";
bootstrapCss.href =
"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css";
bootstrapCss.integrity =
"sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+Fpc+NC";
bootstrapCss.crossOrigin = "anonymous";
document.head.appendChild(bootstrapCss);
const bootstrapJs = document.createElement("script");
bootstrapJs.src =
"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js";
bootstrapJs.integrity =
"sha384-mQ93S0EhrF4Z1nM+fTflmYf0DyzsY5j7F5H3WlClDD6H3WUJh6kxBkF3GDW8n1j6";
bootstrapJs.crossOrigin = "anonymous";
document.body.appendChild(bootstrapJs);
});
</script>
<style scoped>
@import "@/components/canvas/style-project.css";
.canvas {
display: grid;
grid-template-columns: repeat(10, 1fr);
grid-template-rows: repeat(6, 1fr);
gap: 12px;
padding: 30px;
/*background-color: #f8f9fa;*/
position: relative;
height: 90vh;
overflow: auto;
}
.Probleme {
grid-column: 1 / 3;
grid-row: 1 / 5;
}
.Segments {
grid-column: 9 / 11;
grid-row: 1 / 5;
}
.Valeur {
grid-column: 5 / 7;
grid-row: 1 / 5;
}
.Solution {
grid-column: 3 / 5;
grid-row: 1 / 3;
}
.Avantage {
grid-column: 7 / 9;
grid-row: 1 / 3;
}
.Canaux {
grid-column: 7 / 9;
grid-row: 3 / 5;
}
.Indicateurs {
grid-column: 3 / 5;
grid-row: 3 / 5;
}
.Couts {
grid-column: 1 / 6;
grid-row: 5 / 7;
}
.Revenus {
grid-column: 6 / 11;
grid-row: 5 / 7;
}
.canvas-item {
/*background-color: white;*/
border: 1px solid #dee2e6;
border-radius: 0.5rem;
}
</style>

View File

@ -1,156 +0,0 @@
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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -28,46 +28,4 @@ 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 }; export { store };

View File

@ -1,16 +0,0 @@
// 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;

View File

@ -11,35 +11,6 @@ const router = createRouter({
// which is lazy-loaded when the route is visited. // which is lazy-loaded when the route is visited.
component: () => import("../views/testComponent.vue"), component: () => import("../views/testComponent.vue"),
}, },
{
path: "/login",
name: "login",
component: () => import("../components/LoginComponent.vue"),
},
{
path: "/",
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"),
},
], ],
}); });

View File

@ -65,27 +65,4 @@ function callApi(
); );
} }
function postApi( export { callApi };
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 };

View File

@ -54,7 +54,7 @@ const useAuthStore = defineStore("storeAuth", {
async logout() { async logout() {
try { try {
await keycloakService.CallLogout( await keycloakService.CallLogout(
import.meta.env.VITE_APP_URL + "/login" //redirect to login page instead of test... import.meta.env.VITE_APP_URL + "/test"
); );
await this.clearUserData(); await this.clearUserData();
} catch (error) { } catch (error) {

View File

@ -1,160 +0,0 @@
<template>
<Header />
<error-wrapper></error-wrapper>
<div id="container">
<div id="main">
<h3>Projet en cours</h3>
<ProjectComp
v-for="(project, index) in projects"
:key="index"
:project-name="project.name"
:list-name="project.members"
:project-link="project.link"
:project-id="0"
/>
<div id="main">
<h3>Projet en attente</h3>
<PendingProjectComponent
v-for="(project, index) in pendingProjects"
:key="index"
:project-name="project.name"
:creation-date="project.creationDate"
/>
</div>
<AddProjectForm />
</div>
<Agenda :project-r-d-v="rendezVous" />
</div>
</template>
<script setup lang="ts">
import { ref /*, onMounted*/ } from "vue";
//import { callApi } from "@/services/api";
import Header from "../components/HeaderComponent.vue";
import Agenda from "../components/AgendaComponent.vue";
import ProjectComp from "../components/ProjectComponent.vue";
import PendingProjectComponent from "@/components/PendingProjectComponent.vue";
import AddProjectForm from "@/components/AddProjectForm.vue";
//const PORT = "8081";
//const URI = `http://localhost:${PORT}`;
//const projects = ref<{ name: string; link: string; members: string[] }[]>([]);
/* const fetchProjects = () => {
callApi(
`${URI}/admin/projects`,
async (response) => {
console.log(response);
const projectList = response.data;
const projectPromises = projectList.map((project: any) => {
return new Promise(async (resolve) => {
callApi(
`${URI}/shared/projects/entrepreneurs/${project.idProject}`,
(memberResponse) => {
const members = memberResponse.data.map((m: any) => m.userName);
resolve({
name: project.projectName,
link: `/project/${project.idProject}`,
members,
});
},
() => {
// Error fetching members, still resolve with empty members
resolve({
name: project.projectName,
link: `/project/${project.idProject}`,
members: [],
});
}
);
});
});
projects.value = await Promise.all(projectPromises);
},
(error) => {
console.error("Error fetching projects:", error);
}
);
};
onMounted(fetchProjects);
*/
const projects = ref([
{
name: "Projet Alpha",
link: "/canvas", // to test
members: ["Alice", "Bob", "Charlie"],
},
{
name: "Projet Beta",
link: "./canvas", // to test
members: ["David", "Eve", "Frank"],
},
]);
const pendingProjects = ref([
{ name: "l'eau", creationDate: "26-02-2024" },
{ name: "l'air", creationDate: "09-03-2023" },
]);
const rendezVous = ref([
{ projectName: "Projet Alpha", date: "2025-03-10", lieu: "P106" },
{ projectName: "Projet Beta", date: "2025-04-15", lieu: "Td10" },
]);
</script>
<style scoped>
#container {
display: grid;
grid-template-columns: 3fr 1fr;
gap: 2rem;
padding: 2rem;
background-color: #f4f6f9;
min-height: 100vh;
box-sizing: border-box;
}
#main {
background-color: #fff;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
h3 {
font-size: 1.5rem;
color: #333;
margin-bottom: 1.2rem;
border-bottom: 2px solid #ddd;
padding-bottom: 0.5rem;
}
button {
padding: 10px 15px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
border-radius: 6px;
font-weight: 500;
transition: background-color 0.2s ease;
}
button:hover {
background-color: #0056b3;
}
/* Add spacing between project sections */
#main > * + * {
margin-top: 2rem;
}
</style>

View File

@ -1,141 +0,0 @@
<template>
<div>
<header>
<HeaderCanvas :project-id="1" />
</header>
</div>
<div>
<h1 class="page-title">PAGE CANVAS</h1>
<p class="canvas-help-text">
Cliquez sur un champ du tableau pour afficher son contenu en détail
ci-dessous.
</p>
<LeanCanvas :is-admin="isAdmin" />
<div class="info-box">
<p>
Responsable :
<strong>{{ admin.userName }} {{ admin.userSurname }}</strong
><br />
Contact :
<a href="mailto:{{ admin.primaryMail }}">{{
admin.primaryMail
}}</a>
|
<a href="tel:{{ admin.phoneNumber }}">{{
admin.phoneNumber
}}</a>
</p>
</div>
</div>
</template>
<script setup lang="ts">
import HeaderCanvas from "../components/canvas/HeaderCanvas.vue";
import LeanCanvas from "../components/canvas/LeanCanvas.vue";
import { ref, onMounted /*, defineProps*/ } from "vue";
import { axiosInstance } from "@/services/api.ts";
const IS_MOCK_MODE = true;
/*
const props = defineProps<{
projectId: number;
token: TokenPayload;
}>();
is_admin = token.includes("MyINPulse-admin")
*/
const isAdmin = 0;
// Variables pour les informations de l'administrateur
const admin = ref({
idUser: 0,
userSurname: "",
userName: "",
primaryMail: "",
secondaryMail: "",
phoneNumber: "",
});
const mockAdminData = {
idUser: 1,
userSurname: "ALAMI",
userName: "Adnane",
primaryMail: "mock.admin@example.com",
secondaryMail: "admin.backup@example.com",
phoneNumber: "0600000000",
};
// Fonction pour récupérer les données de l'administrateur
const fetchAdminData = async (projectId: number, useMock = IS_MOCK_MODE) => {
try {
if (useMock) {
console.log(
"Utilisation des données mockées pour l'administrateur"
);
admin.value = mockAdminData;
return;
}
const response = await axiosInstance.get(
`/shared/projects/admin/${projectId}`
);
admin.value = response.data;
} catch (error) {
console.error(
"Erreur lors de la récupération des données de l'administrateur :",
error
);
}
};
// Appeler la fonction fetch au montage du composant
onMounted(() => {
const projectId = 1;
fetchAdminData(projectId);
});
</script>
<style scoped>
.page-title {
text-align: center;
font-size: 2.5rem;
margin-top: 20px;
}
.canvas-help-text {
text-align: center;
font-size: 0.7rem;
color: #666;
}
.info-box {
background-color: #f9f9f9;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
font-family: Arial, sans-serif;
width: 30%;
max-width: 600px;
margin: 20px auto;
}
.info-box p {
font-size: 16px;
line-height: 1.5;
color: #333;
}
.info-box a {
color: #007bff;
text-decoration: none;
}
.info-box a:hover {
text-decoration: underline;
}
</style>

View File

@ -1,185 +0,0 @@
<template>
<form class="add-project-form" @submit.prevent="submitForm">
<h2>Ajouter un projet</h2>
<div class="form-group">
<label for="name">Nom du projet</label>
<input id="name" v-model="form.name" type="text" required />
</div>
<h3>Entrepreneur</h3>
<div class="form-group">
<label for="founderName">Nom</label>
<input
id="founderName"
v-model="form.founder.userName"
type="text"
required
/>
</div>
<div class="form-group">
<label for="founderSurname">Prénom</label>
<input
id="founderSurname"
v-model="form.founder.userSurname"
type="text"
required
/>
</div>
<div class="form-group">
<label for="founderPrimaryMail">Email Principal</label>
<input
id="founderPrimaryMail"
v-model="form.founder.primaryMail"
type="email"
required
/>
</div>
<div class="form-group">
<label for="founderSecondaryMail">Email Secondaire</label>
<input
id="founderSecondaryMail"
v-model="form.founder.secondaryMail"
type="email"
/>
</div>
<div class="form-group">
<label for="founderPhoneNumber">Numéro de téléphone</label>
<input
id="founderPhoneNumber"
v-model="form.founder.phoneNumber"
type="tel"
required
/>
</div>
<div class="form-group">
<label for="founderSchool">École</label>
<input
id="founderSchool"
v-model="form.founder.school"
type="text"
required
/>
</div>
<div class="form-group">
<label for="founderCourse">Département</label>
<input
id="founderCourse"
v-model="form.founder.course"
type="text"
required
/>
</div>
<div class="form-group">
<label for="founderSneeStatus">Statut étudiant entrepreneur</label>
<input
id="founderSneeStatus"
v-model="form.founder.sneeStatus"
type="checkbox"
/>
</div>
<button type="submit">Soumettre</button>
</form>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { postApi } from "@/services/api";
const form = ref({
name: "",
founder: {
userSurname: "",
userName: "",
primaryMail: "",
secondaryMail: "",
phoneNumber: "",
school: "",
course: "",
sneeStatus: false,
},
});
function submitForm() {
postApi("/entrepreneur/projects/request", form.value);
}
</script>
<style scoped>
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap");
.add-project-form {
font-family: "Inter", sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
background: #fff;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
/* Le reste reste inchangé */
h2 {
margin-bottom: 20px;
font-size: 24px;
color: #333;
}
h3 {
margin-top: 20px;
font-size: 20px;
color: #555;
}
.form-group {
margin-bottom: 15px;
display: flex;
flex-direction: column;
}
label {
font-weight: 500;
margin-bottom: 5px;
}
input {
padding: 8px;
border-radius: 5px;
border: 1px solid #ccc;
font-size: 1em;
}
input[type="checkbox"] {
width: auto;
margin-right: 10px;
}
button {
background-color: #4caf50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1em;
width: 100%;
font-weight: 500;
}
button:hover {
background-color: #45a049;
}
button:active {
background-color: #388e3c;
}
input[type="text"]:focus,
input[type="email"]:focus,
input[type="tel"]:focus {
border-color: #4caf50;
outline: none;
}
</style>

View File

@ -1,116 +0,0 @@
<template>
<header class="header">
<img
src="@/components/icons/logo inpulse.png"
alt="INPulse Logo"
class="logo"
/>
</header>
<div class="choix-projet">
<h1>Bienvenue sur MyINPulse</h1>
<p>
Souhaitez-vous créer un nouveau projet ou joindre un projet existant
?
</p>
<div class="button-group">
<button @click="choisir('creer')">Créer un projet</button>
<button @click="choisir('joindre')">Joindre un projet</button>
</div>
<div v-if="choix === 'creer'" class="form-creer">
<h2>Créer un projet</h2>
<label for="nomProjet">Nom du projet :</label>
<input
id="nomProjet"
v-model="nomProjet"
type="text"
placeholder="Nom du projet"
/>
<button @click="validerCreation">Valider</button>
</div>
<div v-if="choix === 'joindre'" class="message-indispo">
<h2>Joindre un projet</h2>
<p> Cette fonctionnalité n'est pas encore disponible.</p>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const choix = ref<string | null>(null);
const nomProjet = ref("");
const choisir = (option: "creer" | "joindre") => {
choix.value = option;
};
const validerCreation = () => {
if (!nomProjet.value.trim()) {
alert("Veuillez entrer un nom de projet.");
return;
}
alert(`Projet "${nomProjet.value}" créé avec succès !`);
// Tu peux ensuite naviguer ou faire un POST ici
};
</script>
<style scoped>
@import "@/components/canvas/style-project.css";
.choix-projet {
max-width: 500px;
margin: auto;
padding: 2rem;
background-color: #fefefe;
border-radius: 10px;
font-family: "Inter", sans-serif;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
text-align: center;
}
.button-group {
margin: 20px 0;
display: flex;
justify-content: space-around;
}
button {
padding: 10px 20px;
font-size: 1em;
background-color: #4caf50;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
button:hover {
background-color: #43a047;
}
input {
padding: 10px;
margin-top: 10px;
width: 80%;
font-size: 1em;
border-radius: 5px;
border: 1px solid #ccc;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 30px;
background-color: #f9f9f9;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.logo {
height: 50px;
}
</style>

View File

@ -18,7 +18,7 @@ import ErrorModal from "@/components/errorModal.vue";
.error-wrapper { .error-wrapper {
position: absolute; position: absolute;
left: 70%; left: 70%;
/*background-color: blue;*/ //background-color: blue;
height: 100%; height: 100%;
width: 30%; width: 30%;
} }