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

122 lines
3.9 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 { PageHeader } from "@/components/admin/form";
import { MEDIA_BUCKET_ID, Q, storage } from "@/lib/appwrite-rest";
import { requireSessionSecret } from "@/lib/auth";
import { Upload } from "lucide-react";
import { DeleteButton } from "@/components/admin/delete-button";
import { uploadMedia, deleteMediaFile } from "@/lib/admin-actions";
async function listFiles() {
try {
const secret = await requireSessionSecret();
const res = await storage.listFiles(
MEDIA_BUCKET_ID,
[Q.orderDesc("$createdAt"), Q.limit(100)],
secret,
);
return res.files;
} catch {
return [];
}
}
function fileViewUrl(id: string) {
return storage.fileViewUrl(MEDIA_BUCKET_ID, id);
}
export default async function MediaPage() {
const files = await listFiles();
async function deleteAction(formData: FormData) {
"use server";
const id = String(formData.get("id"));
await deleteMediaFile(id);
}
return (
<div>
<PageHeader
title="Medya kütüphanesi"
description="Görselleri yükleyin ve URL'lerini kopyalayın."
/>
<form
action={uploadMedia}
encType="multipart/form-data"
className="mt-6 rounded-2xl border border-dashed border-[var(--border)] bg-white p-6"
>
<label className="flex flex-col items-center gap-3 text-center">
<Upload className="size-8 text-[var(--sky-600)]" />
<span className="text-sm font-medium text-[var(--navy)]">
Görsel yüklemek için dosya seçin (max 10 MB)
</span>
<input
name="file"
type="file"
accept="image/*"
required
className="block max-w-md text-sm"
/>
<button
type="submit"
className="rounded-full bg-[var(--navy)] px-5 py-2 text-sm font-medium text-white transition hover:bg-[var(--navy-700)]"
>
Yükle
</button>
</label>
</form>
<div className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{files.length === 0 && (
<div className="col-span-full rounded-2xl border border-dashed border-[var(--border)] bg-white p-12 text-center text-sm text-[var(--muted)]">
Henüz görsel yüklenmemiş.
</div>
)}
{files.map((f) => {
const url = fileViewUrl(f.$id);
return (
<div
key={f.$id}
className="overflow-hidden rounded-xl border border-[var(--border)] bg-white"
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={url}
alt={f.name}
className="aspect-square w-full object-cover"
loading="lazy"
/>
<div className="p-3">
<p className="truncate text-xs font-medium text-[var(--navy)]">
{f.name}
</p>
<p className="text-[10px] text-[var(--muted)]">
{(f.sizeOriginal / 1024).toFixed(0)} KB
</p>
<div className="mt-2 flex items-center justify-between gap-2">
<a
href={url}
target="_blank"
rel="noopener"
className="text-xs text-[var(--sky-600)] hover:text-[var(--navy)]"
>
</a>
<form action={deleteAction}>
<input type="hidden" name="id" value={f.$id} />
<DeleteButton label="Sil" />
</form>
</div>
<input
readOnly
value={url}
className="mt-2 w-full truncate rounded-md border border-[var(--border)] bg-[var(--navy-50)] px-2 py-1 text-[10px] text-[var(--muted)]"
/>
</div>
</div>
);
})}
</div>
</div>
);
}