41 lines
2.5 KiB
TypeScript
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(); }
|
|
}
|