import { dbGlobal } from "drizzle-pkg/lib/db"; import { users } from "drizzle-pkg/lib/schema/auth"; import { eq } from "drizzle-orm"; import { visibilitySchema, type Visibility } from "#server/constants/visibility"; import { syncProfileMediaRefs } from "#server/service/media"; import { z } from "zod"; const publicSlugValue = z .string() .regex(/^[a-z0-9]([a-z0-9-]{0,28}[a-z0-9])?$/) .min(3) .max(30); const linkItemSchema = z.object({ label: z.string().min(1).max(80), url: z.string().url().max(2000), visibility: visibilitySchema, }); export type SocialLinkItem = z.infer; export async function getProfileRow(userId: number) { const [row] = await dbGlobal.select().from(users).where(eq(users.id, userId)).limit(1); return row ?? null; } export async function updateProfile( userId: number, patch: { nickname?: string | null; avatar?: string | null; avatarVisibility?: Visibility; bioMarkdown?: string | null; bioVisibility?: Visibility; socialLinks?: SocialLinkItem[]; publicSlug?: string | null; }, ) { const updates: Record = {}; if (patch.nickname !== undefined) { updates.nickname = patch.nickname?.trim() || null; } if (patch.avatar !== undefined) { updates.avatar = patch.avatar?.trim() || null; } if (patch.avatarVisibility !== undefined) { updates.avatarVisibility = visibilitySchema.parse(patch.avatarVisibility); } if (patch.bioMarkdown !== undefined) { updates.bioMarkdown = patch.bioMarkdown?.trim() || null; } if (patch.bioVisibility !== undefined) { updates.bioVisibility = visibilitySchema.parse(patch.bioVisibility); } if (patch.socialLinks !== undefined) { const parsed = z.array(linkItemSchema).max(50).parse(patch.socialLinks); updates.socialLinksJson = JSON.stringify(parsed); } if (patch.publicSlug !== undefined) { if (patch.publicSlug === null || patch.publicSlug === "") { updates.publicSlug = null; } else { updates.publicSlug = publicSlugValue.parse(patch.publicSlug.trim().toLowerCase()); } } if (Object.keys(updates).length === 0) { return getProfileRow(userId); } const syncMedia = patch.bioMarkdown !== undefined || patch.avatar !== undefined; await dbGlobal.update(users).set(updates as never).where(eq(users.id, userId)); const row = await getProfileRow(userId); if (row && syncMedia) { await syncProfileMediaRefs(userId, row.bioMarkdown ?? null, row.avatar ?? null); } return row; } export function parseSocialLinksJson(raw: string | null | undefined): SocialLinkItem[] { if (!raw || raw === "[]") { return []; } try { const data = JSON.parse(raw) as unknown; return z.array(linkItemSchema).parse(data); } catch { return []; } }