myhappywallet
Application fullstack de gestion budgétaire permettant de suivre charges/revenus et de calculer un reste à vivre, réalisée en fil rouge du CDA Simplon. Le socle technique couvre un backend Node.js/TypeScript/Express/Prisma, un frontend React, une CI GitLab et un déploiement VPS OVH avec 212 commits sur 3 dépôts.
🎯 Contexte et objectifs
- Concevoir et développer une application de gestion budgétaire permettant de suivre charges/revenus et calculer un « reste à vivre ».
- Mettre en place un socle fullstack industrialisable: API sécurisée, frontend avec gestion d’état, validation de données et documentation d’API.
- Outiller le cycle projet de bout en bout: cadrage produit (UX, story mapping, user stories), modélisation de données, CI/CD et déploiement.
🛠️ Réalisations
Projets
- cda-my-happy-wallet-backend
- cda-my-happy-wallet-frontend
Projet-00-chef-oeuvre-CDA(Project-00-chef-oeuvre/Projet-00-chef-oeuvre-CDA)
🧩 Conception
La conception est structurée avec vision produit, personas, story mapping, user stories, UML, Merise, puis passage en implémentation. Côté “engineering documentation”, le repo de rapport contient une chaîne LaTeX avancée (minted) et un lexer Python dédié JSX.
- Projet-00-chef-oeuvre-CDA: formalisation du besoin produit et du périmètre fonctionnel (budget, reste à vivre réel/fictif, objectifs). Source:
Project-00-chef-oeuvre/Projet-00-chef-oeuvre-CDA/Rapport-CDA-2021-2022-CAPAI_Andria.tex:475.
\section{P\textsc{résentation du chef d'oeuvre}}
Le produit/service proposé est une application "MyHappyWallet" ...
... ses \textbf{charges et sources de revenu} ... connaitre son \gls{ravr} ...
... atteindre ses \textbf{objectifs financiers} ...
Pour calculer son \gls{ravrg} , l'utilisateur doit être \textbf{enregistré}
... charges et revenus fixes ...
Le système renseigne à l'utilisateur un \textbf{bilan mensuel}
... montant (solde) du "Reste à vivre" ...
- Projet-00-chef-oeuvre-CDA: cadrage méthodologique Agile/Scrum avec priorisation du backlog par itérations courtes. Source:
Project-00-chef-oeuvre/Projet-00-chef-oeuvre-CDA/Rapport-CDA-2021-2022-CAPAI_Andria.tex:629.
Dans ce chapitre est présenté les différents éléments utilisés pour la gestion du projet "MyHappyWallet".
... méthode \Gls{agile}.
Cette méthode implique 3 rôles dans l'équipe \Gls{scrum}.
\begin{enumerate}
\item Le \Gls{po}
\item \Gls{sm}
\item L'équipe de réalisation
\end{enumerate}
... la méthode \Gls{scrum} s'appuie sur des \Glspl{sprint} ...
- Projet-00-chef-oeuvre-CDA: personnalisation de la chaîne documentaire LaTeX pour supporter proprement la coloration JSX dans le rapport technique. Source:
Project-00-chef-oeuvre/Projet-00-chef-oeuvre-CDA/lexer.py:8.
# Use same tokens as `JavascriptLexer`, but with tags and attributes support
TOKENS = JavascriptLexer.tokens
TOKENS.update(
{
"jsx": [
(r"(<)(/?)(>)", bygroups(Punctuation, Punctuation, Punctuation)),
(r"(<)([\w]+)(\.?)", bygroups(Punctuation, Name.Tag, Punctuation), "tag"),
(r"(<)(/)([\w]+)(>)", bygroups(Punctuation, Punctuation, Name.Tag, Punctuation)),
],
"tag": [
(r"([\w]+\s*)(=)(\s*)", bygroups(Name.Attribute, Operator, Text), "attr"),
],
}
)
💻 Développement
- cda-my-happy-wallet-backend: architecture d’initialisation API avec pipeline middleware (parsing, cookies, router, gestion d’erreurs), pratique attendue en backend Node/Express. Source:
src/server.ts.
export const createServer = async () => {
const server: express.Application = express();
server.use(bodyParser.urlencoded({ extended: true }))
server.use(bodyParser.json())
server.use('/api-docs',swaggerUI.serve,swaggerUI.setup(swDocument))
server.use(cookieParser());
if (NODE_ENV === 'development') {
server.use(morgan('dev'));
}
server.use(APP_BASE_URL as string, mainRouter)
server.use(notFoundRouter)
server.use(errorHandler)
if (NODE_ENV === 'development') {
server.use(errorLogging);
}
return server
}
- cda-my-happy-wallet-backend: modélisation relationnelle avec Prisma (types forts, relations, enums), compétence clé sur la couche persistance. Source:
src/database/schema.prisma.
model OperationFixe {
idOperationFixe Int @id @default(autoincrement())
titre String @db.VarChar(50)
montant Decimal @db.Decimal(10, 2)
devise String? @db.VarChar(3)
typeOperation TypeOperationFixeEnum @default(CHARGE)
User Utilisateur @relation(fields: [userId],references: [id])
userId Int
}
model Utilisateur {
id Int @id @default(autoincrement())
email String @db.VarChar(255) @unique
role Role @default(USER)
verified Boolean @default(false)
operationsFixes OperationFixe[]
}
enum TypeOperationFixeEnum { CHARGE REVENU }
enum Role { ADMIN USER }
- cda-my-happy-wallet-backend: authentification robuste (vérification compte + hash Argon2 + génération JWT access/refresh). Source:
src/modules/user/useCases/login/login.ts.
const user = await this.userRepo.getUserByEmail(email);
if (!user) {
throw new ErrorException(ErrorCode.EmailPasswordNotValid);
}
const isAccountVerified = await this.userRepo.isUserAccountVerified(email)
if (!isAccountVerified) {
throw new ErrorException(ErrorCode.EmailPasswordNotValid);
}
const passwordMatches = await argon2.verify(user.password,password)
if (!passwordMatches) {
throw new ErrorException(ErrorCode.EmailPasswordNotValid);
}
const jwtToken = sign({ id: user.id }, ACCESS_TOKEN_SECRET as string, {expiresIn:"60s"})
const refreshToken = sign({ id: user.id }, REFRESH_TOKEN_SECRET as string, {expiresIn:"15min"})
- cda-my-happy-wallet-backend: rotation de token via endpoint dédié (validation contexte utilisateur + renouvellement cookie sécurisé). Source:
src/modules/auth/accessTokenRenew.ts.
jwt.verify(cookies.refresh_token, REFRESH_TOKEN_SECRET as string, (err: any) => {
if (err) {
res.clearCookie("refresh_token");
res.clearCookie("id_user");
return next(new ErrorException(ErrorCode.Unauthorized));
}
const accessToken = jwt.sign({ id: user.id }, ACCESS_TOKEN_SECRET as string, {
expiresIn: "5min",
});
const refreshToken = jwt.sign({ id: user.id }, REFRESH_TOKEN_SECRET as string, {
expiresIn: "20min",
});
res.cookie("id_user", user.id, { httpOnly: true, secure: true, maxAge: 900000 });
res.cookie("refresh_token", refreshToken, { httpOnly: true, secure: true, maxAge: 900000 });
return res.status(200).json({ success: true, payload: { user: data, accessToken } });
});
- cda-my-happy-wallet-backend: logique métier calculatoire encapsulée côté repository/use case pour maintenir la cohérence des agrégats financiers (RaV). Source:
src/modules/operationsFixes/operationFixeRepo.ts.
public async updateRaV(userId:string, idRaV:number){
const allRevenus = await this.getAllRevenus(userId,"REVENU")
const allCharges = await this.getAllCharges(userId,"CHARGE")
const totalCharges = allCharges.data.reduce(
(accumulator:any, current:any) => accumulator + parseFloat(current.montant), 0
);
const totalRevenus = allRevenus.data.reduce(
(accumulator:any, current:any) => accumulator + parseFloat(current.montant), 0
);
const rav = totalRevenus - totalCharges;
await RaVEntity.updateMany({
where: { idRaV: idRaV, userId: parseInt(userId) },
data: { montantRaV: rav, montantTotalDepense: totalCharges, montantTotalEntree: totalRevenus },
})
}
- cda-my-happy-wallet-backend: industrialisation DevOps backend avec GitLab CI + déploiement PM2 (préprod/prod). Source:
.gitlab-ci.yml.
build:
stage: deploy_pre_prod
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
when: always
script:
- node --version
- pm2 deploy ecosystem.config.js development setup 2>&1 || true
- pm2 deploy ecosystem.config.js development
deploy:
stage: deploy_prod
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: always
script:
- pm2 deploy ecosystem.config.js production setup 2>&1 || true
- pm2 deploy ecosystem.config.js production
- cda-my-happy-wallet-frontend: structuration SPA avec routes publiques/privées, navigation protégée et fallback explicite. Source:
src/js/App.jsx.
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/forgot-password" element={<ForgotPassword />} />
<Route path="/new-password" element={<NewPassword />} />
<Route path="home" element={<RequireAuth><Sidebar /><Home /></RequireAuth>} />
<Route path="home/operations-fixes" element={<RequireAuth><Sidebar /><OperationsFixes/></RequireAuth>} />
<Route path="/" element={<Navigate replace to="/home" />} />
<Route path="/calendrier" element={<RequireAuth><Sidebar /><Calendrier /></RequireAuth>} />
<Route path="*" element={<NotFound />}></Route>
</Routes>
- cda-my-happy-wallet-frontend: interception Axios pour refresh automatique du token expiré, pattern recherché pour des frontends sécurisés. Source:
src/utils/axiosHelper.js.
if (user && user?.payload.accessToken) {
let accessToken = user.payload.accessToken;
req.headers.Authorization = `Bearer ${accessToken}`;
const decodedToken = jwt_decode(accessToken);
const isExpired = decodedToken.exp * 1000 < currentDate.getTime();
if (!isExpired) return req;
let body = { grant_type: "refresh_token", email: user.payload.user.email };
await store.dispatch(newRefreshToken({ body, accessToken }));
let newAccessToken = store?.getState()?.auth?.user.payload.accessToken;
req.headers.Authorization = `Bearer ${newAccessToken}`;
req.withCredentials = true;
return req;
}
- cda-my-happy-wallet-frontend: gestion d’état asynchrone avec Redux Toolkit (thunks + reducers de cycle de vie), pattern standard pour applications React en production. Source:
src/js/slices/operationsFixes/operationsFixesSlice.js.
export const revenusApi = createAsyncThunk('operationsFixes/revenus', async (thunkAPI) => {
try {
const response = await operationsFixesService.getAllRevenus()
return response.data
} catch (error) {
const ErrorObjet = await handleExceptionPayload(error)
return thunkAPI.rejectWithValue(ErrorObjet.message)
}
})
export const operationsFixesSlice = createSlice({
name: "operationsFixes",
initialState,
extraReducers:(builder)=>{
builder
.addCase(revenusApi.pending, (state) => { state.isLoading = true })
.addCase(revenusApi.fulfilled, (state, action) => {
state.isLoading = false
state.revenus.isSuccess = true
state.revenus.data = action.payload.data
})
}
});
🏗️ Infrastructure et déploiement
- Backend: pipeline GitLab CI avec déploiement PM2 (branches
developetmain) et scriptspost-deployversionnés. - Frontend: pipeline GitLab CI avec build Vite et synchronisation des fichiers
dist/vers serveur viarsync. - Déploiement multi-environnements observé dans la configuration (
development/production) et gestion des variables via environnement CI.
🧭 Organisation / méthodologie
- Démarche produit outillée: vision, story mapping, user stories, modélisation UML/Merise, puis exécution technique.
- Séparation des responsabilités côté code: routes/controllers/use cases/repos côté backend; services/slices/components côté frontend.
- Documentation technique intégrée au projet (rapport détaillé + annexes de code + Swagger).
📈 Résultats
- Résultat global: 212 commits consolidés sur 3 dépôts, couvrant un flux complet conception -> développement -> déploiement.
Projet cda-my-happy-wallet-backend
- Nb commits: 104
- Nb contributeurs: 2
- Nb PR/issues: non consolidé dans ce périmètre local
- Période: 2022-01-12 -> 2026-02-14
Projet cda-my-happy-wallet-frontend
- Nb commits: 53
- Nb contributeurs: 2
- Nb PR/issues: non consolidé dans ce périmètre local
- Période: 2022-01-12 -> 2026-02-14
Projet Projet-00-chef-oeuvre-CDA
- Nb commits: 55
- Nb contributeurs: 2
- Nb PR/issues: non consolidé dans ce périmètre local
- Période: 2021-08-01 -> 2022-04-25
🔧 Environnement technique
- Backend: TypeScript, Node.js, Express, Prisma, MySQL, Joi, JSON Web Token, Argon2, Swagger UI, PM2.
- Frontend: React, React Router, Redux Toolkit, Axios, Formik, Yup, Styled Components, Vite.
- DevOps: GitLab CI/CD, PM2 deploy, rsync, runners CI, gestion d’environnements.
- Conception/documentation: LaTeX (minted), Figma, UML, Merise, scripts Python auxiliaires pour la publication technique.
Technologies utilisées
Backend
Express
Node.js
DevOps
GitLab CI/CD
Bases de donnees (SGBD & SQL)
MySQL
Design Patterns & Architecture
Prisma
Frontend
React
TypeScript