import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; import { eq } from "drizzle-orm"; import { db } from "@/db/client"; import { users, userProfiles, listings, messages, sessions } from "@/db/schema"; import { cookies } from "next/headers"; import { COOKIE } from "@/lib/auth/session"; import { assertSameOrigin, currentUser, requireUser } from "@/lib/auth/session"; import { encrypt } from "@/lib/auth/crypto"; import { audit } from "@/lib/auth/audit"; import { handle, parseBody } from "@/lib/api"; export const dynamic = "force-dynamic"; export async function GET() { try { const u = await currentUser(); if (!u) return NextResponse.json({ user: null }); return NextResponse.json({ user: { id: u.id, email: u.email, displayName: u.displayName, publicName: u.publicName, sellerType: u.sellerType, roles: u.roles, prefs: u.prefs } }); } catch (e) { return handle(e); } } const Patch = z.object({ displayName: z.string().trim().min(1).max(80).nullable().optional(), publicName: z.string().trim().min(1).max(80).nullable().optional(), phone: z.string().trim().max(30).nullable().optional(), sellerType: z.enum(["OWNER", "AGENT"]).optional(), bio: z.string().max(600).nullable().optional(), prefs: z.object({ notifications: z.object({ inquiries: z.boolean(), viewings: z.boolean(), alerts: z.boolean(), marketing: z.boolean() }).partial().optional(), privacy: z.object({ showPhone: z.boolean().optional(), showNameOnListings: z.boolean().optional() }).optional() }).optional() }); export async function PATCH(req: NextRequest) { try { await assertSameOrigin(); const u = await requireUser(); const b = await parseBody(req, Patch); const d = db(); const prefs = b.prefs ? { ...u.prefs, notifications: { ...((u.prefs.notifications as object) ?? {}), ...(b.prefs.notifications ?? {}) }, privacy: { ...((u.prefs.privacy as object) ?? {}), ...(b.prefs.privacy ?? {}) } } : undefined; await d.update(users).set({ ...(b.displayName !== undefined ? { displayName: b.displayName } : {}), ...(b.phone !== undefined ? { phoneEncrypted: b.phone ? encrypt(b.phone) : null } : {}), ...(prefs ? { prefs } : {}), updatedAt: new Date() }).where(eq(users.id, u.id)); await d.insert(userProfiles).values({ userId: u.id, publicName: b.publicName ?? null, sellerType: b.sellerType ?? "OWNER", bio: b.bio ?? null }).onConflictDoUpdate({ target: userProfiles.userId, set: { ...(b.publicName !== undefined ? { publicName: b.publicName } : {}), ...(b.sellerType ? { sellerType: b.sellerType } : {}), ...(b.bio !== undefined ? { bio: b.bio } : {}), updatedAt: new Date() } }); await audit("me.update", { actorId: u.id, meta: { fields: Object.keys(b) } }); return NextResponse.json({ ok: true }); } catch (e) { return handle(e); } } /** Droit à l'effacement : annonces archivées, messages anonymisés, sessions révoquées, compte anonymisé et marqué DELETED. */ export async function DELETE() { try { await assertSameOrigin(); const u = await requireUser(); const d = db(); await d.transaction(async (tx) => { await tx.update(listings).set({ status: "ARCHIVED", updatedAt: new Date() }).where(eq(listings.ownerUserId, u.id)); await tx.update(messages).set({ body: "[message supprimé par son auteur]" }).where(eq(messages.senderId, u.id)); await tx.update(sessions).set({ revokedAt: new Date() }).where(eq(sessions.userId, u.id)); await tx.delete(userProfiles).where(eq(userProfiles.userId, u.id)); await tx.update(users).set({ email: `deleted+${u.id}@toveli.invalid`, displayName: null, phoneEncrypted: null, totpSecretEncrypted: null, prefs: {}, status: "DELETED", deletedAt: new Date(), updatedAt: new Date() }).where(eq(users.id, u.id)); }); await audit("me.delete", { actorId: u.id }); (await cookies()).delete(COOKIE); return NextResponse.json({ ok: true }); } catch (e) { return handle(e); } }