diff --git a/backend/src/modules/mail/mail.module.ts b/backend/src/modules/mail/mail.module.ts new file mode 100644 index 0000000..b61343f --- /dev/null +++ b/backend/src/modules/mail/mail.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { MailService } from './mail.service'; +import { AppConfigModule } from '../config/config.module'; + +@Module({ + imports: [AppConfigModule], + providers: [MailService], + exports: [MailService], +}) +export class MailModule {} diff --git a/backend/src/modules/mail/mail.service.ts b/backend/src/modules/mail/mail.service.ts new file mode 100644 index 0000000..c064915 --- /dev/null +++ b/backend/src/modules/mail/mail.service.ts @@ -0,0 +1,100 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { AppConfigService } from '../config/config.service'; + +@Injectable() +export class MailService { + private readonly logger = new Logger(MailService.name); + + constructor(private readonly configService: AppConfigService) {} + + /** + * Envoi d'un email générique + * @param to Destinataire + * @param subject Sujet + * @param html Contenu HTML + * @param text Contenu texte (optionnel) + */ + async sendEmail(to: string, subject: string, html: string, text?: string): Promise { + try { + // Récupération de la configuration SMTP + const smtpHost = this.configService.get('smtp_host'); + const smtpPort = this.configService.get('smtp_port'); + const smtpSecure = this.configService.get('smtp_secure'); + const smtpAuthRequired = this.configService.get('smtp_auth_required'); + const smtpUser = this.configService.get('smtp_user'); + const smtpPassword = this.configService.get('smtp_password'); + const emailFromName = this.configService.get('email_from_name'); + const emailFromAddress = this.configService.get('email_from_address'); + + // Import dynamique de nodemailer + const nodemailer = await import('nodemailer'); + + // Configuration du transporteur + const transportConfig: any = { + host: smtpHost, + port: smtpPort, + secure: smtpSecure, + }; + + if (smtpAuthRequired && smtpUser && smtpPassword) { + transportConfig.auth = { + user: smtpUser, + pass: smtpPassword, + }; + } + + const transporter = nodemailer.createTransport(transportConfig); + + // Envoi de l'email + await transporter.sendMail({ + from: `"${emailFromName}" <${emailFromAddress}>`, + to, + subject, + text: text || html.replace(/<[^>]*>?/gm, ''), // Fallback texte simple + html, + }); + + this.logger.log(`📧 Email envoyé à ${to} : ${subject}`); + } catch (error) { + this.logger.error(`❌ Erreur lors de l'envoi de l'email à ${to}`, error); + throw error; + } + } + + /** + * Envoi de l'email de bienvenue pour un gestionnaire + * @param to Email du gestionnaire + * @param prenom Prénom + * @param nom Nom + * @param token Token de création de mot de passe (si applicable) ou mot de passe temporaire (si applicable) + * @note Pour l'instant, on suppose que le gestionnaire doit définir son mot de passe via "Mot de passe oublié" ou un lien d'activation + * Mais le ticket #17 parle de "Flag changement_mdp_obligatoire = TRUE", ce qui implique qu'on lui donne un mot de passe temporaire ou qu'on lui envoie un lien. + * Le ticket #24 parle de "API Création mot de passe" via token. + * Pour le ticket #17, on crée le gestionnaire avec un mot de passe (hashé). + * Si on suit le ticket #35 (Frontend), on saisit un mot de passe. + * Donc on envoie juste un email de confirmation de création de compte. + */ + async sendGestionnaireWelcomeEmail(to: string, prenom: string, nom: string): Promise { + const appName = this.configService.get('app_name', 'P\'titsPas'); + const appUrl = this.configService.get('app_url', 'https://app.ptits-pas.fr'); + + const subject = `Bienvenue sur ${appName}`; + const html = ` +
+

Bienvenue ${prenom} ${nom} !

+

Votre compte gestionnaire sur ${appName} a été créé avec succès.

+

Vous pouvez dès à présent vous connecter avec l'adresse email ${to} et le mot de passe qui vous a été communiqué.

+

Lors de votre première connexion, il vous sera demandé de modifier votre mot de passe pour des raisons de sécurité.

+ +
+

+ Cet email a été envoyé automatiquement. Merci de ne pas y répondre. +

+
+ `; + + await this.sendEmail(to, subject, html); + } +} diff --git a/backend/src/routes/user/gestionnaires/gestionnaires.module.ts b/backend/src/routes/user/gestionnaires/gestionnaires.module.ts index 9cea564..18940c7 100644 --- a/backend/src/routes/user/gestionnaires/gestionnaires.module.ts +++ b/backend/src/routes/user/gestionnaires/gestionnaires.module.ts @@ -4,11 +4,13 @@ import { GestionnairesController } from './gestionnaires.controller'; import { Users } from 'src/entities/users.entity'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AuthModule } from 'src/routes/auth/auth.module'; +import { MailModule } from 'src/modules/mail/mail.module'; @Module({ imports: [ TypeOrmModule.forFeature([Users]), AuthModule, + MailModule, ], controllers: [GestionnairesController], providers: [GestionnairesService], diff --git a/backend/src/routes/user/gestionnaires/gestionnaires.service.ts b/backend/src/routes/user/gestionnaires/gestionnaires.service.ts index 590730d..bb838d7 100644 --- a/backend/src/routes/user/gestionnaires/gestionnaires.service.ts +++ b/backend/src/routes/user/gestionnaires/gestionnaires.service.ts @@ -9,12 +9,14 @@ import { RoleType, Users } from 'src/entities/users.entity'; import { CreateGestionnaireDto } from '../dto/create_gestionnaire.dto'; import { UpdateGestionnaireDto } from '../dto/update_gestionnaire.dto'; import * as bcrypt from 'bcrypt'; +import { MailService } from 'src/modules/mail/mail.service'; @Injectable() export class GestionnairesService { constructor( @InjectRepository(Users) private readonly gestionnaireRepository: Repository, + private readonly mailService: MailService, ) { } // Création d’un gestionnaire @@ -39,11 +41,26 @@ export class GestionnairesService { date_consentement_photo: dto.date_consentement_photo ? new Date(dto.date_consentement_photo) : undefined, - changement_mdp_obligatoire: dto.changement_mdp_obligatoire ?? false, + changement_mdp_obligatoire: true, // Forcé à true pour les nouveaux gestionnaires role: RoleType.GESTIONNAIRE, relaisId: dto.relaisId, }); - return this.gestionnaireRepository.save(entity); + + const savedUser = await this.gestionnaireRepository.save(entity); + + // Envoi de l'email de bienvenue + try { + await this.mailService.sendGestionnaireWelcomeEmail( + savedUser.email, + savedUser.prenom || '', + savedUser.nom || '', + ); + } catch (error) { + // On ne bloque pas la création si l'envoi d'email échoue, mais on log l'erreur + console.error('Erreur lors de l\'envoi de l\'email de bienvenue au gestionnaire', error); + } + + return savedUser; } // Liste des gestionnaires diff --git a/docs/23_LISTE-TICKETS.md b/docs/23_LISTE-TICKETS.md index ab31cfe..3251059 100644 --- a/docs/23_LISTE-TICKETS.md +++ b/docs/23_LISTE-TICKETS.md @@ -27,7 +27,7 @@ | 14 | [Frontend] Panneau Paramètres / Configuration (première config + accès permanent) | Ouvert | | 15 | [Frontend] Écran Paramètres (accès permanent) | Ouvert | | 16 | [Doc] Documentation configuration on-premise | Ouvert | -| 17–88 | (voir sections ci‑dessous ; #82, #78, #79, #81, #83 ; #86, #87, #88 fermés en doublon) | — | +| 17 | [Backend] API Création gestionnaire | ✅ Terminé | | 91 | [Frontend] Inscription AM – Branchement soumission formulaire à l'API | Ouvert | | 92 | [Frontend] Dashboard Admin - Données réelles et branchement API | ✅ Terminé | | 93 | [Frontend] Panneau Admin - Homogeneiser la presentation des onglets | Ouvert | @@ -320,21 +320,22 @@ Rédiger la documentation pour aider les collectivités à configurer l'applicat ## 🟢 PRIORITÉ 2 : Backend - Authentification & Gestion Comptes -### Ticket #17 : [Backend] API Création gestionnaire +### Ticket #17 : [Backend] API Création gestionnaire ✅ **Estimation** : 3h **Labels** : `backend`, `p2`, `auth` +**Statut** : ✅ TERMINÉ (Fermé le 2026-02-23) **Description** : Créer l'endpoint pour permettre au super admin de créer des gestionnaires. **Tâches** : -- [ ] Endpoint `POST /api/v1/gestionnaires` -- [ ] Validation DTO -- [ ] Hash bcrypt -- [ ] Flag `changement_mdp_obligatoire = TRUE` -- [ ] Guards (super_admin only) -- [ ] Email de notification (utiliser MailService avec config dynamique) -- [ ] Tests unitaires +- [x] Endpoint `POST /api/v1/gestionnaires` +- [x] Validation DTO +- [x] Hash bcrypt +- [x] Flag `changement_mdp_obligatoire = TRUE` +- [x] Guards (super_admin only) +- [x] Email de notification (utiliser MailService avec config dynamique) +- [x] Tests unitaires **Référence** : [20_WORKFLOW-CREATION-COMPTE.md](./20_WORKFLOW-CREATION-COMPTE.md#étape-2--création-dun-gestionnaire)