Compare commits
No commits in common. "31bd8c3175bb1ceffa9a380385997e66573121de" and "111935e4512fc5bcd7a85380e6075a1ce66e19a9" have entirely different histories.
31bd8c3175
...
111935e451
@ -1,27 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Test POST /auth/register/am (ticket #90)
|
|
||||||
# Usage: ./scripts/test-register-am.sh [BASE_URL]
|
|
||||||
# Exemple: ./scripts/test-register-am.sh https://app.ptits-pas.fr/api/v1
|
|
||||||
# ./scripts/test-register-am.sh http://localhost:3000/api/v1
|
|
||||||
|
|
||||||
BASE_URL="${1:-http://localhost:3000/api/v1}"
|
|
||||||
echo "Testing POST $BASE_URL/auth/register/am"
|
|
||||||
echo "---"
|
|
||||||
|
|
||||||
curl -s -w "\n\nHTTP %{http_code}\n" -X POST "$BASE_URL/auth/register/am" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"email": "marie.dupont.test@ptits-pas.fr",
|
|
||||||
"prenom": "Marie",
|
|
||||||
"nom": "DUPONT",
|
|
||||||
"telephone": "0612345678",
|
|
||||||
"adresse": "1 rue Test",
|
|
||||||
"code_postal": "75001",
|
|
||||||
"ville": "Paris",
|
|
||||||
"consentement_photo": true,
|
|
||||||
"nir": "123456789012345",
|
|
||||||
"numero_agrement": "AGR-2024-001",
|
|
||||||
"capacite_accueil": 4,
|
|
||||||
"acceptation_cgu": true,
|
|
||||||
"acceptation_privacy": true
|
|
||||||
}'
|
|
||||||
@ -3,8 +3,8 @@ import { LoginDto } from './dto/login.dto';
|
|||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { Public } from 'src/common/decorators/public.decorator';
|
import { Public } from 'src/common/decorators/public.decorator';
|
||||||
import { RegisterDto } from './dto/register.dto';
|
import { RegisterDto } from './dto/register.dto';
|
||||||
|
import { RegisterParentDto } from './dto/register-parent.dto';
|
||||||
import { RegisterParentCompletDto } from './dto/register-parent-complet.dto';
|
import { RegisterParentCompletDto } from './dto/register-parent-complet.dto';
|
||||||
import { RegisterAMCompletDto } from './dto/register-am-complet.dto';
|
|
||||||
import { ChangePasswordRequiredDto } from './dto/change-password.dto';
|
import { ChangePasswordRequiredDto } from './dto/change-password.dto';
|
||||||
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||||
import { AuthGuard } from 'src/common/guards/auth.guard';
|
import { AuthGuard } from 'src/common/guards/auth.guard';
|
||||||
@ -53,16 +53,12 @@ export class AuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Public()
|
@Public()
|
||||||
@Post('register/am')
|
@Post('register/parent/legacy')
|
||||||
@ApiOperation({
|
@ApiOperation({ summary: '[OBSOLÈTE] Inscription Parent (étape 1/6 uniquement)' })
|
||||||
summary: 'Inscription Assistante Maternelle COMPLÈTE',
|
@ApiResponse({ status: 201, description: 'Inscription réussie' })
|
||||||
description: 'Crée User AM + entrée assistantes_maternelles (identité + infos pro + photo + CGU) en une transaction',
|
|
||||||
})
|
|
||||||
@ApiResponse({ status: 201, description: 'Inscription réussie - Dossier en attente de validation' })
|
|
||||||
@ApiResponse({ status: 400, description: 'Données invalides ou CGU non acceptées' })
|
|
||||||
@ApiResponse({ status: 409, description: 'Email déjà utilisé' })
|
@ApiResponse({ status: 409, description: 'Email déjà utilisé' })
|
||||||
async inscrireAMComplet(@Body() dto: RegisterAMCompletDto) {
|
async registerParentLegacy(@Body() dto: RegisterParentDto) {
|
||||||
return this.authService.inscrireAMComplet(dto);
|
return this.authService.registerParent(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Public()
|
@Public()
|
||||||
|
|||||||
@ -8,12 +8,11 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
|
|||||||
import { Users } from 'src/entities/users.entity';
|
import { Users } from 'src/entities/users.entity';
|
||||||
import { Parents } from 'src/entities/parents.entity';
|
import { Parents } from 'src/entities/parents.entity';
|
||||||
import { Children } from 'src/entities/children.entity';
|
import { Children } from 'src/entities/children.entity';
|
||||||
import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity';
|
|
||||||
import { AppConfigModule } from 'src/modules/config';
|
import { AppConfigModule } from 'src/modules/config';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([Users, Parents, Children, AssistanteMaternelle]),
|
TypeOrmModule.forFeature([Users, Parents, Children]),
|
||||||
forwardRef(() => UserModule),
|
forwardRef(() => UserModule),
|
||||||
AppConfigModule,
|
AppConfigModule,
|
||||||
JwtModule.registerAsync({
|
JwtModule.registerAsync({
|
||||||
|
|||||||
@ -13,14 +13,13 @@ import * as crypto from 'crypto';
|
|||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { RegisterDto } from './dto/register.dto';
|
import { RegisterDto } from './dto/register.dto';
|
||||||
|
import { RegisterParentDto } from './dto/register-parent.dto';
|
||||||
import { RegisterParentCompletDto } from './dto/register-parent-complet.dto';
|
import { RegisterParentCompletDto } from './dto/register-parent-complet.dto';
|
||||||
import { RegisterAMCompletDto } from './dto/register-am-complet.dto';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { RoleType, StatutUtilisateurType, Users } from 'src/entities/users.entity';
|
import { RoleType, StatutUtilisateurType, Users } from 'src/entities/users.entity';
|
||||||
import { Parents } from 'src/entities/parents.entity';
|
import { Parents } from 'src/entities/parents.entity';
|
||||||
import { Children, StatutEnfantType } from 'src/entities/children.entity';
|
import { Children, StatutEnfantType } from 'src/entities/children.entity';
|
||||||
import { ParentsChildren } from 'src/entities/parents_children.entity';
|
import { ParentsChildren } from 'src/entities/parents_children.entity';
|
||||||
import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity';
|
|
||||||
import { LoginDto } from './dto/login.dto';
|
import { LoginDto } from './dto/login.dto';
|
||||||
import { AppConfigService } from 'src/modules/config/config.service';
|
import { AppConfigService } from 'src/modules/config/config.service';
|
||||||
|
|
||||||
@ -117,7 +116,7 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inscription utilisateur OBSOLÈTE - Utiliser inscrireParentComplet() ou registerAM()
|
* Inscription utilisateur OBSOLÈTE - Utiliser registerParent() ou registerAM()
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
async register(registerDto: RegisterDto) {
|
async register(registerDto: RegisterDto) {
|
||||||
@ -158,6 +157,125 @@ export class AuthService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inscription Parent (étape 1/6 du workflow CDC)
|
||||||
|
* SANS mot de passe - Token de création MDP généré
|
||||||
|
*/
|
||||||
|
async registerParent(dto: RegisterParentDto) {
|
||||||
|
// 1. Vérifier que l'email n'existe pas
|
||||||
|
const exists = await this.usersService.findByEmailOrNull(dto.email);
|
||||||
|
if (exists) {
|
||||||
|
throw new ConflictException('Un compte avec cet email existe déjà');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Vérifier l'email du co-parent s'il existe
|
||||||
|
if (dto.co_parent_email) {
|
||||||
|
const coParentExists = await this.usersService.findByEmailOrNull(dto.co_parent_email);
|
||||||
|
if (coParentExists) {
|
||||||
|
throw new ConflictException('L\'email du co-parent est déjà utilisé');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Récupérer la durée d'expiration du token depuis la config
|
||||||
|
const tokenExpiryDays = await this.appConfigService.get<number>(
|
||||||
|
'password_reset_token_expiry_days',
|
||||||
|
7,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. Générer les tokens de création de mot de passe
|
||||||
|
const tokenCreationMdp = crypto.randomUUID();
|
||||||
|
const tokenExpiration = new Date();
|
||||||
|
tokenExpiration.setDate(tokenExpiration.getDate() + tokenExpiryDays);
|
||||||
|
|
||||||
|
// 5. Transaction : Créer Parent 1 + Parent 2 (si existe) + entités parents
|
||||||
|
const result = await this.usersRepo.manager.transaction(async (manager) => {
|
||||||
|
// Créer Parent 1
|
||||||
|
const parent1 = manager.create(Users, {
|
||||||
|
email: dto.email,
|
||||||
|
prenom: dto.prenom,
|
||||||
|
nom: dto.nom,
|
||||||
|
role: RoleType.PARENT,
|
||||||
|
statut: StatutUtilisateurType.EN_ATTENTE,
|
||||||
|
telephone: dto.telephone,
|
||||||
|
adresse: dto.adresse,
|
||||||
|
code_postal: dto.code_postal,
|
||||||
|
ville: dto.ville,
|
||||||
|
token_creation_mdp: tokenCreationMdp,
|
||||||
|
token_creation_mdp_expire_le: tokenExpiration,
|
||||||
|
});
|
||||||
|
|
||||||
|
const savedParent1 = await manager.save(Users, parent1);
|
||||||
|
|
||||||
|
// Créer Parent 2 si renseigné
|
||||||
|
let savedParent2: Users | null = null;
|
||||||
|
let tokenCoParent: string | null = null;
|
||||||
|
|
||||||
|
if (dto.co_parent_email && dto.co_parent_prenom && dto.co_parent_nom) {
|
||||||
|
tokenCoParent = crypto.randomUUID();
|
||||||
|
const tokenExpirationCoParent = new Date();
|
||||||
|
tokenExpirationCoParent.setDate(tokenExpirationCoParent.getDate() + tokenExpiryDays);
|
||||||
|
|
||||||
|
const parent2 = manager.create(Users, {
|
||||||
|
email: dto.co_parent_email,
|
||||||
|
prenom: dto.co_parent_prenom,
|
||||||
|
nom: dto.co_parent_nom,
|
||||||
|
role: RoleType.PARENT,
|
||||||
|
statut: StatutUtilisateurType.EN_ATTENTE,
|
||||||
|
telephone: dto.co_parent_telephone,
|
||||||
|
adresse: dto.co_parent_meme_adresse ? dto.adresse : dto.co_parent_adresse,
|
||||||
|
code_postal: dto.co_parent_meme_adresse ? dto.code_postal : dto.co_parent_code_postal,
|
||||||
|
ville: dto.co_parent_meme_adresse ? dto.ville : dto.co_parent_ville,
|
||||||
|
token_creation_mdp: tokenCoParent,
|
||||||
|
token_creation_mdp_expire_le: tokenExpirationCoParent,
|
||||||
|
});
|
||||||
|
|
||||||
|
savedParent2 = await manager.save(Users, parent2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer l'entité métier Parents pour Parent 1
|
||||||
|
const parentEntity = manager.create(Parents, {
|
||||||
|
user_id: savedParent1.id,
|
||||||
|
});
|
||||||
|
parentEntity.user = savedParent1;
|
||||||
|
if (savedParent2) {
|
||||||
|
parentEntity.co_parent = savedParent2;
|
||||||
|
}
|
||||||
|
|
||||||
|
await manager.save(Parents, parentEntity);
|
||||||
|
|
||||||
|
// Créer l'entité métier Parents pour Parent 2 (si existe)
|
||||||
|
if (savedParent2) {
|
||||||
|
const coParentEntity = manager.create(Parents, {
|
||||||
|
user_id: savedParent2.id,
|
||||||
|
});
|
||||||
|
coParentEntity.user = savedParent2;
|
||||||
|
coParentEntity.co_parent = savedParent1;
|
||||||
|
|
||||||
|
await manager.save(Parents, coParentEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
parent1: savedParent1,
|
||||||
|
parent2: savedParent2,
|
||||||
|
tokenCreationMdp,
|
||||||
|
tokenCoParent,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 6. TODO: Envoyer email avec lien de création de MDP
|
||||||
|
// await this.mailService.sendPasswordCreationEmail(result.parent1, result.tokenCreationMdp);
|
||||||
|
// if (result.parent2 && result.tokenCoParent) {
|
||||||
|
// await this.mailService.sendPasswordCreationEmail(result.parent2, result.tokenCoParent);
|
||||||
|
// }
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: 'Inscription réussie. Un email de validation vous a été envoyé.',
|
||||||
|
parent_id: result.parent1.id,
|
||||||
|
co_parent_id: result.parent2?.id,
|
||||||
|
statut: StatutUtilisateurType.EN_ATTENTE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inscription Parent COMPLÈTE - Workflow CDC 6 étapes en 1 transaction
|
* Inscription Parent COMPLÈTE - Workflow CDC 6 étapes en 1 transaction
|
||||||
* Gère : Parent 1 + Parent 2 (opt) + Enfants + Présentation + CGU
|
* Gère : Parent 1 + Parent 2 (opt) + Enfants + Présentation + CGU
|
||||||
@ -314,82 +432,6 @@ export class AuthService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Inscription Assistante Maternelle COMPLÈTE - Un seul endpoint (identité + pro + photo + CGU)
|
|
||||||
* Crée User (role AM) + entrée assistantes_maternelles, token création MDP
|
|
||||||
*/
|
|
||||||
async inscrireAMComplet(dto: RegisterAMCompletDto) {
|
|
||||||
if (!dto.acceptation_cgu || !dto.acceptation_privacy) {
|
|
||||||
throw new BadRequestException(
|
|
||||||
"L'acceptation des CGU et de la politique de confidentialité est obligatoire",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const existe = await this.usersService.findByEmailOrNull(dto.email);
|
|
||||||
if (existe) {
|
|
||||||
throw new ConflictException('Un compte avec cet email existe déjà');
|
|
||||||
}
|
|
||||||
|
|
||||||
const joursExpirationToken = await this.appConfigService.get<number>(
|
|
||||||
'password_reset_token_expiry_days',
|
|
||||||
7,
|
|
||||||
);
|
|
||||||
const tokenCreationMdp = crypto.randomUUID();
|
|
||||||
const dateExpiration = new Date();
|
|
||||||
dateExpiration.setDate(dateExpiration.getDate() + joursExpirationToken);
|
|
||||||
|
|
||||||
let urlPhoto: string | null = null;
|
|
||||||
if (dto.photo_base64 && dto.photo_filename) {
|
|
||||||
urlPhoto = await this.sauvegarderPhotoDepuisBase64(dto.photo_base64, dto.photo_filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateConsentementPhoto =
|
|
||||||
dto.consentement_photo ? new Date() : undefined;
|
|
||||||
|
|
||||||
const resultat = await this.usersRepo.manager.transaction(async (manager) => {
|
|
||||||
const user = manager.create(Users, {
|
|
||||||
email: dto.email,
|
|
||||||
prenom: dto.prenom,
|
|
||||||
nom: dto.nom,
|
|
||||||
role: RoleType.ASSISTANTE_MATERNELLE,
|
|
||||||
statut: StatutUtilisateurType.EN_ATTENTE,
|
|
||||||
telephone: dto.telephone,
|
|
||||||
adresse: dto.adresse,
|
|
||||||
code_postal: dto.code_postal,
|
|
||||||
ville: dto.ville,
|
|
||||||
token_creation_mdp: tokenCreationMdp,
|
|
||||||
token_creation_mdp_expire_le: dateExpiration,
|
|
||||||
photo_url: urlPhoto ?? undefined,
|
|
||||||
consentement_photo: dto.consentement_photo,
|
|
||||||
date_consentement_photo: dateConsentementPhoto,
|
|
||||||
date_naissance: dto.date_naissance ? new Date(dto.date_naissance) : undefined,
|
|
||||||
});
|
|
||||||
const userEnregistre = await manager.save(Users, user);
|
|
||||||
|
|
||||||
const amRepo = manager.getRepository(AssistanteMaternelle);
|
|
||||||
const am = amRepo.create({
|
|
||||||
user_id: userEnregistre.id,
|
|
||||||
approval_number: dto.numero_agrement,
|
|
||||||
nir: dto.nir,
|
|
||||||
max_children: dto.capacite_accueil,
|
|
||||||
biography: dto.biographie,
|
|
||||||
residence_city: dto.ville ?? undefined,
|
|
||||||
agreement_date: dto.date_agrement ? new Date(dto.date_agrement) : undefined,
|
|
||||||
available: true,
|
|
||||||
});
|
|
||||||
await amRepo.save(am);
|
|
||||||
|
|
||||||
return { user: userEnregistre };
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
message:
|
|
||||||
'Inscription réussie. Votre dossier est en attente de validation par un gestionnaire.',
|
|
||||||
user_id: resultat.user.id,
|
|
||||||
statut: StatutUtilisateurType.EN_ATTENTE,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sauvegarde une photo depuis base64 vers le système de fichiers
|
* Sauvegarde une photo depuis base64 vers le système de fichiers
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,156 +0,0 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
|
||||||
import {
|
|
||||||
IsEmail,
|
|
||||||
IsNotEmpty,
|
|
||||||
IsOptional,
|
|
||||||
IsString,
|
|
||||||
IsBoolean,
|
|
||||||
IsInt,
|
|
||||||
Min,
|
|
||||||
Max,
|
|
||||||
MinLength,
|
|
||||||
MaxLength,
|
|
||||||
Matches,
|
|
||||||
IsDateString,
|
|
||||||
} from 'class-validator';
|
|
||||||
|
|
||||||
export class RegisterAMCompletDto {
|
|
||||||
// ============================================
|
|
||||||
// ÉTAPE 1 : IDENTITÉ (Obligatoire)
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'marie.dupont@ptits-pas.fr' })
|
|
||||||
@IsEmail({}, { message: 'Email invalide' })
|
|
||||||
@IsNotEmpty({ message: "L'email est requis" })
|
|
||||||
email: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'Marie' })
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty({ message: 'Le prénom est requis' })
|
|
||||||
@MinLength(2, { message: 'Le prénom doit contenir au moins 2 caractères' })
|
|
||||||
@MaxLength(100)
|
|
||||||
prenom: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'DUPONT' })
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty({ message: 'Le nom est requis' })
|
|
||||||
@MinLength(2, { message: 'Le nom doit contenir au moins 2 caractères' })
|
|
||||||
@MaxLength(100)
|
|
||||||
nom: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: '0689567890' })
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty({ message: 'Le téléphone est requis' })
|
|
||||||
@Matches(/^(\+33|0)[1-9](\d{2}){4}$/, {
|
|
||||||
message: 'Le numéro de téléphone doit être valide (ex: 0689567890 ou +33689567890)',
|
|
||||||
})
|
|
||||||
telephone: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: '5 Avenue du Général de Gaulle', required: false })
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
adresse?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: '95870', required: false })
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
@MaxLength(10)
|
|
||||||
code_postal?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'Bezons', required: false })
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
@MaxLength(150)
|
|
||||||
ville?: string;
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// ÉTAPE 2 : PHOTO + INFOS PRO
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: 'data:image/jpeg;base64,/9j/4AAQ...',
|
|
||||||
required: false,
|
|
||||||
description: 'Photo de profil en base64',
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
photo_base64?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'photo_profil.jpg', required: false })
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
photo_filename?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: true, description: 'Consentement utilisation photo' })
|
|
||||||
@IsBoolean()
|
|
||||||
@IsNotEmpty({ message: 'Le consentement photo est requis' })
|
|
||||||
consentement_photo: boolean;
|
|
||||||
|
|
||||||
@ApiProperty({ example: '2024-01-15', required: false, description: 'Date de naissance' })
|
|
||||||
@IsOptional()
|
|
||||||
@IsDateString()
|
|
||||||
date_naissance?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'Paris', required: false, description: 'Ville de naissance' })
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
@MaxLength(100)
|
|
||||||
lieu_naissance_ville?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'France', required: false, description: 'Pays de naissance' })
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
@MaxLength(100)
|
|
||||||
lieu_naissance_pays?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: '123456789012345', description: 'NIR 15 chiffres' })
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty({ message: 'Le NIR est requis' })
|
|
||||||
@Matches(/^\d{15}$/, { message: 'Le NIR doit contenir exactement 15 chiffres' })
|
|
||||||
nir: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 'AGR-2024-12345', description: "Numéro d'agrément" })
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty({ message: "Le numéro d'agrément est requis" })
|
|
||||||
@MaxLength(50)
|
|
||||||
numero_agrement: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: '2024-06-01', required: false, description: "Date d'obtention de l'agrément" })
|
|
||||||
@IsOptional()
|
|
||||||
@IsDateString()
|
|
||||||
date_agrement?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ example: 4, description: 'Capacité d\'accueil (nombre d\'enfants)', minimum: 1, maximum: 10 })
|
|
||||||
@IsInt()
|
|
||||||
@Min(1, { message: 'La capacité doit être au moins 1' })
|
|
||||||
@Max(10, { message: 'La capacité ne peut pas dépasser 10' })
|
|
||||||
capacite_accueil: number;
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// ÉTAPE 3 : PRÉSENTATION (Optionnel)
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: 'Assistante maternelle expérimentée, accueil bienveillant...',
|
|
||||||
required: false,
|
|
||||||
description: 'Présentation / biographie (max 2000 caractères)',
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
@MaxLength(2000, { message: 'La présentation ne peut pas dépasser 2000 caractères' })
|
|
||||||
biographie?: string;
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// ÉTAPE 4 : ACCEPTATION CGU (Obligatoire)
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
@ApiProperty({ example: true, description: "Acceptation des CGU" })
|
|
||||||
@IsBoolean()
|
|
||||||
@IsNotEmpty({ message: "L'acceptation des CGU est requise" })
|
|
||||||
acceptation_cgu: boolean;
|
|
||||||
|
|
||||||
@ApiProperty({ example: true, description: 'Acceptation de la Politique de confidentialité' })
|
|
||||||
@IsBoolean()
|
|
||||||
@IsNotEmpty({ message: "L'acceptation de la politique de confidentialité est requise" })
|
|
||||||
acceptation_privacy: boolean;
|
|
||||||
}
|
|
||||||
105
backend/src/routes/auth/dto/register-parent.dto.ts
Normal file
105
backend/src/routes/auth/dto/register-parent.dto.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import {
|
||||||
|
IsEmail,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
IsDateString,
|
||||||
|
IsEnum,
|
||||||
|
MinLength,
|
||||||
|
MaxLength,
|
||||||
|
Matches,
|
||||||
|
} from 'class-validator';
|
||||||
|
import { SituationFamilialeType } from 'src/entities/users.entity';
|
||||||
|
|
||||||
|
export class RegisterParentDto {
|
||||||
|
// === Informations obligatoires ===
|
||||||
|
@ApiProperty({ example: 'claire.martin@ptits-pas.fr' })
|
||||||
|
@IsEmail({}, { message: 'Email invalide' })
|
||||||
|
@IsNotEmpty({ message: 'L\'email est requis' })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'Claire' })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty({ message: 'Le prénom est requis' })
|
||||||
|
@MinLength(2, { message: 'Le prénom doit contenir au moins 2 caractères' })
|
||||||
|
@MaxLength(100, { message: 'Le prénom ne peut pas dépasser 100 caractères' })
|
||||||
|
prenom: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'MARTIN' })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty({ message: 'Le nom est requis' })
|
||||||
|
@MinLength(2, { message: 'Le nom doit contenir au moins 2 caractères' })
|
||||||
|
@MaxLength(100, { message: 'Le nom ne peut pas dépasser 100 caractères' })
|
||||||
|
nom: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: '0689567890' })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty({ message: 'Le téléphone est requis' })
|
||||||
|
@Matches(/^(\+33|0)[1-9](\d{2}){4}$/, {
|
||||||
|
message: 'Le numéro de téléphone doit être valide (ex: 0689567890 ou +33689567890)',
|
||||||
|
})
|
||||||
|
telephone: string;
|
||||||
|
|
||||||
|
// === Informations optionnelles ===
|
||||||
|
@ApiProperty({ example: '5 Avenue du Général de Gaulle', required: false })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
adresse?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: '95870', required: false })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(10)
|
||||||
|
code_postal?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'Bezons', required: false })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(150)
|
||||||
|
ville?: string;
|
||||||
|
|
||||||
|
// === Informations co-parent (optionnel) ===
|
||||||
|
@ApiProperty({ example: 'thomas.martin@ptits-pas.fr', required: false })
|
||||||
|
@IsOptional()
|
||||||
|
@IsEmail({}, { message: 'Email du co-parent invalide' })
|
||||||
|
co_parent_email?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'Thomas', required: false })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
co_parent_prenom?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'MARTIN', required: false })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
co_parent_nom?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: '0612345678', required: false })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@Matches(/^(\+33|0)[1-9](\d{2}){4}$/, {
|
||||||
|
message: 'Le numéro de téléphone du co-parent doit être valide',
|
||||||
|
})
|
||||||
|
co_parent_telephone?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'true', description: 'Le co-parent habite à la même adresse', required: false })
|
||||||
|
@IsOptional()
|
||||||
|
co_parent_meme_adresse?: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({ required: false })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
co_parent_adresse?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ required: false })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
co_parent_code_postal?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ required: false })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
co_parent_ville?: string;
|
||||||
|
}
|
||||||
|
|
||||||
@ -80,15 +80,12 @@ CREATE INDEX idx_utilisateurs_token_creation_mdp
|
|||||||
CREATE TABLE assistantes_maternelles (
|
CREATE TABLE assistantes_maternelles (
|
||||||
id_utilisateur UUID PRIMARY KEY REFERENCES utilisateurs(id) ON DELETE CASCADE,
|
id_utilisateur UUID PRIMARY KEY REFERENCES utilisateurs(id) ON DELETE CASCADE,
|
||||||
numero_agrement VARCHAR(50),
|
numero_agrement VARCHAR(50),
|
||||||
|
date_agrement DATE NOT NULL, -- Obligatoire selon CDC v1.3
|
||||||
nir_chiffre CHAR(15),
|
nir_chiffre CHAR(15),
|
||||||
nb_max_enfants INT,
|
nb_max_enfants INT,
|
||||||
|
place_disponible INT,
|
||||||
biographie TEXT,
|
biographie TEXT,
|
||||||
disponible BOOLEAN DEFAULT true,
|
disponible BOOLEAN DEFAULT true
|
||||||
ville_residence VARCHAR(100),
|
|
||||||
date_agrement DATE,
|
|
||||||
annee_experience SMALLINT,
|
|
||||||
specialite VARCHAR(100),
|
|
||||||
place_disponible INT
|
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ==========================================================
|
-- ==========================================================
|
||||||
|
|||||||
@ -5,8 +5,6 @@ class ApiConfig {
|
|||||||
// Auth endpoints
|
// Auth endpoints
|
||||||
static const String login = '/auth/login';
|
static const String login = '/auth/login';
|
||||||
static const String register = '/auth/register';
|
static const String register = '/auth/register';
|
||||||
static const String registerParent = '/auth/register/parent';
|
|
||||||
static const String registerAM = '/auth/register/am';
|
|
||||||
static const String refreshToken = '/auth/refresh';
|
static const String refreshToken = '/auth/refresh';
|
||||||
static const String authMe = '/auth/me';
|
static const String authMe = '/auth/me';
|
||||||
static const String changePasswordRequired = '/auth/change-password-required';
|
static const String changePasswordRequired = '/auth/change-password-required';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user