Implement Phase 2 asset storage pipeline
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
const { Readable } = require("node:stream");
|
||||
const { pipeline } = require("node:stream/promises");
|
||||
const { AssetError } = require("./errors");
|
||||
|
||||
function readableBody(body) {
|
||||
if (body && typeof body.pipe === "function") return body;
|
||||
if (Buffer.isBuffer(body) || body instanceof Uint8Array) {
|
||||
return Readable.from([body]);
|
||||
}
|
||||
if (body && typeof body.getReader === "function") {
|
||||
return Readable.fromWeb(body);
|
||||
}
|
||||
throw new AssetError(
|
||||
"STORAGE_INCONSISTENCY",
|
||||
"Stored asset bytes could not be streamed",
|
||||
{ statusCode: 503 },
|
||||
);
|
||||
}
|
||||
|
||||
function validateDescriptor(descriptor) {
|
||||
if (
|
||||
typeof descriptor.contentType !== "string" ||
|
||||
!Number.isSafeInteger(descriptor.contentLength) ||
|
||||
descriptor.contentLength < 0
|
||||
) {
|
||||
throw new AssetError(
|
||||
"STORAGE_INCONSISTENCY",
|
||||
"Stored asset metadata is incomplete",
|
||||
{ statusCode: 503 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function setCommonHeaders(response, descriptor) {
|
||||
response.set("Content-Type", descriptor.contentType);
|
||||
response.set("Content-Length", String(descriptor.contentLength));
|
||||
response.set("X-Content-Type-Options", "nosniff");
|
||||
}
|
||||
|
||||
async function storedBody(assetService, descriptor) {
|
||||
const stored = await assetService.readStoredObject(descriptor);
|
||||
return readableBody(stored.body);
|
||||
}
|
||||
|
||||
async function sendImmutableMedia(assetService, response, descriptor) {
|
||||
validateDescriptor(descriptor);
|
||||
const body = await storedBody(assetService, descriptor);
|
||||
setCommonHeaders(response, descriptor);
|
||||
response.set("Cache-Control", "public, max-age=31536000, immutable");
|
||||
response.set("ETag", `"${descriptor.hash}"`);
|
||||
await pipeline(body, response);
|
||||
}
|
||||
|
||||
async function sendOriginal(assetService, response, descriptor) {
|
||||
validateDescriptor(descriptor);
|
||||
const body = await storedBody(assetService, descriptor);
|
||||
response.attachment(descriptor.asset.original_filename);
|
||||
setCommonHeaders(response, descriptor);
|
||||
response.set("Cache-Control", "private, no-store");
|
||||
await pipeline(body, response);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendImmutableMedia,
|
||||
sendOriginal,
|
||||
};
|
||||
Reference in New Issue
Block a user