UNPKG

@lucidcms/plugin-local-storage

Version:

The official Local Storage plugin for Lucid

506 lines (483 loc) 13.2 kB
// src/translations/en-gb.json var en_gb_default = { file_not_found: "The file was not found.", invalid_key: "Invalid key", route_localstorage_upload_error_name: "Local Storage Upload Error", route_localstorage_upload_error_message: "There was an error uploading the file to local storage.", invalid_or_expired_token: "Invalid or expired token.", invalid_file: "Invalid file." }; // src/translations/index.ts var selectedLang = en_gb_default; var T = (key, data) => { const translation = selectedLang[key]; if (!translation) { return key; } if (!data) { return translation; } return translation.replace( /\{\{(\w+)\}\}/g, (_, p1) => data[p1] ); }; var translations_default = T; // src/services/steam.ts import fs from "fs-extra"; import path2 from "node:path"; import mime from "mime-types"; // src/utils/helpers.ts import path from "node:path"; var keyPaths = (key, uploadDir) => { const keyPath = key.split("/").slice(0, -1).join("/"); const filename = key.split("/").pop(); if (!filename) throw new Error(translations_default("invalid_key")); const targetDir = path.join(uploadDir, keyPath); const targetPath = path.join(targetDir, filename); return { keyPath, filename, targetDir, targetPath }; }; // src/services/steam.ts var steam_default = (pluginOptions) => { const stream = async (key) => { try { const { targetPath } = keyPaths(key, pluginOptions.uploadDir); const exists = await fs.pathExists(targetPath); if (!exists) { return { error: { message: translations_default("file_not_found"), status: 404 }, data: void 0 }; } const body = fs.createReadStream(targetPath); const stats = await fs.stat(targetPath); const fileExtension = path2.extname(targetPath); const mimeType = mime.lookup(fileExtension); return { error: void 0, data: { contentLength: stats.size, contentType: mimeType || void 0, body } }; } catch (e) { const error = e; return { error: { message: error.message, status: 500 }, data: void 0 }; } }; return stream; }; // src/services/delete-single.ts import fs2 from "fs-extra"; var delete_single_default = (pluginOptions) => { const deletSingle = async (key) => { try { const { targetPath } = keyPaths(key, pluginOptions.uploadDir); const exists = await fs2.pathExists(targetPath); if (!exists) { return { error: { message: translations_default("file_not_found") }, data: void 0 }; } await fs2.unlink(targetPath); return { error: void 0, data: void 0 }; } catch (e) { const error = e; return { error: { message: error.message }, data: void 0 }; } }; return deletSingle; }; // src/services/delete-multiple.ts import fs3 from "fs-extra"; var delete_multiple_default = (pluginOptions) => { const deleteMultiple = async (keys) => { try { for (const key of keys) { const { targetPath } = keyPaths(key, pluginOptions.uploadDir); const exists = await fs3.pathExists(targetPath); if (!exists) continue; await fs3.unlink(targetPath); } return { error: void 0, data: void 0 }; } catch (e) { const error = e; return { error: { message: error.message }, data: void 0 }; } }; return deleteMultiple; }; // src/services/upload-single.ts import fs4 from "fs-extra"; var upload_single_default = (pluginOptions) => { const uploadSingle2 = async (props) => { try { const { targetDir, targetPath } = keyPaths( props.key, pluginOptions.uploadDir ); await fs4.ensureDir(targetDir); if (Buffer.isBuffer(props.data)) { await fs4.writeFile(targetPath, props.data); } else { const writeStream = fs4.createWriteStream(targetPath); props.data.pipe(writeStream); await new Promise((resolve, reject) => { writeStream.on("finish", resolve); writeStream.on("error", reject); }); } return { error: void 0, data: {} }; } catch (e) { const error = e; return { error: { message: error.message }, data: void 0 }; } }; return uploadSingle2; }; // src/services/get-presigned-url.ts import crypto from "node:crypto"; var get_presigned_url_default = (pluginOptions) => { const getPresignedUrl = async (key, meta) => { try { const timestamp = Date.now(); const token = crypto.createHmac("sha256", pluginOptions.secretKey).update(`${key}${timestamp}`).digest("hex"); return { error: void 0, data: { url: `${meta.host}/api/v1/localstorage/upload?key=${key}&token=${token}&timestamp=${timestamp}` } }; } catch (e) { const error = e; return { error: { message: error.message, status: 500 }, data: void 0 }; } }; return getPresignedUrl; }; // src/services/get-metadata.ts import crypto2 from "node:crypto"; import fs5 from "fs-extra"; import mime2 from "mime-types"; var get_metadata_default = (pluginOptions) => { const getMetadata = async (key) => { try { const { targetPath } = keyPaths(key, pluginOptions.uploadDir); const exists = await fs5.pathExists(targetPath); if (!exists) { return { error: { message: translations_default("file_not_found"), status: 404 }, data: void 0 }; } const stats = await fs5.stat(targetPath); const mimeType = mime2.lookup(targetPath) || null; const etag = crypto2.createHash("md5").update(`${stats.mtime.getTime()}-${stats.size}`).digest("hex"); return { error: void 0, data: { size: stats.size, mimeType, etag } }; } catch (e) { const error = e; return { error: { message: error.message, status: 500 }, data: void 0 }; } }; return getMetadata; }; // src/routes/index.ts import { route } from "@lucidcms/core/api"; // src/controllers/upload.ts import { z as z2 } from "@lucidcms/core"; // src/schema/upload.ts import { z } from "@lucidcms/core"; var controllerSchemas = { upload: { body: void 0, query: { string: z.object({ token: z.string().meta({ description: "The presigned URL token", example: "a64825f15c2acd40f8865933a26b7334d2c3dec3aba483cfab17396da0be8abe" }), timestamp: z.string().meta({ description: "Timestamp", example: "1745601807970" }), key: z.string().meta({ description: "The media key", example: "2024/09/5ttogd-placeholder-image.png" }) }), formatted: void 0 }, params: void 0, response: void 0 } }; // src/services/upload-single-endpoint.ts import fs6 from "fs-extra"; // src/services/checks/validate-presigned-token.ts import crypto3 from "node:crypto"; // src/constants.ts var PLUGIN_KEY = "plugin-local-storage"; var LUCID_VERSION = "0.x.x"; var PRESIGNED_URL_EXPIRY = 36e5; var DEFAULT_MIME_TYPES = [ // images "image/jpeg", "image/png", "image/gif", "image/webp", "image/avif", "image/tiff", "image/bmp", "image/svg+xml", // documents "application/pdf", "text/plain", "text/csv", // audio "audio/mpeg", "audio/wav", "audio/ogg", "audio/aac", "audio/webm", "audio/flac", // video "video/mp4", "video/mpeg", "video/ogg", "video/webm", "video/quicktime", "video/x-msvideo", // archives "application/zip", "application/x-rar-compressed", "application/x-7z-compressed" ]; // src/services/checks/validate-presigned-token.ts var validatePresignedToken = async (_, data) => { const expectedToken = crypto3.createHmac("sha256", data.pluginOptions.secretKey).update(`${data.key}${data.timestamp}`).digest("hex"); if (data.token !== expectedToken || Date.now() - Number.parseInt(data.timestamp) > PRESIGNED_URL_EXPIRY) { return { error: { status: 403, type: "basic", message: translations_default("invalid_or_expired_token") }, data: void 0 }; } return { error: void 0, data: void 0 }; }; var validate_presigned_token_default = validatePresignedToken; // src/services/checks/index.ts var checks_default = { validatePresignedToken: validate_presigned_token_default }; // src/services/upload-single-endpoint.ts var uploadSingle = async (context, data) => { const checkPresignedTokenRes = await checks_default.validatePresignedToken(context, { pluginOptions: data.pluginOptions, key: data.key, token: data.token, timestamp: data.timestamp }); if (checkPresignedTokenRes.error) return checkPresignedTokenRes; const { targetDir, targetPath } = keyPaths( data.key, data.pluginOptions.uploadDir ); await fs6.ensureDir(targetDir); if (Buffer.isBuffer(data.buffer)) { await fs6.writeFile(targetPath, data.buffer); } else { return { error: { type: "basic", status: 400, message: translations_default("invalid_file") } }; } return { error: void 0, data: true }; }; var upload_single_endpoint_default = uploadSingle; // src/controllers/upload.ts import { serviceWrapper, LucidAPIError, swaggerResponse } from "@lucidcms/core/api"; var uploadSingleController = (pluginOptions) => async (request, reply) => { const uploadMedia = await serviceWrapper(upload_single_endpoint_default, { transaction: false, defaultError: { type: "basic", name: translations_default("route_localstorage_upload_error_name"), message: translations_default("route_localstorage_upload_error_message") } })( { db: request.server.config.db.client, config: request.server.config, services: request.server.services }, { buffer: request.body, key: request.query.key, token: request.query.token, timestamp: request.query.timestamp, pluginOptions } ); if (uploadMedia.error) throw new LucidAPIError(uploadMedia.error); reply.status(200).send(); }; var upload_default = { controller: uploadSingleController, zodSchema: controllerSchemas.upload, swaggerSchema: (pluginOptions) => ({ description: "Upload a single media file.", tags: ["localstorage-plugin"], summary: "Upload File", consumes: pluginOptions.supportedMimeTypes && pluginOptions.supportedMimeTypes.length > 0 ? pluginOptions.supportedMimeTypes : DEFAULT_MIME_TYPES, querystring: z2.toJSONSchema(controllerSchemas.upload.query.string), response: swaggerResponse({ noProperties: true }) }) }; // src/utils/register-content-type-parser.ts var registerContentTypeParser = (fastify, givenMimeTypes) => { if (givenMimeTypes) { for (const mimeType of givenMimeTypes) { fastify.addContentTypeParser( mimeType, { parseAs: "buffer" }, (_, body, done) => { done(null, body); } ); } } else { for (const mimeType of DEFAULT_MIME_TYPES) { fastify.addContentTypeParser( mimeType, { parseAs: "buffer" }, (_, body, done) => { done(null, body); } ); } } }; var register_content_type_parser_default = registerContentTypeParser; // src/routes/index.ts var routes = (pluginOptions) => async (fastify) => { register_content_type_parser_default(fastify, pluginOptions.supportedMimeTypes); route(fastify, { method: "put", url: "/api/v1/localstorage/upload", bodyLimit: fastify.config.media.maxSize, controller: upload_default.controller(pluginOptions), swaggerSchema: upload_default.swaggerSchema(pluginOptions), zodSchema: upload_default.zodSchema }); }; var routes_default = routes; // src/plugin.ts var plugin = async (config, pluginOptions) => { config.fastifyExtensions?.push(routes_default(pluginOptions)); config.media = { ...config.media, strategy: { getPresignedUrl: get_presigned_url_default(pluginOptions), getMeta: get_metadata_default(pluginOptions), stream: steam_default(pluginOptions), uploadSingle: upload_single_default(pluginOptions), deleteSingle: delete_single_default(pluginOptions), deleteMultiple: delete_multiple_default(pluginOptions) } }; return { key: PLUGIN_KEY, lucid: LUCID_VERSION, config }; }; var plugin_default = plugin; // src/index.ts var lucidLocalStorage = (pluginOptions) => (config) => plugin_default(config, pluginOptions); var index_default = lucidLocalStorage; export { index_default as default }; //# sourceMappingURL=index.js.map