SPB Git forge

spb/toveli

Public
14commits 1branches 0releases
1.1 MBsize
maindefault branch
yesterdaylast push
TypeScript 50.6% JavaScript 46.1% SQL 2.1% Shell 0.6% CSS 0.5%
3.8 KB · 45 lines typescript
Raw Blame History
1import { NextRequest, NextResponse } from "next/server";2import { z } from "zod";3import { eq } from "drizzle-orm";4import { db } from "@/db/client";5import { users, userProfiles, listings, messages, sessions } from "@/db/schema";6import { cookies } from "next/headers";7import { COOKIE } from "@/lib/auth/session";8import { assertSameOrigin, currentUser, requireUser } from "@/lib/auth/session";9import { encrypt } from "@/lib/auth/crypto";10import { audit } from "@/lib/auth/audit";11import { handle, parseBody } from "@/lib/api";12export const dynamic = "force-dynamic";13export async function GET() {14  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); }15}16const 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(),17  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() });18export async function PATCH(req: NextRequest) {19  try {20    await assertSameOrigin(); const u = await requireUser(); const b = await parseBody(req, Patch); const d = db();21    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;22    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));23    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() } });24    await audit("me.update", { actorId: u.id, meta: { fields: Object.keys(b) } });25    return NextResponse.json({ ok: true });26  } catch (e) { return handle(e); }27}2829/** Droit à l'effacement : annonces archivées, messages anonymisés, sessions révoquées, compte anonymisé et marqué DELETED. */30export async function DELETE() {31  try {32    await assertSameOrigin(); const u = await requireUser(); const d = db();33    await d.transaction(async (tx) => {34      await tx.update(listings).set({ status: "ARCHIVED", updatedAt: new Date() }).where(eq(listings.ownerUserId, u.id));35      await tx.update(messages).set({ body: "[message supprimé par son auteur]" }).where(eq(messages.senderId, u.id));36      await tx.update(sessions).set({ revokedAt: new Date() }).where(eq(sessions.userId, u.id));37      await tx.delete(userProfiles).where(eq(userProfiles.userId, u.id));38      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));39    });40    await audit("me.delete", { actorId: u.id });41    (await cookies()).delete(COOKIE);42    return NextResponse.json({ ok: true });43  } catch (e) { return handle(e); }44}45