40 Commits

Author SHA1 Message Date
14a2a59786 merging
Some checks failed
CI / build (push) Failing after 7s
2025-03-26 11:57:13 +01:00
0ae6e7dfda merging 2025-03-26 11:53:49 +01:00
15ccb5630a MERGING 2025-03-26 11:52:04 +01:00
4ec292cca7 fix: merging 2025-03-26 11:50:23 +01:00
14a953536a merging 2025-03-26 11:45:40 +01:00
288f983816 merging 2025-03-26 11:44:49 +01:00
dd6032f3ef fix: css.. 2025-03-26 11:38:33 +01:00
7e2f5bc506 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks are pending
CI / build (push) Waiting to run
2025-03-26 11:32:14 +01:00
4ef92efd0e fix: js to ts in header 2025-03-26 11:31:54 +01:00
e769dd6757 fix: css fixing (again) 2025-03-26 11:30:59 +01:00
550a51523f Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation 2025-03-26 11:24:28 +01:00
323cb05388 fix: css fixing 2025-03-26 11:07:50 +01:00
7e0851bfef Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation 2025-03-24 12:43:04 +01:00
98b6d167e8 fix: minor bugs on views 2025-03-24 12:34:17 +01:00
79baddb8f6 fix + feat: fixed some bugs + added a mock parser since the damn backend is not working, not finished tho (bugs) 2025-03-23 21:59:27 +01:00
0f8c83c2e2 feat: adding the endpoints' doc to the branch for an easy access 2025-03-21 01:20:34 +01:00
fad52644d2 fix : merged the main with the front branch and fixed all possible issues 2025-03-21 01:16:49 +01:00
5f51a1008b fix: fixing conflicts 2025-03-20 22:42:49 +01:00
279c171ba2 fix: Doc added (for me) 2025-03-19 11:54:34 +01:00
9ba8e3e84e feat: fake-data can be edited, switching to end-points branch 2025-03-19 10:52:12 +01:00
6de38a9725 feat: edit-mode added, to test it use the fake api in the 'fake_data' folder 2025-03-19 09:48:04 +01:00
f48b570494 fix: to composition api 2025-02-26 03:39:59 +01:00
0733f8d5af Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation 2025-02-25 22:53:26 +01:00
8071c01c5d fix: fixing the issues regarding the use of href 2025-02-25 22:53:15 +01:00
4ee3d9bc44 fix: data from api is now correctly displayed 2025-02-19 12:15:39 +01:00
d75d45e204 fix + feat: api test + error fix 2025-02-19 11:02:15 +01:00
9f3754776f fix: fixed the code for AdminMain 2025-02-18 22:17:57 +01:00
651fb2b1a1 feat: adding first fake api (use this cmd in fake_data reportory: json-server --watch db.json --port 5000) + fetching witch axios for now (npm install axios) 2025-02-18 07:20:58 +01:00
aa5988ce75 fix: emptyed the app.vue and did some code reorganisation 2025-02-17 23:48:28 +01:00
9ae18e1e4b feat : adding an agenda, not included to the main page yet 2025-02-17 21:51:27 +01:00
22ebb0e1f4 feat: enhancing canvass layout 2025-02-11 20:47:37 +01:00
09e4b3262f feat: switching to composition API standard 2025-02-11 19:56:52 +01:00
6a3d4239ab feat: canvas are now generic 2025-02-10 22:46:59 +01:00
9d71c93b5b feat: layout changes 2025-02-10 15:53:10 +01:00
5145b833ae feat: rendez-vous agenda for admin and user 2025-02-10 15:15:58 +01:00
4080cee818 Meeeerge...
Merge branch 'main' of https://gitea.piair.dev/piair/MyINPulse
2025-02-08 22:40:42 +01:00
f4d73654d1 some minor changes on the main page 2025-02-08 22:40:35 +01:00
4fda5513a9 error corrected 2025-02-08 20:33:03 +01:00
32407b0e8f conflits... 2025-02-08 20:23:43 +01:00
b30e1196f4 canvas included in the main page, still shiting with vue 2025-02-08 20:18:44 +01:00
78 changed files with 2227 additions and 1867 deletions

View File

@ -1,18 +0,0 @@
name: Format
on: [ push, pull_request ]
jobs:
formatting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # v2 minimum required
- uses: actions/setup-java@v4
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- uses: axel-op/googlejavaformat-action@v3
with:
args: "--set-exit-if-changed --skip-sorting-imports --aosp -n"

1
.gitignore vendored
View File

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

View File

@ -0,0 +1,741 @@
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

@ -1,16 +1,13 @@
help:
@echo "make [clean dev-front prod dev-back dev]"
@echo "make [clean dev-front prod dev-back]"
clean:
@cp config/frontdev.env front/MyINPulse-front/.env
@cp config/frontdev.env .env
@cp config/frontdev.env MyINPulse-back/.env
@cp config/prod.docker-compose.yaml docker-compose.yaml
@docker compose down
@rm -f docker-compose.yaml
@rm -f .env
@rm -f front/MyINPulse-front/.env
@rm -f MyINPulse-back/.env
# Install npm packages
front/MyINPulse-front/.installed:
@ -21,37 +18,24 @@ vite: ./front/MyINPulse-front/.installed
dev-front: clean vite
@cp config/frontdev.env front/MyINPulse-front/.env
@cp config/frontdev.env .env
@cp config/frontdev.env MyINPulse-back/.env
@cp config/frontdev.front.env front/MyINPulse-front/.env
@cp config/frontdev.main.env .env
@cp config/frontdev.docker-compose.yaml docker-compose.yaml
@docker compose up -d --build
@cd ./front/MyINPulse-front/ && npm run dev
prod: clean
@cp config/prod.env front/MyINPulse-front/.env
@cp config/prod.env .env
@cp config/prod.env .env
@cp config/prod.docker-compose.yaml docker-compose.yaml
@cp config/prod.front.env front/MyINPulse-front/.env
@cp config/prod.main.env .env
@cp config/frontdev.docker-compose.yaml docker-compose.yaml
@docker compose up -d --build
dev-back:
@cp config/backdev.env front/MyINPulse-front/.env
@cp config/backdev.env .env
@cp config/backdev.env MyINPulse-back/.env
@cp config/backdev.front.env front/MyINPulse-front/.env
@cp config/backdev.main.env .env
@cp config/backdev.docker-compose.yaml docker-compose.yaml
@docker compose up -d --build
@echo "cd MyINPulse-back" && echo 'export $$(cat .env | xargs)'
@echo "cd MyINPulse-back"
@echo "./gradlew bootRun --args='--server.port=8081'"
dev: clean vite
@cp config/dev.env front/MyINPulse-front/.env
@cp config/dev.env .env
@cp config/dev.env MyINPulse-back/.env
@cp config/dev.docker-compose.yaml docker-compose.yaml
@docker compose up -d --build
@echo "cd MyINPulse-back" && echo 'export $$(cat .env | xargs)'
@echo "./gradlew bootRun --args='--server.port=8081'"
@cd ./front/MyINPulse-front/ && npm run dev &

View File

@ -20,10 +20,6 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.postgresql:postgresql'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

View File

@ -1,15 +1,29 @@
package enseirb.myinpulse;
import enseirb.myinpulse.security.KeycloakJwtRolesConverter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.jwt.*;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.*;
import java.util.stream.Collectors;
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;
@SpringBootApplication
public class MyinpulseApplication {
public static void main(String[] args) {
SpringApplication.run(MyinpulseApplication.class, args);
}
public static void main(String[] args) {
SpringApplication.run(MyinpulseApplication.class, args);
}
}

View File

@ -2,14 +2,13 @@ package enseirb.myinpulse.api;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.CrossOrigin;
import java.security.Principal;
import javax.management.relation.RoleNotFoundException;
@SpringBootApplication
@RestController
public class GetUserInfo {
@ -22,19 +21,23 @@ public class GetUserInfo {
}
@CrossOrigin(methods = {RequestMethod.GET, RequestMethod.OPTIONS})
@GetMapping("/unauth/random")
public boolean rand(@RequestHeader("Authorization") String token) throws RoleNotFoundException {
System.err.println(token);
@GetMapping("/random")
public boolean rand(){
System.err.println("HELLO");
return Math.random() > 0.5;
}
@GetMapping("/admin/random")
public boolean rand2() {
@CrossOrigin(methods = {RequestMethod.GET, RequestMethod.OPTIONS})
@GetMapping("/random2")
public boolean rand2(){
System.err.println("HELLO2");
return Math.random() > 0.5;
}
@GetMapping("/entrepreneur/random")
public boolean rand3() {
@CrossOrigin(methods = {RequestMethod.GET, RequestMethod.OPTIONS})
@GetMapping("/random3")
public boolean rand3(){
System.err.println("HELLO");
return Math.random() > 0.5;
}
}

View File

@ -1,10 +1,6 @@
package enseirb.myinpulse.config;
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;
import enseirb.myinpulse.security.KeycloakJwtRolesConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@ -16,62 +12,39 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
import java.util.List;
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;
@Configuration
public class WebSecurityCustomConfiguration {
// CORS configuration
@Value("${VITE_APP_URL}")
private String frontendUrl;
/**
* Configure the CORS (Cross Origin Ressource Sharing -- a security feature) configuration. The
* only allowed website is the frontend, defined in the .env file.
*
* @return the CORS configuration used by the backend
*/
// TODO: make sure to only accept our own domains
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of(frontendUrl));
configuration.setAllowedOrigins(List.of("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "OPTIONS"));
configuration.setAllowedHeaders(
Arrays.asList("authorization", "content-type", "x-auth-token"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type",
"x-auth-token")); // Do not remove, this fixes the CORS errors when unauthenticated
UrlBasedCorsConfigurationSource source = new
UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
/**
* Configure the authorisation required for each path.
*
* <p>admin endpoints are under /admin/* and entrepreneur are under /entrepreneur/*
*
* <p>If endpoints dont require authentication, they are under /unauth/
*
* @param http automatically filled in by spring.
* @return a securityfilterchain, automatically used by spring.
* @throws Exception TODO: figure out when the exception are raised
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(
authorize ->
authorize
.requestMatchers("/entrepreneur/**")
.access(hasRole("REALM_MyINPulse-entrepreneur"))
.requestMatchers("/admin/**")
.access(hasRole("REALM_MyINPulse-admin"))
.requestMatchers("/unauth/**")
.permitAll()
.anyRequest()
.authenticated())
.oauth2ResourceServer(
oauth2 ->
oauth2.jwt(
jwt ->
jwt.jwtAuthenticationConverter(
new KeycloakJwtRolesConverter())));
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/random2").access(hasRole("REALM_MyINPulse-entrepreneur"))
.requestMatchers("/random").access(hasRole("REALM_MyINPulse-admin"))
.requestMatchers("/random3").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.
jwtAuthenticationConverter(new KeycloakJwtRolesConverter())));
return http.build();
}
}

View File

@ -1,7 +0,0 @@
package enseirb.myinpulse.exceptions;
public class RoleNotFoudException extends RuntimeException {
public RoleNotFoudException(String message) {
super(message);
}
}

View File

@ -1,7 +0,0 @@
package enseirb.myinpulse.exceptions;
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}

View File

@ -1,38 +0,0 @@
package enseirb.myinpulse.postgres_db.controller;
import enseirb.myinpulse.postgres_db.model.Administrateurs;
import enseirb.myinpulse.postgres_db.repository.AdministrateursRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.util.Optional;
@RestController
public class AdministrateursController {
@Autowired AdministrateursRepository administrateursRepository;
@GetMapping("/Administrateurs")
@ResponseBody
public Iterable<Administrateurs> allAdministrateurs() {
return this.administrateursRepository.findAll();
}
@GetMapping("/Administrateurs/{id}")
public Administrateurs getAdministrateursById(@PathVariable Long id) {
Optional<Administrateurs> administrateur = this.administrateursRepository.findById(id);
if (administrateur.isEmpty()) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Cet administrateur n'existe pas");
}
return administrateur.get();
}
@PostMapping("/Administrateurs")
public Administrateurs addAdministrateurs(@RequestBody Administrateurs administrateurs) {
return this.administrateursRepository.save(administrateurs);
}
}

View File

@ -1,49 +0,0 @@
package enseirb.myinpulse.postgres_db.controller;
import enseirb.myinpulse.postgres_db.model.ComptesRendus;
import enseirb.myinpulse.postgres_db.repository.ComptesRendusRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.util.Optional;
@RestController
public class ComptesRendusController {
@Autowired ComptesRendusRepository comptesRendusRepository;
@GetMapping("/ComptesRendus")
@ResponseBody
public Iterable<ComptesRendus> allComptesRendus() {
return this.comptesRendusRepository.findAll();
}
@GetMapping("/ComptesRendus/{id}")
public ComptesRendus getComptesRendusById(@PathVariable Long id) {
Optional<ComptesRendus> compteRendu = this.comptesRendusRepository.findById(id);
if (compteRendu.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce compte rendu n'existe pas");
}
return compteRendu.get();
}
@PostMapping("/ComptesRendus")
public ComptesRendus addComptesRendus(@RequestBody ComptesRendus comptesRendus) {
return this.comptesRendusRepository.save(comptesRendus);
}
@PostMapping("/ComptesRendus/{id}")
public ComptesRendus updateProjets(@PathVariable Long id, String contenu_compte_rendu) {
Optional<ComptesRendus> compteRendu = this.comptesRendusRepository.findById(id);
if (compteRendu.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce compte rendu n'existe pas");
}
if (contenu_compte_rendu != null) {
compteRendu.get().setContenu_compte_rendu(contenu_compte_rendu);
}
return this.comptesRendusRepository.save(compteRendu.get());
}
}

View File

@ -1,58 +0,0 @@
package enseirb.myinpulse.postgres_db.controller;
import enseirb.myinpulse.postgres_db.model.Entrepreneurs;
import enseirb.myinpulse.postgres_db.repository.EntrepreneursRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.util.Optional;
@RestController
public class EntrepreneursController {
@Autowired EntrepreneursRepository entrepreneursRepository;
@GetMapping("/Entrepreneurs")
@ResponseBody
public Iterable<Entrepreneurs> allEntrepreneurs() {
return this.entrepreneursRepository.findAll();
}
@GetMapping("/Entrepreneurs/{id}")
public Entrepreneurs getEntrepreneursById(@PathVariable Long id) {
Optional<Entrepreneurs> entrepreneur = entrepreneursRepository.findById(id);
if (entrepreneur.isEmpty()) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Cet entrepreneur n'existe pas");
}
return entrepreneur.get();
}
@PostMapping("/Entrepreneurs")
public Entrepreneurs addEntrepreneurs(@RequestBody Entrepreneurs entrepreneurs) {
return this.entrepreneursRepository.save(entrepreneurs);
}
@PostMapping("/Entrepreneurs/{id}")
public Entrepreneurs updateEntrepreneurs(
@PathVariable Long id, String ecole, String filiere, Boolean status_snee) {
Optional<Entrepreneurs> entrepreneur = entrepreneursRepository.findById(id);
if (entrepreneur.isEmpty()) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Cet entrepreneur n'existe pas");
}
if (ecole != null) {
entrepreneur.get().setEcole(ecole);
}
if (filiere != null) {
entrepreneur.get().setFiliere(filiere);
}
if (status_snee != null) {
entrepreneur.get().setStatus_snee(status_snee);
}
return this.entrepreneursRepository.save(entrepreneur.get());
}
}

View File

@ -1,64 +0,0 @@
package enseirb.myinpulse.postgres_db.controller;
import enseirb.myinpulse.postgres_db.model.Projets;
import enseirb.myinpulse.postgres_db.repository.ProjetsRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDate;
import java.util.Optional;
@RestController
public class ProjetsController {
@Autowired ProjetsRepository projetsRepository;
@GetMapping("/Projets")
@ResponseBody
public Iterable<Projets> allProjets() {
return this.projetsRepository.findAll();
}
@GetMapping("/Projets/{id}")
public Projets getProjetsById(@PathVariable Long id) {
Optional<Projets> projet = this.projetsRepository.findById(id);
if (projet.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce projet n'existe pas");
}
return projet.get();
}
@PostMapping("/Projets")
public Projets addProjets(@RequestBody Projets projet) {
return this.projetsRepository.save(projet);
}
@PostMapping("/Projets/{id}")
public Projets updateProjets(
@PathVariable Long id,
String nom_projet,
byte[] logo,
LocalDate date_creation,
String status_projet) {
Optional<Projets> projet = this.projetsRepository.findById(id);
if (projet.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce projet n'existe pas");
}
if (nom_projet != null) {
projet.get().setNom_projet(nom_projet);
}
if (logo != null) {
projet.get().setLogo(logo);
}
if (date_creation != null) {
projet.get().setDate_creation(date_creation);
}
if (status_projet != null) {
projet.get().setStatus_projet(status_projet);
}
return this.projetsRepository.save(projet.get());
}
}

View File

@ -1,67 +0,0 @@
package enseirb.myinpulse.postgres_db.controller;
import enseirb.myinpulse.postgres_db.model.RendezVous;
import enseirb.myinpulse.postgres_db.repository.RendezVousRepository;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
@RestController
public class RendezVousController {
@Autowired RendezVousRepository rendezVousRepository;
@GetMapping("/RendezVous")
@ResponseBody
public Iterable<RendezVous> allRendezVous() {
return this.rendezVousRepository.findAll();
}
@GetMapping("/RendezVous/{id}")
public RendezVous getRendezVousById(@PathVariable Long id) {
Optional<RendezVous> rendezVous = this.rendezVousRepository.findById(id);
if (rendezVous.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce rendez vous n'existe pas");
}
return rendezVous.get();
}
@PostMapping("/RendezVous")
public RendezVous addRendezVous(@RequestBody RendezVous rendezVous) {
return this.rendezVousRepository.save(rendezVous);
}
@PostMapping("/RendezVous/{id}")
public RendezVous updateRendezVous(
@PathVariable Long id,
LocalDate date_rdv,
LocalTime heure_rdv,
LocalTime duree_rdv,
String lieu_rdv,
String sujet_rdv) {
Optional<RendezVous> rendezVous = this.rendezVousRepository.findById(id);
if (rendezVous.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce rendez vous n'existe pas");
}
if (date_rdv != null) {
rendezVous.get().setDate_rdv(date_rdv);
}
if (heure_rdv != null) {
rendezVous.get().setHeure_rdv(heure_rdv);
}
if (duree_rdv != null) {
rendezVous.get().setDuree_rdv(duree_rdv);
}
if (lieu_rdv != null) {
rendezVous.get().setLieu_rdv(lieu_rdv);
}
if (sujet_rdv != null) {
rendezVous.get().setSujet_rdv(sujet_rdv);
}
return this.rendezVousRepository.save(rendezVous.get());
}
}

View File

@ -1,60 +0,0 @@
package enseirb.myinpulse.postgres_db.controller;
import enseirb.myinpulse.postgres_db.model.Sections;
import enseirb.myinpulse.postgres_db.repository.SectionsRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDateTime;
import java.util.Optional;
@RestController
public class SectionsController {
@Autowired SectionsRepository sectionsRepository;
@GetMapping("/Sections")
@ResponseBody
public Iterable<Sections> allSections() {
return this.sectionsRepository.findAll();
}
@GetMapping("/Sections/{id}")
public Sections getSectionsById(@PathVariable Long id) {
Optional<Sections> section = this.sectionsRepository.findById(id);
if (section.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Cette section n'extise pas");
}
return section.get();
}
@PostMapping("/Sections")
public Sections addSections(@RequestBody Sections sections) {
return this.sectionsRepository.save(sections);
}
@PostMapping("/Sections/{id}")
public Sections updateSections(
@PathVariable Long id,
String titre,
String contenu_section,
LocalDateTime date_modification) {
Optional<Sections> section = this.sectionsRepository.findById(id);
if (section.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Cette section n'extise pas");
}
if (titre != null) {
section.get().setTitre(titre);
}
if (contenu_section != null) {
section.get().setContenu_section(contenu_section);
}
if (date_modification != null) {
section.get().setDate_modification(date_modification);
}
return this.sectionsRepository.save(section.get());
}
}

View File

@ -1,67 +0,0 @@
package enseirb.myinpulse.postgres_db.controller;
import enseirb.myinpulse.postgres_db.model.Utilisateurs;
import enseirb.myinpulse.postgres_db.repository.UtilisateursRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.util.Optional;
@RestController
public class UtilisateursController {
@Autowired UtilisateursRepository utilisateursRepository;
@GetMapping("/Utilisateurs")
@ResponseBody
public Iterable<Utilisateurs> allUtilisateurs() {
return this.utilisateursRepository.findAll();
}
@GetMapping("/Utilisateurs/{id}")
public Utilisateurs getUtilisateursById(@PathVariable Long id) {
Optional<Utilisateurs> utilisateur = utilisateursRepository.findById(id);
if (utilisateur.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Cet utilisateur n'existe pas");
}
return utilisateur.get();
}
@PostMapping("/Utilisateurs")
public Utilisateurs addUtilisateurs(@RequestBody Utilisateurs utilisateurs) {
return this.utilisateursRepository.save(utilisateurs);
}
@PostMapping("/Utilisateurs/{id}")
public Utilisateurs updateUtilisateurs(
@PathVariable Long id,
String nom_utilisateur,
String prenom_utilisateur,
String mail_principal,
String mail_secondaire,
String numero_telephone) {
Optional<Utilisateurs> utilisateur = utilisateursRepository.findById(id);
if (utilisateur.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Cet utilisateur n'existe pas");
}
if (nom_utilisateur != null) {
utilisateur.get().setNom_utilisateur(nom_utilisateur);
}
if (prenom_utilisateur != null) {
utilisateur.get().setPrenom_utilisateur(prenom_utilisateur);
}
if (mail_principal != null) {
utilisateur.get().setMail_principal(mail_principal);
}
if (mail_secondaire != null) {
utilisateur.get().setMail_secondaire(mail_secondaire);
}
if (numero_telephone != null) {
utilisateur.get().setNumero_telephone(numero_telephone);
}
return this.utilisateursRepository.save(utilisateur.get());
}
}

View File

@ -1,43 +0,0 @@
package enseirb.myinpulse.postgres_db.model;
import jakarta.persistence.*;
import jakarta.persistence.PrimaryKeyJoinColumn;
import jakarta.persistence.Table;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "administrateurs")
@PrimaryKeyJoinColumn(name = "id_administrateur", referencedColumnName = "id_utilisateur")
public class Administrateurs extends Utilisateurs {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id_projet")
private Projets projetsAdministrateurs;
@OneToMany(mappedBy = "administrateursSections", fetch = FetchType.LAZY, orphanRemoval = true)
private List<Sections> ListSections = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id_rdv")
private RendezVous rendezVousAdministrateurs;
public Administrateurs() {}
public Administrateurs(
String nom_utilisateur,
Long id_utilisateur,
String prenom_utilisateur,
String mail_principal,
String mail_secondaire,
String numero_telephone) {
super(
nom_utilisateur,
id_utilisateur,
prenom_utilisateur,
mail_principal,
mail_secondaire,
numero_telephone);
}
}

View File

@ -1,46 +0,0 @@
package enseirb.myinpulse.postgres_db.model;
import jakarta.persistence.*;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
@Entity
@Table(name = "comptes_rendus")
public class ComptesRendus {
@Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id_compte_rendu;
private String contenu_compte_rendu;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id_rdv")
private RendezVous rendezVousComptesRendus;
public ComptesRendus() {}
public ComptesRendus(Long id_compte_rendu, String contenu_compte_rendu) {
this.id_compte_rendu = id_compte_rendu;
this.contenu_compte_rendu = contenu_compte_rendu;
}
public Long getId_compte_rendu() {
return id_compte_rendu;
}
public void setId_compte_rendu(Long id_compte_rendu) {
this.id_compte_rendu = id_compte_rendu;
}
public String getContenu_compte_rendu() {
return contenu_compte_rendu;
}
public void setContenu_compte_rendu(String contenu_compte_rendu) {
this.contenu_compte_rendu = contenu_compte_rendu;
}
}

View File

@ -1,80 +0,0 @@
package enseirb.myinpulse.postgres_db.model;
import jakarta.persistence.*;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
@Entity
@Table(name = "entrepreneurs")
@PrimaryKeyJoinColumn(name = "id_entrepreneur", referencedColumnName = "id_utilisateur")
public class Entrepreneurs extends Utilisateurs {
@Column(length = 255)
private String ecole;
@Column(length = 255)
private String filiere;
private boolean status_snee;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id_projet_participation", referencedColumnName = "id_projet")
private Projets projetsParticipation;
// @Column(insertable=false, updatable=false)
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id_projet_propose", referencedColumnName = "id_projet")
private Projets projetsPropose;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id_rdv")
private RendezVous rendezVousEntrepreneurs;
public Entrepreneurs() {}
public Entrepreneurs(
String nom_utilisateur,
Long id_utilisateur,
String prenom_utilisateur,
String mail_principal,
String mail_secondaire,
String numero_telephone,
String ecole,
boolean status_snee,
String filiere) {
super(
nom_utilisateur,
id_utilisateur,
prenom_utilisateur,
mail_principal,
mail_secondaire,
numero_telephone);
this.ecole = ecole;
this.status_snee = status_snee;
this.filiere = filiere;
}
public String getEcole() {
return ecole;
}
public void setEcole(String ecole) {
this.ecole = ecole;
}
public String getFiliere() {
return filiere;
}
public void setFiliere(String filiere) {
this.filiere = filiere;
}
public boolean isStatus_snee() {
return status_snee;
}
public void setStatus_snee(boolean status_snee) {
this.status_snee = status_snee;
}
}

View File

@ -1,98 +0,0 @@
package enseirb.myinpulse.postgres_db.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "projets")
public class Projets {
@Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id_projet;
@Column(length = 255)
private String nom_projet;
private byte[] logo;
private LocalDate date_creation;
@Column(length = 255)
private String status_projet;
@OneToMany(mappedBy = "projetsAdministrateurs", fetch = FetchType.LAZY, orphanRemoval = true)
private List<Administrateurs> listAdministrateurs = new ArrayList<>();
@OneToMany(mappedBy = "projetsParticipation", fetch = FetchType.LAZY, orphanRemoval = true)
private List<Entrepreneurs> ListEntrepreneursParticipation = new ArrayList<>();
@OneToOne(mappedBy = "projetsPropose", fetch = FetchType.LAZY, orphanRemoval = true)
private Entrepreneurs entrepreneursPropose;
@OneToMany(mappedBy = "projetsSections", fetch = FetchType.LAZY, orphanRemoval = true)
private List<Sections> ListSections = new ArrayList<>();
// Hibernate expects entities to have a no-arg constructor,
// though it does not necessarily have to be public.
public Projets() {}
public Projets(
Long id_projet,
String nom_projet,
byte[] logo,
LocalDate date_creation,
String status_projet) {
this.id_projet = id_projet;
this.nom_projet = nom_projet;
this.logo = logo;
this.date_creation = date_creation;
this.status_projet = status_projet;
}
public Long getId_projet() {
return id_projet;
}
public void setId_projet(Long id_projet) {
this.id_projet = id_projet;
}
public String getNom_projet() {
return nom_projet;
}
public void setNom_projet(String nom_projet) {
this.nom_projet = nom_projet;
}
public byte[] getLogo() {
return logo;
}
public void setLogo(byte[] logo) {
this.logo = logo;
}
public LocalDate getDate_creation() {
return date_creation;
}
public void setDate_creation(LocalDate date_creation) {
this.date_creation = date_creation;
}
public String getStatus_projet() {
return status_projet;
}
public void setStatus_projet(String status_projet) {
this.status_projet = status_projet;
}
}

View File

@ -1,111 +0,0 @@
package enseirb.myinpulse.postgres_db.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "rendez_vous")
public class RendezVous {
@OneToMany(mappedBy = "rendezVousEntrepreneurs", fetch = FetchType.LAZY, orphanRemoval = true)
private final List<Entrepreneurs> ListEntrepreneurs = new ArrayList<>();
@OneToMany(mappedBy = "rendezVousAdministrateurs", fetch = FetchType.LAZY, orphanRemoval = true)
private final List<Administrateurs> ListAdministrateurs = new ArrayList<>();
@OneToMany(mappedBy = "rendezVousComptesRendus", fetch = FetchType.LAZY, orphanRemoval = true)
private final List<ComptesRendus> ListComptesRendus = new ArrayList<>();
@ManyToMany(
fetch = FetchType.LAZY,
cascade = {CascadeType.ALL})
@JoinTable(
name = "concerner",
joinColumns = @JoinColumn(name = "id_rdv"),
inverseJoinColumns = @JoinColumn(name = "id_section"))
List<Sections> ListSections = new ArrayList<>();
@Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id_rdv;
private LocalDate date_rdv;
private LocalTime heure_rdv;
private LocalTime duree_rdv;
@Column(length = 255)
private String lieu_rdv;
private String sujet_rdv;
public RendezVous() {}
public RendezVous(
Long id_rdv,
LocalDate date_rdv,
LocalTime heure_rdv,
LocalTime duree_rdv,
String lieu_rdv,
String sujet_rdv) {
this.id_rdv = id_rdv;
this.date_rdv = date_rdv;
this.heure_rdv = heure_rdv;
this.duree_rdv = duree_rdv;
this.lieu_rdv = lieu_rdv;
this.sujet_rdv = sujet_rdv;
}
public Long getId_rdv() {
return id_rdv;
}
public void setId_rdv(Long id_rdv) {
this.id_rdv = id_rdv;
}
public LocalDate getDate_rdv() {
return date_rdv;
}
public void setDate_rdv(LocalDate date_rdv) {
this.date_rdv = date_rdv;
}
public LocalTime getHeure_rdv() {
return heure_rdv;
}
public void setHeure_rdv(LocalTime heure_rdv) {
this.heure_rdv = heure_rdv;
}
public LocalTime getDuree_rdv() {
return duree_rdv;
}
public void setDuree_rdv(LocalTime duree_rdv) {
this.duree_rdv = duree_rdv;
}
public String getLieu_rdv() {
return lieu_rdv;
}
public void setLieu_rdv(String lieu_rdv) {
this.lieu_rdv = lieu_rdv;
}
public String getSujet_rdv() {
return sujet_rdv;
}
public void setSujet_rdv(String sujet_rdv) {
this.sujet_rdv = sujet_rdv;
}
}

View File

@ -1,81 +0,0 @@
package enseirb.myinpulse.postgres_db.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "sections")
public class Sections {
@Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id_section;
@Column(length = 255)
private String titre;
private String contenu_section;
private LocalDateTime date_modification;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id_projet")
private Projets projetsSections;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id_admnistrateur")
private Administrateurs administrateursSections;
@ManyToMany(mappedBy = "ListSections")
private List<RendezVous> rendezVous = new ArrayList<>();
public Sections() {}
public Sections(
Long id_section,
String titre,
String contenu_section,
LocalDateTime date_modification) {
this.id_section = id_section;
this.titre = titre;
this.contenu_section = contenu_section;
this.date_modification = date_modification;
}
public String getTitre() {
return titre;
}
public void setTitre(String titre) {
this.titre = titre;
}
public Long getId_section() {
return id_section;
}
public void setId_section(Long id_section) {
this.id_section = id_section;
}
public String getContenu_section() {
return contenu_section;
}
public void setContenu_section(String contenu_section) {
this.contenu_section = contenu_section;
}
public LocalDateTime getDate_modification() {
return date_modification;
}
public void setDate_modification(LocalDateTime date_modification) {
this.date_modification = date_modification;
}
}

View File

@ -1,95 +0,0 @@
package enseirb.myinpulse.postgres_db.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
@Entity
@Table(name = "utilisateurs")
@Inheritance(strategy = InheritanceType.JOINED)
public class Utilisateurs {
@Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id_utilisateur;
@Column(length = 255)
private String nom_utilisateur;
@Column(length = 255)
private String prenom_utilisateur;
@Column(length = 255)
private String mail_principal;
@Column(length = 255)
private String mail_secondaire;
@Column(length = 20)
private String numero_telephone;
public Utilisateurs() {}
public Utilisateurs(
String nom_utilisateur,
Long id_utilisateur,
String prenom_utilisateur,
String mail_principal,
String mail_secondaire,
String numero_telephone) {
this.nom_utilisateur = nom_utilisateur;
this.id_utilisateur = id_utilisateur;
this.prenom_utilisateur = prenom_utilisateur;
this.mail_principal = mail_principal;
this.mail_secondaire = mail_secondaire;
this.numero_telephone = numero_telephone;
}
public Long getId_utilisateur() {
return id_utilisateur;
}
public void setId_utilisateur(Long id_utilisateur) {
this.id_utilisateur = id_utilisateur;
}
public String getNom_utilisateur() {
return nom_utilisateur;
}
public void setNom_utilisateur(String nom_utilisateur) {
this.nom_utilisateur = nom_utilisateur;
}
public String getPrenom_utilisateur() {
return prenom_utilisateur;
}
public void setPrenom_utilisateur(String prenom_utilisateur) {
this.prenom_utilisateur = prenom_utilisateur;
}
public String getMail_principal() {
return mail_principal;
}
public void setMail_principal(String mail_principal) {
this.mail_principal = mail_principal;
}
public String getMail_secondaire() {
return mail_secondaire;
}
public void setMail_secondaire(String mail_secondaire) {
this.mail_secondaire = mail_secondaire;
}
public String getNumero_telephone() {
return numero_telephone;
}
public void setNumero_telephone(String numero_telephone) {
this.numero_telephone = numero_telephone;
}
}

View File

@ -1,14 +0,0 @@
package enseirb.myinpulse.postgres_db.repository;
import enseirb.myinpulse.postgres_db.model.Administrateurs;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource
public interface AdministrateursRepository extends JpaRepository<Administrateurs, Long> {
/* @Query("SELECT a from Administrateurs a")
Administrateurs findAllAdministrateurs(); */
}

View File

@ -1,9 +0,0 @@
package enseirb.myinpulse.postgres_db.repository;
import enseirb.myinpulse.postgres_db.model.ComptesRendus;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource
public interface ComptesRendusRepository extends JpaRepository<ComptesRendus, Long> {}

View File

@ -1,14 +0,0 @@
package enseirb.myinpulse.postgres_db.repository;
import enseirb.myinpulse.postgres_db.model.Entrepreneurs;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource
public interface EntrepreneursRepository extends JpaRepository<Entrepreneurs, Long> {
/* @Query("SELECT e from Entrepreneurs e")
Entrepreneurs findAllEntrepreneurs(); */
}

View File

@ -1,9 +0,0 @@
package enseirb.myinpulse.postgres_db.repository;
import enseirb.myinpulse.postgres_db.model.Projets;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource
public interface ProjetsRepository extends JpaRepository<Projets, Long> {}

View File

@ -1,9 +0,0 @@
package enseirb.myinpulse.postgres_db.repository;
import enseirb.myinpulse.postgres_db.model.RendezVous;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource
public interface RendezVousRepository extends JpaRepository<RendezVous, Long> {}

View File

@ -1,9 +0,0 @@
package enseirb.myinpulse.postgres_db.repository;
import enseirb.myinpulse.postgres_db.model.Sections;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource
public interface SectionsRepository extends JpaRepository<Sections, Long> {}

View File

@ -1,14 +0,0 @@
package enseirb.myinpulse.postgres_db.repository;
import enseirb.myinpulse.postgres_db.model.Utilisateurs;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource
public interface UtilisateursRepository extends JpaRepository<Utilisateurs, Long> {
/* @Query("SELECT u from Utilisateurs u")
Utilisateurs findAllUtilisateurs(); */
}

View File

@ -1,11 +1,5 @@
/*
* Source: https://github.com/ChristianHuff-DEV/secure-spring-rest-api-using-keycloak/blob/main/src/main/java/io/betweendata/RestApi/security/oauth2/KeycloakJwtRolesConverter.java
* edited by Pierre Tellier
*/
package enseirb.myinpulse.security;
import static java.util.stream.Collectors.toSet;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
@ -20,37 +14,44 @@ import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class KeycloakJwtRolesConverter implements Converter<Jwt, AbstractAuthenticationToken> {
/** Prefix used for realm level roles. */
public static final String PREFIX_REALM_ROLE = "ROLE_REALM_";
import static java.util.stream.Collectors.toSet;
/** Prefix used in combination with the resource (client) name for resource level roles. */
public class KeycloakJwtRolesConverter implements Converter<Jwt, AbstractAuthenticationToken> {
/**
* Prefix used for realm level roles.
*/
public static final String PREFIX_REALM_ROLE = "ROLE_REALM_";
/**
* Prefix used in combination with the resource (client) name for resource level roles.
*/
public static final String PREFIX_RESOURCE_ROLE = "ROLE_";
/** Name of the claim containing the realm level roles */
/**
* Name of the claim containing the realm level roles
*/
private static final String CLAIM_REALM_ACCESS = "realm_access";
/** Name of the claim containing the resources (clients) the user has access to. */
/**
* Name of the claim containing the resources (clients) the user has access to.
*/
private static final String CLAIM_RESOURCE_ACCESS = "resource_access";
/** Name of the claim containing roles. (Applicable to realm and resource level.) */
/**
* Name of the claim containing roles. (Applicable to realm and resource level.)
*/
private static final String CLAIM_ROLES = "roles";
@Override
public AbstractAuthenticationToken convert(Jwt source) {
return new JwtAuthenticationToken(
source,
Stream.concat(
new JwtGrantedAuthoritiesConverter().convert(source).stream(),
tokenRolesExtractor(source).stream())
.collect(toSet()));
public AbstractAuthenticationToken convert(Jwt source)
{
return new JwtAuthenticationToken(source, Stream.concat(new JwtGrantedAuthoritiesConverter().convert(source)
.stream(), TEMPORARNAME(source).stream())
.collect(toSet()));
}
/**
* Extracts the realm and resource level roles from a JWT token distinguishing between them
* using prefixes.
* Extracts the realm and resource level roles from a JWT token distinguishing between them using prefixes.
*/
public Collection<GrantedAuthority> tokenRolesExtractor(Jwt jwt) {
public Collection<GrantedAuthority> TEMPORARNAME(Jwt jwt) {
// Collection that will hold the extracted roles
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
@ -65,43 +66,33 @@ public class KeycloakJwtRolesConverter implements Converter<Jwt, AbstractAuthent
// Check if any roles are present
if (roles != null && !roles.isEmpty()) {
// Iterate of the roles and add them to the granted authorities
Collection<GrantedAuthority> realmRoles =
roles.stream()
// Prefix all realm roles with "ROLE_realm_"
.map(role -> new SimpleGrantedAuthority(PREFIX_REALM_ROLE + role))
.collect(Collectors.toList());
Collection<GrantedAuthority> realmRoles = roles.stream()
// Prefix all realm roles with "ROLE_realm_"
.map(role -> new SimpleGrantedAuthority(PREFIX_REALM_ROLE + role))
.collect(Collectors.toList());
grantedAuthorities.addAll(realmRoles);
}
}
// Resource (client) roles
// A user might have access to multiple resources all containing their own roles. Therefore,
// it is a map of
// A user might have access to multiple resources all containing their own roles. Therefore, it is a map of
// resource each possibly containing a "roles" property.
Map<String, Map<String, Collection<String>>> resourceAccess =
jwt.getClaim(CLAIM_RESOURCE_ACCESS);
Map<String, Map<String, Collection<String>>> resourceAccess = jwt.getClaim(CLAIM_RESOURCE_ACCESS);
// Check if resources are assigned
if (resourceAccess != null && !resourceAccess.isEmpty()) {
// Iterate of all the resources
resourceAccess.forEach(
(resource, resourceClaims) -> {
// Iterate of the "roles" claim inside the resource claims
resourceClaims
.get(CLAIM_ROLES)
.forEach(
// Add the role to the granted authority prefixed with ROLE_
// and the name of the resource
role ->
grantedAuthorities.add(
new SimpleGrantedAuthority(
PREFIX_RESOURCE_ROLE
+ resource
+ "_"
+ role)));
});
resourceAccess.forEach((resource, resourceClaims) -> {
// Iterate of the "roles" claim inside the resource claims
resourceClaims.get(CLAIM_ROLES).forEach(
// Add the role to the granted authority prefixed with ROLE_ and the name of the resource
role -> grantedAuthorities.add(new SimpleGrantedAuthority(PREFIX_RESOURCE_ROLE + resource + "_" + role))
);
});
}
return grantedAuthorities;
}
}

View File

@ -1,135 +0,0 @@
package enseirb.myinpulse.utils.keycloak;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import enseirb.myinpulse.exceptions.UserNotFoundException;
import enseirb.myinpulse.utils.keycloak.datatypes.RoleRepresentation;
import enseirb.myinpulse.utils.keycloak.datatypes.UserRepresentation;
import org.springframework.web.client.RestClient;
import javax.management.relation.RoleNotFoundException;
public class KeycloakApi {
static final String keycloakUrl;
static final String realmName;
static {
if (System.getenv("VITE_KEYCLOAK_URL") == null) {
System.exit(-1);
}
keycloakUrl = System.getenv("VITE_KEYCLOAK_URL");
}
static {
if (System.getenv("VITE_KEYCLOAK_REALM") == null) {
System.exit(-1);
}
realmName = System.getenv("VITE_KEYCLOAK_REALM");
}
/**
* Uses Keycloak API to retrieve a role representation of a role by its name
*
* @param roleName name of the role
* @param bearer authorization header used by the client to authenticate to keycloak
*/
public static RoleRepresentation getRoleRepresentationByName(String roleName, String bearer)
throws RoleNotFoundException {
RoleRepresentation[] response =
RestClient.builder()
.baseUrl(keycloakUrl)
.defaultHeader("Authorization", bearer)
.build()
.get()
.uri("/admin/realms/{realmName}/roles/{roleName}", realmName, roleName)
.retrieve()
.body(RoleRepresentation[].class);
if (response == null || response.length == 0) {
throw new RoleNotFoundException("Role not found");
}
return response[0];
}
/**
* Use keycloak API to to retreive a userID via his name or email.
*
* @param username username or mail of the user
* @param bearer bearer of the user, allowing access to database
* @return the userid, as a String
* @throws UserNotFoundException
*/
public static String getUserIdByName(String username, String bearer)
throws UserNotFoundException {
UserRepresentation[] response =
RestClient.builder()
.baseUrl(keycloakUrl)
.defaultHeader("Authorization", bearer)
.build()
.get()
.uri(
"/admin/realms/{realmName}/users?username={username}",
realmName,
username)
.retrieve()
.body(UserRepresentation[].class);
if (response == null || response.length == 0) {
throw new UserNotFoundException("User not found");
}
return response[0].id;
}
/**
* TODO: check for error
*
* <p>Set a keycloak role to a keycloak user.
*
* <p>Usual roles should be `MyINPulse-admin` and `MyINPulse-entrepreneur`
*
* @param username
* @param roleName
* @param bearer
* @throws RoleNotFoundException
* @throws UserNotFoundException
*/
public static void setRoleToUser(String username, String roleName, String bearer)
throws RoleNotFoundException, UserNotFoundException {
RoleRepresentation roleRepresentation = getRoleRepresentationByName(roleName, bearer);
String userId = getUserIdByName(username, bearer);
RestClient.builder()
.baseUrl(keycloakUrl)
.defaultHeader("Authorization", bearer)
.build()
.post()
.uri(
"/admin/realms/${realmName}/users/${userId}/role-mappings/realm",
realmName,
userId)
.body(roleRepresentation)
.contentType(APPLICATION_JSON)
.retrieve();
}
/**
* Delete a user from Keycloak database. TODO: check the bearer permission.
*
* @param username
* @param bearer
* @throws UserNotFoundException
*/
public static void deleteUser(String username, String bearer) throws UserNotFoundException {
String userId = getUserIdByName(username, bearer);
RestClient.builder()
.baseUrl(keycloakUrl)
.defaultHeader("Authorization", bearer)
.build()
.delete()
.uri("/admin/realms/${realmName}/users/${userId}", realmName, userId)
.retrieve();
}
}

View File

@ -1,7 +0,0 @@
package enseirb.myinpulse.utils.keycloak.datatypes;
public class RoleRepresentation {
public String id;
public String name;
public String description;
}

View File

@ -1,6 +0,0 @@
package enseirb.myinpulse.utils.keycloak.datatypes;
public class UserRepresentation {
public String id;
public String name;
}

View File

@ -2,7 +2,3 @@ spring.application.name=myinpulse
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:7080/realms/test/protocol/openid-connect/certs
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/test
logging.level.org.springframework.security=DEBUG
spring.datasource.url=jdbc:postgresql://${DATABASE_URL}/${BACKEND_DB}
spring.datasource.username=${BACKEND_USER}
spring.datasource.password=${BACKEND_PASSWORD}
spring.jpa.hibernate.ddl-auto=update

View File

@ -1,68 +0,0 @@
TRUNCATE projets, utilisateurs, entrepreneurs, sections, rendez_vous, comptes_rendus CASCADE;
INSERT INTO projets (nom_projet, logo, date_creation, status_projet)
VALUES ('Eau du robinet', decode('013d7d16d7ad4fefb61bd95b765c8ceb', 'hex'), TO_DATE('01-OCT-2023', 'DD-MON-YYYY'),
'En cours'),
('Air oxygéné', decode('150647a0984e8f228cd14b54', 'hex'), TO_DATE('04-APR-2024', 'DD-MON-YYYY'), 'En cours'),
('Débat concours', decode('022024abd5486e245c145dda65116f', 'hex'), TO_DATE('22-NOV-2023', 'DD-MON-YYYY'),
'Suspendu'),
('HDeirbMI', decode('ab548d6c1d595a2975e6476f544d14c55a', 'hex'), TO_DATE('07-DEC-2024', 'DD-MON-YYYY'),
'Lancement');
INSERT INTO utilisateurs (nom_utilisateur, prenom_utilisateur, mail_principal, mail_secondaire, numero_telephone) VALUES
('Dupont', 'Dupond', 'super@mail.fr', 'super2@mail.fr', '06 45 72 45 98'),
('Martin', 'Matin', 'genial@mail.fr', 'genial2@mail.fr', '06 52 14 58 73'),
('Charvet', 'Lautre', 'mieux@tmail.fr', 'mieux2@tmail.fr', '07 49 82 16 35'),
('Leguez', 'Theo', 'bof@mesmails.fr', 'bof2@mesmails.fr', '+33 6 78 14 25 29'),
('Kia', 'Bi', 'special@mail.fr', 'special2@mail.fr', '07 65 31 38 95'),
('Ducaillou', 'Pierre', 'maildefou@xyz.fr', 'maildefou2@xyz.fr', '06 54 78 12 62');
INSERT INTO entrepreneurs (ecole, filiere, status_snee, id_entrepreneur) VALUES
('ENSEIRB-MATMECA', 'INFO', TRUE, 1),
('ENSC', 'Cognitique', TRUE, 2),
('ENSEIRB-MATMECA', 'MATMECA', FALSE, 3),
('SupOptique', 'Classique', TRUE, 4),
('ENSEGID', 'Géoscience', FALSE, 5),
('ENSMAC', 'Matériaux composites - Mécanique', FALSE, 6);
INSERT INTO sections (titre, contenu_section, date_modification) VALUES
('Problème', 'les problèmes...', TO_TIMESTAMP('15-JAN-2025 09:30:20', 'DD-MON-YYYY, HH24:MI:SS')),
('Segment de client', 'Le segment AB passant le client n°8 est de longueur 32mm.
Le segment BC a quant à lui un longueur de 28mm. Quelle la longueur du segment AC ?', TO_TIMESTAMP('12-OCT-2022 17:47:38', 'DD-MON-YYYY, HH24:MI:SS')),
('Proposition de valeur unique', '''Son prix est de 2594€'' ''Ah oui c''est unique en effet', TO_TIMESTAMP('25-MAY-2024 11:12:04', 'DD-MON-YYYY, HH24:MI:SS')),
('Solution', 'Un problème ? Une solution', TO_TIMESTAMP('08-FEB-2024 10:17:53', 'DD-MON-YYYY, HH24:MI:SS')),
('Canaux', 'Ici nous avons la Seine, là-bas le Rhin, oh et plus loin le canal de Suez', TO_TIMESTAMP('19-JUL-2023 19:22:45', 'DD-MON-YYYY, HH24:MI:SS')),
('Sources de revenus', 'Y''en n''a pas on est pas payé. Enfin y''a du café quoi', TO_TIMESTAMP('12-JAN-2025 11:40:26', 'DD-MON-YYYY, HH24:MI:SS')),
('Structure des coûts', '''Ah oui là ça va faire au moins 1000€ par mois'', Eirbware', TO_TIMESTAMP('06-FEB-2025 13:04:06', 'DD-MON-YYYY, HH24:MI:SS')),
('Indicateurs clés', 'On apprend les clés comme des badges, ça se fait', TO_TIMESTAMP('05-FEB-2025 12:42:38', 'DD-MON-YYYY, HH24:MI:SS')),
('Avantages concurrentiel', 'On est meilleur', TO_TIMESTAMP('23-APR-2024 16:24:02', 'DD-MON-YYYY, HH24:MI:SS'));
INSERT INTO rendez_vous (date_rdv, heure_rdv, duree_rdv, lieu_rdv, sujet_rdv) VALUES
(TO_DATE('24-DEC-2023', 'DD-MON-YYYY'), '00:00:00', '00:37:53', 'À la maison', 'Ouvrir les cadeaux'),
(TO_DATE('15-AUG-2024', 'DD-MON-YYYY'), '22:35:00', '00:12:36', 'Sur les quais ou dans un champ probablement', 'BOUM BOUM les feux d''artifices (on fête quoi déjà ?)'),
(TO_DATE('28-FEB-2023', 'DD-MON-YYYY'), '14:20:00', '00:20:00', 'Salle TD 15', 'Ah mince c''est pas une année bissextile !'),
(TO_DATE('23-JAN-2024', 'DD-MON-YYYY'), '12:56:27', '11:03:33', 'Là où le vent nous porte', 'Journée la plus importante de l''année'),
(TO_DATE('25-AUG-2025', 'DD-MON-YYYY'), '00:09:00', '01:00:00', 'Euh c''est par où l''amphi 56 ?', 'Rentrée scolaire (il fait trop froid c''est quoi ça on est en août)');
INSERT INTO comptes_rendus (contenu_compte_rendu) VALUES
('Ah oui ça c''est super, ah ouais j''aime bien, bien vu de penser à ça'),
('Bonne réunion'),
('Ouais, j''ai rien compris mais niquel on fait comme vous avez dit'),
('Non non ça va pas du tout ce que tu me proposes, faut tout refaire'),
('Réponse de la DSI : non'),
('Trop dommage qu''Apple ait sorti leur logiciel avant nous, on avait la même idée et tout on aurait tellement pu leur faire de la concurrence');

View File

@ -1 +0,0 @@
DROP TABLE IF EXISTS administrateurs, projets, utilisateurs, entrepreneurs, sections, rendez_vous, comptes_rendus, concerner CASCADE;

View File

@ -1,134 +0,0 @@
DROP TABLE IF EXISTS projets CASCADE;
DROP TABLE IF EXISTS utilisateurs CASCADE;
DROP TABLE IF EXISTS entrepreneurs CASCADE;
DROP TABLE IF EXISTS administrateurs CASCADE;
DROP TABLE IF EXISTS sections CASCADE;
DROP TABLE IF EXISTS rendez_vous CASCADE;
DROP TABLE IF EXISTS comptes_rendus CASCADE;
DROP TABLE IF EXISTS concerner CASCADE;
DROP TABLE IF EXISTS formes CASCADE;
CREATE TABLE projets
(
id_projet SERIAL NOT NULL,
nom_projet VARCHAR(255),
logo BYTEA,
date_creation DATE,
status_projet VARCHAR(255),
CONSTRAINT pk_projet PRIMARY KEY (id_projet)
);
CREATE TABLE utilisateurs
(
id_utilisateur SERIAL NOT NULL,
nom_utilisateur VARCHAR(255) ,
prenom_utilisateur VARCHAR(255) ,
mail_principal VARCHAR(255) ,
mail_secondaire VARCHAR(255) ,
numero_telephone VARCHAR(20) ,
CONSTRAINT pk_utilisateur PRIMARY KEY (id_utilisateur) );
CREATE TABLE entrepreneurs
(
id_entrepreneur SERIAL REFERENCES utilisateurs (id_utilisateur),
ecole VARCHAR(255),
filiere VARCHAR(255),
status_snee BOOLEAN,
CONSTRAINT pk_entrepreneur PRIMARY KEY (id_entrepreneur)
);
CREATE TABLE administrateurs
(
id_administrateur SERIAL REFERENCES utilisateurs (id_utilisateur),
CONSTRAINT pk_administrateur PRIMARY KEY (id_administrateur)
);
CREATE TABLE sections
(
id_section SERIAL NOT NULL,
titre VARCHAR(255),
contenu_section TEXT,
date_modification TIMESTAMP,
CONSTRAINT pk_section PRIMARY KEY (id_section)
);
CREATE TABLE rendez_vous
(
id_rdv SERIAL NOT NULL,
date_rdv DATE,
heure_rdv TIME,
duree_rdv TIME,
lieu_rdv VARCHAR(255),
sujet_rdv TEXT,
CONSTRAINT pk_rdv PRIMARY KEY (id_rdv)
);
CREATE TABLE comptes_rendus
(
id_compte_rendu SERIAL NOT NULL,
contenu_compte_rendu TEXT,
CONSTRAINT pk_compte_rendu PRIMARY KEY (id_compte_rendu)
);
CREATE TABLE concerner
(
id_section SERIAL REFERENCES sections (id_section),
id_rdv SERIAL REFERENCES sections (id_rdv),
CONSTRAINT pk_concerner PRIMARY KEY (id_section, id_rdv)
);
ALTER TABLE projets
ADD CONSTRAINT fk1_projet FOREIGN KEY (id_administrateur)
REFERENCES administrateurs (id_administrateur)
ON DELETE CASCADE;
ALTER TABLE projets
ADD CONSTRAINT fk2_projet FOREIGN KEY (id_entrepreneur_participation)
REFERENCES entrepreneurs (id_entrepreneur)
ON DELETE CASCADE;
ALTER TABLE entrepreneurs
ADD CONSTRAINT fk1_entrepreneur FOREIGN KEY (id_projet_propose)
REFERENCES projets (id_projet)
ON DELETE CASCADE;
ALTER TABLE sections
ADD CONSTRAINT fk1_section FOREIGN KEY (id_projet)
REFERENCES projets (id_projet)
ON DELETE CASCADE;
ALTER TABLE sections
ADD CONSTRAINT fk2_section FOREIGN KEY (id_administrateur)
REFERENCES administrateurs (id_administrateur)
ON DELETE CASCADE;
ALTER TABLE rendez-vous
ADD CONSTRAINT fk1_rdv FOREIGN KEY (id_entrepreneur)
REFERENCES entrepreneurs (id_entrepreneur)
ON
DELETE
CASCADE;
ALTER TABLE rendez-vous
ADD CONSTRAINT fk2_rdv FOREIGN KEY (id_administrateur)
REFERENCES administrateurs (id_administrateur)
ON
DELETE
CASCADE;
ALTER TABLE comptes-rendus
ADD CONSTRAINT fk1_compte_rendu FOREIGN KEY (id_rdv)
REFERENCES rendez_vous (id_rdv)
ON
DELETE
CASCADE;

View File

@ -6,6 +6,8 @@ import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class MyinpulseApplicationTests {
@Test
void contextLoads() {}
@Test
void contextLoads() {
}
}

View File

@ -1,14 +1,15 @@
services:
postgres:
env_file: .env
build:
context: postgres/
dockerfile: Dockerfile
image: postgres:latest
container_name: MyINPulse-DB
ports:
- 5433:5432
#ports:
# - 5432:5432
volumes:
- ./postgres/data:/var/lib/postgresql/data
- ./postgres:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
keycloak:
container_name: MyINPulse-keycloak

View File

@ -1,22 +0,0 @@
POSTGRES_DB=postgres_db
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres_db_user_password
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=admin
KEYCLOAK_HOSTNAME=localhost
KEYCLOAK_DB=keycloak_db
KEYCLOAK_USER=keycloak_db_user
KEYCLOAK_PASSWORD=keycloak_db_user_password
BACKEND_DB=backend_db
BACKEND_USER=backend_db_user
BACKEND_PASSWORD=backend_db_user_password
DATABASE_URL=localhost:5433
VITE_KEYCLOAK_URL=http://localhost:7080
VITE_KEYCLOAK_CLIENT_ID=myinpulse
VITE_KEYCLOAK_REALM=test
VITE_APP_URL=http://localhost:8080
VITE_BACKEND_URL=http://localhost:8081/

5
config/backdev.front.env Normal file
View File

@ -0,0 +1,5 @@
VITE_KEYCLOAK_URL=http://localhost:7080
VITE_KEYCLOAK_CLIENT_ID=myinpulse
VITE_KEYCLOAK_REALM=test
VITE_APP_URL=http://localhost:8080
VITE_BACKEND_URL=http://localhost:8081/

6
config/backdev.main.env Normal file
View File

@ -0,0 +1,6 @@
POSTGRES_DB=keycloak_db
POSTGRES_USER=keycloak_db_user
POSTGRES_PASSWORD=keycloak_db_user_password
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=admin
KEYCLOAK_HOSTNAME=localhost

View File

@ -1,52 +0,0 @@
services:
postgres:
env_file: .env
build:
context: postgres/
dockerfile: Dockerfile
container_name: MyINPulse-DB
ports:
- 5433:5432
volumes:
- ./postgres/data:/var/lib/postgresql/data
keycloak:
container_name: MyINPulse-keycloak
build:
context: ./keycloak
dockerfile: Dockerfile
args:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres/${POSTGRES_DB}
KC_DB_USERNAME: ${POSTGRES_USER}
KC_DB_PASSWORD: ${POSTGRES_PASSWORD}
environment:
KC_HOSTNAME_PORT: 7080
KC_HOSTNAME_STRICT_BACKCHANNEL: "true"
KC_BOOTSTRAP_ADMIN_USERNAME: ${KEYCLOAK_ADMIN}
KC_BOOTSTRAP_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
KC_LOG_LEVEL: info
command: ["start-dev", "--http-port", "7080", "--https-port", "7443", "--hostname", "${KEYCLOAK_HOSTNAME}"]
ports:
- "7080:7080"
- "7443:7443"
depends_on:
- postgres
#front:
# build:
# context: ./front/
# dockerfile: Dockerfile
# container_name: MyINPulse-front
# ports:
# - "8080:80"
#back:
# build:
# context: ./MyINPulse-back/
# dockerfile: Dockerfile
# container_name: MyINPulse-back
# ports:
# - "8081:8080"

View File

@ -1,22 +0,0 @@
POSTGRES_DB=postgres_db
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres_db_user_password
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=admin
KEYCLOAK_HOSTNAME=localhost
KEYCLOAK_DB=keycloak_db
KEYCLOAK_USER=keycloak_db_user
KEYCLOAK_PASSWORD=keycloak_db_user_password
BACKEND_DB=backend_db
BACKEND_USER=backend_db_user
BACKEND_PASSWORD=backend_db_user_password
DATABASE_URL=localhost:5433
VITE_KEYCLOAK_URL=http://localhost:7080
VITE_KEYCLOAK_CLIENT_ID=myinpulse-dev
VITE_KEYCLOAK_REALM=test
VITE_APP_URL=http://localhost:5173
VITE_BACKEND_URL=http://localhost:8081/

View File

@ -1,15 +1,15 @@
services:
postgres:
env_file: .env
build:
context: postgres/
dockerfile: Dockerfile
image: postgres:latest
container_name: MyINPulse-DB
#ports:
# - 5432:5432
volumes:
- ./postgres/data:/var/lib/postgresql/data
- ./postgres:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
keycloak:
container_name: MyINPulse-keycloak

View File

@ -1,22 +0,0 @@
POSTGRES_DB=postgres_db
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres_db_user_password
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=admin
KEYCLOAK_HOSTNAME=localhost
KEYCLOAK_DB=keycloak_db
KEYCLOAK_USER=keycloak_db_user
KEYCLOAK_PASSWORD=keycloak_db_user_password
BACKEND_DB=backend_db
BACKEND_USER=backend_db_user
BACKEND_PASSWORD=backend_db_user_password
DATABASE_URL=MyINPulse-DB
VITE_KEYCLOAK_URL=http://localhost:7080
VITE_KEYCLOAK_CLIENT_ID=myinpulse-dev
VITE_KEYCLOAK_REALM=test
VITE_APP_URL=http://localhost:5173
VITE_BACKEND_URL=http://localhost:8081/

View File

@ -0,0 +1,5 @@
VITE_KEYCLOAK_URL=http://localhost:7080
VITE_KEYCLOAK_CLIENT_ID=myinpulse-dev
VITE_KEYCLOAK_REALM=test
VITE_APP_URL=http://localhost:5173
VITE_BACKEND_URL=http://localhost:8081/

6
config/frontdev.main.env Normal file
View File

@ -0,0 +1,6 @@
POSTGRES_DB=keycloak_db
POSTGRES_USER=keycloak_db_user
POSTGRES_PASSWORD=keycloak_db_user_password
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=admin
KEYCLOAK_HOSTNAME=localhost

View File

@ -1,14 +1,11 @@
services:
postgres:
env_file: .env
build:
context: postgres/
dockerfile: Dockerfile
image: postgres:latest
container_name: MyINPulse-DB
#ports:
# - 5432:5432
volumes:
- ./postgres/data:/var/lib/postgresql/data
- ./postgres:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
@ -30,10 +27,10 @@ services:
KC_BOOTSTRAP_ADMIN_USERNAME: ${KEYCLOAK_ADMIN}
KC_BOOTSTRAP_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
KC_LOG_LEVEL: info
command: ["start-dev", "--http-port", "7080", "--https-port", "7443", "--hostname", "${KEYCLOAK_HOSTNAME}"] # TODO: remove start-dev
#ports:
# - "7080:7080"
# - "7443:7443"
command: ["start-dev", "--http-port", "7080", "--https-port", "7443", "--hostname", "${KEYCLOAK_HOSTNAME}"]
ports:
- "7080:7080"
- "7443:7443"
depends_on:
- postgres
@ -50,6 +47,6 @@ services:
context: ./MyINPulse-back/
dockerfile: Dockerfile
container_name: MyINPulse-back
#ports:
# - "8081:8080"
ports:
- "8081:8080"

View File

@ -1,22 +0,0 @@
POSTGRES_DB=postgres_db
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres_db_user_password
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=admin
KEYCLOAK_HOSTNAME=0549cd63f912d5dc9b31278d6f.eirb.fr
KEYCLOAK_DB=keycloak_db
KEYCLOAK_USER=keycloak_db_user
KEYCLOAK_PASSWORD=keycloak_db_user_password
BACKEND_DB=backend_db
BACKEND_USER=backend_db_user
BACKEND_PASSWORD=backend_db_user_password
DATABASE_URL=MyINPulse-DB
VITE_KEYCLOAK_URL=https://0549cd63f912d5dc9b31278d6f.eirb.fr
VITE_KEYCLOAK_CLIENT_ID=myinpulse-eirb
VITE_KEYCLOAK_REALM=test
VITE_APP_URL=https://0549cd63f912d5dc9b31278d6f.piair.dev
VITE_BACKEND_URL=http://TODO/

5
config/prod.front.env Normal file
View File

@ -0,0 +1,5 @@
VITE_KEYCLOAK_URL=https://0549cd63f912d5dc9b31278d6f.eirb.fr
VITE_KEYCLOAK_CLIENT_ID=myinpulse-eirb
VITE_KEYCLOAK_REALM=test
VITE_APP_URL=https://0549cd63f912d5dc9b31278d6f.piair.dev
VITE_BACKEND_URL=http://TODO/

6
config/prod.main.env Normal file
View File

@ -0,0 +1,6 @@
POSTGRES_DB=keycloak_db
POSTGRES_USER=keycloak_db_user
POSTGRES_PASSWORD=keycloak_db_user_password
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=admin
KEYCLOAK_HOSTNAME=0549cd63f912d5dc9b31278d6f.eirb.fr

0
front/Dockerfile Normal file → Executable file
View File

View File

@ -0,0 +1,63 @@
{
"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

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

View File

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

@ -0,0 +1,75 @@
<template>
<div id="agenda">
<h3>Rendez-vous</h3>
<table>
<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,
}
const props = defineProps<{
projectRDV: rendezVous[]
}>();
</script>
<style scoped>
#agenda {
padding: 20px;
}
/* Table Styling */
table {
width: 100%;
border-collapse: collapse;
font-family: Arial, sans-serif;
text-align: left;
margin-top: 20px;
border: 1px solid #ccc;
}
/* Header Row (if exists) */
th {
background-color: #f4f4f4;
padding: 12px;
font-weight: bold;
border: 1px solid #ccc;
}
/* 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,21 +1,130 @@
<template>
<div class="project">
<div @click="goToLink" class="project">
<div class="project-header">
<h2>{{ projectName }}</h2>
<h2 >{{ projectName }}</h2>
<div class="project-buttons">
<button class="contact-btn">Contact</button>
</div>
</div>
<div class="project-body">
<ul>
<li v-for="(name, index) in listName" :key="index">{{ name }}</li>
</ul>
</div>
</div>
</template>
<script lang="ts">
import type { PropType } from "vue";
export default {
name: "ProjectComponent",
props: {
projectName: {
type: Object as PropType<string>,
required: true,
},
},
<script setup lang="ts">
import { defineProps } from "vue";
import { useRouter } from 'vue-router'
const props = defineProps<{
projectName: string;
listName: string[];
projectLink: string;
}>();
const router = useRouter();
const goToLink = () => {
if (props.projectLink) {
router.push(props.projectLink);
}
};
</script>
<style scoped>
.project {
background: linear-gradient(to right, #f4f4f4, #ffffff);
border: 1px solid #ddd;
border-radius: 10px;
padding: 20px;
margin: 20px 0;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
font-family: Arial, sans-serif;
}
/* Header Styling */
.project-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.project-header h2 {
font-size: 20px;
color: #333;
margin: 0;
}
/* Button Container */
.project-buttons {
display: flex;
gap: 10px;
}
.info-btn {
background-color: #4CAF50;
color: #fff;
}
.info-btn:hover {
background-color: #45a049;
transform: scale(1.05);
}
.contact-btn {
background-color: #007BFF;
color: #fff;
}
.contact-btn:hover {
background-color: #0056b3;
transform: scale(1.05);
}
.project-body {
margin-top: 15px;
}
.project-body p {
font-size: 16px;
color: #555;
margin-bottom: 10px;
}
.project-body ul {
list-style-type: disc;
margin: 0;
padding-left: 20px;
}
.project-body ul li {
font-size: 14px;
color: #666;
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;
}
</style>

View File

@ -0,0 +1,328 @@
<template>
<div :class="['cell', { expanded }]" @click="handleClick">
<h3>{{ title_text }}</h3>
<div class="section-bloc" v-for="(desc, index) in currentDescriptions" :key="index">
<!-- Mode affichage -->
<template v-if="!isEditing[index]">
<div class="description">
<p>{{ desc }}</p>
</div>
<div class="button-container">
<button v-if="expanded" @click.stop="startEditing(index)" class="edit-button">Éditer</button>
</div>
</template>
<!-- Mode édition -->
<template v-else>
<textarea v-model="editedDescriptions[index]" class="edit-input"></textarea>
<div class="button-container">
<button @click.stop="saveEdit(index)" class="save-button">Enregistrer</button>
<button @click.stop="cancelEdit(index)" class="cancel-button">Annuler</button>
</div>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps } from "vue";
import axios from "axios";
const IS_MOCK_MODE = true;
const props = defineProps<{
projectId: number;
title: number;
title_text: string;
description: string;
}>();
const expanded = ref(false);
const currentDescriptions = ref<string[]>([]);
const editedDescriptions = ref<string[]>([]);
const isEditing = ref<boolean[]>([]);
/*
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 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 the damn front, Why are u still reading dumbass this is just some nonsense sentence XD" },
{ txt: "Ceci est une description 1 pour tester the damn front, Bruh are u still here?" },
{ txt: "Ceci est une description 1 pour tester the damn front, .-. BRUH" }
]);
}, 500); // Simule un délai réseau de 500ms
});
};
// 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 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);
}
};
// 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); // true pour activer le mock
} 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);
}
};
*/
// Fonction de mock pour l'enregistrement
const mockSaveEdit = async (index: number) => {
try {
const id = index + 1; // À adapter selon l'ID réel des données
console.log(`Mock save pour l'ID ${id} avec la description : ${editedDescriptions.value[index]}`);
// Simuler un délai d'enregistrement comme une requête réseau
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);
}
};
// Utilisation de `mockSaveEdit` au lieu de `saveEdit` dans `handleClick` ou tout autre endroit
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);
}
}
};
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: 20px;
font-weight: 500;
/*margin-bottom: 10px;*/
}
.cell p {
font-size: 14px;
color: #666;
}
.expanded {
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; /* Centre verticalement */
justify-content: center; /* Centre horizontalement */
text-align: center; /* Centre le texte à l'intérieur */
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%;
font-size: 16px;
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;
}
</style>

View File

@ -0,0 +1,120 @@
<template>
<header>
<img src="../icons/logo inpulse.png" alt="INPulse Logo">
<div class="header-buttons">
<div class="menu">
<button class="contact-button" @click="toggleDropdown">Contact</button>
<div class="contact-dropdown" v-bind:class="{ 'dropdown-visible': isDropdownOpen }">
<button @click="contactAll">Contact All</button>
<button v-for="(email, index) in entrepreneurEmails" :key="index">
{{ email }}
</button>
</div>
<button class="return-button">
<RouterLink to="/">Return to list project</RouterLink>
</button>
</div>
</div>
</header>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import axios from "axios";
const isDropdownOpen = ref(false);
const entrepreneurEmails = ref([]);
const toggleDropdown = () => {
isDropdownOpen.value = !isDropdownOpen.value;
console.log("Dropdown toggled:", isDropdownOpen.value); // for debug purposes
};
const fetchEntrepreneurs = async () => {
try {
const response = await axios.get("http://localhost:5000/entrepreneurs");
entrepreneurEmails.value = response.data.map((e: { email: string }) => e.email);
} catch (error) {
console.error("Erreur lors de la récupération des entrepreneurs:", error);
}
};
const contactAll = () => {
alert("Contacter tous les entrepreneurs : " + entrepreneurEmails.value.join(", "));
};
onMounted(fetchEntrepreneurs);
</script>
<style scoped>
@import "@/components/canvas/style-project.css";
header {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 10px;
}
.header-buttons {
display: flex;
align-items: flex-start;
}
.menu {
display: flex;
flex-direction: column;
gap: 10px;
}
.contact-button, .return-button {
background-color: #000;
color: white;
border: none;
padding: 10px;
cursor: pointer;
font-size: 14px;
text-align: center;
}
.return-button a {
color: white;
text-decoration: none;
}
.contact-dropdown {
display: none;
position: absolute;
background-color: white;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
border-radius: 8px;
padding: 10px;
margin-top: 5px;
z-index: 1000;
}
.contact-dropdown button {
display: block;
width: 100%;
padding: 5px;
text-align: left;
border: none;
background: none;
cursor: pointer;
}
.contact-dropdown button:hover {
background-color: #f0f0f0;
}
.dropdown-visible {
display: block;
}
header img {
width: 100px;
height: auto;
}
</style>

View File

@ -0,0 +1,59 @@
<template>
<div class="canvas">
<CanvasItem
v-for="(item, index) in items"
:key="index"
:title="item.title"
:title_text="item.title_text"
:description="item.description"
:projectId="item.projectId"
:class="item.class"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import CanvasItem from "@/components/canvas/CanvasItem.vue";
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" }
]);
</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: 10px;
padding: 10px;
max-width: 1200px;
margin: 20px auto;
background-color: #fff;
position: relative;
height: 80vh;
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; }
</style>

View File

@ -0,0 +1,156 @@
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 10px;
}
.row {
display: flex;
}
.cell {
flex: 1;
border: 1px solid #ddd;
padding: 10px;
text-align: center;
background-color: #f1f1f1;
}
.produit {
background-color: #f9e4e4;
}
.marche {
background-color: #e4f1f9;
}
.valeur {
background-color: #f9f4e4;
}
h3 {
margin: 0;
font-size: 18px;
color: #333;
}
p {
margin: 5px 0 0;
font-size: 14px;
}
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f9f9f9;
}
h1 img {
height: 80px;
margin: 40px;
text-align: center;
}
.row {
display: flex;
margin-bottom: 10px;
}
#ade {
max-width: 1200px;
margin: 20px auto;
padding: 20px;
text-align: center;
background-color: #e8f5e9;
border: 2px solid #4caf50;
border-radius: 10px;
}
#ade h3 {
color: #2e7d32;
}
#ade p {
margin: 10px 0;
font-size: 16px;
color: #333;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
background-color: #fff;
border-bottom: 2px solid #ddd;
}
header img {
height: 60px;
}
header .contact-menu {
position: relative;
}
.contact-button, .return {
padding: 10px 15px;
border: none;
border-radius: 4px;
background-color: #2196f3;
color: #fff;
cursor: pointer;
}
.contact-button:hover, .return:hover {
background-color: #1976d2;
}
/* Dropdown styling */
.contact-dropdown {
position: absolute;
right: 0;
top: 50px;
display: none;
flex-direction: column;
gap: 10px;
padding: 15px;
background-color: #fff;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
z-index: 10;
}
.contact-dropdown button {
padding: 8px 12px;
border: none;
border-radius: 4px;
background-color: #4caf50;
color: #fff;
cursor: pointer;
}
.contact-dropdown button:hover {
background-color: #388e3c;
}
.return {
background-color: #f44336;
}
.return:hover {
background-color: #d32f2f;
}
.header-buttons {
display: flex;
align-items: center;
gap: 15px;
}
a{
color: white;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -1,18 +0,0 @@
<script setup lang="ts">
import { addNewMessage } from "@/services/popupDisplayer.ts";
</script>
<template>
<button
@click="
addNewMessage(
'new error from another view',
Math.floor(Math.random() * 4)
)
"
>
Add an error
</button>
</template>
<style scoped></style>

View File

@ -26,6 +26,52 @@ keycloakService.CallInit(() => {
console.error(e);
createApp(App).mount("#app");
}
});
})
// 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 };

View File

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

View File

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

View File

@ -14,8 +14,7 @@ axiosInstance.interceptors.response.use(
async (error) => {
const originalRequest = error.config;
if (
((error.response && error.response.status === 401) ||
error.code == "ERR_NETWORK") &&
error.response.status === 401 &&
!originalRequest._retry &&
store.authenticated
) {

View File

@ -0,0 +1,215 @@
<template>
<div :class="['cell', { expanded }]" @click="handleClick">
<h3>{{ title_text }}</h3>
<!-- Mode affichage -->
<template v-if="!isEditing">
<p>{{ currentDescription }}</p>
<div class="button-container">
<button v-if="expanded" @click.stop="startEditing" class="edit-button">Éditer</button>
</div>
</template>
<!-- Mode édition -->
<template v-else>
<textarea v-model="editedDescription" class="edit-input"></textarea>
<div class="button-container">
<button @click.stop="saveEdit" class="save-button">Enregistrer</button>
<button @click.stop="cancelEdit" class="cancel-button">Annuler</button>
</div>
</template>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps } from "vue";
import axios from "axios";
const props = defineProps<{
projectId: number;
title: number;
title_text: string;
description: string;
}>();
const expanded = ref(false);
const isEditing = ref(false);
const currentDescription = ref(props.description);
const editedDescription = ref(props.description);
/*
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);
}
};
*/
const fetchData = async (projectId: number, title: number, date: string) => {
try {
const response = await axios.get(
`http://localhost:5000/shared/projects/lcsection/${projectId}/${title}/${date}`
);
if (response.data.length > 0) {
currentDescription.value = response.data[0].txt;
editedDescription.value = response.data[0].txt;
} else {
console.warn("Aucune donnée reçue.");
}
} catch (error) {
console.error("Erreur lors de la récupération des données :", error);
}
};
const handleClick = async () => {
if (!expanded.value) {
await fetchData(props.projectId, props.title, "NaN");
} else if (!isEditing.value) {
currentDescription.value = props.description;
editedDescription.value = props.description;
}
if (!isEditing.value) {
expanded.value = !expanded.value;
}
};
const startEditing = () => {
isEditing.value = true;
};
const saveEdit = async () => {
try {
const id = 1;
await axios.put(`http://localhost:5000/data/${id}`, {
canva_data: editedDescription.value
});
// Mettre à jour l'affichage local après la mise à jour réussie
currentDescription.value = editedDescription.value;
isEditing.value = false;
} catch (error) {
console.error("Erreur lors de la mise à jour des données :", error);
}
};
const cancelEdit = () => {
editedDescription.value = currentDescription.value;
isEditing.value = 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: 20px;
font-weight: 500;
/*margin-bottom: 10px;*/
}
.cell p {
font-size: 14px;
color: #666;
}
.expanded {
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);
}
.edit-input {
width: 80%;
height: 100px;
font-size: 16px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
margin-top: 10px;
}
.button-container {
position: absolute;
bottom: 40px;
right: 40px;
display: flex;
gap: 10px;
}
.edit-button, .save-button, .cancel-button {
padding: 10px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s ease;
font-size: 16px;
}
.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;
}
</style>

View File

@ -0,0 +1,68 @@
<template>
<Header />
<error-wrapper></error-wrapper>
<div id="container">
<div id="main">
<ProjectComp
v-for="(project, index) in projects"
:key="index"
:projectName="project.name"
:listName="project.members"
:projectLink="project.link"
/>
</div>
<Agenda :projectRDV="rendezVous" />
</div>
</template>
<script setup lang="ts">
import Header from '../components/HeaderComponent.vue';
import Agenda from "../components/Agenda.vue"
import ProjectComp from '../components/ProjectComponent.vue';
import { ref } from "vue";
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 rendezVous = ref([
{ projectName: "Projet Alpha", date: "2025-03-10", lieu: "P106" },
{ projectName: "Projet Beta", date: "2025-04-15", lieu: "Td10" },
]);
</script>
<style scoped>
#container {
margin: 0;
display: grid;
grid-template-columns: 3fr 1fr; /* Main body takes 3/4, agenda 1/4 */
height: 100vh; /* Full viewport height */
}
button {
padding: 10px 15px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
border-radius: 5px;
}
button:hover {
background-color: #0056b3;
}
</style>

View File

@ -0,0 +1,18 @@
<template>
<div>
<header>
<HeaderCanvas />
</header>
</div>
<div>
<h1>Page Canvas</h1>
<LeanCanvas />
</div>
</template>
<script setup lang="ts">
// @ts-ignore
import HeaderCanvas from "../components/canvas/HeaderCanvas.vue";
import LeanCanvas from '../components/canvas/LeanCanvas.vue';
</script>

View File

@ -15,11 +15,13 @@ import ErrorModal from "@/components/errorModal.vue";
</template>
<style scoped>
.error-wrapper {
position: absolute;
left: 70%;
//background-color: blue;
height: 100%;
width: 30%;
.error-wrapper{
position: absolute;
left: 70%;
/*background-color: blue;*/
height: 100%;
width: 30%;
}
</style>

View File

@ -1,9 +1,6 @@
<script lang="ts" setup>
import { store } from "../main.ts";
import { callApi } from "@/services/api.ts";
import { ref } from "vue";
const CustomRequest = ref("");
</script>
<template>
@ -58,14 +55,6 @@ const CustomRequest = ref("");
<td>res</td>
<td id="3"></td>
</tr>
<tr>
<td>
<input v-model="CustomRequest" placeholder="edit me" />
</td>
<td>
<button @click="callApi(CustomRequest)">call</button>
</td>
</tr>
</tbody>
</table>
</template>

View File

@ -1,5 +0,0 @@
FROM postgres:latest
# Custom initialization scripts
COPY ./create_user.sh /docker-entrypoint-initdb.d/10-create_user.sh
COPY ./create_db.sh /docker-entrypoint-initdb.d/20-create_db.sh

View File

@ -1,17 +0,0 @@
#!/bin/bash
set -e
POSTGRES="psql --username ${POSTGRES_USER}"
echo "Creating database: ${DB_NAME}"
$POSTGRES <<EOSQL
CREATE DATABASE ${BACKEND_DB} OWNER ${BACKEND_USER};
EOSQL
echo "Creating database: ${DB_NAME}"
$POSTGRES <<EOSQL
CREATE DATABASE ${KEYCLOAK_DB} OWNER ${KEYCLOAK_USER};
EOSQL

View File

@ -1,16 +0,0 @@
#!/bin/bash
set -e
POSTGRES="psql --username ${POSTGRES_USER}"
echo "Creating database role: [${BACKEND_USER}]"
$POSTGRES <<-EOSQL
CREATE USER ${BACKEND_USER} WITH CREATEDB PASSWORD '${BACKEND_PASSWORD}';
EOSQL
echo "Creating database role: ${KEYCLOAK_USER}"
$POSTGRES <<-EOSQL
CREATE USER ${KEYCLOAK_USER} WITH CREATEDB PASSWORD '${KEYCLOAK_PASSWORD}';
EOSQL