Files
isletmem-kovakcrm/src/lib/appwrite/customer-actions.ts
T

206 lines
5.3 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 { AppwriteException, ID, Permission, Role } from "node-appwrite";
import { z } from "zod";
import { logAudit } from "./audit";
import {
isPlanLimitError,
planLimitMessage,
requirePlanCapacity,
} from "./plan-limits";
import { DATABASE_ID, TABLES, type Customer } from "./schema";
import { createAdminClient } from "./server";
import { requireTenant } from "./tenant-guard";
import type { CustomerActionState } from "./customer-types";
import { customerSchema } from "@/lib/validation/customers";
function appwriteError(e: unknown): string {
if (e instanceof AppwriteException) {
return e.message || "Beklenmeyen bir hata oluştu.";
}
return "Bağlantı hatası. Tekrar deneyin.";
}
function pickFormFields(formData: FormData) {
return {
name: String(formData.get("name") ?? "").trim(),
email: String(formData.get("email") ?? "").trim(),
phone: String(formData.get("phone") ?? "").trim(),
taxId: String(formData.get("taxId") ?? "").trim(),
taxOffice: String(formData.get("taxOffice") ?? "").trim(),
website: String(formData.get("website") ?? "").trim(),
address: String(formData.get("address") ?? "").trim(),
notes: String(formData.get("notes") ?? "").trim(),
status: (formData.get("status") as "active" | "passive" | null) ?? "active",
};
}
function flattenErrors(err: z.ZodError): Record<string, string> {
const out: Record<string, string> = {};
for (const issue of err.issues) {
const key = issue.path.join(".");
if (key && !out[key]) out[key] = issue.message;
}
return out;
}
function teamRowPermissions(tenantId: string) {
return [
Permission.read(Role.team(tenantId)),
Permission.update(Role.team(tenantId)),
Permission.delete(Role.team(tenantId, "owner")),
Permission.delete(Role.team(tenantId, "admin")),
];
}
export async function createCustomerAction(
_prev: CustomerActionState,
formData: FormData,
): Promise<CustomerActionState> {
let ctx;
try {
ctx = await requireTenant();
} catch {
return { ok: false, error: "Yetkiniz yok." };
}
const parsed = customerSchema.safeParse(pickFormFields(formData));
if (!parsed.success) {
return { ok: false, error: "Form geçersiz.", fieldErrors: flattenErrors(parsed.error) };
}
try {
await requirePlanCapacity(ctx, "customers");
} catch (e) {
if (isPlanLimitError(e)) {
return {
ok: false,
error: planLimitMessage(e.resource, e.limit),
code: "PLAN_LIMIT_EXCEEDED",
};
}
throw e;
}
try {
const { tablesDB } = createAdminClient();
const row = await tablesDB.createRow(
DATABASE_ID,
TABLES.customers,
ID.unique(),
{
tenantId: ctx.tenantId,
createdBy: ctx.user.id,
...parsed.data,
},
teamRowPermissions(ctx.tenantId),
);
await logAudit({
tenantId: ctx.tenantId,
userId: ctx.user.id,
action: "create",
entityType: "customer",
entityId: row.$id,
changes: parsed.data,
});
} catch (e) {
return { ok: false, error: appwriteError(e) };
}
revalidatePath("/customers");
return { ok: true };
}
export async function updateCustomerAction(
_prev: CustomerActionState,
formData: FormData,
): Promise<CustomerActionState> {
const id = String(formData.get("id") ?? "");
if (!id) return { ok: false, error: "ID eksik." };
let ctx;
try {
ctx = await requireTenant();
} catch {
return { ok: false, error: "Yetkiniz yok." };
}
const parsed = customerSchema.safeParse(pickFormFields(formData));
if (!parsed.success) {
return { ok: false, error: "Form geçersiz.", fieldErrors: flattenErrors(parsed.error) };
}
try {
const { tablesDB } = createAdminClient();
const existing = (await tablesDB.getRow(
DATABASE_ID,
TABLES.customers,
id,
)) as unknown as Customer;
if (existing.tenantId !== ctx.tenantId) {
return { ok: false, error: "Erişim engellendi." };
}
await tablesDB.updateRow(DATABASE_ID, TABLES.customers, id, parsed.data);
await logAudit({
tenantId: ctx.tenantId,
userId: ctx.user.id,
action: "update",
entityType: "customer",
entityId: id,
changes: parsed.data,
});
} catch (e) {
return { ok: false, error: appwriteError(e) };
}
revalidatePath("/customers");
return { ok: true };
}
export async function deleteCustomerAction(formData: FormData): Promise<CustomerActionState> {
const id = String(formData.get("id") ?? "");
if (!id) return { ok: false, error: "ID eksik." };
let ctx;
try {
ctx = await requireTenant();
} catch {
return { ok: false, error: "Yetkiniz yok." };
}
try {
const { tablesDB } = createAdminClient();
const existing = (await tablesDB.getRow(
DATABASE_ID,
TABLES.customers,
id,
)) as unknown as Customer;
if (existing.tenantId !== ctx.tenantId) {
return { ok: false, error: "Erişim engellendi." };
}
await tablesDB.deleteRow(DATABASE_ID, TABLES.customers, id);
await logAudit({
tenantId: ctx.tenantId,
userId: ctx.user.id,
action: "delete",
entityType: "customer",
entityId: id,
changes: { name: existing.name },
});
} catch (e) {
return { ok: false, error: appwriteError(e) };
}
revalidatePath("/customers");
return { ok: true };
}