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 };