@adonisjs/drive
Version:
A thin wrapper on top of Flydrive to work seamlessly with AdonisJS
123 lines (122 loc) • 5.33 kB
JavaScript
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 };