@cotype/core
Version:
<h2 align="center"> <img src="https://cotype.dev/logo.svg" alt="cotype" /> </h2>
266 lines (224 loc) • 7.42 kB
text/typescript
/// <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 };
}