UNPKG

@cotype/core

Version:

<h2 align="center"> <img src="https://cotype.dev/logo.svg" alt="cotype" /> </h2>

266 lines (224 loc) • 7.42 kB
/// <reference path="../typings/untyped-modules.d.ts"/> /// <reference path="../typings/request.d.ts"/> import { ModelOpts, NavigationOpts, ThumbnailProvider, BaseUrls, ContentHooks } from "../typings"; import express, { Request, Response, NextFunction, RequestHandler, Express } from "express"; import promiseRouter from "express-promise-router"; import * as path from "path"; import { resolve as resolveUrl } from "url"; import * as fs from "fs-extra"; import session from "./session"; import buildModels from "./model"; import filterModels, { createModelFilter } from "./model/filterModels"; import { buildInfo } from "./model/navigationBuilder"; import persistence from "./persistence"; import icons from "./icons"; import Auth, { AnonymousPermissions } from "./auth"; import withAuth from "./auth/withAuth"; import Content, { getRestApiBuilder as createRestApiBuilder } from "./content"; import Settings from "./settings"; import Media from "./media"; import apiBuilder from "./api/apiBuilder"; import swaggerUi from "./api/swaggerUi"; import HttpError from "./HttpError"; import { PersistenceAdapter } from "./persistence/adapter"; import { provide as provideExternalDataSourceHelper, ExternalDataSourceWithOptionalHelper } from "./externalDataSourceHelper"; import ContentPersistence from "./persistence/ContentPersistence"; import Storage from "./media/storage/Storage"; type SessionOpts = CookieSessionInterfaces.CookieSessionOptions; export { Persistence } from "./persistence"; export { default as knexAdapter } from "./persistence/adapter/knex"; export * from "../typings"; export { default as FsStorage } from "./media/storage/FsStorage"; export * from "./utils"; export { PersistenceAdapter, Storage, ExternalDataSourceWithOptionalHelper, SessionOpts, RequestHandler, AnonymousPermissions, ContentPersistence }; export type Opts = { models: ModelOpts[]; navigation?: NavigationOpts[]; storage: Storage; baseUrls?: Partial<BaseUrls>; basePath?: string; persistenceAdapter: Promise<PersistenceAdapter>; externalDataSources?: ExternalDataSourceWithOptionalHelper[]; sessionOpts?: SessionOpts; thumbnailProvider: ThumbnailProvider; clientMiddleware?: RequestHandler | RequestHandler[]; anonymousPermissions?: AnonymousPermissions; customSetup?: (app: Express, contentPersistence: ContentPersistence) => void; contentHooks?: ContentHooks; }; const root = path.resolve(__dirname, "../dist/client"); let index: string; function getIndexHtml(basePath: string) { if (!index) index = fs.readFileSync(path.join(root, "index.html"), "utf8"); return index.replace(/"src\./g, `"${basePath}/static/src.`); } export const clientMiddleware = promiseRouter() .use( "/static", express.static(root, { maxAge: "1y", // cache all static resources for a year ... immutable: true, // which is fine, as all resource URLs contain a hash index: false // index.html will be served by the fallback middleware }), (_: Request, res: Response, next: NextFunction) => { res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); next(); } ) .use((req, res, next) => { if ( (req.method === "GET" || req.method === "HEAD") && req.accepts("html") ) { const basePath = req.originalUrl.replace(/^(.*\/admin).*/, "$1"); res.send(getIndexHtml(basePath)); } else next(); }); function addSlash(str: string) { return `${str.replace(/\/$/, "")}/`; } function getUrls(opts: Pick<Opts, "basePath" | "baseUrls">) { const basePath = (opts.basePath || "").replace( new RegExp(path.posix.sep, "g"), "/" ); const baseUrls = { cms: basePath, ...(opts.baseUrls || {}) }; return { basePath: addSlash(basePath), baseUrls: { ...baseUrls, cms: addSlash(baseUrls.cms) } }; } function getModels( opts: Pick<Opts, "externalDataSources" | "models">, baseUrls: BaseUrls ) { const externalDataSources = provideExternalDataSourceHelper( opts.externalDataSources, { baseUrls } ).map(withAuth); return { models: buildModels(opts.models, externalDataSources), externalDataSources }; } export async function getRestApiBuilder( opts: Pick<Opts, "models" | "basePath" | "baseUrls" | "externalDataSources"> ) { const { baseUrls } = getUrls(opts); const { models } = getModels(opts, baseUrls); return createRestApiBuilder(models, baseUrls); } export async function init(opts: Opts) { const { baseUrls, basePath } = getUrls(opts); const { models, externalDataSources } = getModels(opts, baseUrls); const p = await persistence(models, await opts.persistenceAdapter, { baseUrls, contentHooks: opts.contentHooks }); const auth = Auth(p, opts.anonymousPermissions); const content = Content(p, models, externalDataSources, baseUrls); const settings = Settings(p, models); const media = Media(p, models, opts.storage, opts.thumbnailProvider); const app = express(); app.use(express.json()); app.use(session(opts.sessionOpts)); app.all("/status", (req, res) => { res.json({ uptime: process.uptime(), nodeVersion: process.version, memory: process.memoryUsage(), pid: process.pid }); }); const router = promiseRouter(); app.use(basePath.replace(/\/$/, ""), router); auth.routes(router); // login, principal, logout media.routes(router); // static, thumbs settings.routes(router); // admin/rest/settings icons.routes(router); // icons router.get("/admin/rest/info", async (req, res) => { if (!req.principal || !req.principal.id) return res.json({}); const filteredModels = filterModels(models, req.principal); const filter = createModelFilter(req.principal); const filteredInfo = buildInfo(opts.navigation || [], models, filter); res.json({ ...filteredInfo, models: filteredModels, baseUrls, user: req.principal }); }); router.get("/admin/rest/info/content", (req, res) => { const filteredModels = filterModels(models, req.principal); res.json(filteredModels.content.map(m => m.name)); }); router.get("/admin/rest/info/settings", (req, res) => { const filteredModels = filterModels(models, req.principal); res.json(filteredModels.settings.map(m => m.name)); }); auth.describe(apiBuilder); media.describe(apiBuilder); content.describe(apiBuilder); settings.describe(apiBuilder); router.get("/admin/rest/swagger.json", (req, res) => { res.json(apiBuilder.getSpec()); }); router.use( "/admin/rest/docs", swaggerUi( resolveUrl(baseUrls.cms, "admin/rest/docs/"), resolveUrl(baseUrls.cms, "admin/rest/swagger.json") ) ); router.get("/admin/rest", (req, res) => res.redirect(resolveUrl(baseUrls.cms, "admin/rest/docs")) ); content.routes(router); router.use("/admin", opts.clientMiddleware || clientMiddleware); if (opts.customSetup) { opts.customSetup(app, p.content); } app.get(basePath, (_, res) => res.redirect(resolveUrl(baseUrls.cms, "admin")) ); app.use((err: Error, req: Request, res: Response, _: () => void) => { if (err instanceof HttpError) { res.status(err.status); } else { console.error(req.method, req.path, err); res.status(500); } res.end(err.message); return; }); return { app, persistence: p }; }