feat: add profiles session API
This commit is contained in:
@@ -11,6 +11,8 @@ import { DocumentsModule } from './documents/documents.module';
|
||||
import { TypesModule } from './types/types.module';
|
||||
import { CustomFieldsModule } from './custom-fields/custom-fields.module';
|
||||
import { ConstructeursModule } from './constructeurs/constructeurs.module';
|
||||
import { ProfilesModule } from './profiles/profiles.module';
|
||||
import { SessionModule } from './session/session.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -26,6 +28,8 @@ import { ConstructeursModule } from './constructeurs/constructeurs.module';
|
||||
TypesModule,
|
||||
CustomFieldsModule,
|
||||
ConstructeursModule,
|
||||
ProfilesModule,
|
||||
SessionModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
|
||||
15
src/main.ts
15
src/main.ts
@@ -1,10 +1,25 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import * as session from 'express-session';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
app.use(
|
||||
session({
|
||||
secret: process.env.SESSION_SECRET || 'change-me',
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
httpOnly: true,
|
||||
sameSite: (process.env.SESSION_SAME_SITE as 'strict' | 'lax' | 'none') ?? 'lax',
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
maxAge: Number(process.env.SESSION_MAX_AGE ?? 1000 * 60 * 60 * 24 * 7),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// Récupérer les origines CORS depuis la variable d'environnement (séparées par des virgules)
|
||||
const allowedOrigins = process.env.CORS_ORIGIN
|
||||
? process.env.CORS_ORIGIN.split(',').map(origin => origin.trim())
|
||||
|
||||
23
src/profiles/profiles.controller.ts
Normal file
23
src/profiles/profiles.controller.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common'
|
||||
import { ProfilesService } from './profiles.service'
|
||||
import { CreateProfileDto } from '../shared/dto/profile.dto'
|
||||
|
||||
@Controller('profiles')
|
||||
export class ProfilesController {
|
||||
constructor(private readonly profilesService: ProfilesService) {}
|
||||
|
||||
@Get()
|
||||
async findAll() {
|
||||
return this.profilesService.findAllActive()
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@Body() dto: CreateProfileDto) {
|
||||
return this.profilesService.create(dto)
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async delete(@Param('id') id: string) {
|
||||
return this.profilesService.deactivate(id)
|
||||
}
|
||||
}
|
||||
10
src/profiles/profiles.module.ts
Normal file
10
src/profiles/profiles.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { ProfilesController } from './profiles.controller'
|
||||
import { ProfilesService } from './profiles.service'
|
||||
|
||||
@Module({
|
||||
controllers: [ProfilesController],
|
||||
providers: [ProfilesService],
|
||||
exports: [ProfilesService],
|
||||
})
|
||||
export class ProfilesModule {}
|
||||
119
src/profiles/profiles.service.ts
Normal file
119
src/profiles/profiles.service.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { Injectable, NotFoundException, BadRequestException, OnModuleInit } from '@nestjs/common'
|
||||
import { PrismaService } from '../prisma/prisma.service'
|
||||
import { CreateProfileDto } from '../shared/dto/profile.dto'
|
||||
|
||||
@Injectable()
|
||||
export class ProfilesService implements OnModuleInit {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async onModuleInit() {
|
||||
await this.ensureDefaultProfile()
|
||||
}
|
||||
|
||||
async findAllActive() {
|
||||
return this.prisma.profile.findMany({
|
||||
where: { isActive: true },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async findActiveById(profileId: string) {
|
||||
if (!profileId) return null
|
||||
|
||||
return this.prisma.profile.findFirst({
|
||||
where: {
|
||||
id: profileId,
|
||||
isActive: true,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
isActive: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async create(dto: CreateProfileDto) {
|
||||
const firstName = dto.firstName.trim()
|
||||
const lastName = dto.lastName.trim()
|
||||
|
||||
if (!firstName || !lastName) {
|
||||
throw new BadRequestException('Le prénom et le nom sont obligatoires.')
|
||||
}
|
||||
|
||||
return this.prisma.profile.create({
|
||||
data: {
|
||||
firstName,
|
||||
lastName,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
isActive: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async deactivate(profileId: string) {
|
||||
const existing = await this.prisma.profile.findUnique({ where: { id: profileId } })
|
||||
if (!existing) {
|
||||
throw new NotFoundException('Profil introuvable')
|
||||
}
|
||||
|
||||
if (!existing.isActive) {
|
||||
return this.prisma.profile.findUnique({
|
||||
where: { id: profileId },
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
isActive: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return this.prisma.profile.update({
|
||||
where: { id: profileId },
|
||||
data: { isActive: false },
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
isActive: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
private async ensureDefaultProfile() {
|
||||
const count = await this.prisma.profile.count({ where: { isActive: true } })
|
||||
if (count > 0) return
|
||||
|
||||
const firstName = process.env.DEFAULT_PROFILE_FIRST_NAME?.trim() || 'Admin'
|
||||
const lastName = process.env.DEFAULT_PROFILE_LAST_NAME?.trim() || 'Général'
|
||||
|
||||
await this.prisma.profile.create({
|
||||
data: {
|
||||
firstName,
|
||||
lastName,
|
||||
isActive: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
64
src/session/session.controller.ts
Normal file
64
src/session/session.controller.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Post,
|
||||
Req,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common'
|
||||
import { Request } from 'express'
|
||||
import { ProfilesService } from '../profiles/profiles.service'
|
||||
import { ActivateProfileDto } from '../shared/dto/profile.dto'
|
||||
|
||||
@Controller('session/profile')
|
||||
export class ProfileSessionController {
|
||||
constructor(private readonly profilesService: ProfilesService) {}
|
||||
|
||||
@Get()
|
||||
async getActiveProfile(@Req() req: Request) {
|
||||
if (!req.session?.profileId) {
|
||||
throw new UnauthorizedException('Aucun profil actif.')
|
||||
}
|
||||
|
||||
const profile = await this.profilesService.findActiveById(req.session.profileId)
|
||||
if (!profile) {
|
||||
req.session.profileId = undefined
|
||||
throw new UnauthorizedException('Profil introuvable ou inactif.')
|
||||
}
|
||||
|
||||
return profile
|
||||
}
|
||||
|
||||
@Post()
|
||||
async activateProfile(@Req() req: Request, @Body() dto: ActivateProfileDto) {
|
||||
if (!dto.profileId) {
|
||||
throw new BadRequestException('profileId est requis.')
|
||||
}
|
||||
|
||||
const profile = await this.profilesService.findActiveById(dto.profileId)
|
||||
if (!profile) {
|
||||
throw new UnauthorizedException('Profil introuvable ou inactif.')
|
||||
}
|
||||
|
||||
req.session.profileId = profile.id
|
||||
return profile
|
||||
}
|
||||
|
||||
@Delete()
|
||||
async logout(@Req() req: Request) {
|
||||
if (!req.session) {
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
req.session.destroy((err) => {
|
||||
if (err) {
|
||||
return reject(new BadRequestException('Impossible de déconnecter la session.'))
|
||||
}
|
||||
resolve({ success: true })
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
9
src/session/session.module.ts
Normal file
9
src/session/session.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { ProfileSessionController } from './session.controller'
|
||||
import { ProfilesModule } from '../profiles/profiles.module'
|
||||
|
||||
@Module({
|
||||
imports: [ProfilesModule],
|
||||
controllers: [ProfileSessionController],
|
||||
})
|
||||
export class SessionModule {}
|
||||
19
src/shared/dto/profile.dto.ts
Normal file
19
src/shared/dto/profile.dto.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { IsNotEmpty, IsString, MaxLength } from 'class-validator'
|
||||
|
||||
export class CreateProfileDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(100)
|
||||
firstName!: string
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(100)
|
||||
lastName!: string
|
||||
}
|
||||
|
||||
export class ActivateProfileDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
profileId!: string
|
||||
}
|
||||
7
src/types/express-session.d.ts
vendored
Normal file
7
src/types/express-session.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import 'express-session'
|
||||
|
||||
declare module 'express-session' {
|
||||
interface SessionData {
|
||||
profileId?: string
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user