From 4096b3d87b7afc8858802b70be3736044f7220d2 Mon Sep 17 00:00:00 2001 From: Ege Can Komur Date: Wed, 20 May 2026 02:21:34 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20Node=2026=20uyumsuzlu=C4=9Funu=20=C3=A7?= =?UTF-8?q?=C3=B6z=20=E2=80=94=20node-appwrite=20->=20appwrite=20SDK?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sorun: - node-appwrite paketi 'node-fetch-native-with-agent' polyfill'i kullanıyor - Node.js 26'nın undici implementation'ı ile uyumsuz - 'fetch failed / InvalidArgumentError: invalid onError method' hatası - Login dahil tüm Appwrite çağrıları başarısız Çözüm: - Tüm node-appwrite kullanımını browser SDK 'appwrite'a geçir - Browser SDK native fetch kullanıyor, Node 26 uyumlu - API key tabanlı admin client yerine session cookie tabanlı user client - Public reads (read('any')): publicDB (auth'suz client) - Admin CRUD: userDB(sessionSecret) (cookie'deki session) - Storage upload doğrudan File objesi alıyor (InputFile.fromBuffer gerekmez) Etkilenen dosyalar: - lib/appwrite-server.ts: publicClient + sessionClient - lib/auth.ts: requireSessionSecret eklendi - lib/admin-actions.ts: tüm action'lar sessionClient kullanıyor - app/actions.ts: publicDB - lib/data.ts: publicDB - app/admin/login/actions.ts: appwrite SDK - app/admin/(protected)/page.tsx, medya/page.tsx: userDB/userStorage End-to-end test edildi: ✓ Login (401 doğru hata) ✓ Public read (services) ✓ Anonim create (contact form) ✓ npm run build 23 route --- app/actions.ts | 6 +- app/admin/(protected)/medya/page.tsx | 8 ++- app/admin/(protected)/page.tsx | 8 ++- app/admin/login/actions.ts | 2 +- lib/admin-actions.ts | 94 ++++++++++++++-------------- lib/appwrite-server.ts | 34 +++++----- lib/auth.ts | 20 ++++-- lib/data.ts | 21 ++++--- package-lock.json | 17 ----- package.json | 1 - 10 files changed, 107 insertions(+), 104 deletions(-) diff --git a/app/actions.ts b/app/actions.ts index 706163e..f20c624 100644 --- a/app/actions.ts +++ b/app/actions.ts @@ -1,7 +1,7 @@ "use server"; -import { ID } from "node-appwrite"; -import { adminDB, DATABASE_ID, TABLES } from "@/lib/appwrite-server"; +import { ID } from "appwrite"; +import { publicDB, DATABASE_ID, TABLES } from "@/lib/appwrite-server"; export type ContactFormState = { ok: boolean; @@ -34,7 +34,7 @@ export async function submitContact( } try { - await adminDB.createRow({ + await publicDB.createRow({ databaseId: DATABASE_ID, tableId: TABLES.contactMessages, rowId: ID.unique(), diff --git a/app/admin/(protected)/medya/page.tsx b/app/admin/(protected)/medya/page.tsx index 128fc2f..971864a 100644 --- a/app/admin/(protected)/medya/page.tsx +++ b/app/admin/(protected)/medya/page.tsx @@ -1,13 +1,15 @@ import { PageHeader } from "@/components/admin/form"; -import { adminStorage, MEDIA_BUCKET_ID } from "@/lib/appwrite-server"; -import { Query } from "node-appwrite"; +import { MEDIA_BUCKET_ID, userStorage } from "@/lib/appwrite-server"; +import { requireSessionSecret } from "@/lib/auth"; +import { Query } from "appwrite"; import { Upload } from "lucide-react"; import { DeleteButton } from "@/components/admin/delete-button"; import { uploadMedia, deleteMediaFile } from "@/lib/admin-actions"; async function listFiles() { try { - const res = await adminStorage.listFiles({ + const secret = await requireSessionSecret(); + const res = await userStorage(secret).listFiles({ bucketId: MEDIA_BUCKET_ID, queries: [Query.orderDesc("$createdAt"), Query.limit(100)], }); diff --git a/app/admin/(protected)/page.tsx b/app/admin/(protected)/page.tsx index 4a4325b..91f0d83 100644 --- a/app/admin/(protected)/page.tsx +++ b/app/admin/(protected)/page.tsx @@ -1,11 +1,13 @@ import Link from "next/link"; import { ArrowRight } from "lucide-react"; -import { adminDB, DATABASE_ID, TABLES } from "@/lib/appwrite-server"; -import { Query } from "node-appwrite"; +import { Query } from "appwrite"; +import { DATABASE_ID, TABLES, userDB } from "@/lib/appwrite-server"; +import { requireSessionSecret } from "@/lib/auth"; async function safeCount(tableId: string, queries: string[] = []) { try { - const res = await adminDB.listRows({ + const secret = await requireSessionSecret(); + const res = await userDB(secret).listRows({ databaseId: DATABASE_ID, tableId, queries: [...queries, Query.limit(1)], diff --git a/app/admin/login/actions.ts b/app/admin/login/actions.ts index 21b7ca7..bfd603a 100644 --- a/app/admin/login/actions.ts +++ b/app/admin/login/actions.ts @@ -2,7 +2,7 @@ import { cookies } from "next/headers"; import { redirect } from "next/navigation"; -import { Account, Client } from "node-appwrite"; +import { Account, Client } from "appwrite"; import { SESSION_COOKIE } from "@/lib/auth"; const endpoint = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!; diff --git a/lib/admin-actions.ts b/lib/admin-actions.ts index ba04a31..ef0b800 100644 --- a/lib/admin-actions.ts +++ b/lib/admin-actions.ts @@ -1,20 +1,23 @@ "use server"; import { revalidatePath } from "next/cache"; -import { ID } from "node-appwrite"; -import { InputFile } from "node-appwrite/file"; +import { ID } from "appwrite"; import { - adminDB, - adminStorage, DATABASE_ID, MEDIA_BUCKET_ID, TABLES, + userDB, + userStorage, } from "@/lib/appwrite-server"; -import { getCurrentUser } from "@/lib/auth"; +import { requireSessionSecret } from "@/lib/auth"; -async function gate() { - const user = await getCurrentUser(); - if (!user) throw new Error("Yetkisiz"); +async function db() { + const secret = await requireSessionSecret(); + return userDB(secret); +} +async function storage() { + const secret = await requireSessionSecret(); + return userStorage(secret); } function slugify(s: string) { @@ -55,30 +58,29 @@ function strArr(v: FormDataEntryValue | null) { // ─── Media upload ──────────────────────────────────────────────── export async function uploadMedia(formData: FormData): Promise { - await gate(); const file = formData.get("file"); if (!(file instanceof File) || file.size === 0) { throw new Error("Dosya seçilmedi"); } - const bytes = Buffer.from(await file.arrayBuffer()); - await adminStorage.createFile({ + const s = await storage(); + await s.createFile({ bucketId: MEDIA_BUCKET_ID, fileId: ID.unique(), - file: InputFile.fromBuffer(bytes, file.name), + file, }); revalidatePath("/admin/medya"); } export async function deleteMediaFile(fileId: string) { - await gate(); - await adminStorage.deleteFile({ bucketId: MEDIA_BUCKET_ID, fileId }); + const s = await storage(); + await s.deleteFile({ bucketId: MEDIA_BUCKET_ID, fileId }); revalidatePath("/admin/medya"); } // ─── Blog ──────────────────────────────────────────────────────── export async function saveBlogPost(formData: FormData) { - await gate(); + const d = await db(); const id = str(formData.get("id")); const title = str(formData.get("title")); if (!title) throw new Error("Başlık zorunlu"); @@ -105,14 +107,14 @@ export async function saveBlogPost(formData: FormData) { }; if (id) { - await adminDB.updateRow({ + await d.updateRow({ databaseId: DATABASE_ID, tableId: TABLES.blogPosts, rowId: id, data, }); } else { - await adminDB.createRow({ + await d.createRow({ databaseId: DATABASE_ID, tableId: TABLES.blogPosts, rowId: ID.unique(), @@ -125,9 +127,9 @@ export async function saveBlogPost(formData: FormData) { } export async function deleteBlogPost(formData: FormData) { - await gate(); + const d = await db(); const id = String(formData.get("id")); - await adminDB.deleteRow({ + await d.deleteRow({ databaseId: DATABASE_ID, tableId: TABLES.blogPosts, rowId: id, @@ -139,7 +141,7 @@ export async function deleteBlogPost(formData: FormData) { // ─── Services ──────────────────────────────────────────────────── export async function saveService(formData: FormData) { - await gate(); + const d = await db(); const id = str(formData.get("id")); const title = str(formData.get("title")); if (!title) throw new Error("Başlık zorunlu"); @@ -156,14 +158,14 @@ export async function saveService(formData: FormData) { featured: bool(formData.get("featured")), }; if (id) { - await adminDB.updateRow({ + await d.updateRow({ databaseId: DATABASE_ID, tableId: TABLES.services, rowId: id, data, }); } else { - await adminDB.createRow({ + await d.createRow({ databaseId: DATABASE_ID, tableId: TABLES.services, rowId: slug, @@ -176,9 +178,9 @@ export async function saveService(formData: FormData) { } export async function deleteService(formData: FormData) { - await gate(); + const d = await db(); const id = String(formData.get("id")); - await adminDB.deleteRow({ + await d.deleteRow({ databaseId: DATABASE_ID, tableId: TABLES.services, rowId: id, @@ -190,7 +192,7 @@ export async function deleteService(formData: FormData) { // ─── Projects ──────────────────────────────────────────────────── export async function saveProject(formData: FormData) { - await gate(); + const d = await db(); const id = str(formData.get("id")); const title = str(formData.get("title")); if (!title) throw new Error("Başlık zorunlu"); @@ -211,14 +213,14 @@ export async function saveProject(formData: FormData) { }; if (id) { - await adminDB.updateRow({ + await d.updateRow({ databaseId: DATABASE_ID, tableId: TABLES.projects, rowId: id, data, }); } else { - await adminDB.createRow({ + await d.createRow({ databaseId: DATABASE_ID, tableId: TABLES.projects, rowId: ID.unique(), @@ -231,9 +233,9 @@ export async function saveProject(formData: FormData) { } export async function deleteProject(formData: FormData) { - await gate(); + const d = await db(); const id = String(formData.get("id")); - await adminDB.deleteRow({ + await d.deleteRow({ databaseId: DATABASE_ID, tableId: TABLES.projects, rowId: id, @@ -245,7 +247,7 @@ export async function deleteProject(formData: FormData) { // ─── Testimonials ──────────────────────────────────────────────── export async function saveTestimonial(formData: FormData) { - await gate(); + const d = await db(); const id = str(formData.get("id")); const name = str(formData.get("name")); if (!name) throw new Error("Ad zorunlu"); @@ -264,14 +266,14 @@ export async function saveTestimonial(formData: FormData) { }; if (id) { - await adminDB.updateRow({ + await d.updateRow({ databaseId: DATABASE_ID, tableId: TABLES.testimonials, rowId: id, data, }); } else { - await adminDB.createRow({ + await d.createRow({ databaseId: DATABASE_ID, tableId: TABLES.testimonials, rowId: ID.unique(), @@ -283,9 +285,9 @@ export async function saveTestimonial(formData: FormData) { } export async function deleteTestimonial(formData: FormData) { - await gate(); + const d = await db(); const id = String(formData.get("id")); - await adminDB.deleteRow({ + await d.deleteRow({ databaseId: DATABASE_ID, tableId: TABLES.testimonials, rowId: id, @@ -296,7 +298,7 @@ export async function deleteTestimonial(formData: FormData) { // ─── SEO Settings (singleton) ──────────────────────────────────── export async function saveSeoSettings(formData: FormData) { - await gate(); + const d = await db(); const data = { site_name: str(formData.get("site_name")), site_description: str(formData.get("site_description")), @@ -309,14 +311,14 @@ export async function saveSeoSettings(formData: FormData) { gtm_id: str(formData.get("gtm_id")), }; try { - await adminDB.updateRow({ + await d.updateRow({ databaseId: DATABASE_ID, tableId: TABLES.seoSettings, rowId: "global", data, }); } catch { - await adminDB.createRow({ + await d.createRow({ databaseId: DATABASE_ID, tableId: TABLES.seoSettings, rowId: "global", @@ -330,7 +332,7 @@ export async function saveSeoSettings(formData: FormData) { // ─── SEO Page (per path) ───────────────────────────────────────── export async function saveSeoPage(formData: FormData) { - await gate(); + const d = await db(); const id = str(formData.get("id")); const path = str(formData.get("path")); if (!path) throw new Error("Path zorunlu"); @@ -345,14 +347,14 @@ export async function saveSeoPage(formData: FormData) { }; if (id) { - await adminDB.updateRow({ + await d.updateRow({ databaseId: DATABASE_ID, tableId: TABLES.seoPages, rowId: id, data, }); } else { - await adminDB.createRow({ + await d.createRow({ databaseId: DATABASE_ID, tableId: TABLES.seoPages, rowId: ID.unique(), @@ -364,9 +366,9 @@ export async function saveSeoPage(formData: FormData) { } export async function deleteSeoPage(formData: FormData) { - await gate(); + const d = await db(); const id = String(formData.get("id")); - await adminDB.deleteRow({ + await d.deleteRow({ databaseId: DATABASE_ID, tableId: TABLES.seoPages, rowId: id, @@ -377,14 +379,14 @@ export async function deleteSeoPage(formData: FormData) { // ─── Contact messages ──────────────────────────────────────────── export async function updateMessageStatus(formData: FormData) { - await gate(); + const d = await db(); const id = String(formData.get("id")); const status = String(formData.get("status")) as | "new" | "read" | "replied" | "archived"; - await adminDB.updateRow({ + await d.updateRow({ databaseId: DATABASE_ID, tableId: TABLES.contactMessages, rowId: id, @@ -394,9 +396,9 @@ export async function updateMessageStatus(formData: FormData) { } export async function deleteMessage(formData: FormData) { - await gate(); + const d = await db(); const id = String(formData.get("id")); - await adminDB.deleteRow({ + await d.deleteRow({ databaseId: DATABASE_ID, tableId: TABLES.contactMessages, rowId: id, diff --git a/lib/appwrite-server.ts b/lib/appwrite-server.ts index 382bf68..94dd42d 100644 --- a/lib/appwrite-server.ts +++ b/lib/appwrite-server.ts @@ -1,9 +1,8 @@ import "server-only"; -import { Account, Client, Storage, TablesDB } from "node-appwrite"; +import { Account, Client, Storage, TablesDB } from "appwrite"; const endpoint = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!; const projectId = process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!; -const apiKey = process.env.APPWRITE_API_KEY; export const DATABASE_ID = process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID!; export const MEDIA_BUCKET_ID = @@ -19,21 +18,28 @@ export const TABLES = { seoSettings: "seo_settings", } as const; -export function adminClient() { - const c = new Client().setEndpoint(endpoint).setProject(projectId); - if (apiKey) c.setKey(apiKey); - return c; +function newClient() { + return new Client().setEndpoint(endpoint).setProject(projectId); +} + +export function publicClient() { + return newClient(); } export function sessionClient(sessionSecret: string) { - return new Client() - .setEndpoint(endpoint) - .setProject(projectId) - .setSession(sessionSecret); + return newClient().setSession(sessionSecret); } -export const adminDB = new TablesDB(adminClient()); -export const adminStorage = new Storage(adminClient()); -export const adminAccount = new Account(adminClient()); +export const publicDB = new TablesDB(publicClient()); +export const publicStorage = new Storage(publicClient()); +export const publicAccount = new Account(publicClient()); -export { Account, TablesDB, Storage }; +export function userDB(secret: string) { + return new TablesDB(sessionClient(secret)); +} +export function userStorage(secret: string) { + return new Storage(sessionClient(secret)); +} +export function userAccount(secret: string) { + return new Account(sessionClient(secret)); +} diff --git a/lib/auth.ts b/lib/auth.ts index 59971f3..03167dc 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -1,18 +1,20 @@ import "server-only"; import { cookies } from "next/headers"; import { redirect } from "next/navigation"; -import { Account } from "node-appwrite"; -import { sessionClient } from "@/lib/appwrite-server"; +import { userAccount } from "@/lib/appwrite-server"; export const SESSION_COOKIE = "kovak_session"; -export async function getCurrentUser() { +export async function getSessionSecret() { const store = await cookies(); - const secret = store.get(SESSION_COOKIE)?.value; + return store.get(SESSION_COOKIE)?.value ?? null; +} + +export async function getCurrentUser() { + const secret = await getSessionSecret(); if (!secret) return null; try { - const account = new Account(sessionClient(secret)); - return await account.get(); + return await userAccount(secret).get(); } catch { return null; } @@ -23,3 +25,9 @@ export async function requireUser() { if (!user) redirect("/admin/login"); return user; } + +export async function requireSessionSecret() { + const secret = await getSessionSecret(); + if (!secret) redirect("/admin/login"); + return secret; +} diff --git a/lib/data.ts b/lib/data.ts index 6737671..69b1593 100644 --- a/lib/data.ts +++ b/lib/data.ts @@ -1,6 +1,6 @@ import "server-only"; -import { Query } from "node-appwrite"; -import { adminDB, DATABASE_ID, TABLES } from "@/lib/appwrite-server"; +import { Query } from "appwrite"; +import { publicDB, DATABASE_ID, TABLES } from "@/lib/appwrite-server"; import type { BlogPostRow, ContactMessageRow, @@ -11,12 +11,9 @@ import type { TestimonialRow, } from "@/lib/types"; -async function safeList( - tableId: string, - queries: string[], -): Promise { +async function safeList(tableId: string, queries: string[]): Promise { try { - const res = await adminDB.listRows({ + const res = await publicDB.listRows({ databaseId: DATABASE_ID, tableId, queries, @@ -91,11 +88,11 @@ export async function listSeoPages() { export async function getSeoSettings(): Promise { try { - return await adminDB.getRow({ + return (await publicDB.getRow({ databaseId: DATABASE_ID, tableId: TABLES.seoSettings, rowId: "global", - }); + })) as unknown as SeoSettingsRow; } catch { return null; } @@ -103,7 +100,11 @@ export async function getSeoSettings(): Promise { export async function getRow(tableId: string, rowId: string): Promise { try { - return (await adminDB.getRow({ databaseId: DATABASE_ID, tableId, rowId })) as T; + return (await publicDB.getRow({ + databaseId: DATABASE_ID, + tableId, + rowId, + })) as unknown as T; } catch { return null; } diff --git a/package-lock.json b/package-lock.json index 25cc4bb..8332522 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "lucide-react": "^1.16.0", "marked": "^18.0.4", "next": "16.2.6", - "node-appwrite": "^25.0.0", "react": "19.2.4", "react-dom": "19.2.4" }, @@ -1599,22 +1598,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/node-appwrite": { - "version": "25.0.0", - "resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-25.0.0.tgz", - "integrity": "sha512-KpZ/3Ed8euz6r5CwjquElX3wRkNuiRuRQqjROiHK+feZ2ZX8HjjcF5IwrjTJYSNaYrmIwsZoex4L0ezzWjYWFg==", - "license": "BSD-3-Clause", - "dependencies": { - "json-bigint": "1.0.0", - "node-fetch-native-with-agent": "1.7.2" - } - }, - "node_modules/node-fetch-native-with-agent": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/node-fetch-native-with-agent/-/node-fetch-native-with-agent-1.7.2.tgz", - "integrity": "sha512-5MaOOCuJEvcckoz7/tjdx1M6OusOY6Xc5f459IaruGStWnKzlI1qpNgaAwmn4LmFYcsSlj+jBMk84wmmRxfk5g==", - "license": "MIT" - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", diff --git a/package.json b/package.json index d7c5772..0057110 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ "lucide-react": "^1.16.0", "marked": "^18.0.4", "next": "16.2.6", - "node-appwrite": "^25.0.0", "react": "19.2.4", "react-dom": "19.2.4" },