Files
kovakyazilim/lib/admin-actions.ts
T
Ege Can Komur 7eb0c1acc2 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
2026-05-20 02:29:19 +03:00

360 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 {
DATABASE_ID,
ID,
MEDIA_BUCKET_ID,
storage,
TABLES,
tablesDB,
} from "@/lib/appwrite-rest";
import { requireSessionSecret } from "@/lib/auth";
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 ───────────────────────────────────────────────────────
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");
}
await storage.createFile(MEDIA_BUCKET_ID, ID.unique(), file, secret);
revalidatePath("/admin/medya");
}
export async function deleteMediaFile(fileId: string) {
const secret = await requireSessionSecret();
await storage.deleteFile(MEDIA_BUCKET_ID, fileId, secret);
revalidatePath("/admin/medya");
}
// ─── Blog ────────────────────────────────────────────────────────
export async function saveBlogPost(formData: FormData) {
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 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 tablesDB.updateRow(DATABASE_ID, TABLES.blogPosts, id, data, secret);
} else {
await tablesDB.createRow(
DATABASE_ID,
TABLES.blogPosts,
ID.unique(),
data,
secret,
);
}
revalidatePath("/admin/blog");
revalidatePath("/blog");
if (slug) revalidatePath(`/blog/${slug}`);
}
export async function deleteBlogPost(formData: FormData) {
const secret = await requireSessionSecret();
const id = String(formData.get("id"));
await tablesDB.deleteRow(DATABASE_ID, TABLES.blogPosts, id, secret);
revalidatePath("/admin/blog");
revalidatePath("/blog");
}
// ─── Services ────────────────────────────────────────────────────
export async function saveService(formData: FormData) {
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 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 tablesDB.updateRow(DATABASE_ID, TABLES.services, id, data, secret);
} else {
await tablesDB.createRow(
DATABASE_ID,
TABLES.services,
slug,
data,
secret,
);
}
revalidatePath("/admin/hizmetler");
revalidatePath("/hizmetler");
revalidatePath("/");
}
export async function deleteService(formData: FormData) {
const secret = await requireSessionSecret();
const id = String(formData.get("id"));
await tablesDB.deleteRow(DATABASE_ID, TABLES.services, id, secret);
revalidatePath("/admin/hizmetler");
revalidatePath("/hizmetler");
}
// ─── Projects ────────────────────────────────────────────────────
export async function saveProject(formData: FormData) {
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 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 tablesDB.updateRow(DATABASE_ID, TABLES.projects, id, data, secret);
} else {
await tablesDB.createRow(
DATABASE_ID,
TABLES.projects,
ID.unique(),
data,
secret,
);
}
revalidatePath("/admin/projeler");
revalidatePath("/projeler");
revalidatePath("/");
}
export async function deleteProject(formData: FormData) {
const secret = await requireSessionSecret();
const id = String(formData.get("id"));
await tablesDB.deleteRow(DATABASE_ID, TABLES.projects, id, secret);
revalidatePath("/admin/projeler");
revalidatePath("/projeler");
}
// ─── Testimonials ────────────────────────────────────────────────
export async function saveTestimonial(formData: FormData) {
const secret = await requireSessionSecret();
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 tablesDB.updateRow(DATABASE_ID, TABLES.testimonials, id, data, secret);
} else {
await tablesDB.createRow(
DATABASE_ID,
TABLES.testimonials,
ID.unique(),
data,
secret,
);
}
revalidatePath("/admin/referanslar");
revalidatePath("/");
}
export async function deleteTestimonial(formData: FormData) {
const secret = await requireSessionSecret();
const id = String(formData.get("id"));
await tablesDB.deleteRow(DATABASE_ID, TABLES.testimonials, id, secret);
revalidatePath("/admin/referanslar");
}
// ─── SEO Settings ────────────────────────────────────────────────
export async function saveSeoSettings(formData: FormData) {
const secret = await requireSessionSecret();
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 tablesDB.updateRow(
DATABASE_ID,
TABLES.seoSettings,
"global",
data,
secret,
);
} catch {
await tablesDB.createRow(
DATABASE_ID,
TABLES.seoSettings,
"global",
data,
secret,
);
}
revalidatePath("/", "layout");
revalidatePath("/admin/seo");
}
// ─── SEO Page ────────────────────────────────────────────────────
export async function saveSeoPage(formData: FormData) {
const secret = await requireSessionSecret();
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 tablesDB.updateRow(DATABASE_ID, TABLES.seoPages, id, data, secret);
} else {
await tablesDB.createRow(
DATABASE_ID,
TABLES.seoPages,
ID.unique(),
data,
secret,
);
}
revalidatePath(path);
revalidatePath("/admin/seo");
}
export async function deleteSeoPage(formData: FormData) {
const secret = await requireSessionSecret();
const id = String(formData.get("id"));
await tablesDB.deleteRow(DATABASE_ID, TABLES.seoPages, id, secret);
revalidatePath("/admin/seo");
}
// ─── Contact ─────────────────────────────────────────────────────
export async function updateMessageStatus(formData: FormData) {
const secret = await requireSessionSecret();
const id = String(formData.get("id"));
const status = String(formData.get("status")) as
| "new"
| "read"
| "replied"
| "archived";
await tablesDB.updateRow(
DATABASE_ID,
TABLES.contactMessages,
id,
{ status },
secret,
);
revalidatePath("/admin/iletisim");
}
export async function deleteMessage(formData: FormData) {
const secret = await requireSessionSecret();
const id = String(formData.get("id"));
await tablesDB.deleteRow(
DATABASE_ID,
TABLES.contactMessages,
id,
secret,
);
revalidatePath("/admin/iletisim");
}