feat: mise en place du projet et création de la page de login

This commit is contained in:
Julien Martin
2025-05-02 21:30:31 +02:00
parent d5015b9c42
commit d3663a28ad
91 changed files with 9494 additions and 191 deletions
+18
View File
@@ -0,0 +1,18 @@
import { Controller, Post, Body, UseGuards, Req } from '@nestjs/common';
import { AdminService } from './admin.service';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
@Controller('admin')
export class AdminController {
constructor(private readonly adminService: AdminService) {}
@Post('change-password')
@UseGuards(JwtAuthGuard)
async changePassword(
@Req() req,
@Body('oldPassword') oldPassword: string,
@Body('newPassword') newPassword: string,
) {
return this.adminService.changePassword(req.user.id, oldPassword, newPassword);
}
}
+18
View File
@@ -0,0 +1,18 @@
import { Module } from '@nestjs/common';
import { AdminController } from './admin.controller';
import { AdminService } from './admin.service';
import { PrismaModule } from '../prisma/prisma.module';
import { JwtModule } from '@nestjs/jwt';
@Module({
imports: [
PrismaModule,
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: '1d' },
}),
],
controllers: [AdminController],
providers: [AdminService],
})
export class AdminModule {}
+40
View File
@@ -0,0 +1,40 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { PrismaService } from '../prisma/prisma.service';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AdminService {
constructor(
private prisma: PrismaService,
private jwtService: JwtService,
) {}
async changePassword(adminId: string, oldPassword: string, newPassword: string) {
// Récupérer l'administrateur
const admin = await this.prisma.admin.findUnique({
where: { id: adminId },
});
if (!admin) {
throw new UnauthorizedException('Administrateur non trouvé');
}
// Vérifier l'ancien mot de passe
const isPasswordValid = await bcrypt.compare(oldPassword, admin.password);
if (!isPasswordValid) {
throw new UnauthorizedException('Ancien mot de passe incorrect');
}
// Hasher le nouveau mot de passe
const hashedPassword = await bcrypt.hash(newPassword, 10);
// Mettre à jour le mot de passe
await this.prisma.admin.update({
where: { id: adminId },
data: { password: hashedPassword },
});
return { message: 'Mot de passe modifié avec succès' };
}
}
+17
View File
@@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { PrismaModule } from './prisma/prisma.module';
import { AuthModule } from './auth/auth.module';
import { AdminModule } from './admin/admin.module';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
PrismaModule,
AuthModule,
AdminModule,
],
})
export class AppModule {}
@@ -0,0 +1,72 @@
import { Request, Response } from 'express';
import { ThemeService, ThemeData } from '../services/theme.service';
export class ThemeController {
// Créer un nouveau thème
static async createTheme(req: Request, res: Response) {
try {
const themeData: ThemeData = req.body;
const theme = await ThemeService.createTheme(themeData);
res.status(201).json(theme);
} catch (error) {
res.status(500).json({ error: 'Erreur lors de la création du thème' });
}
}
// Récupérer tous les thèmes
static async getAllThemes(req: Request, res: Response) {
try {
const themes = await ThemeService.getAllThemes();
res.json(themes);
} catch (error) {
res.status(500).json({ error: 'Erreur lors de la récupération des thèmes' });
}
}
// Récupérer le thème actif
static async getActiveTheme(req: Request, res: Response) {
try {
const theme = await ThemeService.getActiveTheme();
if (!theme) {
return res.status(404).json({ error: 'Aucun thème actif trouvé' });
}
res.json(theme);
} catch (error) {
res.status(500).json({ error: 'Erreur lors de la récupération du thème actif' });
}
}
// Activer un thème
static async activateTheme(req: Request, res: Response) {
try {
const { themeId } = req.params;
const theme = await ThemeService.activateTheme(themeId);
res.json(theme);
} catch (error) {
res.status(500).json({ error: 'Erreur lors de l\'activation du thème' });
}
}
// Mettre à jour un thème
static async updateTheme(req: Request, res: Response) {
try {
const { themeId } = req.params;
const themeData: Partial<ThemeData> = req.body;
const theme = await ThemeService.updateTheme(themeId, themeData);
res.json(theme);
} catch (error) {
res.status(500).json({ error: 'Erreur lors de la mise à jour du thème' });
}
}
// Supprimer un thème
static async deleteTheme(req: Request, res: Response) {
try {
const { themeId } = req.params;
await ThemeService.deleteTheme(themeId);
res.status(204).send();
} catch (error) {
res.status(500).json({ error: 'Erreur lors de la suppression du thème' });
}
}
}
+8 -11
View File
@@ -2,10 +2,10 @@ import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import { PrismaClient } from '@prisma/client';
import themeRoutes from './routes/theme.routes';
const app = express();
const prisma = new PrismaClient();
const port = process.env.PORT || 3000;
// Middleware
app.use(cors());
@@ -13,19 +13,16 @@ app.use(helmet());
app.use(morgan('dev'));
app.use(express.json());
// Routes de base
app.get('/', (req, res) => {
res.json({ message: 'Bienvenue sur l\'API SuperNounou' });
});
// Routes
app.use('/api/themes', themeRoutes);
// Gestion des erreurs
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
console.error(err.stack);
res.status(500).json({ message: 'Une erreur est survenue' });
res.status(500).json({ error: 'Une erreur est survenue' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Serveur démarré sur le port ${PORT}`);
// Démarrage du serveur
app.listen(port, () => {
console.log(`Serveur démarré sur le port ${port}`);
});
+95
View File
@@ -0,0 +1,95 @@
import { Router } from 'express';
import { PrismaClient } from '@prisma/client';
import * as bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
const router = Router();
const prisma = new PrismaClient();
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
// Route de connexion
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
// Vérifier les identifiants
const admin = await prisma.admin.findUnique({
where: { email }
});
if (!admin) {
return res.status(401).json({ error: 'Identifiants invalides' });
}
// Vérifier le mot de passe
const validPassword = await bcrypt.compare(password, admin.password);
if (!validPassword) {
return res.status(401).json({ error: 'Identifiants invalides' });
}
// Vérifier si le mot de passe doit être changé
if (!admin.passwordChanged) {
return res.status(403).json({
error: 'Changement de mot de passe requis',
requiresPasswordChange: true
});
}
// Générer le token JWT
const token = jwt.sign(
{
id: admin.id,
email: admin.email,
role: 'admin'
},
JWT_SECRET,
{ expiresIn: '24h' }
);
res.json({ token });
} catch (error) {
console.error('Erreur lors de la connexion:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
// Route de changement de mot de passe
router.post('/change-password', async (req, res) => {
try {
const { email, currentPassword, newPassword } = req.body;
// Vérifier l'administrateur
const admin = await prisma.admin.findUnique({
where: { email }
});
if (!admin) {
return res.status(404).json({ error: 'Administrateur non trouvé' });
}
// Vérifier l'ancien mot de passe
const validPassword = await bcrypt.compare(currentPassword, admin.password);
if (!validPassword) {
return res.status(401).json({ error: 'Mot de passe actuel incorrect' });
}
// Hasher le nouveau mot de passe
const hashedPassword = await bcrypt.hash(newPassword, 10);
// Mettre à jour le mot de passe
await prisma.admin.update({
where: { id: admin.id },
data: {
password: hashedPassword,
passwordChanged: true
}
});
res.json({ message: 'Mot de passe changé avec succès' });
} catch (error) {
console.error('Erreur lors du changement de mot de passe:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
export default router;
+14
View File
@@ -0,0 +1,14 @@
import { Router } from 'express';
import { ThemeController } from '../controllers/theme.controller';
const router = Router();
// Routes pour les thèmes
router.post('/', ThemeController.createTheme);
router.get('/', ThemeController.getAllThemes);
router.get('/active', ThemeController.getActiveTheme);
router.put('/:themeId/activate', ThemeController.activateTheme);
router.put('/:themeId', ThemeController.updateTheme);
router.delete('/:themeId', ThemeController.deleteTheme);
export default router;
+39
View File
@@ -0,0 +1,39 @@
import { PrismaClient } from '@prisma/client';
import * as bcrypt from 'bcrypt';
const prisma = new PrismaClient();
async function main() {
try {
// Vérifier si l'administrateur existe déjà
const existingAdmin = await prisma.admin.findUnique({
where: { email: 'administrateur@ptitspas.fr' }
});
if (!existingAdmin) {
// Hasher le mot de passe
const hashedPassword = await bcrypt.hash('password', 10);
// Créer l'administrateur
await prisma.admin.create({
data: {
email: 'administrateur@ptitspas.fr',
password: hashedPassword,
firstName: 'Administrateur',
lastName: 'P\'titsPas',
passwordChanged: false
}
});
console.log('✅ Administrateur créé avec succès');
} else {
console.log('️ L\'administrateur existe déjà');
}
} catch (error) {
console.error('❌ Erreur lors de la création de l\'administrateur:', error);
} finally {
await prisma.$disconnect();
}
}
main();
+77
View File
@@ -0,0 +1,77 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export interface ThemeData {
name: string;
primaryColor: string;
secondaryColor: string;
backgroundColor: string;
textColor: string;
}
export class ThemeService {
// Créer un nouveau thème
static async createTheme(data: ThemeData) {
return prisma.theme.create({
data: {
...data,
isActive: false,
},
});
}
// Récupérer tous les thèmes
static async getAllThemes() {
return prisma.theme.findMany();
}
// Récupérer le thème actif
static async getActiveTheme() {
const settings = await prisma.appSettings.findFirst({
include: {
currentTheme: true,
},
});
return settings?.currentTheme;
}
// Activer un thème
static async activateTheme(themeId: string) {
// Désactiver tous les thèmes
await prisma.theme.updateMany({
where: { isActive: true },
data: { isActive: false },
});
// Activer le thème sélectionné
const updatedTheme = await prisma.theme.update({
where: { id: themeId },
data: { isActive: true },
});
// Mettre à jour les paramètres de l'application
await prisma.appSettings.upsert({
where: { id: '1' },
update: { currentThemeId: themeId },
create: { id: '1', currentThemeId: themeId },
});
return updatedTheme;
}
// Mettre à jour un thème
static async updateTheme(themeId: string, data: Partial<ThemeData>) {
return prisma.theme.update({
where: { id: themeId },
data,
});
}
// Supprimer un thème
static async deleteTheme(themeId: string) {
return prisma.theme.delete({
where: { id: themeId },
});
}
}