Add svelte as a view engine to swaf #33
@ -94,8 +94,11 @@ export default abstract class Application implements Extendable<ApplicationCompo
|
||||
// Register migrations
|
||||
MysqlConnectionManager.registerMigrations(this.getMigrations());
|
||||
|
||||
// Register all components and alike
|
||||
// Register and initialize all components and alike
|
||||
await this.init();
|
||||
for (const component of this.components) {
|
||||
await component.init?.();
|
||||
}
|
||||
|
||||
// Process command line
|
||||
if (!this.ignoreCommandLine) {
|
||||
@ -218,14 +221,14 @@ export default abstract class Application implements Extendable<ApplicationCompo
|
||||
|
||||
// Components routes
|
||||
for (const component of this.components) {
|
||||
if (component.init) {
|
||||
if (component.initRoutes) {
|
||||
component.setCurrentRouter(initRouter);
|
||||
await component.init(initRouter);
|
||||
await component.initRoutes(initRouter);
|
||||
}
|
||||
|
||||
if (component.handle) {
|
||||
if (component.handleRoutes) {
|
||||
component.setCurrentRouter(handleRouter);
|
||||
await component.handle(handleRouter);
|
||||
await component.handleRoutes(handleRouter);
|
||||
}
|
||||
|
||||
component.setCurrentRouter(null);
|
||||
@ -292,7 +295,6 @@ export default abstract class Application implements Extendable<ApplicationCompo
|
||||
}
|
||||
|
||||
const frontendToolsComponent = this.as(FrontendToolsComponent);
|
||||
frontendToolsComponent.setupGlobals();
|
||||
await frontendToolsComponent.preCompileViews(flags.watch);
|
||||
break;
|
||||
}
|
||||
|
@ -13,11 +13,13 @@ export default abstract class ApplicationComponent {
|
||||
|
||||
public async checkSecuritySettings?(): Promise<void>;
|
||||
|
||||
public async init?(): Promise<void>;
|
||||
|
||||
public async start?(expressApp: Express): Promise<void>;
|
||||
|
||||
public async init?(router: Router): Promise<void>;
|
||||
public async initRoutes?(router: Router): Promise<void>;
|
||||
|
||||
public async handle?(router: Router): Promise<void>;
|
||||
public async handleRoutes?(router: Router): Promise<void>;
|
||||
|
||||
public async stop?(): Promise<void>;
|
||||
|
||||
|
@ -108,7 +108,7 @@ export default class TestApp extends Application {
|
||||
this.use(new AuthComponent(this, new MagicLinkAuthMethod(this, MAGIC_LINK_MAIL), new PasswordAuthMethod(this)));
|
||||
|
||||
// WebSocket server
|
||||
this.use(new WebSocketServerComponent(this, this.as(ExpressAppComponent), this.as(RedisComponent)));
|
||||
this.use(new WebSocketServerComponent());
|
||||
}
|
||||
|
||||
protected registerWebSocketListeners(): void {
|
||||
|
@ -18,7 +18,7 @@ export default class AuthComponent extends ApplicationComponent {
|
||||
this.authGuard = new AuthGuard(app, ...authMethods);
|
||||
}
|
||||
|
||||
public async init(): Promise<void> {
|
||||
public async initRoutes(): Promise<void> {
|
||||
this.use(AuthMiddleware);
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ export default class AutoUpdateComponent extends ApplicationComponent {
|
||||
this.checkSecurityConfigField('gitlab_webhook_token');
|
||||
}
|
||||
|
||||
public async init(router: Router): Promise<void> {
|
||||
public async initRoutes(router: Router): Promise<void> {
|
||||
router.post('/update/push.json', (req, res) => {
|
||||
const token = req.header('X-Gitlab-Token');
|
||||
if (!token || token !== config.get<string>('gitlab_webhook_token'))
|
||||
|
@ -20,7 +20,7 @@ export default class CsrfProtectionComponent extends ApplicationComponent {
|
||||
this.excluders.push(excluder);
|
||||
}
|
||||
|
||||
public async handle(router: Router): Promise<void> {
|
||||
public async handleRoutes(router: Router): Promise<void> {
|
||||
router.use(async (req, res, next) => {
|
||||
for (const excluder of CsrfProtectionComponent.excluders) {
|
||||
if (excluder(req)) return next();
|
||||
|
@ -30,7 +30,7 @@ export default class ExpressAppComponent extends ApplicationComponent {
|
||||
this.expressApp = app;
|
||||
}
|
||||
|
||||
public async init(router: Router): Promise<void> {
|
||||
public async initRoutes(router: Router): Promise<void> {
|
||||
router.use(preventContextCorruptionMiddleware(express.json({
|
||||
type: req => req.headers['content-type']?.match(/^application\/(.+\+)?json$/),
|
||||
})));
|
||||
|
@ -3,7 +3,7 @@ import {Router} from "express";
|
||||
import ApplicationComponent from "../ApplicationComponent.js";
|
||||
|
||||
export default class FormHelperComponent extends ApplicationComponent {
|
||||
public async init(router: Router): Promise<void> {
|
||||
public async initRoutes(router: Router): Promise<void> {
|
||||
router.use((req, res, next) => {
|
||||
let _validation: unknown | null;
|
||||
res.locals.validation = () => {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import config from "config";
|
||||
import {Express, Router} from "express";
|
||||
import path from "path";
|
||||
import * as querystring from "querystring";
|
||||
import {ParsedUrlQueryInput} from "querystring";
|
||||
import util from "util";
|
||||
|
||||
@ -9,6 +8,7 @@ import ApplicationComponent from "../ApplicationComponent.js";
|
||||
import Controller, {RouteParams} from "../Controller.js";
|
||||
import AssetCompiler from "../frontend/AssetCompiler.js";
|
||||
import AssetPreCompiler from "../frontend/AssetPreCompiler.js";
|
||||
import Globals from "../frontend/Globals.js";
|
||||
import ViewEngine from "../frontend/ViewEngine.js";
|
||||
import {logger} from "../Logger.js";
|
||||
import {listFilesRecursively} from "../Utils.js";
|
||||
@ -18,6 +18,7 @@ export default class FrontendToolsComponent extends ApplicationComponent {
|
||||
private readonly publicDir: string;
|
||||
private readonly publicAssetsCache: FileCache = new FileCache();
|
||||
private readonly assetPreCompilers: AssetPreCompiler[];
|
||||
private readonly globals: Globals = new Globals();
|
||||
|
||||
public constructor(
|
||||
private readonly assetCompiler: AssetCompiler,
|
||||
@ -31,9 +32,30 @@ export default class FrontendToolsComponent extends ApplicationComponent {
|
||||
if (assetPreCompiler.isPublic()) {
|
||||
this.assetCompiler.addExtension(assetPreCompiler.getExtension());
|
||||
}
|
||||
|
||||
assetPreCompiler.setGlobals(this.globals);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async init(): Promise<void> {
|
||||
this.globals.set('route', (
|
||||
route: string,
|
||||
params: RouteParams = [],
|
||||
query: ParsedUrlQueryInput = {},
|
||||
absolute: boolean = false,
|
||||
) => Controller.route(route, params, query, absolute));
|
||||
this.globals.set('app_version', this.getApp().getVersion());
|
||||
this.globals.set('core_version', this.getApp().getCoreVersion());
|
||||
this.globals.set('app', config.get('app'));
|
||||
this.globals.set('dump', (val: unknown) => {
|
||||
return util.inspect(val);
|
||||
});
|
||||
this.globals.set('hex', (v: number) => {
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
public async start(app: Express): Promise<void> {
|
||||
// Cache public assets
|
||||
if (config.get<boolean>('asset_cache')) {
|
||||
@ -55,9 +77,6 @@ export default class FrontendToolsComponent extends ApplicationComponent {
|
||||
main = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add util globals
|
||||
this.setupGlobals();
|
||||
}
|
||||
|
||||
public async stop(): Promise<void> {
|
||||
@ -66,7 +85,7 @@ export default class FrontendToolsComponent extends ApplicationComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public async handle(router: Router): Promise<void> {
|
||||
public async handleRoutes(router: Router): Promise<void> {
|
||||
router.use((req, res, next) => {
|
||||
res.locals.inlineAsset = (urlPath: string) => {
|
||||
return this.publicAssetsCache.getOrFail(path.join(this.publicDir, urlPath));
|
||||
@ -86,25 +105,6 @@ export default class FrontendToolsComponent extends ApplicationComponent {
|
||||
});
|
||||
}
|
||||
|
||||
public setupGlobals(): void {
|
||||
ViewEngine.setGlobal('route', (
|
||||
route: string,
|
||||
params: RouteParams = [],
|
||||
query: ParsedUrlQueryInput = {},
|
||||
absolute: boolean = false,
|
||||
) => Controller.route(route, params, query, absolute));
|
||||
ViewEngine.setGlobal('app_version', this.getApp().getVersion());
|
||||
ViewEngine.setGlobal('core_version', this.getApp().getCoreVersion());
|
||||
ViewEngine.setGlobal('querystring', querystring);
|
||||
ViewEngine.setGlobal('app', config.get('app'));
|
||||
ViewEngine.setGlobal('dump', (val: unknown) => {
|
||||
return util.inspect(val);
|
||||
});
|
||||
ViewEngine.setGlobal('hex', (v: number) => {
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
public hookPreCompilers(): void {
|
||||
for (const assetPreCompiler of this.assetPreCompilers) {
|
||||
assetPreCompiler.onPreCompile(async watch => {
|
||||
@ -127,4 +127,8 @@ export default class FrontendToolsComponent extends ApplicationComponent {
|
||||
this.hookPreCompilers();
|
||||
}
|
||||
}
|
||||
|
||||
public getGlobals(): Globals {
|
||||
return this.globals;
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ export default class LogRequestsComponent extends ApplicationComponent {
|
||||
return '';
|
||||
}
|
||||
|
||||
public async init(router: Router): Promise<void> {
|
||||
public async initRoutes(router: Router): Promise<void> {
|
||||
router.use((req, res, next) => {
|
||||
onFinished(res, (err) => {
|
||||
if (!err) {
|
||||
|
@ -11,6 +11,7 @@ import {logger} from "../Logger.js";
|
||||
import Mail from "../mail/Mail.js";
|
||||
import MailError from "../mail/MailError.js";
|
||||
import SecurityError from "../SecurityError.js";
|
||||
import FrontendToolsComponent from "./FrontendToolsComponent.js";
|
||||
|
||||
export default class MailComponent extends ApplicationComponent {
|
||||
private transporter?: Transporter;
|
||||
@ -91,7 +92,7 @@ export default class MailComponent extends ApplicationComponent {
|
||||
locals.mail_to = options.to;
|
||||
locals.mail_link = config.get<string>('public_url') +
|
||||
Controller.route('mail', [template.template], locals);
|
||||
Object.assign(locals, ViewEngine.getGlobals());
|
||||
Object.assign(locals, this.getApp().as(FrontendToolsComponent).getGlobals().get());
|
||||
|
||||
// Log
|
||||
logger.debug(`Send mail from ${options.from.address} to ${options.to}`);
|
||||
|
@ -15,7 +15,7 @@ export default class MaintenanceComponent extends ApplicationComponent {
|
||||
this.canServe = canServe;
|
||||
}
|
||||
|
||||
public async handle(router: Router): Promise<void> {
|
||||
public async handleRoutes(router: Router): Promise<void> {
|
||||
router.use((req: Request, res: Response, next: NextFunction) => {
|
||||
if (res.headersSent) {
|
||||
return next();
|
||||
|
@ -3,11 +3,18 @@ import onFinished from "on-finished";
|
||||
|
||||
import ApplicationComponent from "../ApplicationComponent.js";
|
||||
import {logger} from "../Logger.js";
|
||||
import FrontendToolsComponent from "./FrontendToolsComponent.js";
|
||||
import SessionComponent from "./SessionComponent.js";
|
||||
|
||||
export default class PreviousUrlComponent extends ApplicationComponent {
|
||||
public async init(): Promise<void> {
|
||||
const globals = this.getApp().asOptional(FrontendToolsComponent)?.getGlobals();
|
||||
if (globals) {
|
||||
globals.set('getPreviousUrl', () => null);
|
||||
}
|
||||
}
|
||||
|
||||
public async handle(router: Router): Promise<void> {
|
||||
public async handleRoutes(router: Router): Promise<void> {
|
||||
router.use((req, res, next) => {
|
||||
req.getPreviousUrl = () => {
|
||||
let url = req.header('referer');
|
||||
|
@ -15,7 +15,7 @@ export default class ServeStaticDirectoryComponent extends ApplicationComponent
|
||||
this.path = routePath;
|
||||
}
|
||||
|
||||
public async init(router: Router): Promise<void> {
|
||||
public async initRoutes(router: Router): Promise<void> {
|
||||
const resolvedRoot = path.resolve(this.root);
|
||||
|
||||
if (this.path) {
|
||||
|
@ -4,7 +4,9 @@ import {Router} from "express";
|
||||
import session from "express-session";
|
||||
|
||||
import ApplicationComponent from "../ApplicationComponent.js";
|
||||
import ViewEngine from "../frontend/ViewEngine.js";
|
||||
import SecurityError from "../SecurityError.js";
|
||||
import FrontendToolsComponent from "./FrontendToolsComponent.js";
|
||||
import RedisComponent from "./RedisComponent.js";
|
||||
|
||||
export default class SessionComponent extends ApplicationComponent {
|
||||
@ -15,6 +17,13 @@ export default class SessionComponent extends ApplicationComponent {
|
||||
this.storeComponent = storeComponent;
|
||||
}
|
||||
|
||||
public async init(): Promise<void> {
|
||||
const globals = this.getApp().asOptional(FrontendToolsComponent)?.getGlobals();
|
||||
if (globals) {
|
||||
globals.set('flash', () => '');
|
||||
}
|
||||
}
|
||||
|
||||
public async checkSecuritySettings(): Promise<void> {
|
||||
this.checkSecurityConfigField('session.secret');
|
||||
if (!config.get<boolean>('session.cookie.secure')) {
|
||||
@ -22,7 +31,7 @@ export default class SessionComponent extends ApplicationComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public async init(router: Router): Promise<void> {
|
||||
public async initRoutes(router: Router): Promise<void> {
|
||||
router.use(session({
|
||||
saveUninitialized: true,
|
||||
secret: config.get('session.secret'),
|
||||
|
@ -7,29 +7,33 @@ import WebSocket from "ws";
|
||||
|
||||
import Application from "../Application.js";
|
||||
import ApplicationComponent from "../ApplicationComponent.js";
|
||||
import ViewEngine from "../frontend/ViewEngine.js";
|
||||
import {logger} from "../Logger.js";
|
||||
import WebSocketListener from "../WebSocketListener.js";
|
||||
import ExpressAppComponent from "./ExpressAppComponent.js";
|
||||
import FrontendToolsComponent from "./FrontendToolsComponent.js";
|
||||
import RedisComponent from "./RedisComponent.js";
|
||||
|
||||
export default class WebSocketServerComponent extends ApplicationComponent {
|
||||
private wss?: WebSocket.Server;
|
||||
|
||||
public constructor(
|
||||
private readonly application: Application,
|
||||
private readonly expressAppComponent: ExpressAppComponent,
|
||||
private readonly storeComponent: RedisComponent,
|
||||
) {
|
||||
super();
|
||||
public async init(): Promise<void> {
|
||||
const app = this.getApp();
|
||||
|
||||
ViewEngine.setGlobal('websocketUrl', config.get('public_websocket_url'));
|
||||
app.require(ExpressAppComponent);
|
||||
app.require(RedisComponent);
|
||||
|
||||
const globals = app.asOptional(FrontendToolsComponent)?.getGlobals();
|
||||
if (globals) {
|
||||
globals.set('websocketUrl', config.get('public_websocket_url'));
|
||||
}
|
||||
}
|
||||
|
||||
public async start(_app: Express): Promise<void> {
|
||||
const listeners: { [p: string]: WebSocketListener<Application> } = this.application.getWebSocketListeners();
|
||||
const app = this.getApp();
|
||||
|
||||
const listeners: { [p: string]: WebSocketListener<Application> } = app.getWebSocketListeners();
|
||||
this.wss = new WebSocket.Server({
|
||||
server: this.expressAppComponent.getServer(),
|
||||
server: app.as(ExpressAppComponent).getServer(),
|
||||
}, () => {
|
||||
logger.info(`Websocket server started over webserver.`);
|
||||
}).on('error', (err) => {
|
||||
@ -57,7 +61,7 @@ export default class WebSocketServerComponent extends ApplicationComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
const store = this.storeComponent.getStore();
|
||||
const store = app.as(RedisComponent).getStore();
|
||||
store.get(sid, (err, session) => {
|
||||
if (err || !session) {
|
||||
logger.error(err, 'Error while initializing session in websocket.');
|
||||
|
@ -4,9 +4,11 @@ import path from "path";
|
||||
|
||||
import {logger} from "../Logger.js";
|
||||
import {doesFileExist, listFilesRecursively} from "../Utils.js";
|
||||
import Globals from "./Globals.js";
|
||||
|
||||
export default abstract class AssetPreCompiler {
|
||||
protected readonly assetPaths: string[];
|
||||
private globals?: Globals;
|
||||
private watcher?: FSWatcher;
|
||||
private afterPreCompileHandlers: ((watch: boolean) => Promise<void>)[] = [];
|
||||
private inputChangeHandler?: (restart: boolean) => Promise<void>;
|
||||
@ -51,6 +53,19 @@ export default abstract class AssetPreCompiler {
|
||||
return this.outputToPublicDir;
|
||||
}
|
||||
|
||||
public getViewPaths(): string[] {
|
||||
return this.assetPaths;
|
||||
}
|
||||
|
||||
protected getGlobals(): Globals {
|
||||
if (!this.globals) throw new Error('globals field not intialized.');
|
||||
return this.globals;
|
||||
}
|
||||
|
||||
public setGlobals(globals: Globals): void {
|
||||
this.globals = globals;
|
||||
}
|
||||
|
||||
public async stop(): Promise<void> {
|
||||
if (this.watcher) {
|
||||
await this.watcher.close();
|
||||
@ -58,10 +73,6 @@ export default abstract class AssetPreCompiler {
|
||||
}
|
||||
}
|
||||
|
||||
public getViewPaths(): string[] {
|
||||
return this.assetPaths;
|
||||
}
|
||||
|
||||
public abstract preCompile(canonicalName: string, alsoCompileDependents: boolean): Promise<void>;
|
||||
|
||||
public onPreCompile(afterPreCompileHandler: (watch: boolean) => Promise<void>): void {
|
||||
|
11
src/frontend/Globals.ts
Normal file
11
src/frontend/Globals.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export default class Globals {
|
||||
private readonly globals: Record<string, unknown> = {};
|
||||
|
||||
public get(): Record<string, unknown> {
|
||||
return {...this.globals};
|
||||
}
|
||||
|
||||
public set(key: string, value: unknown): void {
|
||||
this.globals[key] = value;
|
||||
}
|
||||
}
|
@ -278,7 +278,7 @@ export default class SvelteViewEngine extends ViewEngine {
|
||||
// Load locals into locals store
|
||||
const localsModulePath = "../../build/ts/stores.js";
|
||||
const localsModule = await import(localsModulePath);
|
||||
const locals = ViewEngine.getGlobals();
|
||||
const locals = this.getGlobals().get();
|
||||
const localMap = this.compileBackendCalls(backendCalls, locals, true);
|
||||
localsModule.locals.set((key: string, args: string) => {
|
||||
return localMap[args ?
|
||||
|
@ -3,17 +3,6 @@ import {Express} from "express";
|
||||
import AssetPreCompiler from "./AssetPreCompiler.js";
|
||||
|
||||
export default abstract class ViewEngine extends AssetPreCompiler {
|
||||
private static readonly globals: Record<string, unknown> = {};
|
||||
|
||||
public static getGlobals(): Record<string, unknown> {
|
||||
return {...this.globals};
|
||||
}
|
||||
|
||||
public static setGlobal(key: string, value: unknown): void {
|
||||
this.globals[key] = value;
|
||||
}
|
||||
|
||||
|
||||
protected constructor(
|
||||
targetDir: string,
|
||||
assetType: string,
|
||||
@ -32,7 +21,7 @@ export default abstract class ViewEngine extends AssetPreCompiler {
|
||||
public setup(app: Express, main: boolean): void {
|
||||
app.engine(this.extension, (path, options, callback) => {
|
||||
// Props (locals)
|
||||
const locals = Object.assign(options, ViewEngine.getGlobals());
|
||||
const locals = Object.assign(options, this.getGlobals().get());
|
||||
|
||||
this.render(path, locals)
|
||||
.then(value => callback(null, value))
|
||||
|
Loading…
Reference in New Issue
Block a user