Merge branch 'develop' of https://git.ptits-pas.fr/jmartin/petitspas into develop

This commit is contained in:
MARTIN Julien 2026-02-23 23:01:53 +01:00
commit aa148354ec
5 changed files with 141 additions and 11 deletions

View File

@ -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 {}

View File

@ -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<void> {
try {
// Récupération de la configuration SMTP
const smtpHost = this.configService.get<string>('smtp_host');
const smtpPort = this.configService.get<number>('smtp_port');
const smtpSecure = this.configService.get<boolean>('smtp_secure');
const smtpAuthRequired = this.configService.get<boolean>('smtp_auth_required');
const smtpUser = this.configService.get<string>('smtp_user');
const smtpPassword = this.configService.get<string>('smtp_password');
const emailFromName = this.configService.get<string>('email_from_name');
const emailFromAddress = this.configService.get<string>('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<void> {
const appName = this.configService.get<string>('app_name', 'P\'titsPas');
const appUrl = this.configService.get<string>('app_url', 'https://app.ptits-pas.fr');
const subject = `Bienvenue sur ${appName}`;
const html = `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2 style="color: #4CAF50;">Bienvenue ${prenom} ${nom} !</h2>
<p>Votre compte gestionnaire sur <strong>${appName}</strong> a é créé avec succès.</p>
<p>Vous pouvez dès à présent vous connecter avec l'adresse email <strong>${to}</strong> et le mot de passe qui vous a é communiqué.</p>
<p>Lors de votre première connexion, il vous sera demandé de modifier votre mot de passe pour des raisons de sécurité.</p>
<div style="text-align: center; margin: 30px 0;">
<a href="${appUrl}" style="background-color: #4CAF50; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; font-weight: bold;">Accéder à l'application</a>
</div>
<hr style="border: 1px solid #eee; margin: 20px 0;">
<p style="color: #666; font-size: 12px;">
Cet email a é envoyé automatiquement. Merci de ne pas y répondre.
</p>
</div>
`;
await this.sendEmail(to, subject, html);
}
}

View File

@ -4,11 +4,13 @@ import { GestionnairesController } from './gestionnaires.controller';
import { Users } from 'src/entities/users.entity'; import { Users } from 'src/entities/users.entity';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthModule } from 'src/routes/auth/auth.module'; import { AuthModule } from 'src/routes/auth/auth.module';
import { MailModule } from 'src/modules/mail/mail.module';
@Module({ @Module({
imports: [ imports: [
TypeOrmModule.forFeature([Users]), TypeOrmModule.forFeature([Users]),
AuthModule, AuthModule,
MailModule,
], ],
controllers: [GestionnairesController], controllers: [GestionnairesController],
providers: [GestionnairesService], providers: [GestionnairesService],

View File

@ -9,12 +9,14 @@ import { RoleType, Users } from 'src/entities/users.entity';
import { CreateGestionnaireDto } from '../dto/create_gestionnaire.dto'; import { CreateGestionnaireDto } from '../dto/create_gestionnaire.dto';
import { UpdateGestionnaireDto } from '../dto/update_gestionnaire.dto'; import { UpdateGestionnaireDto } from '../dto/update_gestionnaire.dto';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
import { MailService } from 'src/modules/mail/mail.service';
@Injectable() @Injectable()
export class GestionnairesService { export class GestionnairesService {
constructor( constructor(
@InjectRepository(Users) @InjectRepository(Users)
private readonly gestionnaireRepository: Repository<Users>, private readonly gestionnaireRepository: Repository<Users>,
private readonly mailService: MailService,
) { } ) { }
// Création dun gestionnaire // Création dun gestionnaire
@ -39,11 +41,26 @@ export class GestionnairesService {
date_consentement_photo: dto.date_consentement_photo date_consentement_photo: dto.date_consentement_photo
? new Date(dto.date_consentement_photo) ? new Date(dto.date_consentement_photo)
: undefined, : undefined,
changement_mdp_obligatoire: dto.changement_mdp_obligatoire ?? false, changement_mdp_obligatoire: true, // Forcé à true pour les nouveaux gestionnaires
role: RoleType.GESTIONNAIRE, role: RoleType.GESTIONNAIRE,
relaisId: dto.relaisId, 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 // Liste des gestionnaires

View File

@ -27,7 +27,7 @@
| 14 | [Frontend] Panneau Paramètres / Configuration (première config + accès permanent) | Ouvert | | 14 | [Frontend] Panneau Paramètres / Configuration (première config + accès permanent) | Ouvert |
| 15 | [Frontend] Écran Paramètres (accès permanent) | Ouvert | | 15 | [Frontend] Écran Paramètres (accès permanent) | Ouvert |
| 16 | [Doc] Documentation configuration on-premise | Ouvert | | 16 | [Doc] Documentation configuration on-premise | Ouvert |
| 1788 | (voir sections cidessous ; #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 | | 91 | [Frontend] Inscription AM Branchement soumission formulaire à l'API | Ouvert |
| 92 | [Frontend] Dashboard Admin - Données réelles et branchement API | ✅ Terminé | | 92 | [Frontend] Dashboard Admin - Données réelles et branchement API | ✅ Terminé |
| 93 | [Frontend] Panneau Admin - Homogeneiser la presentation des onglets | Ouvert | | 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 ## 🟢 PRIORITÉ 2 : Backend - Authentification & Gestion Comptes
### Ticket #17 : [Backend] API Création gestionnaire ### Ticket #17 : [Backend] API Création gestionnaire
**Estimation** : 3h **Estimation** : 3h
**Labels** : `backend`, `p2`, `auth` **Labels** : `backend`, `p2`, `auth`
**Statut** : ✅ TERMINÉ (Fermé le 2026-02-23)
**Description** : **Description** :
Créer l'endpoint pour permettre au super admin de créer des gestionnaires. Créer l'endpoint pour permettre au super admin de créer des gestionnaires.
**Tâches** : **Tâches** :
- [ ] Endpoint `POST /api/v1/gestionnaires` - [x] Endpoint `POST /api/v1/gestionnaires`
- [ ] Validation DTO - [x] Validation DTO
- [ ] Hash bcrypt - [x] Hash bcrypt
- [ ] Flag `changement_mdp_obligatoire = TRUE` - [x] Flag `changement_mdp_obligatoire = TRUE`
- [ ] Guards (super_admin only) - [x] Guards (super_admin only)
- [ ] Email de notification (utiliser MailService avec config dynamique) - [x] Email de notification (utiliser MailService avec config dynamique)
- [ ] Tests unitaires - [x] Tests unitaires
**Référence** : [20_WORKFLOW-CREATION-COMPTE.md](./20_WORKFLOW-CREATION-COMPTE.md#étape-2--création-dun-gestionnaire) **Référence** : [20_WORKFLOW-CREATION-COMPTE.md](./20_WORKFLOW-CREATION-COMPTE.md#étape-2--création-dun-gestionnaire)