Files
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

127 lines
4.6 KiB
TypeScript
Raw Permalink 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.
import Link from "next/link";
import { ArrowRight } from "lucide-react";
import { DATABASE_ID, Q, TABLES, tablesDB } from "@/lib/appwrite-rest";
import { requireSessionSecret } from "@/lib/auth";
async function safeCount(tableId: string, queries: string[] = []) {
try {
const secret = await requireSessionSecret();
const res = await tablesDB.listRows(
DATABASE_ID,
tableId,
[...queries, Q.limit(1)],
secret,
);
return res.total ?? 0;
} catch {
return 0;
}
}
export default async function AdminDashboard() {
const [posts, drafts, services, projects, testimonials, newMessages, totalMessages] =
await Promise.all([
safeCount(TABLES.blogPosts, [Q.equal("status", "published")]),
safeCount(TABLES.blogPosts, [Q.equal("status", "draft")]),
safeCount(TABLES.services),
safeCount(TABLES.projects),
safeCount(TABLES.testimonials),
safeCount(TABLES.contactMessages, [Q.equal("status", "new")]),
safeCount(TABLES.contactMessages),
]);
const cards = [
{ label: "Yayında blog", value: posts, href: "/admin/blog", accent: "navy" },
{ label: "Taslak blog", value: drafts, href: "/admin/blog", accent: "amber" },
{ label: "Hizmet", value: services, href: "/admin/hizmetler", accent: "navy" },
{ label: "Proje", value: projects, href: "/admin/projeler", accent: "navy" },
{ label: "Referans", value: testimonials, href: "/admin/referanslar", accent: "navy" },
{ label: "Yeni mesaj", value: newMessages, href: "/admin/iletisim?filter=new", accent: "red" },
{ label: "Toplam mesaj", value: totalMessages, href: "/admin/iletisim", accent: "navy" },
];
return (
<div>
<header>
<h1 className="text-2xl font-bold text-[var(--navy)]">Pano</h1>
<p className="mt-1 text-sm text-[var(--muted)]">
Site içeriklerini buradan yönetin.
</p>
</header>
<div className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{cards.map((c) => (
<Link
key={c.label}
href={c.href}
className="group rounded-2xl border border-[var(--border)] bg-white p-5 transition hover:border-[var(--sky)]/40 hover:shadow-md"
>
<p className="text-xs font-medium uppercase tracking-wider text-[var(--muted)]">
{c.label}
</p>
<div className="mt-2 flex items-end justify-between">
<p
className={`text-3xl font-bold ${
c.accent === "red"
? "text-red-600"
: c.accent === "amber"
? "text-amber-600"
: "text-[var(--navy)]"
}`}
>
{c.value}
</p>
<ArrowRight className="size-4 -translate-x-2 text-[var(--muted)] opacity-0 transition group-hover:translate-x-0 group-hover:opacity-100" />
</div>
</Link>
))}
</div>
<section className="mt-10 grid gap-6 lg:grid-cols-2">
<QuickActions />
<RecentLinks />
</section>
</div>
);
}
function QuickActions() {
const actions = [
{ href: "/admin/blog/new", label: "Yeni blog yazısı" },
{ href: "/admin/projeler/new", label: "Yeni proje ekle" },
{ href: "/admin/referanslar/new", label: "Yeni referans" },
{ href: "/admin/seo", label: "SEO ayarları" },
];
return (
<div className="rounded-2xl border border-[var(--border)] bg-white p-6">
<h2 className="text-base font-semibold text-[var(--navy)]">Hızlı aksiyonlar</h2>
<ul className="mt-4 space-y-2">
{actions.map((a) => (
<li key={a.href}>
<Link
href={a.href}
className="flex items-center justify-between rounded-lg border border-[var(--border)] px-3 py-2 text-sm text-[var(--navy)] transition hover:border-[var(--sky)]"
>
{a.label}
<ArrowRight className="size-4 text-[var(--sky-600)]" />
</Link>
</li>
))}
</ul>
</div>
);
}
function RecentLinks() {
return (
<div className="rounded-2xl border border-[var(--border)] bg-white p-6">
<h2 className="text-base font-semibold text-[var(--navy)]">Kısayollar</h2>
<ul className="mt-4 space-y-1 text-sm text-[var(--muted)]">
<li> <Link href="/" target="_blank" className="hover:text-[var(--navy)]">Siteyi yeni sekmede </Link></li>
<li> <Link href="/admin/medya" className="hover:text-[var(--navy)]">Medya kütüphanesi</Link></li>
<li> <Link href="/admin/seo" className="hover:text-[var(--navy)]">SEO yönetimi</Link></li>
</ul>
</div>
);
}