69 Commits

Author SHA1 Message Date
1cebebf1a5 feat: implemented most of the backend api for administrator
Some checks failed
Format / formatting (push) Failing after 6s
CI / build (push) Successful in 11s
2025-02-26 14:29:38 +01:00
d8bc7cc9b6 feat: added log4j. It's way better than System.stderr. 2025-02-26 14:28:31 +01:00
e66fa33577 feat: added a new database service
All checks were successful
Format / formatting (push) Successful in 7s
CI / build (push) Successful in 13s
2025-02-26 10:32:44 +01:00
27e70ee109 fix: naming issues with database and better data
All checks were successful
Format / formatting (push) Successful in 7s
CI / build (push) Successful in 11s
2025-02-24 17:19:18 +01:00
153501c8d4 Fix: merge
All checks were successful
Format / formatting (push) Successful in 7s
CI / build (push) Successful in 11s
2025-02-19 18:52:43 +01:00
40afde89b7 Fix: renamed all the tables, with repo and controller associated to them (might have missed some), and fix some key dependency issues 2025-02-19 18:41:37 +01:00
4698aa549f feat: frontend call now include the token and send the email from the token to backen servie
All checks were successful
Format / formatting (push) Successful in 6s
CI / build (push) Successful in 11s
2025-02-19 12:09:37 +01:00
04a73073c1 feat: switch to a service layer architecture
All checks were successful
Format / formatting (push) Successful in 6s
CI / build (push) Successful in 11s
2025-02-18 22:15:14 +01:00
11ab5e1dc4 feat: merged Keycloak / postgres
All checks were successful
Format / formatting (push) Successful in 7s
CI / build (push) Successful in 11s
2025-02-18 17:20:08 +01:00
820757c836 fix: corrected formatter error
All checks were successful
Format / formatting (push) Successful in 7s
CI / build (push) Successful in 11s
2025-02-18 16:59:19 +01:00
730aa5f450 Merge remote-tracking branch 'refs/remotes/origin/back-postgres' into back-postgres
Some checks failed
Format / formatting (push) Failing after 7s
CI / build (push) Successful in 11s
2025-02-18 16:56:26 +01:00
dda3e5fcfd fix: most likely fixed merge conflict 2025-02-18 16:55:08 +01:00
5e8e875a37 feat: added user deletion and custom api call in the frontend
All checks were successful
CI / build (push) Successful in 11s
2025-02-18 16:45:41 +01:00
86e7dc7c75 fix: removed the test file that was causing the linter to fail
All checks were successful
CI / build (push) Successful in 12s
2025-02-18 16:37:55 +01:00
6235fe7e68 feat: separated class definition
Some checks failed
CI / build (push) Failing after 8s
2025-02-18 12:07:07 +01:00
3cb12dab4f feat: created signature of all api functions
All checks were successful
Format / formatting (push) Successful in 8s
CI / build (push) Successful in 11s
2025-02-18 10:50:21 +01:00
0a9d83655f feat: query to database from and unaut endpoint
All checks were successful
Format / formatting (push) Successful in 9s
CI / build (push) Successful in 12s
2025-02-13 21:57:41 +01:00
fc73293122 fix: merge
All checks were successful
Format / formatting (push) Successful in 8s
CI / build (push) Successful in 13s
2025-02-12 19:03:39 +01:00
e26f8da662 fix: inserting data in db 2025-02-12 18:51:27 +01:00
b4c05f8c59 fix: formatting again
All checks were successful
Format / formatting (push) Successful in 8s
CI / build (push) Successful in 13s
2025-02-12 15:38:55 +01:00
ca282378ec fix: changed the formatting ?
Some checks failed
Format / formatting (push) Failing after 7s
CI / build (push) Successful in 13s
2025-02-12 15:34:16 +01:00
746fa97cf3 fix: applied formatter (on all import as well :)
Some checks failed
Format / formatting (push) Failing after 8s
CI / build (push) Successful in 13s
2025-02-12 15:29:12 +01:00
8b24456b48 feat: back to google as I can't make the other one work
Some checks failed
Format / formatting (push) Failing after 9s
CI / build (push) Successful in 13s
2025-02-12 15:14:48 +01:00
6befd10735 test: syntax validation
Some checks failed
Format / formatting (push) Failing after 1m24s
CI / build (push) Successful in 16s
2025-02-12 15:10:59 +01:00
db094a8d86 test: syntax validataion
Some checks failed
Format / formatting (push) Failing after 0s
CI / build (push) Successful in 12s
2025-02-12 15:09:42 +01:00
c739b4d26d test: syntax validataion
Some checks failed
Format / formatting (push) Failing after 3s
CI / build (push) Successful in 13s
2025-02-12 15:07:36 +01:00
dacb0dd179 fix: now uses another library
Some checks failed
CI / build (push) Successful in 52s
Format / formatting (push) Has been cancelled
2025-02-12 14:56:09 +01:00
b00c28a02a fix: now uses the correct version
Some checks failed
Format / formatting (push) Failing after 4s
CI / build (push) Successful in 12s
2025-02-12 14:54:27 +01:00
208cbbfa1d feat: now uses intellij
Some checks failed
Format / formatting (push) Failing after 3s
CI / build (push) Successful in 11s
2025-02-12 14:51:37 +01:00
d77f38b405 fix: removed exposed ports on the frontend
Some checks failed
Format / formatting (push) Failing after 7s
CI / build (push) Successful in 12s
2025-02-12 12:24:15 +01:00
525f98a054 feat: new makefile option 2025-02-12 12:23:53 +01:00
43aadac503 feat: reflected changes of path change 2025-02-12 12:23:04 +01:00
07f66f65ed feat: comments and security comfiguration improved. 2025-02-12 12:04:59 +01:00
6e5651c527 fix: remove dialect to supress a warning 2025-02-12 12:04:19 +01:00
1ed976b039 fix: now only show the incorrect files
Some checks failed
Format / formatting (push) Failing after 7s
CI / build (push) Successful in 12s
2025-02-12 11:42:16 +01:00
013b97cec0 test: check if the action is now red
Some checks failed
Format / formatting (push) Failing after 8s
CI / build (push) Successful in 12s
2025-02-12 11:34:57 +01:00
e7cb8cf469 feat: single .env file 2025-02-12 11:34:11 +01:00
184642a750 fix: coherent syntax 2025-02-12 11:32:41 +01:00
a8ae5f14d4 fix: db connection
All checks were successful
Format / formatting (push) Successful in 7s
CI / build (push) Successful in 12s
2025-02-12 11:09:17 +01:00
e6565275c8 fix: removed git action push to not destroy our history
All checks were successful
Format / formatting (push) Successful in 8s
CI / build (push) Successful in 12s
2025-02-12 11:04:49 +01:00
f629fb4a4e Google Java Format 2025-02-12 09:20:00 +00:00
7a9955b781 fix: merged
All checks were successful
Format / formatting (push) Successful in 9s
CI / build (push) Successful in 15s
2025-02-12 10:19:31 +01:00
1e97177777 fix: issue with foreign keys 2025-02-11 21:15:13 +01:00
9e2ab9fa5a Google Java Format 2025-02-11 18:27:36 +00:00
70b00a1996 feat: test on github validation
All checks were successful
Format / formatting (push) Successful in 8s
CI / build (push) Successful in 14s
2025-02-11 19:27:22 +01:00
720b19df93 wqMerge remote-tracking branch 'refs/remotes/origin/back-postgres' into back-postgres 2025-02-11 19:25:59 +01:00
1498b5908b fix: should now catch errors ? 2025-02-11 19:24:04 +01:00
45bbe51897 Google Java Format 2025-02-11 18:21:00 +00:00
c715955758 feat: the backend validator is better
All checks were successful
Format / formatting (push) Successful in 8s
CI / build (push) Successful in 13s
2025-02-11 19:20:49 +01:00
0fc4be2008 test: changed the verification
Some checks failed
Format / formatting (push) Failing after 44s
CI / build (push) Successful in 14s
2025-02-11 19:18:08 +01:00
c7ddc37bf9 test: changed the verification
Some checks failed
Format / formatting (push) Failing after 5s
CI / build (push) Successful in 14s
2025-02-11 19:12:49 +01:00
93bb46b017 test: check backend formatting
Some checks failed
check backend / formatting (push) Failing after 10s
CI / build (push) Successful in 12s
2025-02-11 19:04:02 +01:00
eed4e6f855 feat: multiple database and user in postgres
All checks were successful
CI / build (push) Successful in 11s
2025-02-11 11:07:45 +01:00
d2cc3e00e1 fix: Makefile now build correctly production environment 2025-02-11 10:07:00 +01:00
249d00177c feat: interraction between the backend and keycloak
Some checks failed
CI / build (push) Failing after 9s
2025-02-11 10:00:11 +01:00
36e4967394 Feat: first implementation of postgres db for backend
All checks were successful
CI / build (push) Successful in 13s
2025-02-11 00:08:53 +01:00
c32eea8a40 Merge pull request 'Mise en place d'un linter, de formatteur et d'actions afin de vérifier que le code du frontend compile bien' (#3) from linter into main
All checks were successful
CI / build (push) Successful in 12s
Reviewed-on: #3
Reviewed-by: Theo <tlelez@enseirb-matmeca.fr>
Vu que vous ne voulez pas faire avancer le projet, je vais abuser de mes droits d'administrateur et le faire.
2025-02-10 22:44:34 +01:00
30344a60b7 fix: removed import
All checks were successful
CI / build (push) Successful in 13s
2025-02-09 16:12:43 +01:00
2465545b6b fix: removed temp modal
Some checks failed
CI / build (push) Failing after 10s
2025-02-09 16:11:54 +01:00
83cbeb7a2e feat: single workflow that check prettier, linter and build
Some checks failed
CI / build (push) Failing after 11s
2025-02-09 16:07:40 +01:00
645a10477d feat: now respect codestyle
All checks were successful
CI / build (push) Successful in 4s
2025-02-09 15:59:30 +01:00
a859871265 fix: prettier now fail instead of doing nothing
Some checks failed
CI / build (push) Failing after 5s
2025-02-09 15:57:40 +01:00
0eab9a8063 feat: implemented prettier
All checks were successful
CI / build (push) Successful in 4s
2025-02-09 15:54:19 +01:00
1dff7573ff feat: now compliant with eslint
All checks were successful
CI / build (push) Successful in 10s
2025-02-09 15:28:09 +01:00
8af40bfe50 fix: linter action: installed required modules
Some checks failed
CI / build (push) Failing after 11s
2025-02-09 14:04:12 +01:00
afa4d34ec8 fix: linter action
Some checks failed
CI / build (push) Failing after 7s
2025-02-09 14:02:34 +01:00
c5fc5b600e fix: linter action
Some checks failed
CI / build (push) Failing after 12s
2025-02-09 14:00:49 +01:00
a4939737fe feat: trying to setup linter
Some checks failed
CI / build (push) Failing after 35s
2025-02-09 13:57:17 +01:00
e7ebcc0d3a feat: created eslint config 2025-02-09 12:36:43 +01:00
95 changed files with 4245 additions and 615 deletions

View File

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

View File

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

1
.gitignore vendored
View File

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

View File

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

@ -1,29 +1,35 @@
plugins { plugins {
id 'java' id 'java'
id 'org.springframework.boot' version '3.4.2' id 'org.springframework.boot' version '3.4.2'
id 'io.spring.dependency-management' version '1.1.7' id 'io.spring.dependency-management' version '1.1.7'
} }
group = 'enseirb' group = 'enseirb'
version = '0.0.1-SNAPSHOT' version = '0.0.1-SNAPSHOT'
java { java {
toolchain { toolchain {
languageVersion = JavaLanguageVersion.of(21) languageVersion = JavaLanguageVersion.of(21)
} }
} }
repositories { repositories {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' 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-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.16.0'
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.16.0'
implementation 'org.postgresql:postgresql'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
} }
tasks.named('test') { tasks.named('test') {
useJUnitPlatform() useJUnitPlatform()
} }

View File

@ -1,29 +1,15 @@
package enseirb.myinpulse; package enseirb.myinpulse;
import enseirb.myinpulse.security.KeycloakJwtRolesConverter;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; 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.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.*;
import java.util.stream.Collectors;
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;
@SpringBootApplication @SpringBootApplication
public class MyinpulseApplication { public class MyinpulseApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(MyinpulseApplication.class, args); SpringApplication.run(MyinpulseApplication.class, args);
} }
} }

View File

@ -1,43 +0,0 @@
package enseirb.myinpulse.api;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.core.context.SecurityContextHolder;
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;
@SpringBootApplication
@RestController
public class GetUserInfo {
// TODO: understand how to get data
@GetMapping("/getUserInfo")
public Object user(Principal principal) {
System.out.println("GetUserInfo + " + principal);
System.out.println(SecurityContextHolder.getContext().getAuthentication());
return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
@CrossOrigin(methods = {RequestMethod.GET, RequestMethod.OPTIONS})
@GetMapping("/random")
public boolean rand(){
System.err.println("HELLO");
return Math.random() > 0.5;
}
@CrossOrigin(methods = {RequestMethod.GET, RequestMethod.OPTIONS})
@GetMapping("/random2")
public boolean rand2(){
System.err.println("HELLO2");
return Math.random() > 0.5;
}
@CrossOrigin(methods = {RequestMethod.GET, RequestMethod.OPTIONS})
@GetMapping("/random3")
public boolean rand3(){
System.err.println("HELLO");
return Math.random() > 0.5;
}
}

View File

@ -0,0 +1,107 @@
/*
* 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.config;
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;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import java.util.ArrayList;
import java.util.Collection;
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_";
/** 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 */
private static final String CLAIM_REALM_ACCESS = "realm_access";
/** 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.) */
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()));
}
/**
* Extracts the realm and resource level roles from a JWT token distinguishing between them
* using prefixes.
*/
public Collection<GrantedAuthority> tokenRolesExtractor(Jwt jwt) {
// Collection that will hold the extracted roles
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
// Realm roles
// Get the part of the access token that holds the roles assigned on realm level
Map<String, Collection<String>> realmAccess = jwt.getClaim(CLAIM_REALM_ACCESS);
// Verify that the claim exists and is not empty
if (realmAccess != null && !realmAccess.isEmpty()) {
// From the realm_access claim get the roles
Collection<String> roles = realmAccess.get(CLAIM_ROLES);
// 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());
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
// resource each possibly containing a "roles" property.
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)));
});
}
return grantedAuthorities;
}
}

View File

@ -1,6 +1,8 @@
package enseirb.myinpulse.config; package enseirb.myinpulse.config;
import enseirb.myinpulse.security.KeycloakJwtRolesConverter; import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@ -12,39 +14,62 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;
@Configuration @Configuration
public class WebSecurityCustomConfiguration { public class WebSecurityCustomConfiguration {
// CORS configuration // CORS configuration
// TODO: make sure to only accept our own domains
@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
*/
@Bean @Bean
public CorsConfigurationSource corsConfigurationSource() { public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration(); CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("*")); configuration.setAllowedOrigins(List.of(frontendUrl));
configuration.setAllowedMethods(Arrays.asList("GET", "OPTIONS")); configuration.setAllowedMethods(Arrays.asList("GET", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", configuration.setAllowedHeaders(
"x-auth-token")); // Do not remove, this fixes the CORS errors when unauthenticated Arrays.asList("authorization", "content-type", "x-auth-token"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration); source.registerCorsConfiguration("/**", configuration);
return source; 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 @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http http.authorizeHttpRequests(
.authorizeHttpRequests(authorize -> authorize authorize ->
.requestMatchers("/random2").access(hasRole("REALM_MyINPulse-entrepreneur")) authorize
.requestMatchers("/random").access(hasRole("REALM_MyINPulse-admin")) .requestMatchers("/entrepreneur/**", "/shared/**")
.requestMatchers("/random3").permitAll() .access(hasRole("REALM_MyINPulse-entrepreneur"))
.anyRequest().authenticated() .requestMatchers("/admin/**", "/shared/**")
) .access(hasRole("REALM_MyINPulse-admin"))
.oauth2ResourceServer(oauth2 -> oauth2 .requestMatchers("/unauth/**")
.jwt(jwt -> jwt. .permitAll()
jwtAuthenticationConverter(new KeycloakJwtRolesConverter()))); .anyRequest()
.authenticated())
.oauth2ResourceServer(
oauth2 ->
oauth2.jwt(
jwt ->
jwt.jwtAuthenticationConverter(
new KeycloakJwtRolesConverter())));
return http.build(); return http.build();
} }
} }

View File

@ -0,0 +1,102 @@
package enseirb.myinpulse.controller;
import enseirb.myinpulse.model.*;
import enseirb.myinpulse.service.AdminApiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.*;
@SpringBootApplication
@RestController
public class AdminApi {
private final AdminApiService adminApiService;
@Autowired
AdminApi(AdminApiService adminApiService) {
this.adminApiService = adminApiService;
}
/**
* TODO: description
*
* @return a list of all project managed by the current admin user
*/
@GetMapping("/admin/projects")
public Iterable<Project> getProjects(@AuthenticationPrincipal Jwt principal) {
return adminApiService.getProjectsOfAdmin(principal.getClaimAsString("email"));
}
/**
* TODO: Why in admin instead of shared ? + desc
*
* @return a list of upcoming appointments for the current user
*/
@GetMapping("/admin/appointments/upcoming")
public Iterable<Appointment> getUpcomingAppointments(@AuthenticationPrincipal Jwt principal) {
return adminApiService.getUpcomingAppointments(principal.getClaimAsString("email"));
}
/**
* TODO: description
*
* @return a list of current unvalidated projects, waiting to be accepted
*/
@GetMapping("/admin/projects/pending")
public Iterable<Project> getPendingProjects() {
return adminApiService.getPendingProjects();
}
/**
* Endpoint used to make a decision about a project.
*
* <p>The decision must contain the administrator
*
* @return the status code of the request
*/
@PostMapping("/admin/projects/decision")
public void validateProject(@RequestBody ProjectDecision decision) {
adminApiService.validateProject(decision);
}
/**
* Endpoint used to manually add a project by an admin
*
* @return the status code of the request
*/
@PostMapping("/admin/project/add")
public void addNewProject(@RequestBody Project project) {
adminApiService.addNewProject(project);
}
/**
* TODO: shouldn't it be an PUT request ? / What is the rerun type
*
* <p>Endpoint used to add a new report to an appointment
*
* @return the status code of the request
*/
@PostMapping("/admin/appoitements/report/{appointmentId}")
public void createAppointmentReport(
@PathVariable String appointmentId,
@RequestBody Report report,
@AuthenticationPrincipal Jwt principal) {
adminApiService.createAppointmentReport(
appointmentId, report, principal.getClaimAsString("email"));
}
/**
* TODO: Shouldn't a project be kept in history ? 2 different endpoints ?
*
* <p>Endpoint used to completely remove a project.
*
* @return the status code of the request
*/
@DeleteMapping("/admin/projects/remove/{projectId}")
public void deleteProject(@PathVariable long projectId) {
adminApiService.deleteProject(projectId);
}
}

View File

@ -0,0 +1,81 @@
package enseirb.myinpulse.controller;
import enseirb.myinpulse.model.LCSection;
import enseirb.myinpulse.model.Project;
import enseirb.myinpulse.service.EntrepreneurApiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.*;
@SpringBootApplication
@RestController
public class EntrepreneurApi {
private final EntrepreneurApiService entrepreneurApiService;
@Autowired
EntrepreneurApi(EntrepreneurApiService entrepreneurApiService) {
this.entrepreneurApiService = entrepreneurApiService;
}
/**
* TODO: check return type
*
* <p>Endpoint used to update a LC section.
*
* @return status code
*/
@PutMapping("/entrepreneur/lcsection/modify/{sectionId}")
public void editLCSection(
@PathVariable String sectionId,
@RequestBody LCSection section,
@AuthenticationPrincipal Jwt principal) {
entrepreneurApiService.editLCSection(
sectionId, section, principal.getClaimAsString("email"));
}
/**
* TODO: checkReturn Type
*
* <p>Endpoint used to delete a LC section
*
* @return status code
*/
@DeleteMapping("/entrepreneur/lcsection/remove/{sectionId}")
public void removeLCSection(
@PathVariable String sectionId, @AuthenticationPrincipal Jwt principal) {
entrepreneurApiService.removeLCSection(sectionId, principal.getClaimAsString("email"));
}
/**
* TODO: check return type
*
* <p>Endpoint used to create a new LC section
*
* @return status code
*/
@PostMapping("/entrepreneur/lcsection/add/{sectionId}")
public void addLCSection(
@PathVariable String sectionId,
@RequestBody LCSection section,
@AuthenticationPrincipal Jwt principal) {
entrepreneurApiService.addLCSection(
sectionId, section, principal.getClaimAsString("email"));
}
/**
* TODO: check return type
*
* <p>Endpoint used to request the creation of a new project
*
* @return status code
*/
@PostMapping("/entrepreneur/project/request")
public void requestNewProject(
@RequestBody Project project, @AuthenticationPrincipal Jwt principal) {
entrepreneurApiService.requestNewProject(project, principal.getClaimAsString("email"));
}
}

View File

@ -0,0 +1,96 @@
package enseirb.myinpulse.controller;
import enseirb.myinpulse.model.*;
import enseirb.myinpulse.service.SharedApiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.*;
@SpringBootApplication
@RestController
public class SharedApi {
private final SharedApiService sharedApiService;
@Autowired
SharedApi(SharedApiService sharedApiService) {
this.sharedApiService = sharedApiService;
}
/**
* TODO: It does not looks like a good id to have the title and the date in the url. What even
* TODO: is the title btw ? if this is the LC section, wouldn't it be better to use an ID ?
* TODO: choose return type, cf comment in LCSection
*
* <p>Endpoint used to get the data inside the lean canvas
*
* @return a list of lean canvas sections
*/
@GetMapping("/shared/project/lcsection/{projectId}/{title}/{date}")
public Iterable<SectionCell> getLCSection(
@PathVariable("projectId") String projectId,
@PathVariable("title") String title,
@PathVariable("date") String date,
@AuthenticationPrincipal Jwt principal) {
return sharedApiService.getLCSection(
projectId, title, date, principal.getClaimAsString("email"));
}
/**
* Endpoint used to get entrepreneurs details
*
* @return a list of all entrepreneurs in a project
*/
@GetMapping("/shared/entrepreneurs/{projectId}")
public Iterable<Entrepreneur> getEntrepreneursByProjectId(
@PathVariable int projectId, @AuthenticationPrincipal Jwt principal) {
return sharedApiService.getEntrepreneursByProjectId(
projectId, principal.getClaimAsString("email"));
}
/**
* Endpoint used to get the administrator of a project.
*
* @return a list of all project managed by the current admin user
*/
@GetMapping("/shared/projects/admin/{projectId}")
public Iterable<Administrator> getAdminByProjectId(
@PathVariable int projectId, @AuthenticationPrincipal Jwt principal) {
return sharedApiService.getAdminByProjectId(projectId, principal.getClaimAsString("email"));
}
/**
* Endpoint used to get all appointments of a single project.
*
* @return a list of all appointments.
*/
@GetMapping("/shared/projects/appointments/{projectId}")
public Iterable<Appointment> getAppointmentsByProjectId(
@PathVariable int projectId, @AuthenticationPrincipal Jwt principal) {
return sharedApiService.getAppointmentsByProjectId(
projectId, principal.getClaimAsString("email"));
}
/**
* Endpoint used to generate a PDF report
*
* @return a PDF file? TODO: how does that works ?
*/
@GetMapping("/shared/projects/appointments/report/{appointmentId}")
public void getPDFReport(
@PathVariable int appointmentId, @AuthenticationPrincipal Jwt principal) {
sharedApiService.getPDFReport(appointmentId, principal.getClaimAsString("email"));
}
/**
* @return TODO
*/
@PostMapping("/shared/appointment/request")
public void createAppointmentRequest(
@RequestBody Appointment appointment, @AuthenticationPrincipal Jwt principal) {
sharedApiService.createAppointmentRequest(appointment, principal.getClaimAsString("email"));
}
}

View File

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

View File

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

View File

@ -0,0 +1,43 @@
package enseirb.myinpulse.model;
import jakarta.persistence.*;
import jakarta.persistence.PrimaryKeyJoinColumn;
import jakarta.persistence.Table;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "administrator")
@PrimaryKeyJoinColumn(name = "idAdministrator", referencedColumnName = "idUser")
public class Administrator extends User {
@OneToMany(mappedBy = "projectAdministrator", fetch = FetchType.LAZY, orphanRemoval = true)
private List<Project> listProject = new ArrayList<>();
/*@OneToMany(mappedBy = "administratorSectionCell", fetch = FetchType.LAZY, orphanRemoval = true)
private List<SectionCell> listSectionCell = new ArrayList<>();*/
// should now be useless
@OneToMany(mappedBy = "administratorAnnotation", fetch = FetchType.LAZY, orphanRemoval = true)
private List<Annotation> listAnnotation = new ArrayList<>();
/*@OneToMany(mappedBy = "administratorAppointment", fetch = FetchType.LAZY, orphanRemoval = true)
private final List<Appointment> listAppointment = new ArrayList<>();*/
// should now be useless
@OneToOne(mappedBy = "administratorAppointment", fetch = FetchType.LAZY, orphanRemoval = true)
private MakeAppointment makeAppointment;
public Administrator() {}
public Administrator(
Long idUser,
String userSurname,
String username,
String mainMail,
String secondaryMail,
String phoneNumber) {
super(idUser, userSurname, username, mainMail, secondaryMail, phoneNumber);
}
}

View File

@ -0,0 +1,39 @@
package enseirb.myinpulse.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
@Entity
@Table(name = "annotation")
public class Annotation {
@Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idAnnotation;
private String comment;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idSectionCell")
private SectionCell sectionCellAnnotation;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idAdministrator")
private Administrator administratorAnnotation;
public Annotation() {}
public Annotation(Long idAnnotation, String commentary) {
this.idAnnotation = idAnnotation;
this.comment = comment;
}
public String getComment() {
return comment;
}
public void setCommentary(String comment) {
this.comment = comment;
}
}

View File

@ -0,0 +1,112 @@
package enseirb.myinpulse.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 = "appointment")
public class Appointment {
/*@OneToMany(mappedBy = "appointmentEntrepreneurs", fetch = FetchType.LAZY, orphanRemoval = true)
private final List<Entrepreneur> listEntrepreneur =
new ArrayList<>(); */
// should now be useless
@OneToMany(mappedBy = "appointmentReport", fetch = FetchType.LAZY, orphanRemoval = true)
private final List<Report> listReport = new ArrayList<>();
@ManyToMany(
fetch = FetchType.LAZY,
cascade = {CascadeType.ALL})
@JoinTable(
name = "concern",
joinColumns = @JoinColumn(name = "idAppointment"),
inverseJoinColumns = @JoinColumn(name = "idSectionCell"))
List<SectionCell> listSectionCell = new ArrayList<>();
@Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idAppointment;
private LocalDate appointmentDate;
private LocalTime appointmentTime;
private LocalTime appointmentDuration;
@Column(length = 255)
private String appointmentPlace;
private String appointmentSubject;
public Appointment() {}
public Appointment(
Long idAppointment,
LocalDate appointmentDate,
LocalTime appointmentTime,
LocalTime appointmentDuration,
String appointmentPlace,
String appointmentSubject) {
this.idAppointment = idAppointment;
this.appointmentDate = appointmentDate;
this.appointmentTime = appointmentTime;
this.appointmentDuration = appointmentDuration;
this.appointmentPlace = appointmentPlace;
this.appointmentSubject = appointmentSubject;
}
public Long getIdAppointment() {
return idAppointment;
}
public void setIdAppointment(Long idAppointment) {
this.idAppointment = idAppointment;
}
public LocalDate getAppointmentDate() {
return appointmentDate;
}
public void setAppointmentDate(LocalDate appointmentDate) {
this.appointmentDate = appointmentDate;
}
public LocalTime getAppointmentTime() {
return appointmentTime;
}
public void setAppointmentTime(LocalTime appointmentTime) {
this.appointmentTime = appointmentTime;
}
public LocalTime getAppointmentDuration() {
return appointmentDuration;
}
public void setAppointmentDuration(LocalTime appointmentDuration) {
this.appointmentDuration = appointmentDuration;
}
public String getAppointmentPlace() {
return appointmentPlace;
}
public void setAppointmentPlace(String appointmentPlace) {
this.appointmentPlace = appointmentPlace;
}
public String getAppointmentSubject() {
return appointmentSubject;
}
public void setAppointmentSubject(String appointmentSubject) {
this.appointmentSubject = appointmentSubject;
}
}

View File

@ -0,0 +1,8 @@
package enseirb.myinpulse.model;
public class DelAppointment {
int validated;
int[] akserId;
int[] destId;
String date; // TODO: date type ?
}

View File

@ -0,0 +1,7 @@
package enseirb.myinpulse.model;
public class DelProject {
int projectId;
String projectName;
String projectDescription;
}

View File

@ -0,0 +1,6 @@
package enseirb.myinpulse.model;
public class DelReport {
int projectId;
String reportContent;
}

View File

@ -0,0 +1,78 @@
package enseirb.myinpulse.model;
import jakarta.persistence.*;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
@Entity
@Table(name = "entrepreneur")
@PrimaryKeyJoinColumn(name = "idEntrepreneur", referencedColumnName = "idUser")
public class Entrepreneur extends User {
@Column(length = 255)
private String school;
@Column(length = 255)
private String course;
private boolean sneeStatus;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idProjectParticipation", referencedColumnName = "idProject")
private Project projectParticipation;
// @Column(insertable=false, updatable=false)
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idProjectProposed", referencedColumnName = "idProject")
private Project projectProposed;
/*@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idAppointment")
private Appointment appointmentEntrepreneur;*/
// should now be useless
@OneToOne(mappedBy = "entrepreneurAppointment", fetch = FetchType.LAZY, orphanRemoval = true)
private MakeAppointment makeAppointment;
public Entrepreneur() {}
public Entrepreneur(
Long idUser,
String userSurname,
String username,
String mainMail,
String secondaryMail,
String phoneNumber,
String school,
String course,
boolean sneeStatus) {
super(idUser, userSurname, username, mainMail, secondaryMail, phoneNumber);
this.school = school;
this.course = course;
this.sneeStatus = sneeStatus;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
public String getCourse() {
return course;
}
public void setCourse(String course) {
this.course = course;
}
public boolean isSneeStatus() {
return sneeStatus;
}
public void setSneeStatus(boolean statusSnee) {
this.sneeStatus = sneeStatus;
}
}

View File

@ -0,0 +1,7 @@
package enseirb.myinpulse.model;
// TODO: is this redundant with the Section class from the database ?
// TODO: In the one hand it represent the same data, and on the other it should be much lighter.
// TODO: btw why does a LC section have an administrator ?
public class LCSection {}

View File

@ -0,0 +1,28 @@
package enseirb.myinpulse.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
@Entity
@Table(name = "make_appointment")
public class MakeAppointment {
@Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idMakeAppointment;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idAdministrator")
private Administrator administratorAppointment;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idEntrepreneur")
private Entrepreneur entrepreneurAppointment;
public MakeAppointment() {}
public MakeAppointment(Long idMakeAppointment) {
this.idMakeAppointment = idMakeAppointment;
}
}

View File

@ -0,0 +1,92 @@
package enseirb.myinpulse.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "project")
public class Project {
@OneToMany(mappedBy = "projectParticipation", fetch = FetchType.LAZY, orphanRemoval = true)
private final List<Entrepreneur> listEntrepreneurParticipation = new ArrayList<>();
@OneToMany(mappedBy = "projectSectionCell", fetch = FetchType.LAZY, orphanRemoval = true)
private final List<SectionCell> listSectionCell = new ArrayList<>();
@Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idProject;
@Column(length = 255)
private String projectName;
private byte[] logo;
private LocalDate creationDate;
@Column(length = 255)
private String projectStatus;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idAdministrator")
private Administrator projectAdministrator;
@OneToOne(mappedBy = "projectProposed", fetch = FetchType.LAZY, orphanRemoval = true)
private Entrepreneur entrepreneurProposed;
public Project() {}
public Project(
Long idProject,
String projectName,
byte[] logo,
LocalDate creationDate,
String projectStatus) {
this.idProject = idProject;
this.projectName = projectName;
this.logo = logo;
this.creationDate = creationDate;
this.projectStatus = projectStatus;
}
public Long getIdProject() {
return idProject;
}
public void setIdProject(Long idProject) {
this.idProject = idProject;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public byte[] getLogo() {
return logo;
}
public void setLogo(byte[] logo) {
this.logo = logo;
}
public LocalDate getCreationDate() {
return creationDate;
}
public void setCreationDate(LocalDate creationDate) {
this.creationDate = creationDate;
}
public String getProjectStatus() {
return projectStatus;
}
public void setProjectStatus(String projectStatus) {
this.projectStatus = projectStatus;
}
public void setAdministrator(Administrator administrator) {
this.projectAdministrator = administrator;
}
}

View File

@ -0,0 +1,7 @@
package enseirb.myinpulse.model;
public class ProjectDecision {
public long projectId;
public long adminId;
public long isAccepted;
}

View File

@ -0,0 +1,38 @@
package enseirb.myinpulse.model;
import jakarta.persistence.*;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
@Entity
@Table(name = "report")
public class Report {
@Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idReport;
private String reportContent;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idAppointment")
private Appointment appointmentReport;
public Report() {}
public Report(Long idReport, String reportContent) {
this.idReport = idReport;
this.reportContent = reportContent;
}
public String getReportContent() {
return reportContent;
}
public void setReportContent(String reportContent) {
this.reportContent = reportContent;
}
}

View File

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

View File

@ -0,0 +1,85 @@
package enseirb.myinpulse.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "section_cell")
public class SectionCell {
@Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idSectionCell;
@Column(length = 255)
private String title;
private String contentSectionCell;
private LocalDateTime modificationDate;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idProject")
private Project projectSectionCell;
/*@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idAdministrator")
private Administrator administratorSectionCell;*/
// should now be useless
@ManyToMany(mappedBy = "listSectionCell")
private List<Appointment> appointment = new ArrayList<>();
@OneToMany(mappedBy = "sectionCellAnnotation", fetch = FetchType.LAZY, orphanRemoval = true)
private List<Annotation> listAnnotation = new ArrayList<>();
public SectionCell() {}
public SectionCell(
Long idSectionCell,
String title,
String contentSectionCell,
LocalDateTime modificationDate) {
this.idSectionCell = idSectionCell;
this.title = title;
this.contentSectionCell = contentSectionCell;
this.modificationDate = modificationDate;
}
public Long getIdSectionCell() {
return idSectionCell;
}
public void setIdSectionCell(Long idSectionCell) {
this.idSectionCell = idSectionCell;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContentSectionCell() {
return contentSectionCell;
}
public void setContentSectionCell(String contentSectionCell) {
this.contentSectionCell = contentSectionCell;
}
public LocalDateTime getModificationDate() {
return modificationDate;
}
public void setModificationDate(LocalDateTime modificationDate) {
this.modificationDate = modificationDate;
}
}

View File

@ -0,0 +1,95 @@
package enseirb.myinpulse.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
@Entity
@Table(name = "user_inpulse")
@Inheritance(strategy = InheritanceType.JOINED)
public class User {
@Id
@NotNull
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idUser;
@Column(length = 255)
private String userSurname;
@Column(length = 255)
private String userName;
@Column(length = 255)
private String primaryMail;
@Column(length = 255)
private String secondaryMail;
@Column(length = 20)
private String phoneNumber;
public User() {}
public User(
Long idUser,
String userSurname,
String userName,
String primaryMail,
String secondaryMail,
String phoneNumber) {
this.idUser = idUser;
this.userSurname = userSurname;
this.userName = userName;
this.primaryMail = primaryMail;
this.secondaryMail = secondaryMail;
this.phoneNumber = phoneNumber;
}
public Long getIdUser() {
return idUser;
}
public void setIdUser(Long idUser) {
this.idUser = idUser;
}
public String getUserSurname() {
return userSurname;
}
public void setUserSurname(String userSurname) {
userSurname = userSurname;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
userName = userName;
}
public String getPrimaryMail() {
return primaryMail;
}
public void setPrimaryMail(String mainMail) {
this.primaryMail = mainMail;
}
public String getSecondaryMail() {
return secondaryMail;
}
public void setSecondaryMail(String secondaryMail) {
this.secondaryMail = secondaryMail;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
phoneNumber = phoneNumber;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,14 @@
package enseirb.myinpulse.repository;
import enseirb.myinpulse.model.Administrator;
import enseirb.myinpulse.model.Project;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource
public interface ProjectRepository extends JpaRepository<Project, Long> {
Iterable<Project> findByProjectAdministrator(Administrator administrator);
Iterable<Project> findByProjectStatus(String status);
}

View File

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

View File

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

View File

@ -0,0 +1,17 @@
package enseirb.myinpulse.repository;
import enseirb.myinpulse.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import java.util.Optional;
@RepositoryRestResource
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByPrimaryMail(String email);
/* @Query("SELECT u from User u")
User findAllUser(); */
}

View File

@ -1,98 +0,0 @@
package enseirb.myinpulse.security;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toSet;
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
*/
private static final String CLAIM_REALM_ACCESS = "realm_access";
/**
* 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.)
*/
private static final String CLAIM_ROLES = "roles";
@Override
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.
*/
public Collection<GrantedAuthority> TEMPORARNAME(Jwt jwt) {
// Collection that will hold the extracted roles
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
// Realm roles
// Get the part of the access token that holds the roles assigned on realm level
Map<String, Collection<String>> realmAccess = jwt.getClaim(CLAIM_REALM_ACCESS);
// Verify that the claim exists and is not empty
if (realmAccess != null && !realmAccess.isEmpty()) {
// From the realm_access claim get the roles
Collection<String> roles = realmAccess.get(CLAIM_ROLES);
// 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());
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
// resource each possibly containing a "roles" property.
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))
);
});
}
return grantedAuthorities;
}
}

View File

@ -0,0 +1,72 @@
package enseirb.myinpulse.service;
import enseirb.myinpulse.model.*;
import enseirb.myinpulse.service.database.AdministratorService;
import enseirb.myinpulse.service.database.ProjectService;
import enseirb.myinpulse.service.database.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
@Service
public class AdminApiService {
private final ProjectService projectService;
private final UserService userService;
private final AdministratorService administratorService;
@Autowired
AdminApiService(
ProjectService projectService,
UserService userService,
AdministratorService administratorService) {
this.projectService = projectService;
this.userService = userService;
this.administratorService = administratorService;
}
// TODO: test
public Iterable<Project> getProjectsOfAdmin(String email) {
return projectService.getProjectsByAdminId(
administratorService.getAdministratorById(
this.userService.getIdUserByEmail(email)));
}
// TODO
public Iterable<Appointment> getUpcomingAppointments(String email) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet");
}
// TODO: test
public Iterable<Project> getPendingProjects() {
return this.projectService.getPendingProjects();
}
// TODO: test
public void validateProject(ProjectDecision decision) {
projectService.updateProject(
decision.projectId,
null,
null,
null,
"ACTIVE",
this.administratorService.getAdministratorById(decision.projectId));
}
// TODO: solve todo + test
public void addNewProject(Project project) {
projectService.addNewProject(project); // TODO: how can the user know the ID ?
}
// TODO
public void createAppointmentReport(String appointmentId, Report report, String email) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet");
}
// TODO: test
public void deleteProject(long projectId) {
this.projectService.deleteProjectById(projectId);
}
}

View File

@ -0,0 +1,29 @@
package enseirb.myinpulse.service;
import enseirb.myinpulse.model.LCSection;
import enseirb.myinpulse.model.Project;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
@Service
public class EntrepreneurApiService {
public void editLCSection(String sectionId, LCSection section, String mail) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet");
}
public void removeLCSection(String sectionId, String mail) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet");
}
public void addLCSection(String sectionId, LCSection section, String mail) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet");
}
public void requestNewProject(Project project, String mail) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet");
}
}

View File

@ -0,0 +1,135 @@
package enseirb.myinpulse.service;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import enseirb.myinpulse.exception.UserNotFoundException;
import enseirb.myinpulse.model.RoleRepresentation;
import enseirb.myinpulse.model.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

@ -0,0 +1,36 @@
package enseirb.myinpulse.service;
import enseirb.myinpulse.model.*;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
@Service
public class SharedApiService {
public Iterable<SectionCell> getLCSection(
String projectId, String title, String date, String mail) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet");
}
public Iterable<Entrepreneur> getEntrepreneursByProjectId(int projectId, String mail) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet");
}
public Iterable<Administrator> getAdminByProjectId(int projectId, String mail) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet");
}
public Iterable<Appointment> getAppointmentsByProjectId(int projectId, String mail) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet");
}
public void getPDFReport(int appointmentId, String mail) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet");
}
public void createAppointmentRequest(Appointment appointment, String mail) {
throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED, "Not implemented yet");
}
}

View File

@ -0,0 +1,43 @@
package enseirb.myinpulse.service.database;
import enseirb.myinpulse.model.Administrator;
import enseirb.myinpulse.repository.AdministratorRepository;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.util.Optional;
@Service
public class AdministratorService {
protected static final Logger logger = LogManager.getLogger();
private final AdministratorRepository administratorRepository;
@Autowired
AdministratorService(AdministratorRepository administratorRepository) {
this.administratorRepository = administratorRepository;
}
public Iterable<Administrator> allAdministrators() {
return this.administratorRepository.findAll();
}
public Administrator getAdministratorById(long id) {
Optional<Administrator> administrator = this.administratorRepository.findById(id);
if (administrator.isEmpty()) {
logger.error("No administrator found with id {}", id);
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Cet administrateur n'existe pas");
}
return administrator.get();
}
public Administrator addAdministrator(Administrator administrator) {
return this.administratorRepository.save(administrator);
}
}

View File

@ -0,0 +1,105 @@
package enseirb.myinpulse.service.database;
import enseirb.myinpulse.model.Administrator;
import enseirb.myinpulse.model.Project;
import enseirb.myinpulse.repository.ProjectRepository;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
@Service
public class ProjectService {
protected static final Logger logger = LogManager.getLogger();
private final ProjectRepository projectRepository;
@Autowired
ProjectService(ProjectRepository projectRepository) {
this.projectRepository = projectRepository;
}
public Iterable<Project> getAllProjects() {
return this.projectRepository.findAll();
}
public Project getProjectById(Long id) {
Optional<Project> project = this.projectRepository.findById(id);
if (project.isEmpty()) {
System.err.println("Project with id " + id + " not found");
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce projet n'existe pas");
}
return project.get();
}
public Iterable<Project> getProjectsByAdminId(Administrator administrator) {
return this.projectRepository.findByProjectAdministrator(administrator);
}
// TODO: validation
public Project addNewProject(Project project) {
return this.projectRepository.save(project);
}
public Project updateProject(
Long id,
String projectName,
byte[] logo,
LocalDate creationDate,
String projectStatus,
Administrator administrator) {
Optional<Project> project = this.projectRepository.findById(id);
if (project.isEmpty()) {
logger.error("Project with id {} not found.", id);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce projet n'existe pas");
}
if (projectName != null) {
project.get().setProjectName(projectName);
}
if (logo != null) {
project.get().setLogo(logo);
}
if (creationDate != null) {
project.get().setCreationDate(creationDate);
}
if (projectStatus != null) {
if (!validateStatus(projectStatus)) {
System.err.println("updateProjectStatus: Invalid status " + projectStatus);
throw new ResponseStatusException(
HttpStatus.NOT_ACCEPTABLE, "Ce status n'est pas accepté");
}
project.get().setProjectStatus(projectStatus);
}
if (administrator != null) {
project.get().setAdministrator(administrator);
}
return this.projectRepository.save(project.get());
}
public Boolean validateStatus(String status) {
return List.of("PENDING", "ACTIVE", "ENDED").contains(status);
}
public Iterable<Project> getPendingProjects() {
return this.projectRepository.findByProjectStatus("PENDING");
}
public void deleteProjectById(Long id) {
this.projectRepository.deleteById(id);
}
}

View File

@ -0,0 +1,34 @@
package enseirb.myinpulse.service.database;
import enseirb.myinpulse.model.Report;
import enseirb.myinpulse.repository.ReportRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.util.Optional;
@Service
public class ReportService {
private final ReportRepository reportRepository;
@Autowired
ReportService(ReportRepository reportRepository) {
this.reportRepository = reportRepository;
}
Report getReportById(int id) {
Optional<Report> report = reportRepository.findById(id);
if (report.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce compte rendu n'existe pas");
}
return report.get();
}
// TODO: do some validation
void saveReport(Report report) {
reportRepository.save(report);
}
}

View File

@ -0,0 +1,76 @@
package enseirb.myinpulse.service.database;
import enseirb.myinpulse.model.User;
import enseirb.myinpulse.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.server.ResponseStatusException;
import java.util.Optional;
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public Iterable<User> getAllUsers() {
return this.userRepository.findAll();
}
// TODO
public long getIdUserByEmail(String email) {
Optional<User> opt_user = this.userRepository.findByPrimaryMail(email);
if (opt_user.isEmpty()) {
System.err.println("Couldn't find user with email " + email);
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
}
User user = opt_user.get();
return user.getIdUser();
}
public Iterable<User> allUsers() {
return this.userRepository.findAll();
}
public User addUser(@RequestBody User user) {
return this.userRepository.save(user);
}
public User updateUser(
@PathVariable Long id,
String userSurname,
String userName,
String mainMail,
String secondaryMail,
String phoneNumber) {
Optional<User> user = userRepository.findById(id);
if (user.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Cet utilisateur n'existe pas");
}
if (userName != null) {
user.get().setUserName(userName);
}
if (userSurname != null) {
user.get().setUserSurname(userSurname);
}
if (mainMail != null) {
user.get().setPrimaryMail(mainMail);
}
if (secondaryMail != null) {
user.get().setSecondaryMail(secondaryMail);
}
if (phoneNumber != null) {
user.get().setPhoneNumber(phoneNumber);
}
return this.userRepository.save(user.get());
}
}

View File

@ -0,0 +1,69 @@
package enseirb.myinpulse.service.database.old_controllers_to_convert_to_services;
import enseirb.myinpulse.model.Appointment;
import enseirb.myinpulse.repository.AppointmentRepository;
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.time.LocalTime;
import java.util.Optional;
@RestController
public class AppointmentController {
@Autowired AppointmentRepository appointmentRepository;
@GetMapping("/Appointment")
@ResponseBody
public Iterable<Appointment> allAppointments() {
return this.appointmentRepository.findAll();
}
@GetMapping("/Appointment/{id}")
public Appointment getAppointmentById(@PathVariable Long id) {
Optional<Appointment> appointment = this.appointmentRepository.findById(id);
if (appointment.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce rendez vous n'existe pas");
}
return appointment.get();
}
@PostMapping("/Appointment")
public Appointment addAppointment(@RequestBody Appointment appointment) {
return this.appointmentRepository.save(appointment);
}
@PostMapping("/Appointment/{id}")
public Appointment updateAppointment(
@PathVariable Long id,
LocalDate appointmentDate,
LocalTime appointmentTime,
LocalTime appointmentDuration,
String appointmentPlace,
String appointmentSubject) {
Optional<Appointment> appointment = this.appointmentRepository.findById(id);
if (appointment.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce rendez vous n'existe pas");
}
if (appointmentDate != null) {
appointment.get().setAppointmentDate(appointmentDate);
}
if (appointmentTime != null) {
appointment.get().setAppointmentTime(appointmentTime);
}
if (appointmentDuration != null) {
appointment.get().setAppointmentDuration(appointmentDuration);
}
if (appointmentPlace != null) {
appointment.get().setAppointmentPlace(appointmentPlace);
}
if (appointmentSubject != null) {
appointment.get().setAppointmentSubject(appointmentSubject);
}
return this.appointmentRepository.save(appointment.get());
}
}

View File

@ -0,0 +1,58 @@
package enseirb.myinpulse.service.database.old_controllers_to_convert_to_services;
import enseirb.myinpulse.model.Entrepreneur;
import enseirb.myinpulse.repository.EntrepreneurRepository;
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 EntrepreneurController {
@Autowired EntrepreneurRepository entrepreneurRepository;
@GetMapping("/Entrepreneur")
@ResponseBody
public Iterable<Entrepreneur> allEntrepreneurs() {
return this.entrepreneurRepository.findAll();
}
@GetMapping("/Entrepreneur/{id}")
public Entrepreneur getEntrepreneurById(@PathVariable Long id) {
Optional<Entrepreneur> entrepreneur = entrepreneurRepository.findById(id);
if (entrepreneur.isEmpty()) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Cet entrepreneur n'existe pas");
}
return entrepreneur.get();
}
@PostMapping("/Entrepreneur")
public Entrepreneur addEntrepreneur(@RequestBody Entrepreneur entrepreneur) {
return this.entrepreneurRepository.save(entrepreneur);
}
@PostMapping("/Entrepreneur/{id}")
public Entrepreneur updateEntrepreneur(
@PathVariable Long id, String school, String course, Boolean sneeStatus) {
Optional<Entrepreneur> entrepreneur = entrepreneurRepository.findById(id);
if (entrepreneur.isEmpty()) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Cet entrepreneur n'existe pas");
}
if (school != null) {
entrepreneur.get().setSchool(school);
}
if (course != null) {
entrepreneur.get().setCourse(course);
}
if (sneeStatus != null) {
entrepreneur.get().setSneeStatus(sneeStatus);
}
return this.entrepreneurRepository.save(entrepreneur.get());
}
}

View File

@ -0,0 +1,43 @@
package enseirb.myinpulse.service.database.old_controllers_to_convert_to_services;
import org.springframework.web.bind.annotation.*;
@RestController
public class ReportController {
/*
private final ReportRepository reportRepository;
@Autowired
public ReportController(ReportRepository reportRepository) {
this.reportRepository = reportRepository;
}
@GetMapping("/Report")
@ResponseBody
public Iterable<Report> allReports() {
System.out.println("\n\n");
System.out.println(ReportRepository);
System.out.println("\n\n");
return this.reportRepository.findAll();
}
@PostMapping("/Report")
public Report addReport(@RequestBody Report report) {
return this.reportRepository.save(report);
}
@PostMapping("/Report/{id}")
public Report updateProject(@PathVariable Long id, String reportContent) {
Optional<Report> report = this.reportRepository.findById(id);
if (report.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ce compte rendu n'existe pas");
}
if (reportContent != null) {
report.get().setReportContent(reportContent);
}
return this.reportRepository.save(report.get());
}
*/
}

View File

@ -0,0 +1,62 @@
package enseirb.myinpulse.service.database.old_controllers_to_convert_to_services;
import enseirb.myinpulse.model.SectionCell;
import enseirb.myinpulse.repository.SectionCellRepository;
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 SectionCellController {
@Autowired SectionCellRepository sectionCellRepository;
@GetMapping("/SectionCell")
@ResponseBody
public Iterable<SectionCell> allSectionCells() {
return this.sectionCellRepository.findAll();
}
@GetMapping("/SectionCell/{id}")
public SectionCell getSectionCellById(@PathVariable Long id) {
Optional<SectionCell> sectionCell = this.sectionCellRepository.findById(id);
if (sectionCell.isEmpty()) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Cette cellule de section n'existe pas");
}
return sectionCell.get();
}
@PostMapping("/SectionCell")
public SectionCell addSectionCell(@RequestBody SectionCell sectionCell) {
return this.sectionCellRepository.save(sectionCell);
}
@PostMapping("/SectionCell/{id}")
public SectionCell updateSectionCell(
@PathVariable Long id,
String title,
String contentSectionCell,
LocalDateTime modificationDate) {
Optional<SectionCell> sectionCell = this.sectionCellRepository.findById(id);
if (sectionCell.isEmpty()) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Cette cellule de section n'existe pas");
}
if (title != null) {
sectionCell.get().setTitle(title);
}
if (contentSectionCell != null) {
sectionCell.get().setContentSectionCell(contentSectionCell);
}
if (modificationDate != null) {
sectionCell.get().setModificationDate(modificationDate);
}
return this.sectionCellRepository.save(sectionCell.get());
}
}

View File

@ -2,3 +2,7 @@ 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.jwk-set-uri=http://localhost:7080/realms/test/protocol/openid-connect/certs
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/test spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/test
logging.level.org.springframework.security=DEBUG 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

@ -0,0 +1,99 @@
TRUNCATE project, user_inpulse, entrepreneur, administrator, section_cell, appointment, report, annotation CASCADE;
SELECT setval('annotation_id_annotation_seq', 1, false);
SELECT setval('appointment_id_appointment_seq', 1, false);
SELECT setval('make_appointment_id_make_appointment_seq', 1, false);
SELECT setval('project_id_project_seq', 1, false);
SELECT setval('report_id_report_seq', 1, false);
SELECT setval('section_cell_id_section_cell_seq', 1, false);
SELECT setval('user_inpulse_id_user_seq', 1, false);
INSERT INTO user_inpulse (user_surname, user_name, primary_mail, secondary_mail, phone_number)
VALUES ('Dupont', 'Dupond', 'super@mail.fr', 'super2@mail.fr', '06 45 72 45 98'),
('Martin', 'Matin', 'genial@mail.fr', 'genial2@mail.fr', '06 52 14 58 73'),
('Charvet', 'Lautre', 'mieux@tmail.fr', 'mieux2@tmail.fr', '07 49 82 16 35'),
('Leguez', 'Theo', 'bof@mesmails.fr', 'bof2@mesmails.fr', '+33 6 78 14 25 29'),
('Kia', 'Bi', 'special@mail.fr', 'special2@mail.fr', '07 65 31 38 95'),
('Ducaillou', 'Pierre', 'maildefou@xyz.fr', 'maildefou2@xyz.fr', '06 54 78 12 62'),
('Janine', 'Dave', 'janine@labri.fr', 'janine2@labri.fr', '06 87 12 45 95');
INSERT INTO administrator (id_administrator)
VALUES (7);
INSERT INTO project (project_name, logo, creation_date, project_status, id_administrator)
VALUES ('Eau du robinet', decode('013d7d16d7ad4fefb61bd95b765c8ceb', 'hex'), TO_DATE('01-OCT-2023', 'DD-MON-YYYY'),
'En cours', 7),
('Air oxygéné', decode('150647a0984e8f228cd14b54', 'hex'), TO_DATE('04-APR-2024', 'DD-MON-YYYY'), 'En cours', 7),
('Débat concours', decode('022024abd5486e245c145dda65116f', 'hex'), TO_DATE('22-NOV-2023', 'DD-MON-YYYY'),
'Suspendu', 7),
('HDeirbMI', decode('ab548d6c1d595a2975e6476f544d14c55a', 'hex'), TO_DATE('07-DEC-2024', 'DD-MON-YYYY'),
'Lancement', 7);
INSERT INTO entrepreneur (school, course, snee_status, id_entrepreneur, id_project_participation, id_project_proposed)
VALUES ('ENSEIRB-MATMECA', 'INFO', TRUE, 1, 4, 4),
('ENSC', 'Cognitique', TRUE, 2, 2, null),
('ENSEIRB-MATMECA', 'MATMECA', FALSE, 3, 3, 3),
('SupOptique', 'Classique', TRUE, 4, 1, 1),
('ENSEGID', 'Géoscience', FALSE, 5, 1, null),
('ENSMAC', 'Matériaux composites - Mécanique', FALSE, 6, 2, 2);
INSERT INTO section_cell (title, content_section_cell, modification_date, id_project)
VALUES ('Problème', 'les problèmes...', TO_TIMESTAMP('15-JAN-2025 09:30:20', 'DD-MON-YYYY, HH24:MI:SS'), 2),
('Segment de client', 'Le segment AB passant le client n°8 est de longueur 32mm.
Le segment BC a quant à lui un longueur de 28mm. Quelle la longueur du segment AC ?',
TO_TIMESTAMP('12-OCT-2022 17:47:38', 'DD-MON-YYYY, HH24:MI:SS'), 3),
('Proposition de valeur unique', '''Son prix est de 2594€'' ''Ah oui c''est unique en effet',
TO_TIMESTAMP('25-MAY-2024 11:12:04', 'DD-MON-YYYY, HH24:MI:SS'), 2),
('Solution', 'Un problème ? Une solution', TO_TIMESTAMP('08-FEB-2024 10:17:53', 'DD-MON-YYYY, HH24:MI:SS'), 1),
('Canaux', 'Ici nous avons la Seine, là-bas le Rhin, oh et plus loin le canal de Suez',
TO_TIMESTAMP('19-JUL-2023 19:22:45', 'DD-MON-YYYY, HH24:MI:SS'), 4),
('Sources de revenus', 'Y''en n''a pas on est pas payé. Enfin y''a du café quoi',
TO_TIMESTAMP('12-JAN-2025 11:40:26', 'DD-MON-YYYY, HH24:MI:SS'), 1),
('Structure des coûts', '''Ah oui là ça va faire au moins 1000€ par mois'', Eirbware',
TO_TIMESTAMP('06-FEB-2025 13:04:06', 'DD-MON-YYYY, HH24:MI:SS'), 3),
('Indicateurs clés', 'On apprend les clés comme des badges, ça se fait',
TO_TIMESTAMP('05-FEB-2025 12:42:38', 'DD-MON-YYYY, HH24:MI:SS'), 4),
('Avantages concurrentiel', 'On est meilleur', TO_TIMESTAMP('23-APR-2024 16:24:02', 'DD-MON-YYYY, HH24:MI:SS'),
2);
INSERT INTO appointment (appointment_date, appointment_time, appointment_duration, appointment_place,
appointment_subject)
VALUES (TO_DATE('24-DEC-2023', 'DD-MON-YYYY'), '00:00:00', '00:37:53', 'À la maison', 'Ouvrir les cadeaux'),
(TO_DATE('15-AUG-2024', 'DD-MON-YYYY'), '22:35:00', '00:12:36', 'Sur les quais ou dans un champ probablement',
'BOUM BOUM les feux d''artifices (on fête quoi déjà ?)'),
(TO_DATE('28-FEB-2023', 'DD-MON-YYYY'), '14:20:00', '00:20:00', 'Salle TD 15',
'Ah mince c''est pas une année bissextile !'),
(TO_DATE('23-JAN-2024', 'DD-MON-YYYY'), '12:56:27', '11:03:33', 'Là où le vent nous porte',
'Journée la plus importante de l''année'),
(TO_DATE('25-AUG-2025', 'DD-MON-YYYY'), '00:09:00', '01:00:00', 'Euh c''est par où l''amphi 56 ?',
'Rentrée scolaire (il fait trop froid c''est quoi ça on est en août)');
INSERT INTO report (report_content, id_appointment)
VALUES ('Ah oui ça c''est super, ah ouais j''aime bien, bien vu de penser à ça', 1),
('Bonne réunion', 3),
('Ouais, j''ai rien compris mais niquel on fait comme vous avez dit', 3),
('Non non ça va pas du tout ce que tu me proposes, faut tout refaire', 4),
('Réponse de la DSI : non', 2),
('Trop dommage qu''Apple ait sorti leur logiciel avant nous, on avait la même idée et tout on aurait tellement pu leur faire de la concurrence',
5);
INSERT INTO annotation (comment, id_administrator, id_section_cell)
VALUES ('faut changer ça hein', 7, 5),
('??? sérieusement, vous pensez que c''est une bonne idée ?', 7, 7),
('ok donc ça c''est votre business plan, bah glhf la team', 7, 2);

View File

@ -0,0 +1,2 @@
DROP TABLE IF EXISTS administrateurs, projets, utilisateurs, entrepreneurs, sections, rendez_vous, comptes_rendus, concerner CASCADE;
DROP TABLE IF EXISTS administrator, project, user_inpulse, entrepreneur, section_cell, appointment, make_appointment, report, annotation, concern CASCADE;

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="console"/>
</Root>
</Loggers>
</Configuration>

View File

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

View File

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

22
config/backdev.env Normal file
View File

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

View File

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

View File

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

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

22
config/dev.env Normal file
View File

@ -0,0 +1,22 @@
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: services:
postgres: postgres:
image: postgres:latest env_file: .env
build:
context: postgres/
dockerfile: Dockerfile
container_name: MyINPulse-DB container_name: MyINPulse-DB
#ports: #ports:
# - 5432:5432 # - 5432:5432
volumes: volumes:
- ./postgres:/var/lib/postgresql/data - ./postgres/data:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
keycloak: keycloak:
container_name: MyINPulse-keycloak container_name: MyINPulse-keycloak

22
config/frontdev.env Normal file
View File

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

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

22
config/prod.env Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

5
postgres/Dockerfile Normal file
View File

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

17
postgres/create_db.sh Normal file
View File

@ -0,0 +1,17 @@
#!/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

16
postgres/create_user.sh Normal file
View File

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