16 Commits

Author SHA1 Message Date
47be7d340b fix: formatter
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 13s
Format / formatting (pull_request) Successful in 7s
2025-05-14 09:06:52 +02:00
232d10b164 fix: formatter
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 43s
CI / build (push) Successful in 13s
Format / formatting (pull_request) Failing after 6s
2025-05-14 09:04:35 +02:00
bc7ce888ad fix: build
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 44s
CI / build (push) Successful in 12s
Format / formatting (pull_request) Failing after 5s
2025-05-13 20:00:51 +02:00
ed67a3734a fix: fix build
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 43s
CI / build (push) Failing after 12s
2025-05-13 19:56:03 +02:00
95eb154556 fix: eslint
Some checks failed
Format / formatting (push) Failing after 7s
Build / build (push) Successful in 43s
CI / build (push) Failing after 11s
2025-05-13 19:52:31 +02:00
19fef63b0e fix: merge
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 45s
CI / build (push) Failing after 11s
2025-05-11 21:18:38 +02:00
1fd95265ea fix: this is not gonna work in time, switching to mock mod for canvas so we can show what we supposed to have atleast (local canvas) and we can explain what is the problem (you can disable the mock by putting the var USE_MOCK to false in CanvasItem.vue but dont do it) 2025-05-11 21:17:09 +02:00
3ef2d8a198 fix: join or create
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 43s
CI / build (push) Failing after 10s
2025-05-11 20:37:57 +02:00
6b49bbbe57 fix: handlers weren't used properly in the unauth code
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 10s
2025-05-10 23:35:30 +02:00
4c15cab607 fix: still working on the pending
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 12s
2025-05-10 23:16:08 +02:00
abfe92bc87 Merge: branch 'backend-test' into front_foundation
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 9s
2025-05-10 21:54:20 +02:00
85b4fe6a4c fix: finalize logic
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 10s
2025-05-10 21:48:41 +02:00
f2448a029f added two endpoints necessary for routing in project request phase
All checks were successful
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 41s
CI / build (push) Successful in 11s
2025-05-10 20:39:45 +02:00
cef4daef15 feat: finalize2
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 42s
CI / build (push) Failing after 13s
2025-05-10 18:10:39 +02:00
f5aba70017 feat: add finalise logic
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 12s
2025-05-10 17:59:05 +02:00
27adc81ddc fix: minor change
Some checks failed
Format / formatting (push) Failing after 6s
Build / build (push) Successful in 41s
CI / build (push) Failing after 9s
2025-05-10 01:16:00 +02:00
15 changed files with 413 additions and 1038 deletions

View File

@ -95,4 +95,22 @@ public class EntrepreneurApi {
@RequestBody Project project, @AuthenticationPrincipal Jwt principal) { @RequestBody Project project, @AuthenticationPrincipal Jwt principal) {
entrepreneurApiService.requestNewProject(project, principal.getClaimAsString("email")); entrepreneurApiService.requestNewProject(project, principal.getClaimAsString("email"));
} }
/*
* <p>Endpoint to check if project is has already been validated by an admin
*/
@GetMapping("/entrepreneur/projects/project-is-active")
public Boolean checkIfProjectValidated(@AuthenticationPrincipal Jwt principal) {
return entrepreneurApiService.checkIfEntrepreneurProjectActive(
principal.getClaimAsString("email"));
}
/*
* <p>Endpoint to check if a user requested a project (used when project is pending)
*/
@GetMapping("/entrepreneur/projects/has-pending-request")
public Boolean checkIfHasRequested(@AuthenticationPrincipal Jwt principal) {
return entrepreneurApiService.entrepreneurHasPendingRequestedProject(
principal.getClaimAsString("email"));
}
} }

View File

@ -1,8 +1,6 @@
package enseirb.myinpulse.controller; package enseirb.myinpulse.controller;
import enseirb.myinpulse.model.Administrator;
import enseirb.myinpulse.model.Entrepreneur; import enseirb.myinpulse.model.Entrepreneur;
import enseirb.myinpulse.service.AdminApiService;
import enseirb.myinpulse.service.EntrepreneurApiService; import enseirb.myinpulse.service.EntrepreneurApiService;
import enseirb.myinpulse.service.UtilsService; import enseirb.myinpulse.service.UtilsService;

View File

@ -1,10 +1,12 @@
package enseirb.myinpulse.service; package enseirb.myinpulse.service;
import static enseirb.myinpulse.model.ProjectDecisionValue.PENDING; import static enseirb.myinpulse.model.ProjectDecisionValue.PENDING;
import static enseirb.myinpulse.model.ProjectDecisionValue.ACTIVE;
import enseirb.myinpulse.model.Entrepreneur; import enseirb.myinpulse.model.Entrepreneur;
import enseirb.myinpulse.model.Project; import enseirb.myinpulse.model.Project;
import enseirb.myinpulse.model.SectionCell; import enseirb.myinpulse.model.SectionCell;
import enseirb.myinpulse.model.User;
import enseirb.myinpulse.service.database.*; import enseirb.myinpulse.service.database.*;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -234,4 +236,49 @@ public class EntrepreneurApiService {
public Iterable<Entrepreneur> getAllEntrepreneurs() { public Iterable<Entrepreneur> getAllEntrepreneurs() {
return entrepreneurService.getAllEntrepreneurs(); return entrepreneurService.getAllEntrepreneurs();
} }
/**
* Checks if an entrepreneur with the given email has a project that is ACTIVE.
*
* @param email The email of the entrepreneur.
* @return true if the entrepreneur has an active project, false otherwise.
*/
public Boolean checkIfEntrepreneurProjectActive(String email) {
User user = this.userService.getUserByEmail(email);
if (user == null) {
return false;
}
Long userId = user.getIdUser();
Entrepreneur entrepreneur = this.entrepreneurService.getEntrepreneurById(userId);
if (entrepreneur == null) {
return false;
}
Project proposedProject = entrepreneur.getProjectProposed();
return proposedProject != null && proposedProject.getProjectStatus() == ACTIVE;
}
/**
* Checks if an entrepreneur with the given email has proposed a project.
*
* @param email The email of the entrepreneur.
* @return true if the entrepreneur has a proposed project, false otherwise.
*/
public Boolean entrepreneurHasPendingRequestedProject(String email) {
User user = this.userService.getUserByEmail(email);
if (user == null) {
return false;
}
Long userId = user.getIdUser();
Entrepreneur entrepreneur = this.entrepreneurService.getEntrepreneurById(userId);
if (entrepreneur == null) {
return false;
}
Project proposedProject = entrepreneur.getProjectProposed();
if (entrepreneur.getProjectProposed() == null) {
return false;
}
return proposedProject.getProjectStatus() == PENDING;
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -142,5 +142,56 @@ paths:
description: Bad Request - Invalid input or ID mismatch. description: Bad Request - Invalid input or ID mismatch.
"401": "401":
description: Unauthorized or identity not found description: Unauthorized or identity not found
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/projects/project-is-active:
get:
summary: checks if the project associated with an entrepreneur is active
description: returns a boolean if the project associated with an entrepreneur has an active status
(i.e has been validated by an admin). The user should be routed to LeanCanvas. any other response code
should be treated as false
tags:
- Entrepreneurs API
security:
- MyINPulse: [MyINPulse-entrepreneur]
parameters:
responses:
"200":
description: OK - got the value successfully any other response code should be treated as false.
content:
application/json:
schema:
type: boolean
"404":
description: Bad Request - Invalid input or ID mismatch.
"401":
description: Unauthorized or identity not found
"403":
description: Bad Token - Invalid Keycloack configuration.
/entrepreneur/projects/has-pending-request:
get:
summary: checks if the user has a pending projectRequest
description: returns a boolean if the project associated with an entrepreneur has a pending status
(i.e has not yet been validated by an admin). The user should be routed to a page telling him that he should
wait for admin validation. any other response code should be treated as false.
tags:
- Entrepreneurs API
security:
- MyINPulse: [MyINPulse-entrepreneur]
parameters:
responses:
"200":
description: OK - got the value successfully any other response code should be treated as false.
content:
application/json:
schema:
type: boolean
"404":
description: Bad Request - Invalid input or ID mismatch.
"401":
description: Unauthorized or identity not found
"403": "403":
description: Bad Token - Invalid Keycloack configuration. description: Bad Token - Invalid Keycloack configuration.

View File

@ -152,4 +152,8 @@ paths:
/entrepreneur/sectionCells: /entrepreneur/sectionCells:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells" $ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells"
/entrepreneur/sectionCells/{sectionCellId}: /entrepreneur/sectionCells/{sectionCellId}:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells~1{sectionCellId}" $ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1sectionCells~1{sectionCellId}"
/entrepreneur/projects/project-is-active:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects~1project-is-active"
/entrepreneur/projects/has-pending-request:
$ref: "./entrepreneurApi.yaml#/paths/~1entrepreneur~1projects~1has-pending-request"

View File

@ -1,9 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref } from "vue"; import { onMounted } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { jwtDecode } from "jwt-decode"; // i hope this doesn't break the code later import { jwtDecode } from "jwt-decode"; // i hope this doesn't break the code later
import { store } from "../main.ts"; import { store } from "../main.ts";
import { callApi } from "@/services/api.ts"; import { checkPending } from "@/services/Apis/Unauth";
import Header from "@/components/HeaderComponent.vue"; import Header from "@/components/HeaderComponent.vue";
const router = useRouter(); const router = useRouter();
@ -13,7 +13,7 @@ type TokenPayload = {
}; };
}; };
const customRequest = ref(""); //const customRequest = ref("");
onMounted(() => { onMounted(() => {
if (store.authenticated && store.user.token) { if (store.authenticated && store.user.token) {
@ -23,26 +23,44 @@ onMounted(() => {
if (roles.includes("MyINPulse-admin")) { if (roles.includes("MyINPulse-admin")) {
router.push("/admin"); router.push("/admin");
} else if (roles.includes("MyINPulse-entrepreneur")) { return;
}
if (roles.includes("MyINPulse-entrepreneur")) {
router.push("/canvas"); router.push("/canvas");
return;
} }
else{
router.push("/JorCproject") checkPending(
} (response) => {
const isValidated = response.data === true;
if (
isValidated &&
roles.includes("MyINPulse-entrepreneur")
) {
router.push("/canvas");
//router.push("/JorCproject");
} else {
router.push("/JorCproject");
//router.push("/finalize");
}
},
(error) => {
if (error.response?.status === 403) {
router.push("/finalize");
} else {
console.error(
"Unexpected error during checkPending",
error
);
}
}
);
} catch (err) { } catch (err) {
console.error("Failed to decode token", err); console.error("Failed to decode token", err);
} }
} }
}); });
/*
const loading = ref(false);
const callApiWithLoading = async (path: string) => {
loading.value = true;
await callApi(path);
loading.value = false;
};
*/
</script> </script>
<template> <template>
@ -50,7 +68,7 @@ const callApiWithLoading = async (path: string) => {
<error-wrapper></error-wrapper> <error-wrapper></error-wrapper>
<div class="auth-container"> <div class="auth-container">
<div class="auth-card"> <div class="auth-card">
<h1>Bienvenue</h1> <h1>Bienvenue à MyINPulse</h1>
<div <div
class="status" class="status"
@ -68,11 +86,12 @@ const callApiWithLoading = async (path: string) => {
<div class="actions"> <div class="actions">
<button @click="store.login">Login</button> <button @click="store.login">Login</button>
<button @click="store.logout">Logout</button> <button @click="store.logout">Logout</button>
<button @click="store.signup">Signup-admin</button> <!--<button @click="store.signup">Signup-admin</button>
<button @click="store.signup">Signup-Entrepreneur</button> <button @click="store.signup">Signup-Entrepreneur</button>
<button @click="store.refreshUserToken">Refresh Token</button> <button @click="store.refreshUserToken">Refresh Token</button>-->
</div> </div>
<!--
<div v-if="store.authenticated" class="token-section"> <div v-if="store.authenticated" class="token-section">
<p><strong>Access Token:</strong></p> <p><strong>Access Token:</strong></p>
<pre>{{ store.user.token }}</pre> <pre>{{ store.user.token }}</pre>
@ -96,7 +115,7 @@ const callApiWithLoading = async (path: string) => {
/> />
<button @click="callApi(customRequest)">Call</button> <button @click="callApi(customRequest)">Call</button>
</div> </div>
</div> </div>-->
</div> </div>
</div> </div>
</template> </template>

View File

@ -110,7 +110,7 @@ const props = defineProps<{
isAdmin: boolean; isAdmin: boolean;
}>(); }>();
const IS_MOCK_MODE = false; const IS_MOCK_MODE = true;
const IS_ADMIN = props.isAdmin; const IS_ADMIN = props.isAdmin;
const expanded = ref(false); const expanded = ref(false);

View File

@ -40,6 +40,18 @@ const router = createRouter({
name: "JorCproject", name: "JorCproject",
component: () => import("../views/JoinOrCreatProjectForEntrep.vue"), component: () => import("../views/JoinOrCreatProjectForEntrep.vue"),
}, },
{
path: "/finalize",
name: "finalize",
component: () => import("../views/FinalizeAccount.vue"),
},
{
path: "/pending-approval",
name: "PendingApproval",
component: () => import("@/views/PendingApproval.vue"),
},
], ],
}); });

View File

@ -123,10 +123,58 @@ function removeSectionCell(
}); });
} }
// Checks if the entrepreneur has a pending project request
function checkPendingProjectRequest(
onSuccessHandler?: (response: AxiosResponse<boolean>) => void,
onErrorHandler?: (error: AxiosError) => void
): void {
axiosInstance
.get("/entrepreneur/projects/has-pending-request")
.then((response) => {
if (onSuccessHandler) {
onSuccessHandler(response);
} else {
defaultApiSuccessHandler(response);
}
})
.catch((error: AxiosError) => {
if (onErrorHandler) {
onErrorHandler(error);
} else {
defaultApiErrorHandler(error);
}
});
}
// Checks if the entrepreneur has an active project
function checkIfProjectIsActive(
onSuccessHandler?: (response: AxiosResponse<boolean>) => void,
onErrorHandler?: (error: AxiosError) => void
): void {
axiosInstance
.get("/entrepreneur/projects/project-is-active")
.then((response) => {
if (onSuccessHandler) {
onSuccessHandler(response);
} else {
defaultApiSuccessHandler(response);
}
})
.catch((error: AxiosError) => {
if (onErrorHandler) {
onErrorHandler(error);
} else {
defaultApiErrorHandler(error);
}
});
}
export { export {
getEntrepreneurProjectId, getEntrepreneurProjectId,
requestProjectCreation, requestProjectCreation,
addSectionCell, addSectionCell,
modifySectionCell, modifySectionCell,
removeSectionCell, removeSectionCell,
checkPendingProjectRequest,
checkIfProjectIsActive,
}; };

View File

@ -74,8 +74,30 @@ function getAllEntrepreneurs(
}); });
} }
function checkPending(
onSuccessHandler?: (response: AxiosResponse<boolean>) => void,
onErrorHandler?: (error: AxiosError) => void
): void {
axiosInstance
.get("/unauth/check-if-not-pending")
.then((response) => {
if (onSuccessHandler) {
onSuccessHandler(response);
} else {
defaultApiSuccessHandler(response);
}
})
.catch((error: AxiosError) => {
if (onErrorHandler) {
onErrorHandler(error);
} else {
defaultApiErrorHandler(error);
}
});
}
export { export {
finalizeAccount, finalizeAccount,
getAllEntrepreneurs, getAllEntrepreneurs,
checkPending,
// requestJoinProject, // Not yet implemented [cite: 4] // requestJoinProject, // Not yet implemented [cite: 4]
}; };

View File

@ -70,7 +70,6 @@ const fallbackProjects = [
}, },
]; ];
const createFirstAdmin = () => { const createFirstAdmin = () => {
createAdmin( createAdmin(
(response) => { (response) => {
@ -85,7 +84,7 @@ const createFirstAdmin = () => {
); );
}; };
onMounted(createFirstAdmin) onMounted(createFirstAdmin);
const fetchProjects = () => { const fetchProjects = () => {
getAdminProjects( getAdminProjects(

View File

@ -0,0 +1,81 @@
<template>
<Header />
<div class="finalize-page">
<div class="loader-container">
<button class="return-button" @click="store.logout">Logout</button>
<div class="spinner"></div>
<p>Finalisation du compte en cours...</p>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted } from "vue";
//import { useRouter } from "vue-router";
import { finalizeAccount } from "@/services/Apis/Unauth";
import Header from "@/components/HeaderComponent.vue";
import { store } from "@/main.ts";
//const router = useRouter();
onMounted(() => {
finalizeAccount(
() => {
console.log("finalize sended");
},
(error) => {
console.error("Erreur lors de la finalisation :", error);
}
);
});
</script>
<style scoped>
.finalize-page {
display: flex;
justify-content: center;
align-items: center;
min-height: 80vh;
background-color: #f9fbfd;
}
.loader-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
color: #333;
font-size: 1.1rem;
font-style: italic;
}
.spinner {
width: 50px;
height: 50px;
border: 5px solid #cfd8dc;
border-top: 5px solid #3498db;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
.return-button {
background-color: #009cde;
color: white;
border: none;
padding: 10px 15px;
cursor: pointer;
font-size: 14px;
border-radius: 5px;
text-decoration: none;
transition: background-color 0.2s ease;
font-family: Arial, sans-serif;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@ -1,10 +1,12 @@
<template> <template>
<Header />
<header class="header"> <header class="header">
<img <img
src="@/components/icons/logo inpulse.png" src="@/components/icons/logo inpulse.png"
alt="INPulse Logo" alt="INPulse Logo"
class="logo" class="logo"
/> />
<button class="return-button" @click="store.logout">Logout</button>
</header> </header>
<div class="choix-projet"> <div class="choix-projet">
@ -39,10 +41,18 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";
import Project from "@/ApiClasses/Project"; import Project from "@/ApiClasses/Project";
import { requestProjectCreation } from "@/services/Apis/Entrepreneurs.ts"; import Header from "../components/HeaderComponent.vue";
import { store } from "@/main.ts";
import {
requestProjectCreation,
checkIfProjectIsActive,
checkPendingProjectRequest,
} from "@/services/Apis/Entrepreneurs";
const router = useRouter();
const choix = ref<string | null>(null); const choix = ref<string | null>(null);
const nomProjet = ref(""); const nomProjet = ref("");
@ -56,21 +66,18 @@ const validerCreation = () => {
return; return;
} }
// Obtenir la date actuelle au format YYYY-MM-DD
const today = new Date(); const today = new Date();
const yyyy = today.getFullYear(); const yyyy = today.getFullYear();
const mm = String(today.getMonth() + 1).padStart(2, "0"); const mm = String(today.getMonth() + 1).padStart(2, "0");
const dd = String(today.getDate()).padStart(2, "0"); const dd = String(today.getDate()).padStart(2, "0");
const formattedDate = `${yyyy}-${mm}-${dd}`; const formattedDate = `${yyyy}-${mm}-${dd}`;
// Créer une instance de Project
const nouveauProjet = new Project({ const nouveauProjet = new Project({
projectName: nomProjet.value.trim(), projectName: nomProjet.value.trim(),
creationDate: formattedDate, creationDate: formattedDate,
status: "PENDING", status: "PENDING",
}); });
// Appeler lAPI
requestProjectCreation( requestProjectCreation(
nouveauProjet, nouveauProjet,
(response) => { (response) => {
@ -83,6 +90,28 @@ const validerCreation = () => {
} }
); );
}; };
onMounted(() => {
checkIfProjectIsActive(
(response) => {
if (response.data === true) {
router.push("/canvas");
}
},
() => {
checkPendingProjectRequest(
(response) => {
if (response.data === true) {
router.push("/pending-approval");
}
},
(error) => {
console.warn("No active or pending project:", error);
}
);
}
);
});
</script> </script>
<style scoped> <style scoped>
@ -140,4 +169,17 @@ input {
.logo { .logo {
height: 50px; height: 50px;
} }
.return-button {
background-color: #009cde;
color: white;
border: none;
padding: 10px 15px;
cursor: pointer;
font-size: 14px;
border-radius: 5px;
text-decoration: none;
transition: background-color 0.2s ease;
font-family: Arial, sans-serif;
}
</style> </style>

View File

@ -0,0 +1,41 @@
<template>
<Header />
<div class="pending-container">
<h1>Projet en attente de validation</h1>
<p>
Votre demande de création de projet a bien été reçue.<br />
Un administrateur doit valider votre projet avant que vous puissiez
continuer.
</p>
</div>
</template>
<script setup lang="ts">
import Header from "@/components/HeaderComponent.vue";
</script>
<style scoped>
.pending-container {
max-width: 600px;
margin: 100px auto;
padding: 2rem;
text-align: center;
background-color: #fffdf8;
border: 1px solid #ececec;
border-radius: 12px;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
font-family: "Inter", sans-serif;
}
h1 {
color: #f57c00;
font-size: 1.8rem;
margin-bottom: 1rem;
}
p {
font-size: 1.1rem;
color: #333;
margin-bottom: 1.5rem;
}
</style>