1299cd10ce
- /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)
72 lines
2.1 KiB
TypeScript
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",
|
|
},
|
|
});
|
|
}
|