Files
kovakyazilim/lib/admin-actions.ts
T
Ege Can Komur 4096b3d87b fix: Node 26 uyumsuzluğunu çöz — node-appwrite -> appwrite SDK
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
2026-05-20 02:21:34 +03:00

408 lines
11 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 } from "appwrite";
import {
DATABASE_ID,
MEDIA_BUCKET_ID,
TABLES,
userDB,
userStorage,
} from "@/lib/appwrite-server";
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()
.replace(/[ğ]/g, "g")
.replace(/[ü]/g, "u")
.replace(/[ş]/g, "s")
.replace(/[ı]/g, "i")
.replace(/[ö]/g, "o")
.replace(/[ç]/g, "c")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "")
.slice(0, 80);
}
function num(v: FormDataEntryValue | null) {
if (v === null || v === "") return null;
const n = Number(v);
return Number.isFinite(n) ? n : null;
}
function bool(v: FormDataEntryValue | null) {
return v === "on" || v === "true" || v === "1";
}
function str(v: FormDataEntryValue | null) {
if (v === null) return null;
const s = String(v).trim();
return s === "" ? null : s;
}
function strArr(v: FormDataEntryValue | null) {
if (v === null) return null;
return String(v)
.split(",")
.map((x) => x.trim())
.filter(Boolean);
}
// ─── Media upload ────────────────────────────────────────────────
export async function uploadMedia(formData: FormData): Promise<void> {
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,
});
revalidatePath("/admin/medya");
}
export async function deleteMediaFile(fileId: string) {
const s = await storage();
await s.deleteFile({ bucketId: MEDIA_BUCKET_ID, fileId });
revalidatePath("/admin/medya");
}
// ─── Blog ────────────────────────────────────────────────────────
export async function saveBlogPost(formData: FormData) {
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");
const slug = str(formData.get("slug")) || slugify(title);
const status = (str(formData.get("status")) ?? "draft") as "draft" | "published";
const data = {
slug,
title,
excerpt: str(formData.get("excerpt")),
content: str(formData.get("content")),
cover_image: str(formData.get("cover_image")),
cover_file_id: str(formData.get("cover_file_id")),
author: str(formData.get("author")),
status,
published_at:
status === "published"
? str(formData.get("published_at")) || new Date().toISOString()
: null,
tags: strArr(formData.get("tags")),
seo_title: str(formData.get("seo_title")),
seo_description: str(formData.get("seo_description")),
seo_image: str(formData.get("seo_image")),
};
if (id) {
await d.updateRow({
databaseId: DATABASE_ID,
tableId: TABLES.blogPosts,
rowId: id,
data,
});
} else {
await d.createRow({
databaseId: DATABASE_ID,
tableId: TABLES.blogPosts,
rowId: ID.unique(),
data,
});
}
revalidatePath("/admin/blog");
revalidatePath("/blog");
if (slug) revalidatePath(`/blog/${slug}`);
}
export async function deleteBlogPost(formData: FormData) {
const d = await db();
const id = String(formData.get("id"));
await d.deleteRow({
databaseId: DATABASE_ID,
tableId: TABLES.blogPosts,
rowId: id,
});
revalidatePath("/admin/blog");
revalidatePath("/blog");
}
// ─── Services ────────────────────────────────────────────────────
export async function saveService(formData: FormData) {
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");
const description = str(formData.get("description"));
if (!description) throw new Error("Açıklama zorunlu");
const slug = str(formData.get("slug")) || slugify(title);
const data = {
slug,
title,
description,
icon: str(formData.get("icon")),
order: num(formData.get("order")) ?? 0,
featured: bool(formData.get("featured")),
};
if (id) {
await d.updateRow({
databaseId: DATABASE_ID,
tableId: TABLES.services,
rowId: id,
data,
});
} else {
await d.createRow({
databaseId: DATABASE_ID,
tableId: TABLES.services,
rowId: slug,
data,
});
}
revalidatePath("/admin/hizmetler");
revalidatePath("/hizmetler");
revalidatePath("/");
}
export async function deleteService(formData: FormData) {
const d = await db();
const id = String(formData.get("id"));
await d.deleteRow({
databaseId: DATABASE_ID,
tableId: TABLES.services,
rowId: id,
});
revalidatePath("/admin/hizmetler");
revalidatePath("/hizmetler");
}
// ─── Projects ────────────────────────────────────────────────────
export async function saveProject(formData: FormData) {
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");
const slug = str(formData.get("slug")) || slugify(title);
const description = str(formData.get("description"));
if (!description) throw new Error("Açıklama zorunlu");
const data = {
slug,
title,
description,
image_url: str(formData.get("image_url")),
live_url: str(formData.get("live_url")),
category: str(formData.get("category")),
technologies: strArr(formData.get("technologies")),
year: num(formData.get("year")),
featured: bool(formData.get("featured")),
};
if (id) {
await d.updateRow({
databaseId: DATABASE_ID,
tableId: TABLES.projects,
rowId: id,
data,
});
} else {
await d.createRow({
databaseId: DATABASE_ID,
tableId: TABLES.projects,
rowId: ID.unique(),
data,
});
}
revalidatePath("/admin/projeler");
revalidatePath("/projeler");
revalidatePath("/");
}
export async function deleteProject(formData: FormData) {
const d = await db();
const id = String(formData.get("id"));
await d.deleteRow({
databaseId: DATABASE_ID,
tableId: TABLES.projects,
rowId: id,
});
revalidatePath("/admin/projeler");
revalidatePath("/projeler");
}
// ─── Testimonials ────────────────────────────────────────────────
export async function saveTestimonial(formData: FormData) {
const d = await db();
const id = str(formData.get("id"));
const name = str(formData.get("name"));
if (!name) throw new Error("Ad zorunlu");
const message = str(formData.get("message"));
if (!message) throw new Error("Mesaj zorunlu");
const data = {
name,
role: str(formData.get("role")),
company: str(formData.get("company")),
message,
rating: num(formData.get("rating")) ?? 5,
image_url: str(formData.get("image_url")),
order: num(formData.get("order")) ?? 0,
featured: bool(formData.get("featured")),
};
if (id) {
await d.updateRow({
databaseId: DATABASE_ID,
tableId: TABLES.testimonials,
rowId: id,
data,
});
} else {
await d.createRow({
databaseId: DATABASE_ID,
tableId: TABLES.testimonials,
rowId: ID.unique(),
data,
});
}
revalidatePath("/admin/referanslar");
revalidatePath("/");
}
export async function deleteTestimonial(formData: FormData) {
const d = await db();
const id = String(formData.get("id"));
await d.deleteRow({
databaseId: DATABASE_ID,
tableId: TABLES.testimonials,
rowId: id,
});
revalidatePath("/admin/referanslar");
}
// ─── SEO Settings (singleton) ────────────────────────────────────
export async function saveSeoSettings(formData: FormData) {
const d = await db();
const data = {
site_name: str(formData.get("site_name")),
site_description: str(formData.get("site_description")),
default_og_image: str(formData.get("default_og_image")),
twitter_handle: str(formData.get("twitter_handle")),
facebook_url: str(formData.get("facebook_url")),
linkedin_url: str(formData.get("linkedin_url")),
instagram_url: str(formData.get("instagram_url")),
google_site_verification: str(formData.get("google_site_verification")),
gtm_id: str(formData.get("gtm_id")),
};
try {
await d.updateRow({
databaseId: DATABASE_ID,
tableId: TABLES.seoSettings,
rowId: "global",
data,
});
} catch {
await d.createRow({
databaseId: DATABASE_ID,
tableId: TABLES.seoSettings,
rowId: "global",
data,
});
}
revalidatePath("/", "layout");
revalidatePath("/admin/seo");
}
// ─── SEO Page (per path) ─────────────────────────────────────────
export async function saveSeoPage(formData: FormData) {
const d = await db();
const id = str(formData.get("id"));
const path = str(formData.get("path"));
if (!path) throw new Error("Path zorunlu");
const data = {
path,
title: str(formData.get("title")),
description: str(formData.get("description")),
og_image: str(formData.get("og_image")),
canonical: str(formData.get("canonical")),
noindex: bool(formData.get("noindex")),
};
if (id) {
await d.updateRow({
databaseId: DATABASE_ID,
tableId: TABLES.seoPages,
rowId: id,
data,
});
} else {
await d.createRow({
databaseId: DATABASE_ID,
tableId: TABLES.seoPages,
rowId: ID.unique(),
data,
});
}
revalidatePath(path);
revalidatePath("/admin/seo");
}
export async function deleteSeoPage(formData: FormData) {
const d = await db();
const id = String(formData.get("id"));
await d.deleteRow({
databaseId: DATABASE_ID,
tableId: TABLES.seoPages,
rowId: id,
});
revalidatePath("/admin/seo");
}
// ─── Contact messages ────────────────────────────────────────────
export async function updateMessageStatus(formData: FormData) {
const d = await db();
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 },
});
revalidatePath("/admin/iletisim");
}
export async function deleteMessage(formData: FormData) {
const d = await db();
const id = String(formData.get("id"));
await d.deleteRow({
databaseId: DATABASE_ID,
tableId: TABLES.contactMessages,
rowId: id,
});
revalidatePath("/admin/iletisim");
}