ai-chatbot/lib/storage/canvas-zip-delivery.ts

41 lines
2.5 KiB
TypeScript

import "server-only";
import { createReadStream } from "node:fs";
import { PutObjectCommand } from "@aws-sdk/client-s3";
import { getDefaultStorageConfig, getStorageConfigById } from "@/lib/db/queries";
import { createS3Client, toDbS3UploadConfig, buildS3PublicUrl } from "./service";
/** Only a completely written archive is uploaded. Local GET remains a fallback. */
export async function publishCanvasZip(params: { id: string; path: string; bytes: number; disposition: string }) {
const raw = await getDefaultStorageConfig();
const config = toDbS3UploadConfig(raw);
if (config?.provider !== "qiniu-kodo" || !config.publicUrl?.startsWith("https://")) return null;
const key = `canvas-downloads/${params.id}.zip`;
const client = createS3Client(config);
// Qiniu's S3 endpoint expects an ordinary known-length body, without AWS trailers.
client.config.requestChecksumCalculation = async () => "WHEN_REQUIRED";
const body = createReadStream(params.path);
try {
await client.send(new PutObjectCommand({ Bucket: config.bucket, Key: key, Body: body,
ContentLength: params.bytes, ContentType: "application/zip", ContentDisposition: params.disposition,
CacheControl: "public, max-age=1800" }), { abortSignal: AbortSignal.timeout(90_000) });
const url = buildS3PublicUrl(config, key);
const response = await fetch(url, { method: "HEAD", redirect: "error", signal: AbortSignal.timeout(10_000) });
const length = Number(response.headers.get("content-length"));
if (!response.ok || length !== params.bytes) throw new Error("CDN object is not ready");
return { url, storageConfigId: raw!.id, key };
} catch (error) {
console.warn("[CanvasDownloadZipTask] CDN upload/check failed; using complete local file", { taskId: params.id, kind: (error as Error).name });
return { url: null, storageConfigId: raw!.id, key };
} finally { body.destroy(); client.destroy(); }
}
export async function removeCanvasZipObject(object: { storageConfigId: string; key: string }) {
if (!/^canvas-downloads\/[0-9a-f-]{36}\.zip$/i.test(object.key)) return;
const config = toDbS3UploadConfig(await getStorageConfigById({ id: object.storageConfigId }));
if (!config || config.provider !== "qiniu-kodo") return;
const { DeleteObjectCommand } = await import("@aws-sdk/client-s3");
const client = createS3Client(config);
try { await client.send(new DeleteObjectCommand({ Bucket: config.bucket, Key: object.key }), { abortSignal: AbortSignal.timeout(5000) }); }
finally { client.destroy(); }
}