server: new backend
This CR adds a new backend based on TypeScript, Express, WebSocket and
Rollup.
To build the server:
> npm run build
To develop with dev mode:
> npm run start
GitLab: #47
Change-Id: I6cdacab14104ea67c559e3f9e892fbc1b17a022d
diff --git a/server/src/constants.ts b/server/src/constants.ts
new file mode 100644
index 0000000..5a1666b
--- /dev/null
+++ b/server/src/constants.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+
+enum StatusCode {
+ OK = 200,
+ CREATED = 201,
+ ACCEPTED = 202,
+ NO_CONTENT = 204,
+ BAD_REQUEST = 400,
+ UNAUTHORIZED = 401,
+ FORBIDDEN = 403,
+ NOT_FOUND = 404,
+ NOT_ACCEPTABLE = 406,
+ CONFLICT = 409,
+ GONE = 410,
+ IM_A_TEAPOT = 418,
+ TOO_MANY_REQUESTS = 429,
+ INTERNAL_SERVER_ERROR = 500,
+ NOT_IMPLEMENTED = 501,
+}
+
+export { StatusCode };
diff --git a/server/src/index.ts b/server/src/index.ts
new file mode 100644
index 0000000..587438c
--- /dev/null
+++ b/server/src/index.ts
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import 'reflect-metadata';
+
+import { createServer } from 'node:http';
+
+import log from 'loglevel';
+import { Container } from 'typedi';
+
+import { Router } from './router.js';
+import { Ws } from './ws.js';
+
+log.setLevel(process.env.NODE_ENV === 'production' ? 'error' : 'trace');
+
+const app = await Container.get(Router).build();
+const wss = await Container.get(Ws).build();
+
+// Disable HTTP 1.1 Keep-Alive
+const server = createServer((_, res) => res.setHeader('Connection', 'close'));
+server.on('request', app);
+server.on('upgrade', wss);
+server.listen({
+ host: '0.0.0.0',
+ port: 5000,
+ exclusive: true,
+});
+
+log.debug('Server started (HTTP + WS)');
+
+const closeFn: NodeJS.SignalsListener = (signal) => {
+ log.info(signal);
+ server.close();
+ log.info('server closed');
+};
+process.once('SIGTERM', closeFn);
+process.once('SIGHUP', closeFn);
+process.once('SIGINT', closeFn);
diff --git a/server/src/router.ts b/server/src/router.ts
new file mode 100644
index 0000000..0b00780
--- /dev/null
+++ b/server/src/router.ts
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import express, { json, Router as ERouter } from 'express';
+import asyncHandler from 'express-async-handler';
+import { Service } from 'typedi';
+
+import { StatusCode } from './constants.js';
+
+@Service()
+class Router {
+ async build() {
+ const router = ERouter();
+
+ router.use(json());
+
+ await Promise.resolve(42);
+
+ router.post(
+ '/new-account',
+ asyncHandler(async (_req, res, _next) => {
+ await Promise.resolve(42);
+ res.sendStatus(StatusCode.NOT_IMPLEMENTED);
+ })
+ );
+
+ router.post(
+ '/login',
+ asyncHandler(async (_req, res, _next) => {
+ await Promise.resolve(42);
+ res.sendStatus(StatusCode.NOT_IMPLEMENTED);
+ })
+ );
+
+ const app = express();
+ app.use('/', router);
+ return app;
+ }
+}
+
+export { Router };
diff --git a/server/src/ws.ts b/server/src/ws.ts
new file mode 100644
index 0000000..f75260a
--- /dev/null
+++ b/server/src/ws.ts
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import { IncomingMessage } from 'node:http';
+import { Duplex } from 'node:stream';
+
+import { Service } from 'typedi';
+import { WebSocket, WebSocketServer } from 'ws';
+
+@Service()
+class Ws {
+ async build() {
+ await Promise.resolve(42);
+
+ const wss = new WebSocketServer({
+ noServer: true,
+ });
+ wss.on('connection', (ws: WebSocket, _req: IncomingMessage, id: string) => {
+ ws.on('message', (data) => {
+ ws.send(
+ JSON.stringify({
+ id,
+ data,
+ })
+ );
+ });
+ });
+
+ return (request: IncomingMessage, socket: Duplex, head: Buffer) => {
+ wss.handleUpgrade(request, socket, head, (ws) => wss.emit('connection', ws, request, '42'));
+ };
+ }
+}
+
+export { Ws };