16 Commits

Author SHA1 Message Date
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
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
71 changed files with 3161 additions and 1261 deletions

View File

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

1
.gitignore vendored
View File

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

View File

@ -27,7 +27,7 @@ dev-front: clean vite
prod: clean
@cp config/prod.front.env front/MyINPulse-front/.env
@cp config/prod.main.env .env
@cp config/frontdev.docker-compose.yaml docker-compose.yaml
@cp config/prod.docker-compose.yaml docker-compose.yaml
@docker compose up -d --build

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,3 +2,10 @@ spring.application.name=myinpulse
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:7080/realms/test/protocol/openid-connect/certs
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/test
logging.level.org.springframework.security=DEBUG
spring.datasource.url=jdbc:postgresql://localhost:5432/${MyINPulse_DB}
spring.datasource.username=${POSTGRES_USER}
spring.datasource.password=${POSTGRES_PASSWORD}
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.data.rest.base-path=/my/base/path

View File

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

View File

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

View File

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

View File

@ -1,6 +1,14 @@
POSTGRES_DB=keycloak_db
POSTGRES_USER=keycloak_db_user
POSTGRES_PASSWORD=keycloak_db_user_password
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

View File

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

View File

@ -1,6 +1,14 @@
POSTGRES_DB=keycloak_db
POSTGRES_USER=keycloak_db_user
POSTGRES_PASSWORD=keycloak_db_user_password
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

View File

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

View File

@ -1,6 +1,14 @@
POSTGRES_DB=keycloak_db
POSTGRES_USER=keycloak_db_user
POSTGRES_PASSWORD=keycloak_db_user_password
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

View File

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

View File

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

View File

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

View File

@ -1,10 +0,0 @@
{
"entrepreneurs": [
{ "id": 1, "name": "Alice", "email": "alice@example.com" },
{ "id": 2, "name": "Bob", "email": "bob@example.com" },
{ "id": 3, "name": "Charlie", "email": "charlie@example.com" }
],
"data": [
{ "canva_data": "this is a fake data to test api" }
]
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -1,75 +0,0 @@
<template>
<div id="agenda">
<h3>Rendez-vous</h3>
<table>
<tbody>
<tr v-for=" (p, index) in projectRDV" :key="index" >
<td>{{ p.projectName }} </td> <td>{{ p.date }}</td> <td>{{ p.lieu }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup lang="ts">
import { defineProps } from "vue";
interface rendezVous{
projectName: String,
date: String,
lieu: String,
}
const props = defineProps<{
projectRDV: rendezVous[]
}>();
</script>
<style scoped>
#agenda {
padding: 20px;
}
/* Table Styling */
table {
width: 100%;
border-collapse: collapse;
font-family: Arial, sans-serif;
text-align: left;
margin-top: 20px;
border: 1px solid #ccc;
}
/* Header Row (if exists) */
th {
background-color: #f4f4f4;
padding: 12px;
font-weight: bold;
border: 1px solid #ccc;
}
/* Table Body Rows */
tbody tr {
border-bottom: 1px solid #ddd;
transition: background-color 0.2s ease; /* Smooth hover effect */
}
tbody tr:hover {
background-color: #f9f9f9; /* Highlight row on hover */
}
/* Cells Styling */
td {
padding: 10px;
border: 1px solid #eee;
font-size: 14px;
vertical-align: middle; /* Align text to middle */
}
/* First Column Styling */
td:first-child {
text-align: center;
width: 50px; /* Adjust width as needed */
}
</style>

View File

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

View File

@ -1,129 +0,0 @@
<template>
<div @click="goToLink" class="project">
<div class="project-header">
<h2 >{{ projectName }}</h2>
<div class="project-buttons">
<button class="contact-btn">Contact</button>
</div>
</div>
<div class="project-body">
<ul>
<li v-for="(name, index) in listName" :key="index">{{ name }}</li>
</ul>
</div>
</div>
</template>
<script setup lang="ts">
import { defineProps } from "vue";
import { useRouter } from 'vue-router'
const props = defineProps<{
projectName: string;
listName: string[];
projectLink: string;
}>();
const router = useRouter();
const goToLink = () => {
if (props.projectLink) {
router.push(props.projectLink);
}
};
</script>
<style scoped>
.project {
background: linear-gradient(to right, #f4f4f4, #ffffff);
border: 1px solid #ddd;
border-radius: 10px;
padding: 20px;
margin: 20px 0;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
font-family: Arial, sans-serif;
}
/* Header Styling */
.project-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.project-header h2 {
font-size: 20px;
color: #333;
margin: 0;
}
/* Button Container */
.project-buttons {
display: flex;
gap: 10px;
}
.info-btn {
background-color: #4CAF50;
color: #fff;
}
.info-btn:hover {
background-color: #45a049;
transform: scale(1.05);
}
.contact-btn {
background-color: #007BFF;
color: #fff;
}
.contact-btn:hover {
background-color: #0056b3;
transform: scale(1.05);
}
.project-body {
margin-top: 15px;
}
.project-body p {
font-size: 16px;
color: #555;
margin-bottom: 10px;
}
.project-body ul {
list-style-type: disc;
margin: 0;
padding-left: 20px;
}
.project-body ul li {
font-size: 14px;
color: #666;
line-height: 1.6;
}
button {
padding: 10px 15px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
border-radius: 5px;
}
button:hover {
background-color: #0056b3;
}
</style>

View File

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

View File

@ -1,88 +0,0 @@
<template>
<div :class="['cell', { expanded }]"
@click="toggleExpand"
:style="{ justifyContent: expanded ? 'flex-start' : 'center' }"> <!-- Looking for finding a way
to make this style in the toggleExpand event -->
<h3>{{ title }}</h3>
<p>{{ currentDescription }}</p>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps, onMounted } from "vue";
import axios from "axios";
const props = defineProps<{
title: string;
description: string;
}>();
const expanded = ref(false);
const currentDescription = ref(props.description);
const fetchData = async () => {
try {
const response = await axios.get("http://localhost:5000/data"); // Update the URL if needed
currentDescription.value = response.data[0].canva_data;
} catch (error) {
console.error("Erreur lors de la récupération des données :", error);
}
};
const toggleExpand = async () => {
if (!expanded.value) {
await fetchData();
} else {
currentDescription.value = props.description;
}
expanded.value = !expanded.value;
};
</script>
<style scoped>
@import "@/components/canvas/style-project.css";
.cell {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.1);
}
.cell:not(.expanded):hover {
transform: scale(1.05);
box-shadow: 0 8px 9px rgba(0, 0, 0, 0.2);
}
.cell h3 {
font-size: 20px;
font-weight: 500;
/*margin-bottom: 10px;*/
}
.cell p {
font-size: 14px;
color: #666;
}
.expanded {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: white;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
</style>

View File

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

View File

@ -1,56 +0,0 @@
<template>
<div class="canvas">
<CanvasItem
v-for="(item, index) in items"
:key="index"
:title="item.title"
:description="item.description"
:class="item.class"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import CanvasItem from "@/components/canvas/CanvasItem.vue";
const items = ref([
{ title: "1. Problème", description: "3 problèmes essentiels à résoudre pour le client", class: "Probleme" },
{ title: "2. Segments", description: "Les segments de clientèle visés", class: "Segments" },
{ title: "3. Valeur", description: "La proposition de valeur", class: "Valeur" },
{ title: "4. Solution", description: "Les solutions proposées", class: "Solution" },
{ title: "5. Avantage", description: "Les avantages concurrentiels", class: "Avantage" },
{ title: "6. Canaux", description: "Les canaux de distribution", class: "Canaux" },
{ title: "7. Indicateurs", description: "Les indicateurs clés de performance", class: "Indicateurs" },
{ title: "8. Coûts", description: "Les coûts associés", class: "Couts" },
{ title: "9. Revenus", description: "Les sources de revenus", class: "Revenus" }
]);
</script>
<style scoped>
@import "@/components/canvas/style-project.css";
.canvas {
display: grid;
grid-template-columns: repeat(10, 1fr);
grid-template-rows: repeat(6, 1fr);
gap: 10px;
padding: 10px;
max-width: 1200px;
margin: 20px auto;
background-color: #fff;
position: relative;
height: 80vh;
overflow: auto;
}
.Probleme { grid-column: 1 / 3; grid-row: 1 / 5; }
.Segments { grid-column: 9 / 11; grid-row: 1 / 5; }
.Valeur { grid-column: 5 / 7; grid-row: 1 / 5; }
.Solution { grid-column: 3 / 5; grid-row: 1 / 3; }
.Avantage { grid-column: 7 / 9; grid-row: 1 / 3; }
.Canaux { grid-column: 7 / 9; grid-row: 3 / 5; }
.Indicateurs { grid-column: 3 / 5; grid-row: 3 / 5; }
.Couts { grid-column: 1 / 6; grid-row: 5 / 7; }
.Revenus { grid-column: 6 / 11; grid-row: 5 / 7; }
</style>

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

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,77 +1,31 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/router.ts'
import {createPinia} from "pinia";
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
import AuthStorePlugin from './plugins/authStore';
import keycloakService from './services/keycloak';
import {useAuthStore} from "@/stores/authStore.ts";
let store: any;
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router/router.ts";
import { createPinia } from "pinia";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
import keycloakService from "./services/keycloak";
import { type AuthStore, useAuthStore } from "@/stores/authStore.ts";
let store: AuthStore;
keycloakService.CallInit(() => {
try {
const app = createApp(App)
const app = createApp(App);
// Setup pinia store, allowing user to keep logged in status after refresh
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
app.use(pinia);
app.use(AuthStorePlugin, { pinia });
store = useAuthStore();
app.use(router)
keycloakService.CallInitStore(store);
app.use(router);
app.mount('#app');
app.mount("#app");
} catch (e) {
console.error("Error while initiating Keycloak.")
console.error(e)
createApp(App).mount('#app');
console.error("Error while initiating Keycloak.");
console.error(e);
createApp(App).mount("#app");
}
});
})
// this shit made by me so i can run the canva vue app
createApp(App).use(router).mount('#app');
// TODO: fix the comment
/*
function tokenInterceptor () {
axios.interceptors.request.use(config => {
const keycloak = useKeycloak()
if (keycloak.authenticated) {
// Note that this is a simple example.
// you should be careful not to leak tokens to third parties.
// in this example the token is added to all usage of axios.
config.headers.Authorization = `Bearer ${keycloak.token}`
}
return config
}, error => {
console.error("tokenInterceptor: Rejected")
return Promise.reject(error)
})
}
*/
/*
app.use(VueKeyCloak,{
onReady: (keycloak) => {
console.log("Ready !")
tokenInterceptor()
},
init: {
onLoad: 'login-required',
checkLoginIframe: false,
},
config: {
realm: 'test',
url: 'http://localhost:7080',
clientId: 'myinpulse'
}
} );
*/
export {store};
export { store };

View File

@ -1,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,30 +1,17 @@
import { createRouter, createWebHistory } from 'vue-router'
import { createRouter, createWebHistory } from "vue-router";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/test',
name: 'test',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/test.vue'),
},
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/test",
name: "test",
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import("../views/testComponent.vue"),
},
],
});
{
path: '/',
name: 'Admin-main',
component: () => import('../views/AdminMain.vue'),
},
// route pour les canvas (made by adnane), in fact the two vue apps are separated for now
{
path: '/canvas',
name: 'canvas',
component: () => import('../views/CanvasView.vue'),
},
],
})
export default router
export default router;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -1,19 +0,0 @@
// @ts-check
import { test, expect } from '@playwright/test';
test('has title', async ({ page }) => {
await page.goto('https://playwright.dev/');
// Expect a title "to contain" a substring.
await expect(page).toHaveTitle(/Playwright/);
});
test('get started link', async ({ page }) => {
await page.goto('https://playwright.dev/');
// Click the get started link.
await page.getByRole('link', { name: 'Get started' }).click();
// Expects page to have a heading with the name of Installation.
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});

View File

@ -1,123 +0,0 @@
import { test,expect } from '@playwright/test'
test('Title Page',async({page}) =>{
await page.goto('http://localhost:5173/')
await expect(page).toHaveTitle(/Vite App/)
})
test('Navigation between pages', async ({ page }) => {
// Aller à la page d'accueil
await page.goto('http://localhost:5173/');
// Vérifier que l'URL a changé pour la page Canvas
await page.click('text=Canvas');
await expect(page).toHaveURL(/canvas/);
// Vérifier que l'URL a changé pour la page Home
await page.click('text=Home');
await expect(page).toHaveURL(/\//);
});
test.describe('Tests de la Page Canvas', () => {
test('Vérifier que tous les blocs Canvas sont affichés', async ({ page }) => {
// Aller à la page Canvas
await page.goto('http://localhost:5173/canvas');
await expect(page.locator('h1')).toHaveText('Page Canvas');
const sections = [
'1. Problème', '2. Segments', '3. Valeur', '4. Solution',
'5. Avantage', '6. Canaux', '7. Indicateurs', '8. Coûts', '9. Revenus'
];
for (const section of sections) {
await expect(page.locator(`text=${section}`)).toBeVisible();
}
});
test('Vérifier le contenu de la page Canvas', async ({ page }) => {
await page.goto('http://localhost:5173/canvas');
// Attendre le chargement des éléments
await page.waitForSelector('.canvas');
// Vérifier le titre
await expect(page.locator('h1')).toHaveText('Page Canvas');
// Nouveau sélecteur plus précis
const canvasItems = page.locator('.canvas > div'); // Sélectionne les div directes dans .canvas
// Vérifier le nombre d'éléments
await expect(canvasItems).toHaveCount(9);
// Contenu attendu
const expectedContent = [
{ title: '1. Problème', description: '3 problèmes essentiels à résoudre pour le client' },
{ title: '2. Segments', description: 'Les segments de clientèle visés' },
{ title: '3. Valeur', description: 'La proposition de valeur' },
{ title: '4. Solution', description: 'Les solutions proposées' },
{ title: '5. Avantage', description: 'Les avantages concurrentiels' },
{ title: '6. Canaux', description: 'Les canaux de distribution' },
{ title: '7. Indicateurs', description: 'Les indicateurs clés de performance' },
{ title: '8. Coûts', description: 'Les coûts associés' },
{ title: '9. Revenus', description: 'Les sources de revenus' }
];
// Vérifier chaque élément
for (let i = 0; i < expectedContent.length; i++) {
const item = canvasItems.nth(i);
await expect(item.locator('h3')).toHaveText(expectedContent[i].title);
await expect(item.locator('p')).toHaveText(expectedContent[i].description);
}
});
});
test.describe('Tests de la page Home', () => {
test('Vérifier la présence des projets', async ({ page }) => {
await page.goto('http://localhost:5173');
// Vérifier les titres des projets avec getByRole pour éviter les doublons
await expect(page.getByRole('heading', { name: 'Projet Alpha' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Projet Beta' })).toBeVisible();
});
test('Vérifier les membres des projets', async ({ page }) => {
await page.goto('http://localhost:5173');
const membresAlpha = ['Alice', 'Bob', 'Charlie'];
const membresBeta = ['David', 'Eve', 'Frank'];
for (const membre of membresAlpha) {
await expect(page.getByText(membre)).toBeVisible();
}
for (const membre of membresBeta) {
await expect(page.getByText(membre)).toBeVisible();
}
});
test('Vérifier les boutons Contact', async ({ page }) => {
await page.goto('http://localhost:5173');
// Vérifier que les boutons "Contact" existent et sont visibles
const contactButtons = await page.locator('button:has-text("Contact")').count();
expect(contactButtons).toBe(2); // Vérifie qu'il y a bien 2 boutons Contact
});
test('Vérifier la table des rendez-vous', async ({ page }) => {
await page.goto('http://localhost:5173');
await expect(page.getByRole('heading', { name: 'Rendez-vous' })).toBeVisible();
// Vérifier la première ligne du tableau
await expect(page.locator('table').getByRole('cell', { name: 'Projet Alpha' })).toBeVisible();
await expect(page.getByText('2025-03-10')).toBeVisible();
await expect(page.getByText('P106')).toBeVisible();
// Vérifier la deuxième ligne du tableau
await expect(page.locator('table').getByRole('cell', { name: 'Projet Beta' })).toBeVisible();
await expect(page.getByText('2025-04-15')).toBeVisible();
await expect(page.getByText('Td10')).toBeVisible();
});
});