♻️ oo-connector: refactored router system to something more coherent (#525)

This commit is contained in:
Eric Doughty-Papassideris
2024-09-04 03:47:59 +02:00
parent afe6562804
commit 9bc9dcafee
10 changed files with 97 additions and 83 deletions
@@ -5,24 +5,25 @@ import cors from 'cors';
import { renderFile } from 'eta'; import { renderFile } from 'eta';
import path from 'path'; import path from 'path';
import errorMiddleware from './middlewares/error.middleware'; import errorMiddleware from './middlewares/error.middleware';
import { SERVER_PORT, SERVER_PREFIX } from '@config'; import { SERVER_PORT } from '@config';
import logger from './lib/logger'; import logger from './lib/logger';
import cookieParser from 'cookie-parser'; import cookieParser from 'cookie-parser';
import apiService from './services/api.service'; import apiService from './services/api.service';
import onlyofficeService from './services/onlyoffice.service'; import onlyofficeService from './services/onlyoffice.service';
import { makeURLTo, mountRoutes } from './routes';
class App { class App {
public app: express.Application; public app: express.Application;
public env: string; public env: string;
public port: string | number; public port: string | number;
constructor(routes: Routes[]) { constructor() {
this.app = express(); this.app = express();
this.env = NODE_ENV; this.env = NODE_ENV;
this.initViews(); this.initViews();
this.initMiddlewares(); this.initMiddlewares();
this.initRoutes(routes); this.initRoutes();
this.initErrorHandling(); this.initErrorHandling();
} }
@@ -34,15 +35,13 @@ class App {
public getServer = () => this.app; public getServer = () => this.app;
private initRoutes = (routes: Routes[]) => { private initRoutes = () => {
this.app.use((req, res, next) => { this.app.use((req, res, next) => {
logger.info(`Received request: ${req.method} ${req.originalUrl} from ${req.header('user-agent')} (${req.ip})`); logger.info(`Received request: ${req.method} ${req.originalUrl} from ${req.header('user-agent')} (${req.ip})`);
next(); next();
}); });
routes.forEach(route => { mountRoutes(this.app);
this.app.use(route.path ?? '/', route.router);
});
this.app.get('/health', (_req, res) => { this.app.get('/health', (_req, res) => {
Promise.all([onlyofficeService.getLatestLicence(), apiService.hasToken(), onlyofficeService.getForgottenList()]).then( Promise.all([onlyofficeService.getLatestLicence(), apiService.hasToken(), onlyofficeService.getForgottenList()]).then(
@@ -58,7 +57,7 @@ class App {
}); });
this.app.use( this.app.use(
SERVER_PREFIX.replace(/\/$/, '') + '/assets', makeURLTo.assets(),
(req, res, next) => { (req, res, next) => {
res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'X-Requested-With'); res.header('Access-Control-Allow-Headers', 'X-Requested-With');
@@ -16,3 +16,5 @@ export const {
export const twakeDriveTokenRefrehPeriodMS = 10 * 60 * 1000; export const twakeDriveTokenRefrehPeriodMS = 10 * 60 * 1000;
export const onlyOfficeForgottenFilesCheckPeriodMS = 10 * 60 * 1000; export const onlyOfficeForgottenFilesCheckPeriodMS = 10 * 60 * 1000;
export const onlyOfficeConnectivityCheckPeriodMS = 10 * 60 * 1000; export const onlyOfficeConnectivityCheckPeriodMS = 10 * 60 * 1000;
export const SERVER_TDRIVE_API_PREFIX = '/tdriveApi/1';
@@ -1,13 +1,13 @@
import editorService from '@/services/editor.service'; import editorService from '@/services/editor.service';
import { NextFunction, Request, Response } from 'express'; import { NextFunction, Request, Response } from 'express';
import { CREDENTIALS_SECRET, SERVER_ORIGIN, SERVER_PREFIX } from '@config'; import { CREDENTIALS_SECRET } from '@config';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import driveService from '@/services/drive.service'; import driveService from '@/services/drive.service';
import { DriveFileType } from '@/interfaces/drive.interface'; import { DriveFileType } from '@/interfaces/drive.interface';
import fileService from '@/services/file.service'; import fileService from '@/services/file.service';
import { OfficeToken } from '@/interfaces/routes.interface'; import { OfficeToken } from '@/interfaces/routes.interface';
import logger from '@/lib/logger'; import logger from '@/lib/logger';
import * as Utils from '@/utils'; import { makeURLTo } from '@/routes';
interface RequestQuery { interface RequestQuery {
mode: string; mode: string;
@@ -29,7 +29,7 @@ interface RequestEditorQuery {
* The user is redirected from there to open directly the OnlyOffice edition server's web UI, with appropriate preview or not * The user is redirected from there to open directly the OnlyOffice edition server's web UI, with appropriate preview or not
* and rights checks. * and rights checks.
*/ */
class IndexController { class BrowserEditorController {
/** /**
* Opened by the user's browser, proxied through the Twake Drive backend. Checks access to the * Opened by the user's browser, proxied through the Twake Drive backend. Checks access to the
* file with the backend, then redirects the user to the `editor` method but directly on this * file with the backend, then redirects the user to the `editor` method but directly on this
@@ -98,9 +98,8 @@ class IndexController {
expiresIn: 60 * 60 * 24 * 30, expiresIn: 60 * 60 * 24 * 30,
}, },
); );
res.redirect( res.redirect(
Utils.joinURL([SERVER_ORIGIN ?? '', SERVER_PREFIX, 'editor'], { makeURLTo.editorAbsolute({
token, token,
file_id, file_id,
editing_session_key: editingSessionKey, editing_session_key: editingSessionKey,
@@ -145,7 +144,7 @@ class IndexController {
res.render('index', { res.render('index', {
...initResponse, ...initResponse,
docId: preview ? file_id : editing_session_key, docId: preview ? file_id : editing_session_key,
server: Utils.joinURL([SERVER_ORIGIN, SERVER_PREFIX]), server: makeURLTo.rootAbsolute(),
token: inPageToken, token: inPageToken,
}); });
} catch (error) { } catch (error) {
@@ -155,4 +154,4 @@ class IndexController {
}; };
} }
export default IndexController; export default BrowserEditorController;
@@ -1,7 +1,6 @@
import logger from '@/lib/logger'; import logger from '@/lib/logger';
import { NextFunction, Request, Response } from 'express'; import { NextFunction, Request, Response } from 'express';
import * as Utils from '@/utils'; import { makeURLTo } from '@/routes';
import { SERVER_ORIGIN, SERVER_PREFIX } from '@config';
export default (error: Error & { status?: number }, req: Request, res: Response, next: NextFunction): void => { export default (error: Error & { status?: number }, req: Request, res: Response, next: NextFunction): void => {
try { try {
@@ -12,7 +11,7 @@ export default (error: Error & { status?: number }, req: Request, res: Response,
res.status(status); res.status(status);
res.render('error', { res.render('error', {
server: Utils.joinURL([SERVER_ORIGIN, SERVER_PREFIX]), server: makeURLTo.rootAbsolute(),
errorMessage: message, errorMessage: message,
}); });
} catch (error) { } catch (error) {
@@ -1,21 +1,16 @@
import TwakeDriveBackendCallbackController from '@/controllers/backend-callbacks.controller'; import TwakeDriveBackendCallbackController from '@/controllers/backend-callbacks.controller';
import { Routes } from '@/interfaces/routes.interface';
import authMiddleware from '@/middlewares/auth.middleware'; import authMiddleware from '@/middlewares/auth.middleware';
import { Router } from 'express'; import type { Router } from 'express';
/** /**
* These routes are called by Twake Drive backend, for ex. before editing or retreiving a file, * These routes are called by Twake Drive backend, for ex. before editing or retreiving a file,
* if it has an editing_session_key still, get the status of that and force a resolution. * if it has an editing_session_key still, get the status of that and force a resolution.
*/ */
export default class TwakeDriveBackendCallbackRoutes implements Routes { export const TwakeDriveBackendCallbackRoutes = {
public readonly router = Router(); mount(router: Router) {
public readonly path = '/tdriveApi/1'; const controller = new TwakeDriveBackendCallbackController();
private readonly controller: TwakeDriveBackendCallbackController;
constructor() {
this.controller = new TwakeDriveBackendCallbackController();
// Why post ? to garantee it is never cached and always ran // Why post ? to garantee it is never cached and always ran
this.router.post('/session/:editing_session_key/check', authMiddleware, this.controller.checkSessionStatus); router.post('/session/:editing_session_key/check', authMiddleware, controller.checkSessionStatus);
this.router.delete('/session/:editing_session_key', authMiddleware, this.controller.deleteSessionKey); router.delete('/session/:editing_session_key', authMiddleware, controller.deleteSessionKey);
} },
} };
@@ -0,0 +1,16 @@
import BrowserEditorController from '@/controllers/browser-editor.controller';
import authMiddleware from '@/middlewares/auth.middleware';
import requirementsMiddleware from '@/middlewares/requirements.middleware';
import type { Router } from 'express';
/**
* When the user previews or edits a file in Twake Drive, their browser is sent to these routes
* which return a webpage that instantiates the client side JS Only Office component.
*/
export const BrowserEditorRoutes = {
mount(router: Router) {
const controller = new BrowserEditorController();
router.get('/', requirementsMiddleware, authMiddleware, controller.index);
router.get('/editor', requirementsMiddleware, authMiddleware, controller.editor);
},
};
@@ -1,28 +0,0 @@
import IndexController from '@/controllers/index.controller';
import { Routes } from '@/interfaces/routes.interface';
import authMiddleware from '@/middlewares/auth.middleware';
import requirementsMiddleware from '@/middlewares/requirements.middleware';
import { Router } from 'express';
import { SERVER_PREFIX } from '@config';
/**
* When the user previews or edits a file in Twake Drive, their browser is sent to these routes
* which return a webpage that instantiates the client side JS Only Office component.
*/
class IndexRoute implements Routes {
public path = SERVER_PREFIX;
public router = Router();
public indexController: IndexController;
constructor() {
this.indexController = new IndexController();
this.initRoutes();
}
private initRoutes = () => {
this.router.get('/', requirementsMiddleware, authMiddleware, this.indexController.index);
this.router.get('/editor', requirementsMiddleware, authMiddleware, this.indexController.editor);
};
}
export default IndexRoute;
@@ -0,0 +1,43 @@
import { Application, Router } from 'express';
import * as Utils from '@/utils';
import { TwakeDriveBackendCallbackRoutes } from './backend-callbacks.route';
import { BrowserEditorRoutes } from './browser-editor.route';
import { OnlyOfficeRoutes } from './onlyoffice.route';
import { SERVER_ORIGIN, SERVER_PREFIX, SERVER_TDRIVE_API_PREFIX } from '@config';
export function mountRoutes(app: Application) {
// These routes are forwarded through the Twake Drive front, back and here
const proxiedRouter = Router();
BrowserEditorRoutes.mount(proxiedRouter);
OnlyOfficeRoutes.mount(proxiedRouter);
console.log('Mounting at ' + SERVER_PREFIX);
app.use(SERVER_PREFIX, proxiedRouter);
// These endpoints should only be accessible to the Twake Drive backend
const apiRouter = Router();
console.log('Mounting at ' + SERVER_TDRIVE_API_PREFIX);
TwakeDriveBackendCallbackRoutes.mount(apiRouter);
app.use(SERVER_TDRIVE_API_PREFIX, apiRouter);
}
export const makeURLTo = {
rootAbsolute: () => Utils.joinURL([SERVER_ORIGIN, SERVER_PREFIX]),
assets: () => Utils.joinURL([SERVER_PREFIX, 'assets']),
editorAbsolute(params: { token: string; file_id: string; editing_session_key: string; company_id: string; preview: string; office_token: string }) {
return Utils.joinURL([SERVER_ORIGIN ?? '', SERVER_PREFIX, 'editor'], params);
},
};
// export function makeURLToEditor2() {
// const initResponse = await editorService.init(company_id, file_name, file_id, user, preview, drive_file_id || file_id);
// res.render('index', {
// ...initResponse,
// docId: preview ? file_id : editing_session_key,
// server: Utils.joinURL([SERVER_ORIGIN, SERVER_PREFIX]),
// token: inPageToken,
// });
// }
@@ -1,23 +1,15 @@
import OnlyOfficeController from '@/controllers/onlyoffice.controller'; import OnlyOfficeController from '@/controllers/onlyoffice.controller';
import { Routes } from '@/interfaces/routes.interface';
import requirementsMiddleware from '@/middlewares/requirements.middleware'; import requirementsMiddleware from '@/middlewares/requirements.middleware';
import { Router } from 'express'; import type { Router } from 'express';
import { SERVER_PREFIX } from '@config';
class OnlyOfficeRoute implements Routes { /**
public path = SERVER_PREFIX; * These routes are called by the Only Office server
public router = Router(); * when reading a document or updating an editing session
public onlyOfficeController: OnlyOfficeController; */
export const OnlyOfficeRoutes = {
constructor() { mount(router: Router) {
this.onlyOfficeController = new OnlyOfficeController(); const controller = new OnlyOfficeController();
this.initRoutes(); router.get(`/:mode/read`, requirementsMiddleware, controller.read);
} router.post(`/:mode/callback`, requirementsMiddleware, controller.ooCallback);
},
private initRoutes = () => { };
this.router.get(`:mode/read`, requirementsMiddleware, this.onlyOfficeController.read);
this.router.post(`:mode/callback`, requirementsMiddleware, this.onlyOfficeController.ooCallback);
};
}
export default OnlyOfficeRoute;
@@ -1,8 +1,5 @@
import App from '@/app'; import App from '@/app';
import IndexRoute from './routes/index.route';
import OnlyOfficeRoute from './routes/onlyoffice.route';
import TwakeDriveBackendCallbacksRoutes from './routes/backend-callbacks.route';
const app = new App([new IndexRoute(), new OnlyOfficeRoute(), new TwakeDriveBackendCallbacksRoutes()]); const app = new App();
app.listen(); app.listen();