fix(auth): SDK'yı kaldırıp ince REST katmanına geç (lib/appwrite-rest.ts)
Sorun: - node-appwrite v20-25 hepsi Node 26'da bozuk (node-fetch-native-with-agent polyfill) - appwrite browser SDK Server Action context'inde 'unexpected response' veriyor (büyük olasılıkla browser-only global'ları kontrol ederken) Çözüm: - Tüm Appwrite SDK'larını sil (appwrite + node-appwrite) - lib/appwrite-rest.ts: native fetch üzerinde ~250 satırlık ince REST wrapper - account: createEmailPasswordSession, get, deleteSession - tablesDB: listRows, getRow, createRow, updateRow, deleteRow - storage: listFiles, createFile, deleteFile, fileViewUrl - Q helpers: equal, orderAsc/Desc, limit, offset - AppwriteError class - Session secret cookie tabanlı auth korundu (isletmem-kovakcrm'deki desen) - Tüm CRUD action ve query'ler REST katmanına bağlandı End-to-end test edildi: ✓ Login (proper 401 hata mesajları, başarılı durumda redirect) ✓ Public read (services, blog, testimonials) ✓ Anonim create (contact form) ✓ Build (24 route) ✓ Sıfır SDK bağımlılığı — Node 26 sorunu yok
This commit is contained in:
+92
-140
@@ -1,25 +1,16 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { ID } from "appwrite";
|
||||
import {
|
||||
DATABASE_ID,
|
||||
ID,
|
||||
MEDIA_BUCKET_ID,
|
||||
storage,
|
||||
TABLES,
|
||||
userDB,
|
||||
userStorage,
|
||||
} from "@/lib/appwrite-server";
|
||||
tablesDB,
|
||||
} from "@/lib/appwrite-rest";
|
||||
import { requireSessionSecret } from "@/lib/auth";
|
||||
|
||||
async function db() {
|
||||
const secret = await requireSessionSecret();
|
||||
return userDB(secret);
|
||||
}
|
||||
async function storage() {
|
||||
const secret = await requireSessionSecret();
|
||||
return userStorage(secret);
|
||||
}
|
||||
|
||||
function slugify(s: string) {
|
||||
return s
|
||||
.toLowerCase()
|
||||
@@ -55,37 +46,34 @@ function strArr(v: FormDataEntryValue | null) {
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
// ─── Media upload ────────────────────────────────────────────────
|
||||
// ─── Media ───────────────────────────────────────────────────────
|
||||
|
||||
export async function uploadMedia(formData: FormData): Promise<void> {
|
||||
const secret = await requireSessionSecret();
|
||||
const file = formData.get("file");
|
||||
if (!(file instanceof File) || file.size === 0) {
|
||||
throw new Error("Dosya seçilmedi");
|
||||
}
|
||||
const s = await storage();
|
||||
await s.createFile({
|
||||
bucketId: MEDIA_BUCKET_ID,
|
||||
fileId: ID.unique(),
|
||||
file,
|
||||
});
|
||||
await storage.createFile(MEDIA_BUCKET_ID, ID.unique(), file, secret);
|
||||
revalidatePath("/admin/medya");
|
||||
}
|
||||
|
||||
export async function deleteMediaFile(fileId: string) {
|
||||
const s = await storage();
|
||||
await s.deleteFile({ bucketId: MEDIA_BUCKET_ID, fileId });
|
||||
const secret = await requireSessionSecret();
|
||||
await storage.deleteFile(MEDIA_BUCKET_ID, fileId, secret);
|
||||
revalidatePath("/admin/medya");
|
||||
}
|
||||
|
||||
// ─── Blog ────────────────────────────────────────────────────────
|
||||
|
||||
export async function saveBlogPost(formData: FormData) {
|
||||
const d = await db();
|
||||
const secret = await requireSessionSecret();
|
||||
const id = str(formData.get("id"));
|
||||
const title = str(formData.get("title"));
|
||||
if (!title) throw new Error("Başlık zorunlu");
|
||||
const slug = str(formData.get("slug")) || slugify(title);
|
||||
const status = (str(formData.get("status")) ?? "draft") as "draft" | "published";
|
||||
const status =
|
||||
(str(formData.get("status")) ?? "draft") as "draft" | "published";
|
||||
|
||||
const data = {
|
||||
slug,
|
||||
@@ -107,19 +95,15 @@ export async function saveBlogPost(formData: FormData) {
|
||||
};
|
||||
|
||||
if (id) {
|
||||
await d.updateRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.blogPosts,
|
||||
rowId: id,
|
||||
data,
|
||||
});
|
||||
await tablesDB.updateRow(DATABASE_ID, TABLES.blogPosts, id, data, secret);
|
||||
} else {
|
||||
await d.createRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.blogPosts,
|
||||
rowId: ID.unique(),
|
||||
await tablesDB.createRow(
|
||||
DATABASE_ID,
|
||||
TABLES.blogPosts,
|
||||
ID.unique(),
|
||||
data,
|
||||
});
|
||||
secret,
|
||||
);
|
||||
}
|
||||
revalidatePath("/admin/blog");
|
||||
revalidatePath("/blog");
|
||||
@@ -127,13 +111,9 @@ export async function saveBlogPost(formData: FormData) {
|
||||
}
|
||||
|
||||
export async function deleteBlogPost(formData: FormData) {
|
||||
const d = await db();
|
||||
const secret = await requireSessionSecret();
|
||||
const id = String(formData.get("id"));
|
||||
await d.deleteRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.blogPosts,
|
||||
rowId: id,
|
||||
});
|
||||
await tablesDB.deleteRow(DATABASE_ID, TABLES.blogPosts, id, secret);
|
||||
revalidatePath("/admin/blog");
|
||||
revalidatePath("/blog");
|
||||
}
|
||||
@@ -141,7 +121,7 @@ export async function deleteBlogPost(formData: FormData) {
|
||||
// ─── Services ────────────────────────────────────────────────────
|
||||
|
||||
export async function saveService(formData: FormData) {
|
||||
const d = await db();
|
||||
const secret = await requireSessionSecret();
|
||||
const id = str(formData.get("id"));
|
||||
const title = str(formData.get("title"));
|
||||
if (!title) throw new Error("Başlık zorunlu");
|
||||
@@ -158,19 +138,15 @@ export async function saveService(formData: FormData) {
|
||||
featured: bool(formData.get("featured")),
|
||||
};
|
||||
if (id) {
|
||||
await d.updateRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.services,
|
||||
rowId: id,
|
||||
data,
|
||||
});
|
||||
await tablesDB.updateRow(DATABASE_ID, TABLES.services, id, data, secret);
|
||||
} else {
|
||||
await d.createRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.services,
|
||||
rowId: slug,
|
||||
await tablesDB.createRow(
|
||||
DATABASE_ID,
|
||||
TABLES.services,
|
||||
slug,
|
||||
data,
|
||||
});
|
||||
secret,
|
||||
);
|
||||
}
|
||||
revalidatePath("/admin/hizmetler");
|
||||
revalidatePath("/hizmetler");
|
||||
@@ -178,13 +154,9 @@ export async function saveService(formData: FormData) {
|
||||
}
|
||||
|
||||
export async function deleteService(formData: FormData) {
|
||||
const d = await db();
|
||||
const secret = await requireSessionSecret();
|
||||
const id = String(formData.get("id"));
|
||||
await d.deleteRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.services,
|
||||
rowId: id,
|
||||
});
|
||||
await tablesDB.deleteRow(DATABASE_ID, TABLES.services, id, secret);
|
||||
revalidatePath("/admin/hizmetler");
|
||||
revalidatePath("/hizmetler");
|
||||
}
|
||||
@@ -192,7 +164,7 @@ export async function deleteService(formData: FormData) {
|
||||
// ─── Projects ────────────────────────────────────────────────────
|
||||
|
||||
export async function saveProject(formData: FormData) {
|
||||
const d = await db();
|
||||
const secret = await requireSessionSecret();
|
||||
const id = str(formData.get("id"));
|
||||
const title = str(formData.get("title"));
|
||||
if (!title) throw new Error("Başlık zorunlu");
|
||||
@@ -213,19 +185,15 @@ export async function saveProject(formData: FormData) {
|
||||
};
|
||||
|
||||
if (id) {
|
||||
await d.updateRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.projects,
|
||||
rowId: id,
|
||||
data,
|
||||
});
|
||||
await tablesDB.updateRow(DATABASE_ID, TABLES.projects, id, data, secret);
|
||||
} else {
|
||||
await d.createRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.projects,
|
||||
rowId: ID.unique(),
|
||||
await tablesDB.createRow(
|
||||
DATABASE_ID,
|
||||
TABLES.projects,
|
||||
ID.unique(),
|
||||
data,
|
||||
});
|
||||
secret,
|
||||
);
|
||||
}
|
||||
revalidatePath("/admin/projeler");
|
||||
revalidatePath("/projeler");
|
||||
@@ -233,13 +201,9 @@ export async function saveProject(formData: FormData) {
|
||||
}
|
||||
|
||||
export async function deleteProject(formData: FormData) {
|
||||
const d = await db();
|
||||
const secret = await requireSessionSecret();
|
||||
const id = String(formData.get("id"));
|
||||
await d.deleteRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.projects,
|
||||
rowId: id,
|
||||
});
|
||||
await tablesDB.deleteRow(DATABASE_ID, TABLES.projects, id, secret);
|
||||
revalidatePath("/admin/projeler");
|
||||
revalidatePath("/projeler");
|
||||
}
|
||||
@@ -247,7 +211,7 @@ export async function deleteProject(formData: FormData) {
|
||||
// ─── Testimonials ────────────────────────────────────────────────
|
||||
|
||||
export async function saveTestimonial(formData: FormData) {
|
||||
const d = await db();
|
||||
const secret = await requireSessionSecret();
|
||||
const id = str(formData.get("id"));
|
||||
const name = str(formData.get("name"));
|
||||
if (!name) throw new Error("Ad zorunlu");
|
||||
@@ -266,39 +230,31 @@ export async function saveTestimonial(formData: FormData) {
|
||||
};
|
||||
|
||||
if (id) {
|
||||
await d.updateRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.testimonials,
|
||||
rowId: id,
|
||||
data,
|
||||
});
|
||||
await tablesDB.updateRow(DATABASE_ID, TABLES.testimonials, id, data, secret);
|
||||
} else {
|
||||
await d.createRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.testimonials,
|
||||
rowId: ID.unique(),
|
||||
await tablesDB.createRow(
|
||||
DATABASE_ID,
|
||||
TABLES.testimonials,
|
||||
ID.unique(),
|
||||
data,
|
||||
});
|
||||
secret,
|
||||
);
|
||||
}
|
||||
revalidatePath("/admin/referanslar");
|
||||
revalidatePath("/");
|
||||
}
|
||||
|
||||
export async function deleteTestimonial(formData: FormData) {
|
||||
const d = await db();
|
||||
const secret = await requireSessionSecret();
|
||||
const id = String(formData.get("id"));
|
||||
await d.deleteRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.testimonials,
|
||||
rowId: id,
|
||||
});
|
||||
await tablesDB.deleteRow(DATABASE_ID, TABLES.testimonials, id, secret);
|
||||
revalidatePath("/admin/referanslar");
|
||||
}
|
||||
|
||||
// ─── SEO Settings (singleton) ────────────────────────────────────
|
||||
// ─── SEO Settings ────────────────────────────────────────────────
|
||||
|
||||
export async function saveSeoSettings(formData: FormData) {
|
||||
const d = await db();
|
||||
const secret = await requireSessionSecret();
|
||||
const data = {
|
||||
site_name: str(formData.get("site_name")),
|
||||
site_description: str(formData.get("site_description")),
|
||||
@@ -311,28 +267,30 @@ export async function saveSeoSettings(formData: FormData) {
|
||||
gtm_id: str(formData.get("gtm_id")),
|
||||
};
|
||||
try {
|
||||
await d.updateRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.seoSettings,
|
||||
rowId: "global",
|
||||
await tablesDB.updateRow(
|
||||
DATABASE_ID,
|
||||
TABLES.seoSettings,
|
||||
"global",
|
||||
data,
|
||||
});
|
||||
secret,
|
||||
);
|
||||
} catch {
|
||||
await d.createRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.seoSettings,
|
||||
rowId: "global",
|
||||
await tablesDB.createRow(
|
||||
DATABASE_ID,
|
||||
TABLES.seoSettings,
|
||||
"global",
|
||||
data,
|
||||
});
|
||||
secret,
|
||||
);
|
||||
}
|
||||
revalidatePath("/", "layout");
|
||||
revalidatePath("/admin/seo");
|
||||
}
|
||||
|
||||
// ─── SEO Page (per path) ─────────────────────────────────────────
|
||||
// ─── SEO Page ────────────────────────────────────────────────────
|
||||
|
||||
export async function saveSeoPage(formData: FormData) {
|
||||
const d = await db();
|
||||
const secret = await requireSessionSecret();
|
||||
const id = str(formData.get("id"));
|
||||
const path = str(formData.get("path"));
|
||||
if (!path) throw new Error("Path zorunlu");
|
||||
@@ -347,61 +305,55 @@ export async function saveSeoPage(formData: FormData) {
|
||||
};
|
||||
|
||||
if (id) {
|
||||
await d.updateRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.seoPages,
|
||||
rowId: id,
|
||||
data,
|
||||
});
|
||||
await tablesDB.updateRow(DATABASE_ID, TABLES.seoPages, id, data, secret);
|
||||
} else {
|
||||
await d.createRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.seoPages,
|
||||
rowId: ID.unique(),
|
||||
await tablesDB.createRow(
|
||||
DATABASE_ID,
|
||||
TABLES.seoPages,
|
||||
ID.unique(),
|
||||
data,
|
||||
});
|
||||
secret,
|
||||
);
|
||||
}
|
||||
revalidatePath(path);
|
||||
revalidatePath("/admin/seo");
|
||||
}
|
||||
|
||||
export async function deleteSeoPage(formData: FormData) {
|
||||
const d = await db();
|
||||
const secret = await requireSessionSecret();
|
||||
const id = String(formData.get("id"));
|
||||
await d.deleteRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.seoPages,
|
||||
rowId: id,
|
||||
});
|
||||
await tablesDB.deleteRow(DATABASE_ID, TABLES.seoPages, id, secret);
|
||||
revalidatePath("/admin/seo");
|
||||
}
|
||||
|
||||
// ─── Contact messages ────────────────────────────────────────────
|
||||
// ─── Contact ─────────────────────────────────────────────────────
|
||||
|
||||
export async function updateMessageStatus(formData: FormData) {
|
||||
const d = await db();
|
||||
const secret = await requireSessionSecret();
|
||||
const id = String(formData.get("id"));
|
||||
const status = String(formData.get("status")) as
|
||||
| "new"
|
||||
| "read"
|
||||
| "replied"
|
||||
| "archived";
|
||||
await d.updateRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.contactMessages,
|
||||
rowId: id,
|
||||
data: { status },
|
||||
});
|
||||
await tablesDB.updateRow(
|
||||
DATABASE_ID,
|
||||
TABLES.contactMessages,
|
||||
id,
|
||||
{ status },
|
||||
secret,
|
||||
);
|
||||
revalidatePath("/admin/iletisim");
|
||||
}
|
||||
|
||||
export async function deleteMessage(formData: FormData) {
|
||||
const d = await db();
|
||||
const secret = await requireSessionSecret();
|
||||
const id = String(formData.get("id"));
|
||||
await d.deleteRow({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.contactMessages,
|
||||
rowId: id,
|
||||
});
|
||||
await tablesDB.deleteRow(
|
||||
DATABASE_ID,
|
||||
TABLES.contactMessages,
|
||||
id,
|
||||
secret,
|
||||
);
|
||||
revalidatePath("/admin/iletisim");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user