@lucidcms/plugin-local-storage
Version:
The official Local Storage plugin for Lucid
506 lines (483 loc) • 13.2 kB
JavaScript
// 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}×tamp=${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