feat: phase 1 — auth + user endpoints, Prisma v7 adapter, DB init

This commit is contained in:
2026-03-26 03:41:39 +00:00
parent 2d5030c521
commit 48446b483c
13 changed files with 675 additions and 16 deletions

View 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);
}

View 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);
}