Skip to content

Commit 5caca95

Browse files
authored
Merge pull request #65 from stevenferey/develop
Release: dependency updates, Zod 4 fix & maintenance
2 parents 4e4c043 + b95dc4f commit 5caca95

8 files changed

Lines changed: 1273 additions & 1423 deletions

File tree

backend/package-lock.json

Lines changed: 775 additions & 969 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"jsonwebtoken": "^9.0.3",
3535
"multer": "^2.0.2",
3636
"nodemailer": "^7.0.11",
37-
"pdfmake": "^0.2.20",
37+
"pdfmake": "^0.3.0",
3838
"pg": "^8.13.1",
3939
"resend": "^6.6.0"
4040
},
@@ -48,7 +48,7 @@
4848
"@types/nodemailer": "^7.0.4",
4949
"@types/pg": "^8.11.10",
5050
"eslint": "^9.17.0",
51-
"globals": "^16.5.0",
51+
"globals": "^17.0.0",
5252
"nodemon": "^3.1.9",
5353
"prettier": "^3.4.2",
5454
"tsx": "^4.19.2",

docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ services:
99
POSTGRES_DB: cra_db
1010
POSTGRES_USER: cra_user
1111
POSTGRES_PASSWORD: cra_password
12+
PGDATA: /var/lib/postgresql/data/18/docker
1213
volumes:
1314
- postgres_data:/var/lib/postgresql/data
1415
- ./backend/migrations:/docker-entrypoint-initdb.d

package-lock.json

Lines changed: 356 additions & 353 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"eslint-config-prettier": "^10.1.8",
4545
"eslint-plugin-react-hooks": "^7.0.0",
4646
"eslint-plugin-react-refresh": "^0.4.16",
47-
"globals": "^16.0.0",
47+
"globals": "^17.0.0",
4848
"postcss": "^8.5.6",
4949
"prettier": "^3.6.2",
5050
"tailwindcss": "^4.1.16",

src/components/layout/AppLayout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ export function AppLayout() {
257257
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
258258
<div className="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0">
259259
<div className="text-sm text-[rgb(var(--color-text-muted))]">
260-
© 2025 DiscoData. Tous droits réservés.
260+
© {new Date().getFullYear()} DiscoData. Tous droits réservés.
261261
</div>
262262
<div className="flex items-center space-x-6 text-sm text-[rgb(var(--color-text-muted))]">
263263
{hasDonationAddresses && (

src/schemas/cra.schema.ts

Lines changed: 112 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -9,103 +9,118 @@ function getDaysInMonth(month: number, year: number): number {
99
}
1010

1111
/**
12-
* Schéma de validation pour la création/édition d'un CRA mensuel
12+
* Valide qu'un tableau de jours n'a pas de doublons
13+
*/
14+
function hasNoDuplicateDays(days: number[]): boolean {
15+
return new Set(days).size === days.length;
16+
}
17+
18+
/**
19+
* Valide que tous les jours sont dans la plage 1-31
20+
*/
21+
function allDaysInValidRange(days: number[]): boolean {
22+
return days.every((day) => day >= 1 && day <= 31);
23+
}
24+
25+
/**
26+
* Valide que tous les jours sont valides pour un mois/année donnés
27+
*/
28+
function allDaysValidForMonth(
29+
days: number[],
30+
month: number,
31+
year: number,
32+
): boolean {
33+
const daysInMonth = getDaysInMonth(month, year);
34+
return days.every((day) => day <= daysInMonth);
35+
}
36+
37+
/**
38+
* Expression régulière pour valider un UUID
39+
*/
40+
const UUID_REGEX =
41+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
42+
43+
/**
44+
* Schéma de base pour un CRA (sans worked_days ni refinements cross-field)
45+
* Utilisé pour dériver les schémas complet et draft
1346
*/
14-
export const craFormSchema = z
15-
.object({
16-
month: z
17-
.number({ message: 'Le mois est requis' })
18-
.int('Le mois doit être un nombre entier')
19-
.min(1, 'Le mois doit être entre 1 et 12')
20-
.max(12, 'Le mois doit être entre 1 et 12'),
21-
year: z
22-
.number({ message: "L'année est requise" })
23-
.int("L'année doit être un nombre entier")
24-
.min(2000, "L'année doit être supérieure ou égale à 2000")
25-
.max(2100, "L'année doit être inférieure ou égale à 2100")
26-
.refine(
27-
(year) => {
28-
// Ne pas permettre les années trop lointaines dans le futur
29-
const currentYear = new Date().getFullYear();
30-
return year <= currentYear + 5;
31-
},
32-
{ message: "L'année ne peut pas être trop éloignée dans le futur" },
33-
),
34-
worked_days: z
35-
.array(z.number().int('Les jours doivent être des nombres entiers'))
36-
.min(1, 'Au moins un jour travaillé est requis')
37-
.max(31, 'Le nombre de jours travaillés ne peut pas dépasser 31')
38-
.refine(
39-
(days) => {
40-
// Vérifier qu'il n'y a pas de doublons
41-
const uniqueDays = new Set(days);
42-
return uniqueDays.size === days.length;
43-
},
44-
{ message: 'Les jours travaillés ne doivent pas contenir de doublons' },
45-
)
46-
.refine(
47-
(days) => {
48-
// Vérifier que tous les jours sont dans la plage 1-31
49-
return days.every((day) => day >= 1 && day <= 31);
50-
},
51-
{ message: 'Les jours doivent être entre 1 et 31' },
52-
),
53-
comment: z
54-
.string()
55-
.max(1000, 'Le commentaire ne peut pas dépasser 1000 caractères')
56-
.optional(),
57-
client_id: z
58-
.string()
59-
.min(1, 'Le client est requis')
60-
.refine(
61-
(val) => {
62-
// Si la valeur est vide, c'est déjà géré par .min(1)
63-
// Sinon, vérifier que c'est un UUID valide
64-
if (val.length === 0) return false;
65-
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
66-
val,
67-
);
68-
},
69-
{ message: 'Le client sélectionné est invalide' },
70-
),
71-
provider_id: z
72-
.string()
73-
.min(1, 'Le prestataire est requis')
74-
.refine(
75-
(val) => {
76-
// Si la valeur est vide, c'est déjà géré par .min(1)
77-
// Sinon, vérifier que c'est un UUID valide
78-
if (val.length === 0) return false;
79-
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
80-
val,
81-
);
82-
},
83-
{ message: 'Le prestataire sélectionné est invalide' },
84-
),
85-
status: z.enum(CRA_STATUSES).optional().default('draft'),
47+
const craFormBaseSchema = z.object({
48+
month: z
49+
.number({ message: 'Le mois est requis' })
50+
.int('Le mois doit être un nombre entier')
51+
.min(1, 'Le mois doit être entre 1 et 12')
52+
.max(12, 'Le mois doit être entre 1 et 12'),
53+
year: z
54+
.number({ message: "L'année est requise" })
55+
.int("L'année doit être un nombre entier")
56+
.min(2000, "L'année doit être supérieure ou égale à 2000")
57+
.max(2100, "L'année doit être inférieure ou égale à 2100")
58+
.refine(
59+
(year) => {
60+
const currentYear = new Date().getFullYear();
61+
return year <= currentYear + 5;
62+
},
63+
{ message: "L'année ne peut pas être trop éloignée dans le futur" },
64+
),
65+
comment: z
66+
.string()
67+
.max(1000, 'Le commentaire ne peut pas dépasser 1000 caractères')
68+
.optional(),
69+
client_id: z
70+
.string()
71+
.min(1, 'Le client est requis')
72+
.refine((val) => val.length === 0 || UUID_REGEX.test(val), {
73+
message: 'Le client sélectionné est invalide',
74+
}),
75+
provider_id: z
76+
.string()
77+
.min(1, 'Le prestataire est requis')
78+
.refine((val) => val.length === 0 || UUID_REGEX.test(val), {
79+
message: 'Le prestataire sélectionné est invalide',
80+
}),
81+
status: z.enum(CRA_STATUSES).optional().default('draft'),
82+
83+
// Signatures (optionnelles, overrides des signatures par défaut)
84+
client_signatory_name: z.string().max(255).optional(),
85+
client_signatory_title: z.string().max(255).optional(),
86+
client_signature_image: z.string().optional(),
87+
client_signature_location: z.string().max(255).optional(),
88+
client_use_current_date: z.boolean().optional(),
89+
provider_signatory_name: z.string().max(255).optional(),
90+
provider_signatory_title: z.string().max(255).optional(),
91+
provider_signature_image: z.string().optional(),
92+
provider_signature_location: z.string().max(255).optional(),
93+
provider_use_current_date: z.boolean().optional(),
94+
});
8695

87-
// Signatures (optionnelles, overrides des signatures par défaut)
88-
client_signatory_name: z.string().max(255).optional(),
89-
client_signatory_title: z.string().max(255).optional(),
90-
client_signature_image: z.string().optional(), // Base64 data URL, pas de limite
91-
client_signature_location: z.string().max(255).optional(),
92-
client_use_current_date: z.boolean().optional(),
93-
provider_signatory_name: z.string().max(255).optional(),
94-
provider_signatory_title: z.string().max(255).optional(),
95-
provider_signature_image: z.string().optional(), // Base64 data URL, pas de limite
96-
provider_signature_location: z.string().max(255).optional(),
97-
provider_use_current_date: z.boolean().optional(),
96+
/**
97+
* Schéma pour worked_days avec validations complètes (requis)
98+
*/
99+
const workedDaysRequiredSchema = z
100+
.array(z.number().int('Les jours doivent être des nombres entiers'))
101+
.min(1, 'Au moins un jour travaillé est requis')
102+
.max(31, 'Le nombre de jours travaillés ne peut pas dépasser 31')
103+
.refine(hasNoDuplicateDays, {
104+
message: 'Les jours travaillés ne doivent pas contenir de doublons',
105+
})
106+
.refine(allDaysInValidRange, {
107+
message: 'Les jours doivent être entre 1 et 31',
108+
});
109+
110+
/**
111+
* Schéma de validation pour la création/édition d'un CRA mensuel
112+
* Inclut les validations cross-field (client != provider, jours valides pour le mois)
113+
*/
114+
export const craFormSchema = craFormBaseSchema
115+
.extend({
116+
worked_days: workedDaysRequiredSchema,
98117
})
99118
.refine((data) => data.client_id !== data.provider_id, {
100119
message: 'Le client et le prestataire doivent être différents',
101120
path: ['provider_id'],
102121
})
103122
.refine(
104-
(data) => {
105-
// Vérifier que tous les jours travaillés sont valides pour le mois sélectionné
106-
const daysInMonth = getDaysInMonth(data.month, data.year);
107-
return data.worked_days.every((day) => day <= daysInMonth);
108-
},
123+
(data) => allDaysValidForMonth(data.worked_days, data.month, data.year),
109124
{
110125
message: 'Certains jours sélectionnés sont invalides pour le mois choisi',
111126
path: ['worked_days'],
@@ -119,14 +134,15 @@ export type CRAFormData = z.infer<typeof craFormSchema>;
119134

120135
/**
121136
* Validation partielle pour la sauvegarde en brouillon
137+
* - worked_days est optionnel (tableau vide par défaut)
138+
* - client_id et provider_id sont optionnels
139+
* - Pas de validation cross-field
122140
*/
123-
export const craFormDraftSchema = craFormSchema
124-
.partial({
125-
worked_days: true,
126-
})
127-
.extend({
128-
worked_days: z.array(z.number().int()).optional().default([]),
129-
});
141+
export const craFormDraftSchema = craFormBaseSchema.extend({
142+
worked_days: z.array(z.number().int()).optional().default([]),
143+
client_id: z.string().optional().default(''),
144+
provider_id: z.string().optional().default(''),
145+
});
130146

131147
/**
132148
* Type pour le brouillon

start-dev.sh

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
# Script de démarrage pour l'environnement de développement Crafter
44
# Ce script démarre Docker, initialise la base de données, et lance le backend et le frontend
5+
#
6+
# Usage:
7+
# ./start-dev.sh # Démarrage normal
8+
# ./start-dev.sh --fresh # Réinstalle les dépendances (npm ci) - à utiliser après git pull
9+
# ./start-dev.sh -f # Alias pour --fresh
510

611
set -e # Arrêter en cas d'erreur
712

@@ -20,6 +25,12 @@ echo "║ ║"
2025
echo "╚═══════════════════════════════════════════╝"
2126
echo ""
2227

28+
# Option --fresh pour forcer npm ci
29+
FRESH_INSTALL=false
30+
if [ "$1" = "--fresh" ] || [ "$1" = "-f" ]; then
31+
FRESH_INSTALL=true
32+
fi
33+
2334
# Fonction pour afficher les messages
2435
log_info() {
2536
echo -e "${BLUE}[INFO]${NC} $1"
@@ -95,8 +106,21 @@ else
95106
log_warning "Database tables already exist. Skipping initialization."
96107
fi
97108

109+
# Installation des dépendances
110+
if [ "$FRESH_INSTALL" = true ]; then
111+
log_info "Fresh install requested, running npm ci..."
112+
npm ci
113+
log_success "Frontend dependencies installed (fresh)"
114+
cd backend && npm ci && cd ..
115+
log_success "Backend dependencies installed (fresh)"
116+
elif [ ! -d "node_modules" ]; then
117+
log_info "Installing frontend dependencies..."
118+
npm install
119+
log_success "Frontend dependencies installed"
120+
fi
121+
98122
# Vérifier si node_modules existe dans backend
99-
if [ ! -d "backend/node_modules" ]; then
123+
if [ "$FRESH_INSTALL" = false ] && [ ! -d "backend/node_modules" ]; then
100124
log_info "Installing backend dependencies..."
101125
cd backend && npm install && cd ..
102126
log_success "Backend dependencies installed"

0 commit comments

Comments
 (0)