// Auteur : Simon-Pierre Boucher — contact@spboucher.ai // Photo de profil du KA ID — téléversée ici, elle apparaît sur la carte de // membre du site, sur la carte Apple Wallet (vignette) et, via le jeton SSO, // sur toutes les plateformes du groupe à la prochaine connexion. // POST multipart (champ « photo », JPG/PNG/WebP ≤ 8 Mo) → carré 512 px JPEG // DELETE → retire la photo (retour à l'initiale ; une connexion Google // ultérieure remettra la photo Google) import { NextRequest, NextResponse } from "next/server"; import fs from "fs/promises"; import path from "path"; import sharp from "sharp"; import { getSessionUser, BASE_URL } from "@/lib/auth"; import { db, ensureKaId } from "@/lib/db"; const AVATAR_DIR = path.join(process.cwd(), "data", "avatars"); const MAX_BYTES = 8 * 1024 * 1024; export async function POST(req: NextRequest) { const user = await getSessionUser(); if (!user) return NextResponse.json({ error: "Non connecté." }, { status: 401 }); const fd = await req.formData().catch(() => null); const file = fd?.get("photo"); if (!(file instanceof File) || file.size === 0) return NextResponse.json( { error: "Aucune image reçue (champ « photo »)." }, { status: 400 }, ); if (file.size > MAX_BYTES) return NextResponse.json( { error: "Image trop lourde — 8 Mo maximum." }, { status: 413 }, ); if (!/^image\/(jpeg|png|webp|heic|heif|gif)$/i.test(file.type)) return NextResponse.json( { error: "Format refusé — JPG, PNG ou WebP." }, { status: 415 }, ); const kaId = user.kaId ?? ensureKaId(user.id); let out: Buffer; try { out = await sharp(Buffer.from(await file.arrayBuffer())) .rotate() // respecte l'orientation EXIF .resize(512, 512, { fit: "cover", position: "attention" }) .jpeg({ quality: 88 }) .toBuffer(); } catch { return NextResponse.json( { error: "Image illisible — réessayez avec un JPG ou un PNG." }, { status: 422 }, ); } await fs.mkdir(AVATAR_DIR, { recursive: true }); await fs.writeFile(path.join(AVATAR_DIR, `${kaId}.jpg`), out); // URL absolue (+cache-bust) : servie par /avatars/{kaId}, transportée // telle quelle dans le jeton SSO vers les plateformes. const url = `${BASE_URL}/avatars/${kaId}?v=${out.length.toString(36)}${Math.floor(out.byteLength % 997)}`; db.prepare("UPDATE users SET avatar_url = ? WHERE id = ?").run(url, user.id); return NextResponse.json({ ok: true, avatarUrl: url }); } export async function DELETE() { const user = await getSessionUser(); if (!user) return NextResponse.json({ error: "Non connecté." }, { status: 401 }); const kaId = user.kaId ?? ensureKaId(user.id); await fs.rm(path.join(AVATAR_DIR, `${kaId}.jpg`), { force: true }); db.prepare("UPDATE users SET avatar_url = NULL WHERE id = ?").run(user.id); return NextResponse.json({ ok: true }); }