import "server-only"; const ENDPOINT = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!; const PROJECT_ID = process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!; if (!ENDPOINT || !PROJECT_ID) { throw new Error( "Missing NEXT_PUBLIC_APPWRITE_ENDPOINT or NEXT_PUBLIC_APPWRITE_PROJECT_ID", ); } export const DATABASE_ID = process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID!; export const MEDIA_BUCKET_ID = process.env.NEXT_PUBLIC_APPWRITE_MEDIA_BUCKET_ID ?? "kovak-yazilim-media"; export const TABLES = { contactMessages: "contact_messages", services: "services", projects: "projects", blogPosts: "blog_posts", testimonials: "testimonials", seoPages: "seo_pages", seoSettings: "seo_settings", } as const; export class AppwriteError extends Error { code: number; type?: string; constructor(message: string, code: number, type?: string) { super(message); this.name = "AppwriteError"; this.code = code; this.type = type; } } type FetchOpts = { method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; body?: unknown; session?: string; query?: Record; formData?: FormData; }; async function aw(path: string, opts: FetchOpts = {}): Promise { const url = new URL(ENDPOINT + path); if (opts.query) { for (const [k, v] of Object.entries(opts.query)) { if (v === undefined) continue; if (Array.isArray(v)) v.forEach((x) => url.searchParams.append(k + "[]", String(x))); else url.searchParams.set(k, String(v)); } } const headers: Record = { "X-Appwrite-Project": PROJECT_ID, "X-Appwrite-Response-Format": "1.9.0", }; if (opts.session) headers["X-Appwrite-Session"] = opts.session; let body: BodyInit | undefined; if (opts.formData) { body = opts.formData; } else if (opts.body !== undefined) { headers["Content-Type"] = "application/json"; body = JSON.stringify(opts.body); } const res = await fetch(url, { method: opts.method ?? "GET", headers, body, cache: "no-store", }); if (!res.ok) { let data: { message?: string; type?: string } = {}; try { data = await res.json(); } catch { /* ignore */ } throw new AppwriteError( data.message || `HTTP ${res.status}`, res.status, data.type, ); } const ct = res.headers.get("content-type") ?? ""; if (ct.includes("application/json")) { return (await res.json()) as T; } return undefined as T; } // ─── Query helpers ─────────────────────────────────────────────── export const Q = { equal: (attr: string, value: string | number | boolean | string[]) => JSON.stringify({ method: "equal", attribute: attr, values: Array.isArray(value) ? value : [value], }), notEqual: (attr: string, value: string | number) => JSON.stringify({ method: "notEqual", attribute: attr, values: [value] }), orderAsc: (attr: string) => JSON.stringify({ method: "orderAsc", attribute: attr }), orderDesc: (attr: string) => JSON.stringify({ method: "orderDesc", attribute: attr }), limit: (n: number) => JSON.stringify({ method: "limit", values: [n] }), offset: (n: number) => JSON.stringify({ method: "offset", values: [n] }), }; // ─── Types ─────────────────────────────────────────────────────── export interface AwRow { $id: string; $createdAt: string; $updatedAt: string; [k: string]: unknown; } export interface AwListResponse { total: number; rows: T[]; } export interface AwFile { $id: string; name: string; sizeOriginal: number; mimeType: string; $createdAt: string; } export interface AwSession { $id: string; secret: string; expire: string; userId: string; provider: string; } export interface AwUser { $id: string; email: string; name: string; status: boolean; } // ─── Account ───────────────────────────────────────────────────── export const account = { createEmailPasswordSession(email: string, password: string) { return aw("/account/sessions/email", { method: "POST", body: { email, password }, }); }, get(session: string) { return aw("/account", { session }); }, deleteSession(sessionId: string, session: string) { return aw(`/account/sessions/${sessionId}`, { method: "DELETE", session, }); }, }; // ─── TablesDB ──────────────────────────────────────────────────── export const tablesDB = { listRows( databaseId: string, tableId: string, queries: string[] = [], session?: string, ) { return aw>( `/tablesdb/${databaseId}/tables/${tableId}/rows`, { query: { queries }, session }, ); }, getRow( databaseId: string, tableId: string, rowId: string, session?: string, ) { return aw(`/tablesdb/${databaseId}/tables/${tableId}/rows/${rowId}`, { session, }); }, createRow( databaseId: string, tableId: string, rowId: string, data: Record, session?: string, ) { return aw(`/tablesdb/${databaseId}/tables/${tableId}/rows`, { method: "POST", body: { rowId, data }, session, }); }, updateRow( databaseId: string, tableId: string, rowId: string, data: Record, session?: string, ) { return aw(`/tablesdb/${databaseId}/tables/${tableId}/rows/${rowId}`, { method: "PATCH", body: { data }, session, }); }, deleteRow( databaseId: string, tableId: string, rowId: string, session?: string, ) { return aw(`/tablesdb/${databaseId}/tables/${tableId}/rows/${rowId}`, { method: "DELETE", session, }); }, }; // ─── Storage ───────────────────────────────────────────────────── export const storage = { listFiles(bucketId: string, queries: string[] = [], session?: string) { return aw<{ total: number; files: AwFile[] }>( `/storage/buckets/${bucketId}/files`, { query: { queries }, session }, ); }, createFile( bucketId: string, fileId: string, file: File, session?: string, ) { const fd = new FormData(); fd.append("fileId", fileId); fd.append("file", file); return aw(`/storage/buckets/${bucketId}/files`, { method: "POST", formData: fd, session, }); }, deleteFile(bucketId: string, fileId: string, session?: string) { return aw(`/storage/buckets/${bucketId}/files/${fileId}`, { method: "DELETE", session, }); }, fileViewUrl(bucketId: string, fileId: string) { return `${ENDPOINT}/storage/buckets/${bucketId}/files/${fileId}/view?project=${PROJECT_ID}`; }, }; // ─── ID generator (Appwrite "unique()") ────────────────────────── export const ID = { unique() { return "unique()"; }, custom(id: string) { return id; }, };