@directus/api
Version:
Directus is a real-time API and App dashboard for managing SQL database content
134 lines (132 loc) • 6.29 kB
JavaScript
import async_handler_default from "../utils/async-handler.js";
import { useLogger } from "../logger/index.js";
import { clearSystemCache } from "../cache.js";
import { getDatabase } from "../database/index.js";
import { RevisionsService } from "../services/revisions.js";
import { ExportService } from "../services/export.js";
import { respond } from "../middleware/respond.js";
import { resolveLoginRedirect } from "../auth/utils/resolve-login-redirect.js";
import { sanitizeQuery } from "../utils/sanitize-query.js";
import { UtilsService } from "../services/utils.js";
import { ImportService } from "../services/import/import.js";
import collection_exists_default from "../middleware/collection-exists.js";
import { readMultipartFile } from "../utils/read-multipart-file.js";
import readFileUploadBody from "../middleware/read-file-upload-body.js";
import { queryFlag } from "../utils/query-flag.js";
import { generateTranslations } from "../utils/generate-translations.js";
import { useEnv } from "@directus/env";
import { ForbiddenError, InvalidPayloadError, InvalidQueryError } from "@directus/errors";
import { Router } from "express";
import { fromZodError } from "zod-validation-error";
import { z as z$1 } from "zod";
import bytes from "bytes";
//#region src/controllers/utils.ts
const router = Router();
const env = useEnv();
const IMPORT_MAX_FILE_SIZE = bytes.parse(env["IMPORT_MAX_FILE_SIZE"]) ?? void 0;
const randomStringSchema = z$1.object({ length: z$1.coerce.number().int().min(1).max(500).default(32) });
router.get("/random/string", async_handler_default(async (req, res) => {
const { nanoid } = await import("nanoid");
const { data: value, error } = randomStringSchema.safeParse(req.query);
if (error) throw new InvalidQueryError({ reason: fromZodError(error).message });
return res.json({ data: nanoid(value.length) });
}));
const SortSchema = z$1.object({
item: z$1.union([z$1.string(), z$1.number()]),
to: z$1.union([z$1.string(), z$1.number()])
}).strict();
router.post("/sort/:collection", collection_exists_default, async_handler_default(async (req, res) => {
const { error } = SortSchema.safeParse(req.body);
if (error) throw new InvalidPayloadError({ reason: fromZodError(error).message });
await new UtilsService({
accountability: req.accountability,
schema: req.schema
}).sort(req.collection, req.body);
return res.status(200).end();
}));
router.post("/revert/:revision", async_handler_default(async (req, _res, next) => {
await new RevisionsService({
accountability: req.accountability,
schema: req.schema
}).revert(req.params["revision"]);
next();
}), respond);
router.post("/import/:collection", collection_exists_default, async_handler_default(async (req, res) => {
const service = new ImportService({
accountability: req.accountability,
schema: req.schema
});
const { mimetype, stream } = await readMultipartFile(req, { maxFileSize: IMPORT_MAX_FILE_SIZE });
await service.import(req.params["collection"], mimetype, stream, { background: queryFlag(req.query["background"]) });
return res.status(200).end();
}));
const ImportBatchSchema = z$1.array(z$1.object({
collection: z$1.string(),
items: z$1.array(z$1.record(z$1.string(), z$1.unknown()))
})).min(1);
router.post("/import", readFileUploadBody({ maxFileSize: IMPORT_MAX_FILE_SIZE }), async_handler_default(async (req, res, next) => {
const { data: value, error } = ImportBatchSchema.safeParse(req.body);
if (error) throw new InvalidPayloadError({ reason: fromZodError(error).message });
if (req.query["mode"] !== void 0 && ["add", "merge"].includes(String(req.query["mode"])) === false) throw new InvalidQueryError({ reason: `"mode" must be one of ["add", "merge"]` });
const mode = req.query["mode"] === "merge" ? "merge" : "add";
const dryRun = queryFlag(req.query["dryRun"]);
const dangerouslyAllowDelete = queryFlag(req.query["dangerouslyAllowDelete"]);
const result = await new ImportService({
accountability: req.accountability,
schema: req.schema
}).importBatch(value, {
mode,
dryRun,
dangerouslyAllowDelete
});
res.locals["payload"] = { data: result };
return next();
}), respond);
router.post("/export/:collection", collection_exists_default, async_handler_default(async (req, _res, next) => {
if (!req.body.query) throw new InvalidPayloadError({ reason: `"query" is required` });
if (!req.body.format) throw new InvalidPayloadError({ reason: `"format" is required` });
const service = new ExportService({
accountability: req.accountability,
schema: req.schema
});
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability ?? null);
service.exportToFile(req.params["collection"], sanitizedQuery, req.body.format, { file: req.body.file });
return next();
}), respond);
router.post("/translations/generate", async_handler_default(async (req, res, next) => {
if (!req.accountability?.admin) throw new ForbiddenError();
const result = await generateTranslations(req.body, {
knex: getDatabase(),
schema: req.schema,
accountability: req.accountability
});
try {
await clearSystemCache({ forced: true });
} catch (error) {
useLogger().error(error, "Failed to clear system cache after translation changes");
}
res.locals["payload"] = { data: result };
return next();
}), respond);
router.post("/cache/clear", async_handler_default(async (req, res) => {
const service = new UtilsService({
accountability: req.accountability,
schema: req.schema
});
const clearSystemCache$1 = "system" in req.query && (req.query["system"] === "" || Boolean(req.query["system"]));
await service.clearCache({ system: clearSystemCache$1 });
res.status(200).end();
}));
router.post("/resolve-redirect", async_handler_default(async (req, res) => {
if (!req.body?.redirect) throw new InvalidPayloadError({ reason: `"redirect" is required` });
if (req.body?.provider && typeof req.body.provider !== "string") throw new InvalidPayloadError({ reason: `"provider" must be a string` });
try {
const resolved = resolveLoginRedirect(req.body.redirect, { provider: req.body.provider });
return res.json({ data: resolved });
} catch {
throw new InvalidPayloadError({ reason: `Invalid "redirect" provided` });
}
}));
var utils_default = router;
//#endregion
export { utils_default as default };