UNPKG

@adonisjs/drive

Version:

A thin wrapper on top of Flydrive to work seamlessly with AdonisJS

123 lines (122 loc) 5.33 kB
import { n as CannotServeFileException, t as debug_default } from "../debug-BaNwyLMa.js"; import { Disk, DriveManager } from "flydrive"; import { RuntimeException } from "@adonisjs/core/exceptions"; import { configProvider } from "@adonisjs/core"; import { readFile } from "node:fs/promises"; import { MultipartFile } from "@adonisjs/core/bodyparser"; function decodeLocation(location) { try { return decodeURIComponent(location); } catch { return location; } } function createFileServer(disk) { return async function({ request, response }) { const location = decodeLocation(request.param("*").join("/")); const file = disk.file(location); const isPrivate = await file.getVisibility() === "private"; const usingSignature = !!request.input("signature"); const hasValidSignature = request.hasValidSignature(); if (isPrivate && !hasValidSignature || usingSignature && !hasValidSignature) { debug_default("Access denied for file \"%s\". Failed condition %o", location, { isPrivate, hasValidSignature, usingSignature }); return response.unauthorized("Access denied"); } try { const metadata = await file.getMetaData(); response.header("etag", metadata.etag); if (usingSignature && request.input("cacheControl")) response.header("Cache-Control", request.input("cacheControl")); else response.header("Last-Modified", metadata.lastModified.toUTCString()); if (usingSignature && request.input("contentType")) response.header("Content-Type", request.input("contentType")); else if (metadata.contentType) response.type(metadata.contentType); if (usingSignature && request.input("contentDisposition")) response.header("Content-Disposition", request.input("contentDisposition")); if (usingSignature && request.input("contentEncoding")) response.header("Content-Encoding", request.input("contentEncoding")); if (usingSignature && request.input("contentLanguage")) response.header("Content-Language", request.input("contentLanguage")); if (request.method() === "HEAD") { response.status(response.fresh() ? 304 : 200); return; } if (response.fresh()) { response.status(304); return; } response.header("Content-length", metadata.contentLength.toString()); return response.stream(await file.getStream()); } catch (error) { throw new CannotServeFileException(error); } }; } var DriveProvider = class { #locallyServedServices = []; constructor(app) { this.app = app; } async registerViewHelpers(drive) { if (this.app.usingEdgeJS) { const edge = await import("edge.js"); debug_default("detected edge installation. Registering drive global helpers"); edge.default.global("driveUrl", function(key, diskName) { return (diskName ? drive.use(diskName) : drive.use()).getUrl(key); }); edge.default.global("driveSignedUrl", function(key, diskNameOrOptions, signedUrlOptions) { let diskName; let options = signedUrlOptions; if (typeof diskNameOrOptions === "string") diskName = diskNameOrOptions; else if (diskNameOrOptions && !signedUrlOptions) options = diskNameOrOptions; return (diskName ? drive.use(diskName) : drive.use()).getSignedUrl(key, options); }); } } async extendMultipartFile(drive) { debug_default("Adding \"MultipartFile.moveToDisk\" method"); MultipartFile.macro("moveToDisk", async function(key, diskNameOrOptions, writeOptions) { if (!this.tmpPath) throw new RuntimeException("property \"tmpPath\" must be set on the file before moving it", { status: 500, code: "E_MISSING_FILE_TMP_PATH" }); let diskName; let options = {}; if (typeof diskNameOrOptions === "string") { diskName = diskNameOrOptions; options = writeOptions ?? {}; } else if (diskNameOrOptions && !writeOptions) options = diskNameOrOptions; else if (writeOptions) options = writeOptions; const moveAs = options.moveAs ?? "stream"; const disk = diskName ? drive.use(diskName) : drive.use(); if (moveAs === "stream") await disk.moveFromFs(this.tmpPath, key, options); else await disk.put(key, await readFile(this.tmpPath), options); try { this.meta.url = await disk.getUrl(key); } catch {} this.markAsMoved(key, key); }); } register() { this.app.container.singleton("drive.manager", async () => { const driveConfigProvider = this.app.config.get("drive"); const config = await configProvider.resolve(this.app, driveConfigProvider); if (!config) throw new RuntimeException("Invalid \"config/drive.ts\" file. Make sure you are using the \"defineConfig\" method"); this.#locallyServedServices = config.locallyServed; return new DriveManager(config.config); }); this.app.container.bind(Disk, async (resolver) => { return (await resolver.make("drive.manager")).use(); }); } async boot() { const drive = await this.app.container.make("drive.manager"); const router = await this.app.container.make("router"); this.#locallyServedServices.forEach((service) => { debug_default("configuring drive local file server for \"%s\", route \"%s\"", service.service, service.routePattern); router.get(service.routePattern, createFileServer(drive.use(service.service))).as(service.routeName); }); await this.registerViewHelpers(drive); await this.extendMultipartFile(drive); } }; export { DriveProvider as default };