|
|
@@ -3,11 +3,18 @@ import * as express from 'express';
|
|
|
import { AddressInfo } from "net";
|
|
|
import * as path from 'path';
|
|
|
import * as bodyParser from 'body-parser';
|
|
|
+const i18next = require('i18next'); // FIXME import from
|
|
|
+const i18nextMiddleware = require('i18next-http-middleware'); // FIXME import from
|
|
|
+const resourcesToBackend = require('i18next-resources-to-backend'); // FIXME import from
|
|
|
import route_index from './routes/index';
|
|
|
import route_login from './routes/login';
|
|
|
+import route_calendar from './routes/calendar';
|
|
|
+import route_trombinoscope from './routes/trombinoscope';
|
|
|
+import route_accounting from './routes/accounting';
|
|
|
import Security from './src/Security';
|
|
|
import { Session, SessionManager } from './src/Session';
|
|
|
-import UserConnectorFactory from './src/UserConnector/UserConnectorFactory';
|
|
|
+import UserConnectorFactory, { IUserConnector } from './src/UserConnector/UserConnectorFactory';
|
|
|
+import DBConnectorFactory from './src/DbConnector/DBConnectorFactory';
|
|
|
|
|
|
const debug = require('debug')('my express app');
|
|
|
const app = express();
|
|
|
@@ -17,103 +24,176 @@ declare global {
|
|
|
interface Request {
|
|
|
mSession: Session
|
|
|
mCookies: Map<string, string>
|
|
|
+ t: (s: string) => string // i18n translation function
|
|
|
+ }
|
|
|
+
|
|
|
+ interface Response {
|
|
|
+ i18next: any
|
|
|
+ renderWithTranslations: (translationNs: string, renderView: string, args: any) => Promise<void>
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Check config
|
|
|
-(_ => {
|
|
|
+(async _ => {
|
|
|
+ await i18next.use(i18nextMiddleware.LanguageDetector)
|
|
|
+ .use(resourcesToBackend((language: string, namespace: string, callback: Function) => {
|
|
|
+ import(`./locales/${language}/${namespace}.json`)
|
|
|
+ .then((resources) => {
|
|
|
+ callback(null, resources)
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ callback(error, null)
|
|
|
+ })
|
|
|
+ }))
|
|
|
+ .init({
|
|
|
+ preload: ['fr', 'en'],
|
|
|
+ nonExplicitSupportedLngs: true,
|
|
|
+ lowerCaseLng: true
|
|
|
+ });
|
|
|
+
|
|
|
let cc: AConfigChecker[] = [
|
|
|
- UserConnectorFactory
|
|
|
+ UserConnectorFactory,
|
|
|
+ await DBConnectorFactory.GetConnector()
|
|
|
];
|
|
|
for (let i of cc) {
|
|
|
- let err = i.CheckConfig();
|
|
|
- if (err) {
|
|
|
- err = i.constructor.name + ": " + err;
|
|
|
- console.error("Configuration error: " + err);
|
|
|
+ let err: Promise<void> | Error | null = i.CheckConfig();
|
|
|
+ let errResult: Error | null = null;
|
|
|
+ try {
|
|
|
+ if (err instanceof Promise)
|
|
|
+ await err;
|
|
|
+ else
|
|
|
+ errResult = err;
|
|
|
+ }
|
|
|
+ catch (_err) {
|
|
|
+ errResult = _err;
|
|
|
+ }
|
|
|
+ if (errResult !== null) {
|
|
|
+ console.error("Configuration error: " + i.constructor.name +": "+ errResult.message);
|
|
|
throw "Configuration error";
|
|
|
}
|
|
|
}
|
|
|
-})();
|
|
|
|
|
|
-// view engine setup
|
|
|
-app.set('views', [path.join(__dirname, 'views'), path.join(__dirname, 'views/template')]);
|
|
|
-app.set('view engine', 'pug');
|
|
|
+ // view engine setup
|
|
|
+ app.set('views', [path.join(__dirname, 'views'), path.join(__dirname, 'views/template')]);
|
|
|
+ app.set('view engine', 'pug');
|
|
|
|
|
|
-app.use(express.static(path.join(__dirname, 'public')));
|
|
|
-app.use(bodyParser.json());
|
|
|
-app.use(bodyParser.urlencoded({ extended: true }));
|
|
|
+ app.use(express.static(path.join(__dirname, 'public')));
|
|
|
+ app.use(bodyParser.json());
|
|
|
+ app.use(bodyParser.urlencoded({ extended: true }));
|
|
|
+ app.use(i18nextMiddleware.handle(i18next, {
|
|
|
+ ignoreRoutes: [] // or function(req, res, options, i18next) { /* return true to ignore */ }
|
|
|
+ }));
|
|
|
|
|
|
-// Security and session setup
|
|
|
-app.use((req, _, next) => {
|
|
|
- req.mCookies = new Map();
|
|
|
- (req.headers?.cookie || "").split(";").forEach(i => {
|
|
|
- let keyValue = i.split("=", 2);
|
|
|
- req.mCookies.set(keyValue[0].trim(), keyValue[1].trim());
|
|
|
+ // Security and session setup
|
|
|
+ app.use((req: express.Request, res: express.Response, next) => {
|
|
|
+ res.i18next = i18next;
|
|
|
+ res.renderWithTranslations = async (translationNs: string, renderView: string, args: any) => {
|
|
|
+ await i18next.loadNamespaces(translationNs);
|
|
|
+ i18next.setDefaultNamespace(translationNs);
|
|
|
+ args = args || {};
|
|
|
+ args["t"] = req.t;
|
|
|
+ res.render(renderView, args);
|
|
|
+ };
|
|
|
+
|
|
|
+ req.mCookies = new Map();
|
|
|
+ (req.headers?.cookie || "").split(";").forEach(i => {
|
|
|
+ let keyValue = i.split("=", 2);
|
|
|
+ req.mCookies.set(keyValue[0].trim(), keyValue[1].trim());
|
|
|
+ });
|
|
|
+ req.mSession = SessionManager.GetSession(req);
|
|
|
+ if (req.mSession.IsValid())
|
|
|
+ req.mSession.Ping();
|
|
|
+ if (req.query["API_KEY"]) {
|
|
|
+ Security.TryLoginApiKey(req.query["API_KEY"].toString()).then(user => {
|
|
|
+ req.mSession.Login(user);
|
|
|
+ })
|
|
|
+ .catch(e => {
|
|
|
+ let err: any = new Error("Access denied");
|
|
|
+ err['status'] = 403;
|
|
|
+ next(err);
|
|
|
+ return;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ next();
|
|
|
});
|
|
|
- req.mSession = SessionManager.GetSession(req);
|
|
|
- if (req.mSession.IsValid())
|
|
|
- req.mSession.Ping();
|
|
|
- if (req.query["API_KEY"])
|
|
|
- if (!Security.TryLoginApiKey(req.query["API_KEY"].toString())) {
|
|
|
- let err: any = new Error("Access denied");
|
|
|
- err['status'] = 403;
|
|
|
- next(err);
|
|
|
+
|
|
|
+ let HasAccount = await UserConnectorFactory.GetConnector().HasUsers();
|
|
|
+
|
|
|
+ // Anonymous pages
|
|
|
+ !HasAccount && app.use(async (req, res, next) => {
|
|
|
+ HasAccount = HasAccount || await UserConnectorFactory.GetConnector().HasUsers();
|
|
|
+ if (HasAccount) {
|
|
|
+ next();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let connector: IUserConnector = UserConnectorFactory.GetConnector();
|
|
|
+ if (!connector.CanCreateAccount()) {
|
|
|
+ res.end("No account defined, and cannot create account from Connector Backend");
|
|
|
return;
|
|
|
}
|
|
|
- next();
|
|
|
-});
|
|
|
-
|
|
|
-// Annonymous pages
|
|
|
-app.use('/', route_index);
|
|
|
-app.use('/login', route_login);
|
|
|
-
|
|
|
-// Login check
|
|
|
-app.use((req, res, next) => {
|
|
|
- if (!req.mSession.IsValid()) {
|
|
|
- const URL = '/login?redirect=' + encodeURIComponent(req.url);
|
|
|
- res.status(302);
|
|
|
- res.setHeader("Location", URL);
|
|
|
- res.send("<!DOCTYPE html><html><body><a href='" + URL + "'>" + URL + "</a><script>document.location.href='" + URL + "';</script></html>");
|
|
|
- } else {
|
|
|
- next();
|
|
|
- }
|
|
|
-});
|
|
|
-// Any following routes need login
|
|
|
-app.use('/logged', route_index);
|
|
|
-
|
|
|
-// catch 404 and forward to error handler
|
|
|
-app.use((_, __, next) => {
|
|
|
- const err: any = new Error('Not Found');
|
|
|
- err['status'] = 404;
|
|
|
- next(err);
|
|
|
-});
|
|
|
-// error handlers
|
|
|
-
|
|
|
-// development error handler
|
|
|
-// will print stacktrace
|
|
|
-if (app.get('env') === 'development') {
|
|
|
- app.use((err: any, req: Express.Request, res: any, next: any) => { // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
|
- res.status(err['status'] || 500);
|
|
|
+ if (req.method.toUpperCase() === "POST" && (req.body["username"] || "").trim().length && (req.body["password"] || "").trim().length) {
|
|
|
+ let user = await connector.CreateAccount(req.body["username"].trim(), req.body["password"].trim());
|
|
|
+ req.mSession.Login(user);
|
|
|
+ HasAccount = true;
|
|
|
+ SessionManager.Write(res, req.mSession);
|
|
|
+ res.redirect(302, "/");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (req.method.toUpperCase() === "GET") {
|
|
|
+ res.render('setup_user');
|
|
|
+ }
|
|
|
+ });
|
|
|
+ app.use('/', route_index);
|
|
|
+ app.use('/login', route_login);
|
|
|
+
|
|
|
+ // Login check
|
|
|
+ app.use((req, res, next) => {
|
|
|
+ if (!req.mSession.IsValid())
|
|
|
+ res.redirect(302, '/login?redirect=' + encodeURIComponent(req.url));
|
|
|
+ else
|
|
|
+ next();
|
|
|
+ });
|
|
|
+ // Any following routes need login
|
|
|
+ app.use('/calendar', route_calendar);
|
|
|
+ app.use('/trombinoscope', route_trombinoscope);
|
|
|
+ app.use('/accounting', route_accounting);
|
|
|
+
|
|
|
+ // catch 404 and forward to error handler
|
|
|
+ app.use((_, __, next) => {
|
|
|
+ const err: any = new Error('Not Found');
|
|
|
+ err['status'] = 404;
|
|
|
+ next(err);
|
|
|
+ });
|
|
|
+ // error handlers
|
|
|
+
|
|
|
+ // development error handler
|
|
|
+ // will print stacktrace
|
|
|
+ if (app.get('env') === 'development') {
|
|
|
+ app.use((err: any, req: Express.Request, res: any, next: any) => { // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
|
+ err && console.error(err);
|
|
|
+ res.status(err['status'] || 500);
|
|
|
+ res.render('error', {
|
|
|
+ message: err.message,
|
|
|
+ error: err
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // production error handler
|
|
|
+ // no stacktraces leaked to user
|
|
|
+ app.use((err: any, _: any, res: any, __: any) => { // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
|
+ err && console.error(err);
|
|
|
+ res.status(err.status || 500);
|
|
|
res.render('error', {
|
|
|
message: err.message,
|
|
|
- error: err
|
|
|
+ error: {}
|
|
|
});
|
|
|
});
|
|
|
-}
|
|
|
-
|
|
|
-// production error handler
|
|
|
-// no stacktraces leaked to user
|
|
|
-app.use((err: any, _: any, res: any, __: any) => { // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
|
- res.status(err.status || 500);
|
|
|
- res.render('error', {
|
|
|
- message: err.message,
|
|
|
- error: {}
|
|
|
- });
|
|
|
-});
|
|
|
|
|
|
-app.set('port', process.env.port || 1337);
|
|
|
+ app.set('port', process.env.port || 1337);
|
|
|
|
|
|
-const server = app.listen(app.get('port'), function () {
|
|
|
- debug(`Express server listening on port ${(server.address() as AddressInfo).port}`);
|
|
|
-});
|
|
|
+ const server = app.listen(app.get('port'), function () {
|
|
|
+ debug(`Express server listening on port ${(server.address() as AddressInfo).port}`);
|
|
|
+ });
|
|
|
+})();
|