Skip to content

Commit 9c03efd

Browse files
authored
Make a fusion of the domains + Refactoring + Responsive design (better) + Contact page (#17)
* Fusion of two domains into one : local + dev * PASS VITE_API_URL in arg * Correct the Brevo ceertif thing * Correct the Brevo ceertif trying printf * log the appearence * awk * log file brevo.crt * echo -e * try with secret file if render * correct path to brevo.pem in render * no_certif and repair send_verification_mail * Responsive design for mobile corrected * Add id to correct linting probs in ts * Correct avatar images and no hover burger * ADD README + Contact + some corrections and refactoring * add images to readme * add images to readme * add images to readme * add images to readme * Correct dev.yml * mock in test * remove fusion-domains
1 parent fd557c0 commit 9c03efd

36 files changed

+745
-199
lines changed

.dockerignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,3 @@ WS/db.sqlite3
2323

2424
# Certs
2525
*.pem
26-
WS/brevo.pem

.github/workflows/dev.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,7 @@ jobs:
4040
echo "DATABASE_PASSWORD=test_pass" >> .env
4141
echo "DATABASE_HOST=db" >> .env
4242
echo "DATABASE_PORT=5432" >> .env
43-
- name: Recreate brevo.pem
44-
run: |
45-
echo "${{ secrets.BREVO_PEM }}" > ./WS/brevo.pem
43+
echo "EMAIL_BACKEND=django.core.mail.backends.locmem.EmailBackend" >> .env
4644
- name: Testing on Docker
4745
run: |
4846
docker-compose -f docker-compose.yml --env-file .env -p nextshape_test up --abort-on-container-exit --build

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,4 @@ dist-ssr
197197
*.sln
198198
*.sw?
199199

200-
WS/brevo.pem
200+
.env.prod

README.md

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,91 @@
11
# NextShape
22

3-
Une application web permettant de calculer les calories, l'IMC d'un utilisateur et de lui donner un suivi de son évolution au cours du temps
3+
<p align="center">
4+
<img src="UI/public/App.png" alt="APP">
5+
</p>
6+
7+
NextShape est une application web de suivi de forme physique, qui permet à un utilisateur de :
8+
9+
- Gérer son **compte utilisateur** (inscription avec vérification e-mail, connexion, modification, suppression, réinitialisation du mot de passe).
10+
- Calculer son **IMC**, son **BMR**, son **TDEE** et ses **calories recommandées** en fonction de ses données personnelles.
11+
- Visualiser l'évolution de ses données corporelles dans un **historique**.
12+
13+
## Fonctionnalités
14+
15+
- Authentification JWT via cookies HttpOnly
16+
- Calcul de l'IMC
17+
- Calcul du métabolisme de base (BMR), du TDEE et des calories recommandées
18+
- Historique dynamique avec tableau et modale responsive
19+
- Mise à jour et suppression des enregistrements
20+
- Envoi de codes par email via SMTP (Brevo)
21+
- Pipeline CI/CD avec test et déploiement Render (dev/prod)
22+
23+
## Outils :
24+
25+
L'application est réalisée avec :
26+
27+
- **Frontend** : Vue 3, TypeScript, PrimeVue, Pinia, Vite
28+
- **Backend** : Django, Django REST Framework, JWT par cookie HttpOnly
29+
- **Base de données** : PostgreSQL
30+
- **CI/CD** : Docker, docker-compose, Render, GitHub Actions
31+
32+
<p align="center">
33+
<img src="UI/public/Architecture.PNG" alt="Architecture Front-Back-BDD" width="500">
34+
</p>
35+
36+
## Installation
37+
38+
### Prérequis
39+
40+
- Docker + Docker Compose + PostgreSQL
41+
- Créer un fichier `.env` à la racine avec les variables suivantes :
42+
43+
```
44+
# Environnement et secrets Django
45+
ENV=local
46+
DJANGO_SECRET_KEY=django-insecure-...
47+
DJANGO_DEBUG=True
48+
DJANGO_ALLOWED_HOSTS=127.0.0.1,localhost
49+
50+
# Secrets de la base de données
51+
DATABASE_NAME=nextshape_db
52+
DATABASE_USER=nextshape_user
53+
DATABASE_PASSWORD=nextshape_pwd
54+
DATABASE_HOST=db #(en lançant avec Docker et localhost si on veut lancer le serveur avec le terminal)
55+
DATABASE_PORT=5432
56+
57+
# Secrets pour l'envoi de mails à partir du serveur Django
58+
#(Exemple : utilisation de Brevo)
59+
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
60+
EMAIL_HOST=smtp.brevo.com
61+
EMAIL_PORT=587
62+
EMAIL_HOST_USER=user@email.com
63+
EMAIL_HOST_PASSWORD=motdepasse
64+
DEFAULT_FROM_EMAIL=NextShape <dev@email.com>
65+
EMAIL_USE_TLS=True
66+
67+
# URL vers l'API qu'utilisera le front pour envoyer les requêtes.
68+
VITE_API_URL=http://localhost:8000/api/
69+
```
70+
71+
- Lancer en local
72+
73+
```
74+
docker-compose up --build
75+
```
76+
77+
- Puis aller sur :
78+
79+
- Interface web : http://localhost
80+
81+
- API : http://localhost:8000/api/
82+
83+
### Base de données de test
84+
85+
Un utilisateur fictif avec des données déjà initialisées peut être un bon moyen de faire une démo très rapidement.
86+
C'est pour cela que durant le ENTRYPOINT, on réalise un **seed** qui injecte ces données fictives :
87+
88+
- Utilisateur :
89+
- Email : jessem@mail.com
90+
- Mot de passe : 123 (Pas du tout sécurisé comme mot de passe)
91+
- Les données de l'utilisateur (des calculs de besoins caloriques) trouvés à la page Historique.

UI/Dockerfile

Lines changed: 0 additions & 26 deletions
This file was deleted.

UI/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"type": "module",
66
"scripts": {
77
"dev": "vite",
8-
"build": "vue-tsc -b && vite build",
8+
"build": "vue-tsc --noEmit && vite build",
99
"preview": "vite preview"
1010
},
1111
"dependencies": {

UI/public/App.png

178 KB
Loading

UI/public/Architecture.PNG

1.08 MB
Loading

UI/src/assets/js/interfaces.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export interface User {
4141

4242
// ProgressRecord structure
4343
export interface ProgressRecord {
44+
id: number
4445
user: User | null
4546
date: string | null
4647
weight_kg: number | null
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<script setup lang="ts">
2+
import Dialog from "primevue/dialog"
3+
import InputNumber from "primevue/inputnumber"
4+
import Select from "primevue/select"
5+
import Button from "primevue/button"
6+
import { ACTIVITY_LEVELS, ACTIVITY_DESCRIPTIONS } from "@/assets/js/constants"
7+
import { useProgressRecords } from "@/stores/progressRecordsStore"
8+
import { showToast } from "@/assets/js/utils"
9+
import { useToast } from "primevue"
10+
import { ref, watch } from "vue"
11+
12+
const toast = useToast()
13+
const store = useProgressRecords()
14+
15+
const visible = defineModel<boolean>("visible")
16+
const recordId = defineModel<number | null>("recordId")
17+
18+
const weight_kg = ref<number | null>(null)
19+
const height_cm = ref<number | null>(null)
20+
const activity_level = ref("")
21+
22+
const goal = ref("")
23+
const goalOptions: Array<Record<string, string>> = [
24+
{ label: "Maintien", value: "maintien" },
25+
{ label: "Perte de poids", value: "perte" },
26+
{ label: "Prise de masse", value: "prise" },
27+
]
28+
// Préremplir quand on ouvre la modale
29+
const load = () => {
30+
const record = store.progressRecords.find((r) => r.id === recordId.value)
31+
if (record) {
32+
weight_kg.value = record.weight_kg
33+
height_cm.value = record.height_cm
34+
activity_level.value = record.activity_level ?? ""
35+
goal.value = record.goal ?? ""
36+
}
37+
}
38+
39+
watch(visible, (val) => {
40+
if (val) load()
41+
})
42+
43+
const save = async () => {
44+
if (
45+
weight_kg.value === null ||
46+
height_cm.value === null ||
47+
activity_level.value === "" ||
48+
goal.value === ""
49+
) {
50+
showToast(
51+
toast,
52+
"error",
53+
"Champs requis",
54+
"Veuillez remplir tous les champs."
55+
)
56+
return
57+
}
58+
59+
try {
60+
await store.updateRecord(recordId.value!, {
61+
weight_kg: weight_kg.value,
62+
height_cm: height_cm.value,
63+
activity_level: activity_level.value,
64+
goal: goal.value,
65+
})
66+
showToast(toast, "success", "Succès", "Enregistrement modifié avec succès.")
67+
visible.value = false
68+
} catch (err) {
69+
showToast(
70+
toast,
71+
"error",
72+
"Erreur",
73+
"Impossible de modifier l’enregistrement."
74+
)
75+
}
76+
}
77+
</script>
78+
79+
<template>
80+
<Dialog
81+
v-model:visible="visible"
82+
modal
83+
header="Modifier l'enregistrement"
84+
class="mx-1 w-full sm:w-10 md:w-6"
85+
>
86+
<div class="formgrid grid">
87+
<div class="field col-12">
88+
<label>Poids (kg)</label>
89+
<InputNumber
90+
v-model="weight_kg"
91+
suffix=" kg"
92+
showButtons
93+
class="w-full"
94+
:min="0"
95+
:minFractionDigits="1"
96+
:maxFractionDigits="1"
97+
/>
98+
</div>
99+
100+
<div class="field col-12">
101+
<label>Taille (cm)</label>
102+
<InputNumber
103+
v-model="height_cm"
104+
suffix=" cm"
105+
showButtons
106+
class="w-full"
107+
:min="0"
108+
:minFractionDigits="1"
109+
:maxFractionDigits="1"
110+
/>
111+
</div>
112+
113+
<div class="field col-12">
114+
<label>Activité physique</label>
115+
<Select
116+
v-model="activity_level"
117+
:options="ACTIVITY_LEVELS"
118+
optionLabel="label"
119+
optionValue="value"
120+
class="w-full"
121+
>
122+
<template #option="slotProps">
123+
<div class="flex flex-col gap-1">
124+
<span class="font-semibold">{{ slotProps.option.label }}</span>
125+
<small class="text-xs text-gray-500">
126+
{{ ACTIVITY_DESCRIPTIONS[slotProps.option.value] }}
127+
</small>
128+
</div>
129+
</template>
130+
</Select>
131+
</div>
132+
133+
<div class="field col-12">
134+
<label>Objectif</label>
135+
<Select
136+
v-model="goal"
137+
:options="goalOptions"
138+
optionLabel="label"
139+
optionValue="value"
140+
class="w-full"
141+
/>
142+
</div>
143+
</div>
144+
145+
<div class="flex justify-end gap-2 mt-4">
146+
<Button label="Annuler" severity="secondary" @click="visible = false" />
147+
<Button
148+
label="Enregistrer"
149+
icon="pi pi-check"
150+
severity="success"
151+
@click="save"
152+
/>
153+
</div>
154+
</Dialog>
155+
</template>

0 commit comments

Comments
 (0)