feat: sprint 3 — profile endpoints + avatar
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 32s

- GET /api/auth/me enrichi : avatar, plan actif, subscriptionDate
- GET /api/users/me/profile : profil complet (local UUID, sub, rôles)
- PATCH /api/users/me : update nickname / avatar (validation URL + longueur)
- User entity : champ avatar VARCHAR(500) nullable
- Migration 1742000000000-AddUserAvatar (appliquée VPS)
This commit is contained in:
2026-03-14 22:25:22 +01:00
parent 24ae8854ce
commit 30ef7312b5
5 changed files with 187 additions and 4 deletions

View File

@@ -1,6 +1,7 @@
import { Router, Request, Response } from "express";
import { AppDataSource } from "../config/data-source";
import { User } from "../entities/User";
import { UserSubscription } from "../entities/UserSubscription";
import { requireAuth, AuthenticatedRequest } from "../middleware/auth.middleware";
const router = Router();
@@ -189,17 +190,51 @@ router.post("/logout", (_req: Request, res: Response): void => {
/**
* GET /api/auth/me
* Retourne l'utilisateur courant (cookie ou Bearer) + ses rôles locaux.
* Retourne l'utilisateur courant + rôles locaux + plan actif + avatar.
*/
router.get("/me", requireAuth, async (req: Request, res: Response): Promise<void> => {
const { user } = req as AuthenticatedRequest;
const localUser = await AppDataSource.getRepository(User)
.findOne({ where: { superOAuthId: user.id }, relations: ["userRoles", "userRoles.role"] });
const localUser = await AppDataSource.getRepository(User).findOne({
where: { superOAuthId: user.id },
relations: ["userRoles", "userRoles.role"],
});
const roles = localUser?.userRoles.map((ur) => ur.role.slug) ?? [];
res.json({ success: true, data: { user: { ...user, roles } } });
let plan: { slug: string; name: string; level: number } | null = null;
let subscriptionDate: string | null = null;
if (localUser) {
const now = new Date();
const activeSub = await AppDataSource.getRepository(UserSubscription)
.createQueryBuilder("sub")
.leftJoinAndSelect("sub.plan", "plan")
.where("sub.userId = :userId", { userId: localUser.id })
.andWhere("sub.status IN (:...statuses)", { statuses: ["active", "trial"] })
.andWhere("(sub.endsAt IS NULL OR sub.endsAt > :now)", { now })
.orderBy("plan.level", "DESC")
.addOrderBy("sub.startsAt", "DESC")
.getOne();
if (activeSub) {
plan = { slug: activeSub.plan.slug, name: activeSub.plan.name, level: activeSub.plan.level };
subscriptionDate = activeSub.startsAt.toISOString();
}
}
res.json({
success: true,
data: {
user: {
...user,
avatar: localUser?.avatar ?? null,
roles,
plan,
subscriptionDate,
},
},
});
});
/**