"use server"; import { revalidatePath } from "next/cache"; import { ID, Permission, Role } from "node-appwrite"; import { InputFile } from "node-appwrite/file"; import { logAudit } from "./audit"; import type { LogoActionState } from "./logo-types"; import { BUCKETS, DATABASE_ID, TABLES } from "./schema"; import { createAdminClient } from "./server"; import { requireRole, requireTenant } from "./tenant-guard"; const MAX_BYTES = 2 * 1024 * 1024; const ALLOWED_TYPES = new Set([ "image/png", "image/jpeg", "image/jpg", "image/webp", "image/svg+xml", ]); function teamLogoPermissions(tenantId: string) { return [ Permission.read(Role.any()), Permission.update(Role.team(tenantId, "owner")), Permission.update(Role.team(tenantId, "admin")), Permission.delete(Role.team(tenantId, "owner")), Permission.delete(Role.team(tenantId, "admin")), ]; } export async function uploadLogoAction( _prev: LogoActionState, formData: FormData, ): Promise { let ctx; try { ctx = await requireTenant(); requireRole(ctx, ["owner", "admin"]); } catch { return { ok: false, error: "Logo yüklemek için yönetici yetkisi gerekli." }; } const file = formData.get("logo"); if (!(file instanceof File) || file.size === 0) { return { ok: false, error: "Dosya seçin." }; } if (file.size > MAX_BYTES) { return { ok: false, error: "Dosya 2MB'dan büyük olamaz." }; } if (!ALLOWED_TYPES.has(file.type)) { return { ok: false, error: "Sadece PNG, JPG, WebP veya SVG yükleyebilirsiniz." }; } if (!ctx.settings) { return { ok: false, error: "Çalışma alanı ayarları bulunamadı." }; } const { storage, tablesDB } = createAdminClient(); const previousLogoId = ctx.settings.logo; let newFileId: string | null = null; try { const buffer = Buffer.from(await file.arrayBuffer()); const inputFile = InputFile.fromBuffer(buffer, file.name); const created = await storage.createFile({ bucketId: BUCKETS.tenantLogos, fileId: ID.unique(), file: inputFile, permissions: teamLogoPermissions(ctx.tenantId), }); newFileId = created.$id; await tablesDB.updateRow(DATABASE_ID, TABLES.tenantSettings, ctx.settings.$id, { logo: newFileId, }); if (previousLogoId && previousLogoId !== newFileId) { try { await storage.deleteFile({ bucketId: BUCKETS.tenantLogos, fileId: previousLogoId, }); } catch { // best-effort — orphaned file is acceptable, won't block the new logo } } await logAudit({ tenantId: ctx.tenantId, userId: ctx.user.id, action: "update", entityType: "tenant_logo", entityId: newFileId, changes: { previous: previousLogoId ?? null }, }); } catch (e) { if (newFileId) { try { await storage.deleteFile({ bucketId: BUCKETS.tenantLogos, fileId: newFileId, }); } catch { /* ignore cleanup error */ } } return { ok: false, error: e instanceof Error ? e.message : "Logo yüklenemedi.", }; } revalidatePath("/settings/workspace"); revalidatePath("/", "layout"); return { ok: true }; } export async function removeLogoAction(): Promise { let ctx; try { ctx = await requireTenant(); requireRole(ctx, ["owner", "admin"]); } catch { return { ok: false, error: "Logo silmek için yönetici yetkisi gerekli." }; } if (!ctx.settings) { return { ok: false, error: "Çalışma alanı ayarları bulunamadı." }; } const previousLogoId = ctx.settings.logo; if (!previousLogoId) { return { ok: true }; } try { const { storage, tablesDB } = createAdminClient(); await tablesDB.updateRow(DATABASE_ID, TABLES.tenantSettings, ctx.settings.$id, { logo: null, }); try { await storage.deleteFile({ bucketId: BUCKETS.tenantLogos, fileId: previousLogoId, }); } catch { /* file already gone, fine */ } await logAudit({ tenantId: ctx.tenantId, userId: ctx.user.id, action: "delete", entityType: "tenant_logo", entityId: previousLogoId, }); } catch (e) { return { ok: false, error: e instanceof Error ? e.message : "Logo silinemedi.", }; } revalidatePath("/settings/workspace"); revalidatePath("/", "layout"); return { ok: true }; }