Files
kovakemlak-crm/src/lib/appwrite/logo-actions.ts
T
egecankomur 37679e83e6 init: kovakemlak-crm project scaffold
- Next.js 16 + Appwrite multi-tenant emlak CRM
- Database: kovakemlak-db (properties, customers, customer_searches, property_matches, presentations, investors, activities, tenant_settings)
- Same stack as isletmem-kovakcrm (shadcn/ui template base)
- Modules: portföy, müşteri takibi, arama kriterleri, otomatik eşleştirme, sunum linki, yatırımcı portalı
2026-05-05 04:37:04 +03:00

172 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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<LogoActionState> {
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<LogoActionState> {
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 };
}