// server/media.mjs — Openinary (media.ayris.tech) proxy, "mucomutfak" klasörüne kilitli const BASE = () => process.env.OPENINARY_API_URL?.replace(/\/$/, ""); const KEY = () => process.env.OPENINARY_API_KEY; export const MEDIA_ROOT = "mucomutfak"; export function mediaEnabled() { return Boolean(BASE() && KEY()); } function assertInsideRoot(p) { const clean = String(p || "").replace(/^\/+|\/+$/g, ""); if ( clean !== MEDIA_ROOT && !clean.startsWith(`${MEDIA_ROOT}/`) ) { throw Object.assign(new Error(`Yol '${MEDIA_ROOT}' klasörü içinde olmalı`), { status: 400, }); } if (clean.includes("..")) { throw Object.assign(new Error("Geçersiz yol"), { status: 400 }); } return clean; } function deliveryUrl(path, transform = "") { const t = transform ? `${transform}/` : ""; return `${BASE()}/t/${t}${path}`; } // Klasör içeriğini listele export async function listMedia(path = MEDIA_ROOT) { const clean = assertInsideRoot(path); const res = await fetch( `${BASE()}/api/storage?path=${encodeURIComponent(clean)}`, { headers: { Authorization: `Bearer ${KEY()}` } }, ); if (!res.ok) { throw Object.assign(new Error(`Openinary listeleme hatası (${res.status})`), { status: 502, }); } const data = await res.json(); const IMG_RE = /\.(webp|jpe?g|png|gif|svg|avif|heic|heif)$/i; return { path: clean, folders: (data.folders || []).map((f) => ({ name: f.name, path: f.path })), files: (data.files || []) .filter((f) => IMG_RE.test(f.name)) .map((f) => ({ name: f.name, path: f.path, size: f.size, mtime: f.mtime, url: deliveryUrl(f.path), thumb: deliveryUrl(f.path, "w_240,h_240,c_fill"), })), }; } // Multipart isteği olduğu gibi Openinary'ye aktar (folder alanı client FormData'sında) export async function proxyUpload(req) { const res = await fetch(`${BASE()}/api/upload`, { method: "POST", headers: { Authorization: `Bearer ${KEY()}`, "content-type": req.headers["content-type"], ...(req.headers["content-length"] ? { "content-length": req.headers["content-length"] } : {}), }, body: req, duplex: "half", }); const body = await res.json().catch(() => ({})); if (body?.files) { for (const f of body.files) { if (f.path) { f.url = deliveryUrl(f.path); f.thumb = deliveryUrl(f.path, "w_240,h_240,c_fill"); } } } return { status: res.status, body }; } export async function deleteMedia(path) { const clean = assertInsideRoot(path); if (clean === MEDIA_ROOT) { throw Object.assign(new Error("Kök klasör silinemez"), { status: 400 }); } const res = await fetch( `${BASE()}/api/storage/${clean .split("/") .map(encodeURIComponent) .join("/")}`, { method: "DELETE", headers: { Authorization: `Bearer ${KEY()}` } }, ); if (!res.ok) { throw Object.assign(new Error(`Silme hatası (${res.status})`), { status: 502, }); } return true; }