feat: phase 1 — auth + user endpoints, Prisma v7 adapter, DB init
This commit is contained in:
93
backend/src/controllers/auth.controller.ts
Normal file
93
backend/src/controllers/auth.controller.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import type { Response } from "express";
|
||||
import argon2 from "argon2";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { prisma } from "../index";
|
||||
import type { AppRequest } from "../types/context";
|
||||
import { loginSchema, registerSchema } from "../validators/auth.validators";
|
||||
|
||||
const COOKIE_OPTIONS = {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: "lax" as const,
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 jours
|
||||
};
|
||||
|
||||
export async function register(req: AppRequest, res: Response): Promise<void> {
|
||||
const parsed = registerSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
res.status(400).json({ message: parsed.error.issues[0].message });
|
||||
return;
|
||||
}
|
||||
|
||||
const { username, email, password } = parsed.data;
|
||||
|
||||
const existing = await prisma.user.findFirst({
|
||||
where: { OR: [{ email }, { username }] },
|
||||
});
|
||||
if (existing) {
|
||||
res.status(409).json({ message: "Email ou username déjà utilisé." });
|
||||
return;
|
||||
}
|
||||
|
||||
const hashed = await argon2.hash(password);
|
||||
const user = await prisma.user.create({
|
||||
data: { username, email, password: hashed },
|
||||
});
|
||||
|
||||
const token = jwt.sign(
|
||||
{ id: user.id, email: user.email, role: user.role },
|
||||
process.env.JWT_SECRET as string,
|
||||
{ expiresIn: "7d" }
|
||||
);
|
||||
|
||||
res.cookie("token", token, COOKIE_OPTIONS);
|
||||
res.status(201).json({ id: user.id, username: user.username, email: user.email, role: user.role });
|
||||
}
|
||||
|
||||
export async function login(req: AppRequest, res: Response): Promise<void> {
|
||||
const parsed = loginSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
res.status(400).json({ message: parsed.error.issues[0].message });
|
||||
return;
|
||||
}
|
||||
|
||||
const { email, password } = parsed.data;
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { email } });
|
||||
if (!user) {
|
||||
res.status(401).json({ message: "Identifiants invalides." });
|
||||
return;
|
||||
}
|
||||
|
||||
const valid = await argon2.verify(user.password, password);
|
||||
if (!valid) {
|
||||
res.status(401).json({ message: "Identifiants invalides." });
|
||||
return;
|
||||
}
|
||||
|
||||
const token = jwt.sign(
|
||||
{ id: user.id, email: user.email, role: user.role },
|
||||
process.env.JWT_SECRET as string,
|
||||
{ expiresIn: "7d" }
|
||||
);
|
||||
|
||||
res.cookie("token", token, COOKIE_OPTIONS);
|
||||
res.json({ id: user.id, username: user.username, email: user.email, role: user.role });
|
||||
}
|
||||
|
||||
export function logout(_req: AppRequest, res: Response): void {
|
||||
res.clearCookie("token");
|
||||
res.json({ message: "Déconnecté." });
|
||||
}
|
||||
|
||||
export async function me(req: AppRequest, res: Response): Promise<void> {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: req.user!.id },
|
||||
select: { id: true, username: true, email: true, role: true, avatar: true, bio: true, createdAt: true },
|
||||
});
|
||||
if (!user) {
|
||||
res.status(404).json({ message: "Utilisateur introuvable." });
|
||||
return;
|
||||
}
|
||||
res.json(user);
|
||||
}
|
||||
41
backend/src/controllers/user.controller.ts
Normal file
41
backend/src/controllers/user.controller.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { Response } from "express";
|
||||
import { prisma } from "../index";
|
||||
import type { AppRequest } from "../types/context";
|
||||
import { updateUserSchema } from "../validators/user.validators";
|
||||
|
||||
export async function getMe(req: AppRequest, res: Response): Promise<void> {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: req.user!.id },
|
||||
select: { id: true, username: true, email: true, role: true, avatar: true, bio: true, createdAt: true },
|
||||
});
|
||||
if (!user) {
|
||||
res.status(404).json({ message: "Utilisateur introuvable." });
|
||||
return;
|
||||
}
|
||||
res.json(user);
|
||||
}
|
||||
|
||||
export async function updateMe(req: AppRequest, res: Response): Promise<void> {
|
||||
const parsed = updateUserSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
res.status(400).json({ message: parsed.error.issues[0].message });
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsed.data.username) {
|
||||
const taken = await prisma.user.findFirst({
|
||||
where: { username: parsed.data.username, NOT: { id: req.user!.id } },
|
||||
});
|
||||
if (taken) {
|
||||
res.status(409).json({ message: "Username déjà utilisé." });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const user = await prisma.user.update({
|
||||
where: { id: req.user!.id },
|
||||
data: parsed.data,
|
||||
select: { id: true, username: true, email: true, role: true, avatar: true, bio: true },
|
||||
});
|
||||
res.json(user);
|
||||
}
|
||||
Reference in New Issue
Block a user