summaryrefslogtreecommitdiffstats
path: root/server/utils
diff options
context:
space:
mode:
Diffstat (limited to 'server/utils')
-rw-r--r--server/utils/array-with-key.js85
-rw-r--r--server/utils/knex/lib/dialects/mysql2/schema/mysql2-columncompiler.js22
-rw-r--r--server/utils/limit-queue.js48
-rw-r--r--server/utils/simple-migration-server.js84
4 files changed, 239 insertions, 0 deletions
diff --git a/server/utils/array-with-key.js b/server/utils/array-with-key.js
new file mode 100644
index 0000000..94afc79
--- /dev/null
+++ b/server/utils/array-with-key.js
@@ -0,0 +1,85 @@
+/**
+ * An object that can be used as an array with a key
+ * Like PHP's array
+ * @template K
+ * @template V
+ */
+class ArrayWithKey {
+ /**
+ * All keys that are stored in the current object
+ * @type {K[]}
+ * @private
+ */
+ __stack = [];
+
+ /**
+ * Push an element to the end of the array
+ * @param {K} key The key of the element
+ * @param {V} value The value of the element
+ * @returns {void}
+ */
+ push(key, value) {
+ this[key] = value;
+ this.__stack.push(key);
+ }
+
+ /**
+ * Get the last element and remove it from the array
+ * @returns {V|undefined} The first value, or undefined if there is no element to pop
+ */
+ pop() {
+ let key = this.__stack.pop();
+ let prop = this[key];
+ delete this[key];
+ return prop;
+ }
+
+ /**
+ * Get the last key
+ * @returns {K|null} The last key, or null if the array is empty
+ */
+ getLastKey() {
+ if (this.__stack.length === 0) {
+ return null;
+ }
+ return this.__stack[this.__stack.length - 1];
+ }
+
+ /**
+ * Get the first element
+ * @returns {{key:K,value:V}|null} The first element, or null if the array is empty
+ */
+ shift() {
+ let key = this.__stack.shift();
+ let value = this[key];
+ delete this[key];
+ return {
+ key,
+ value,
+ };
+ }
+
+ /**
+ * Get the length of the array
+ * @returns {number} Amount of elements stored
+ */
+ length() {
+ return this.__stack.length;
+ }
+
+ /**
+ * Get the last value
+ * @returns {V|null} The last element without removing it, or null if the array is empty
+ */
+ last() {
+ let key = this.getLastKey();
+ if (key === null) {
+ return null;
+ }
+ return this[key];
+ }
+}
+
+module.exports = {
+ ArrayWithKey
+};
diff --git a/server/utils/knex/lib/dialects/mysql2/schema/mysql2-columncompiler.js b/server/utils/knex/lib/dialects/mysql2/schema/mysql2-columncompiler.js
new file mode 100644
index 0000000..d05a6bc
--- /dev/null
+++ b/server/utils/knex/lib/dialects/mysql2/schema/mysql2-columncompiler.js
@@ -0,0 +1,22 @@
+const ColumnCompilerMySQL = require("knex/lib/dialects/mysql/schema/mysql-columncompiler");
+const { formatDefault } = require("knex/lib/formatter/formatterUtils");
+const { log } = require("../../../../../../../src/util");
+
+class KumaColumnCompiler extends ColumnCompilerMySQL {
+ /**
+ * Override defaultTo method to handle default value for TEXT fields
+ * @param {any} value Value
+ * @returns {string|void} Default value (Don't understand why it can return void or string, but it's the original code, lol)
+ */
+ defaultTo(value) {
+ if (this.type === "text" && typeof value === "string") {
+ log.debug("defaultTo", `${this.args[0]}: ${this.type} ${value} ${typeof value}`);
+ // MySQL 8.0 is required and only if the value is written as an expression: https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html
+ // MariaDB 10.2 is required: https://mariadb.com/kb/en/text/
+ return `default (${formatDefault(value, this.type, this.client)})`;
+ }
+ return super.defaultTo.apply(this, arguments);
+ }
+}
+
+module.exports = KumaColumnCompiler;
diff --git a/server/utils/limit-queue.js b/server/utils/limit-queue.js
new file mode 100644
index 0000000..9da6d41
--- /dev/null
+++ b/server/utils/limit-queue.js
@@ -0,0 +1,48 @@
+const { ArrayWithKey } = require("./array-with-key");
+
+/**
+ * Limit Queue
+ * The first element will be removed when the length exceeds the limit
+ */
+class LimitQueue extends ArrayWithKey {
+
+ /**
+ * The limit of the queue after which the first element will be removed
+ * @private
+ * @type {number}
+ */
+ __limit;
+ /**
+ * The callback function when the queue exceeds the limit
+ * @private
+ * @callback onExceedCallback
+ * @param {{key:K,value:V}|nul} item
+ */
+ __onExceed = null;
+
+ /**
+ * @param {number} limit The limit of the queue after which the first element will be removed
+ */
+ constructor(limit) {
+ super();
+ this.__limit = limit;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ push(key, value) {
+ super.push(key, value);
+ if (this.length() > this.__limit) {
+ let item = this.shift();
+ if (this.__onExceed) {
+ this.__onExceed(item);
+ }
+ }
+ }
+
+}
+
+module.exports = {
+ LimitQueue
+};
diff --git a/server/utils/simple-migration-server.js b/server/utils/simple-migration-server.js
new file mode 100644
index 0000000..680f8df
--- /dev/null
+++ b/server/utils/simple-migration-server.js
@@ -0,0 +1,84 @@
+const express = require("express");
+const http = require("node:http");
+const { log } = require("../../src/util");
+
+/**
+ * SimpleMigrationServer
+ * For displaying the migration status of the server
+ * Also, it is used to let Docker healthcheck know the status of the server, as the main server is not started yet, healthcheck will think the server is down incorrectly.
+ */
+class SimpleMigrationServer {
+ /**
+ * Express app instance
+ * @type {?Express}
+ */
+ app;
+
+ /**
+ * Server instance
+ * @type {?Server}
+ */
+ server;
+
+ /**
+ * Response object
+ * @type {?Response}
+ */
+ response;
+
+ /**
+ * Start the server
+ * @param {number} port Port
+ * @param {string} hostname Hostname
+ * @returns {Promise<void>}
+ */
+ start(port, hostname) {
+ this.app = express();
+ this.server = http.createServer(this.app);
+
+ this.app.get("/", (req, res) => {
+ res.set("Content-Type", "text/plain");
+ res.write("Migration is in progress, listening message...\n");
+ if (this.response) {
+ this.response.write("Disconnected\n");
+ this.response.end();
+ }
+ this.response = res;
+ // never ending response
+ });
+
+ return new Promise((resolve) => {
+ this.server.listen(port, hostname, () => {
+ if (hostname) {
+ log.info("migration", `Migration server is running on http://${hostname}:${port}`);
+ } else {
+ log.info("migration", `Migration server is running on http://localhost:${port}`);
+ }
+ resolve();
+ });
+ });
+ }
+
+ /**
+ * Update the message
+ * @param {string} msg Message to update
+ * @returns {void}
+ */
+ update(msg) {
+ this.response?.write(msg + "\n");
+ }
+
+ /**
+ * Stop the server
+ * @returns {Promise<void>}
+ */
+ async stop() {
+ this.response?.write("Finished, please refresh this page.\n");
+ this.response?.end();
+ await this.server?.close();
+ }
+}
+
+module.exports = {
+ SimpleMigrationServer,
+};