Files
isletmem-kovakcrm/src/app/api/files/[attachmentId]/route.ts
T
kovakmedya 1299cd10ce feat: fatura PDF, hizmet/yazılım atama dosya ekleri
- /print/invoices/[id] sayfası: A4 fatura yazdırma/PDF (AutoPrint + PrintActionBar)
- Fatura detayı header'ına PDF butonu eklendi (Yazdır yerine)
- Appwrite Storage: entity-attachments bucket (20MB, şifreli)
- Appwrite Tables: attachments collection (tenantId, entityType, entityId, fileId, name, size, mimeType)
- attachment-actions.ts: fetchAttachmentsAction, uploadAttachmentAction, deleteAttachmentAction
- AttachmentsPanel bileşeni: dosya yükleme/listeleme/silme, edit modunda görünür
- Hizmet ve yazılım atama form sheet'lerine AttachmentsPanel entegrasyonu
- /api/files/[attachmentId]: güvenli proxy indirme (tenant doğrulama + admin key ile Appwrite'a istek)
2026-05-07 20:22:17 +03:00

72 lines
2.1 KiB
TypeScript

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { BUCKETS, DATABASE_ID, TABLES, type Attachment } from "@/lib/appwrite/schema";
import { createAdminClient } from "@/lib/appwrite/server";
import { requireTenant } from "@/lib/appwrite/tenant-guard";
export async function GET(
_request: NextRequest,
{ params }: { params: Promise<{ attachmentId: string }> },
) {
const { attachmentId } = await params;
let ctx;
try {
ctx = await requireTenant();
} catch {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { tablesDB } = createAdminClient();
let attachment: Attachment;
try {
attachment = (await tablesDB.getRow(
DATABASE_ID,
TABLES.attachments,
attachmentId,
)) as unknown as Attachment;
} catch {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
if (attachment.tenantId !== ctx.tenantId) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const endpoint = (process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT ?? "").replace(/\/$/, "");
const projectId = process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID ?? "";
const apiKey = process.env.APPWRITE_API_KEY ?? "";
const fileUrl = `${endpoint}/storage/buckets/${BUCKETS.entityAttachments}/files/${attachment.fileId}/download`;
let upstream: Response;
try {
upstream = await fetch(fileUrl, {
headers: {
"X-Appwrite-Project": projectId,
"X-Appwrite-Key": apiKey,
},
});
} catch {
return NextResponse.json({ error: "Storage unavailable" }, { status: 502 });
}
if (!upstream.ok) {
return NextResponse.json({ error: "File not found" }, { status: 404 });
}
const contentType =
upstream.headers.get("Content-Type") || "application/octet-stream";
const encodedName = encodeURIComponent(attachment.name);
return new NextResponse(upstream.body, {
headers: {
"Content-Type": contentType,
"Content-Disposition": `attachment; filename*=UTF-8''${encodedName}`,
"Cache-Control": "private, no-store",
},
});
}