Switch backend to node, docker likely broken

main
Talon 2024-08-26 00:18:00 +02:00
parent bf1719d75d
commit b487ab82cc
15 changed files with 7101 additions and 56 deletions

Binary file not shown.

BIN
backend/db.sqlite 100644

Binary file not shown.

View File

@ -2,7 +2,8 @@ CREATE TABLE IF NOT EXISTS channels (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, name TEXT NOT NULL,
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP createdAt DATETIME DEFAULT CURRENT_TIMESTAMP
) CREATE TABLE IF NOT EXISTS files ( );
CREATE TABLE IF NOT EXISTS files (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
channelId INTEGER, channelId INTEGER,
filePath TEXT, filePath TEXT,
@ -11,7 +12,8 @@ CREATE TABLE IF NOT EXISTS channels (
originalName TEXT, originalName TEXT,
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP, createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (channelId) REFERENCES channels (id) ON DELETE CASCADE FOREIGN KEY (channelId) REFERENCES channels (id) ON DELETE CASCADE
) CREATE TABLE IF NOT EXISTS messages ( );
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
channelId INTEGER, channelId INTEGER,
content TEXT, content TEXT,
@ -21,7 +23,8 @@ CREATE TABLE IF NOT EXISTS channels (
FOREIGN KEY (fileId) REFERENCES files (id) ON DELETE FOREIGN KEY (fileId) REFERENCES files (id) ON DELETE
SET SET
NULL NULL
) CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5( );
CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
content, content,
content = 'messages', content = 'messages',
content_rowid = 'id' content_rowid = 'id'

2084
backend/package-lock.json generated 100644

File diff suppressed because it is too large Load Diff

View File

@ -3,8 +3,8 @@
"module": "src/server.ts", "module": "src/server.ts",
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "bun run src/server.ts", "start": "tsx src/server.ts",
"dev": "bun run --watch src/server.ts", "dev": "tsx --watch src/server.ts",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"devDependencies": { "devDependencies": {
@ -20,12 +20,15 @@
"@types/jsonwebtoken": "^9.0.6", "@types/jsonwebtoken": "^9.0.6",
"@types/multer": "^1.4.11", "@types/multer": "^1.4.11",
"@types/ws": "^8.5.12", "@types/ws": "^8.5.12",
"better-sqlite3": "^11.2.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2", "express": "^4.19.2",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"ollama": "^0.5.8", "ollama": "^0.5.8",
"openai": "^4.56.0", "openai": "^4.56.0",
"sharp": "^0.33.5", "sharp": "^0.33.5",
"tsx": "^4.18.0",
"ws": "^8.18.0" "ws": "^8.18.0"
} }
} }

View File

@ -1,3 +1,6 @@
import dotenv from "dotenv";
dotenv.config();
export const DB_PATH = process.env["DB_PATH"] || "/usr/src/app/data/db.sqlite"; export const DB_PATH = process.env["DB_PATH"] || "/usr/src/app/data/db.sqlite";
export const SECRET_KEY = process.env["API_TOKEN"] || ""; export const SECRET_KEY = process.env["API_TOKEN"] || "";
export const UPLOAD_DIR = process.env["UPLOAD_DIR"] || "/usr/src/app/data/uploads/"; export const UPLOAD_DIR = process.env["UPLOAD_DIR"] || "/usr/src/app/data/uploads/";

View File

@ -1,14 +1,14 @@
import { Database } from 'bun:sqlite'; import Database from 'better-sqlite3';
import { DB_PATH } from './config'; import { DB_PATH } from './config';
import { logger } from './globals'; import { logger } from './globals';
import { readdir, readFile } from "fs/promises"; import { readdir, readFile } from "fs/promises";
import { join } from "path"; import { join, dirname } from "path";
export let FTS5Enabled = true; export let FTS5Enabled = true;
export const initializeDB = () => { export const initializeDB = () => {
logger.info("Checking fts"); logger.info("Checking fts");
const ftstest = db.query(`pragma compile_options;`); const ftstest = db.prepare(`pragma compile_options;`);
const result = ftstest.all() as { compile_options: string }[]; const result = ftstest.all() as { compile_options: string }[];
if (result.find((o) => o["compile_options"].includes("ENABLE_FTS5"))) { if (result.find((o) => o["compile_options"].includes("ENABLE_FTS5"))) {
logger.info("FTS5 is enabled"); logger.info("FTS5 is enabled");
@ -27,28 +27,28 @@ export const initializeDB = () => {
export const migrate = async () => { export const migrate = async () => {
logger.info(`Checking for migrations...`); logger.info(`Checking for migrations...`);
const result = db.query(`SELECT name FROM sqlite_master WHERE type='table' AND name='meta'`); const result = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='meta'`);
if (result.all().length === 0) { if (result.all().length === 0) {
logger.info(`Creating meta table...`); logger.info(`Creating meta table...`);
db.run(`CREATE TABLE meta (version INTEGER)`); db.exec(`CREATE TABLE meta (version INTEGER)`);
db.run(`INSERT INTO meta (version) VALUES (0)`); db.exec(`INSERT INTO meta (version) VALUES (-1)`);
} }
const version = db.query(`SELECT version FROM meta`).get() as { version: number }; const version = db.prepare(`SELECT version FROM meta`).get() as { version: number };
logger.info(`Migration version: ${version.version}`); logger.info(`Migration version: ${version.version}`);
// we are in bun.js. use its API's to read the file list. // we are in bun.js. use its API's to read the file list.
logger.info(`Searching for migrations in ${join(__dirname, "migrations")}`); logger.info(`Searching for migrations in ${join("migrations")}`);
const files = await readdir(join(__dirname, "migrations")); const files = await readdir(join("migrations"));
for (const file of files) { for (const file of files) {
const [fileVersion, ...rest] = file.split("_"); const [fileVersion, ...rest] = file.split("_");
logger.info(`Found migration ${fileVersion}`); logger.info(`Found migration ${fileVersion}`);
if (fileVersion && Number(fileVersion) > version.version) { if (fileVersion && Number(fileVersion) > version.version) {
logger.info(`Running migration ${file}`); logger.info(`Running migration ${file}`);
const sql = new TextDecoder().decode(await readFile(join(__dirname, `migrations/${file}`))); const sql = new TextDecoder().decode(await readFile(join(`migrations/${file}`)));
db.run(sql); db.exec(sql);
const query = db.query(`UPDATE meta SET version = $version`); const query = db.prepare(`UPDATE meta SET version = ($version)`);
const res = query.run({ $version: Number(fileVersion) }) const res = query.run({ version: fileVersion })
logger.info(`Migration ${file} done`); logger.info(`Migration ${file} done`);
} }
} }
@ -60,5 +60,8 @@ logger.info(`Loading database at ${DB_PATH}`);
export const db = new Database(DB_PATH); export const db = new Database(DB_PATH);
initializeDB(); initializeDB();
migrate(); migrate();

View File

@ -4,6 +4,6 @@ import { db } from "../db";
export const scheduleVacuum = () => { export const scheduleVacuum = () => {
scheduler.register('vacuum', () => { scheduler.register('vacuum', () => {
db.query('VACUUM'); db.exec('VACUUM');
}, 1, TimeUnit.DAY); }, 1, TimeUnit.DAY);
} }

View File

@ -2,36 +2,36 @@ import { db } from "../db";
import { events } from "../globals"; import { events } from "../globals";
export const createChannel = async (name: string) => { export const createChannel = async (name: string) => {
const query = db.query(`INSERT INTO channels (name) VALUES ($name)`); const query = db.prepare(`INSERT INTO channels (name) VALUES ($name)`);
const result = query.run({ $name: name }); const result = query.run({ name: name });
events.emit('channel-created', { id: result.lastInsertRowid, name }); events.emit('channel-created', { id: result.lastInsertRowid, name });
return { id: result.lastInsertRowid, name }; return { id: result.lastInsertRowid, name };
} }
export const deleteChannel = async (id: string) => { export const deleteChannel = async (id: string) => {
const query = db.query(`DELETE FROM channels WHERE id = ($channelId)`); const query = db.prepare(`DELETE FROM channels WHERE id = ($channelId)`);
const result = query.run({$channelId: id}); const result = query.run({channelId: id});
// No need to manually delete messages and files as they are set to cascade on delete in the schema // No need to manually delete messages and files as they are set to cascade on delete in the schema
events .emit('channel-deleted', id); events .emit('channel-deleted', id);
return result; return result;
} }
export const getChannels = async () => { export const getChannels = async () => {
const query = db.query(`SELECT * FROM channels`); const query = db.prepare(`SELECT * FROM channels`);
const rows = query.all(); const rows = query.all();
return rows; return rows;
} }
export const mergeChannel = async (channelId: string, targetChannelId: string) => { export const mergeChannel = async (channelId: string, targetChannelId: string) => {
const query = db.query(`UPDATE messages SET channelId = $targetChannelId WHERE channelId = $channelId`); const query = db.prepare(`UPDATE messages SET channelId = $targetChannelId WHERE channelId = $channelId`);
const result = query.run({ $channelId: channelId, $targetChannelId: targetChannelId }); const result = query.run({ channelId: channelId, targetChannelId: targetChannelId });
events.emit('channel-merged', channelId, targetChannelId); events.emit('channel-merged', channelId, targetChannelId);
return result; return result;
} }
export const updateChannel = async (id: string, name: string) => { export const updateChannel = async (id: string, name: string) => {
const query = db.query(`UPDATE channels SET name = $name WHERE id = $id`); const query = db.prepare(`UPDATE channels SET name = $name WHERE id = $id`);
const result = query.run({ $id: id, $name: name }); const result = query.run({ id: id, name: name });
events.emit('channel-updated', id, name); events.emit('channel-updated', id, name);
return result; return result;
} }

View File

@ -2,20 +2,20 @@ import { db } from "../db";
import { events } from "../globals"; import { events } from "../globals";
export const uploadFile = async (channelId: string, messageId: string, filePath: string, fileType: string, fileSize: number, originalName: string) => { export const uploadFile = async (channelId: string, messageId: string, filePath: string, fileType: string, fileSize: number, originalName: string) => {
const query = db.query(`INSERT INTO files (channelId, filePath, fileType, fileSize, originalName) VALUES ($channelId, $filePath, $fileType, $fileSize, $originalName)`); const query = db.prepare(`INSERT INTO files (channelId, filePath, fileType, fileSize, originalName) VALUES ($channelId, $filePath, $fileType, $fileSize, $originalName)`);
const result = query.run({ $channelId: channelId, $filePath: filePath, $fileType: fileType, $fileSize: fileSize, $originalName: originalName }); const result = query.run({ channelId: channelId, filePath: filePath, fileType: fileType, fileSize: fileSize, originalName: originalName });
const fileId = result.lastInsertRowid; const fileId = result.lastInsertRowid;
const updateQuery = db.query(`UPDATE messages SET fileId = $fileId WHERE id = $messageId`); const updateQuery = db.prepare(`UPDATE messages SET fileId = $fileId WHERE id = $messageId`);
const result2 = updateQuery.run({ $fileId: fileId, $messageId: messageId }); const result2 = updateQuery.run({ fileId: fileId, messageId: messageId });
events.emit('file-uploaded', result.lastInsertRowid, channelId, messageId, filePath, fileType, fileSize, originalName); events.emit('file-uploaded', result.lastInsertRowid, channelId, messageId, filePath, fileType, fileSize, originalName);
return result2; '' return result2; ''
} }
export const getFiles = async (messageId: string) => { export const getFiles = async (messageId: string) => {
const query = db.query(`SELECT * FROM files WHERE messageId = $messageId`); const query = db.prepare(`SELECT * FROM files WHERE messageId = $messageId`);
const rows = query.all({ $messageId: messageId }); const rows = query.all({ messageId: messageId });
return rows; return rows;
} }

View File

@ -2,15 +2,15 @@ import { db, FTS5Enabled } from "../db";
import { events } from "../globals"; import { events } from "../globals";
export const createMessage = async (channelId: string, content: string) => { export const createMessage = async (channelId: string, content: string) => {
const query = db.query(`INSERT INTO messages (channelId, content) VALUES ($channelId, $content)`); const query = db.prepare(`INSERT INTO messages (channelId, content) VALUES ($channelId, $content)`);
const result = query.run({ $channelId: channelId, $content: content }); const result = query.run({ channelId: channelId, content: content });
const messageId = result.lastInsertRowid; const messageId = result.lastInsertRowid;
console.log(`Adding message for search with id ${messageId}`); console.log(`Adding message for search with id ${messageId}`);
// Insert into FTS table if FTS is enabled. // Insert into FTS table if FTS is enabled.
if (FTS5Enabled) { if (FTS5Enabled) {
const query2 = db.query(`INSERT INTO messages_fts (rowid, content) VALUES ($rowId, $content)`); const query2 = db.prepare(`INSERT INTO messages_fts (rowid, content) VALUES ($rowId, $content)`);
const result2 = query2.run({ $rowId: messageId, $content: content }); const result2 = query2.run({ rowId: messageId, content: content });
} }
events.emit('message-created', messageId, channelId, content); events.emit('message-created', messageId, channelId, content);
@ -18,36 +18,36 @@ export const createMessage = async (channelId: string, content: string) => {
} }
export const updateMessage = async (messageId: string, content: string, append: boolean = false) => { export const updateMessage = async (messageId: string, content: string, append: boolean = false) => {
const query = db.query(`UPDATE messages SET content = $content WHERE id = $id`); const query = db.prepare(`UPDATE messages SET content = $content WHERE id = $id`);
const result = query.run({ $content: content, $id: messageId }); const result = query.run({ content: content, id: messageId });
// Update FTS table if enabled // Update FTS table if enabled
if (!FTS5Enabled) { if (!FTS5Enabled) {
const query2 = db.query(`INSERT INTO messages_fts (rowid, content) VALUES ($rowId, $content) ON CONFLICT(rowid) DO UPDATE SET content = excluded.content`); const query2 = db.prepare(`INSERT INTO messages_fts (rowid, content) VALUES ($rowId, $content) ON CONFLICT(rowid) DO UPDATE SET content = excluded.content`);
const result2 = query.run({ $rowId: messageId, $content: content }); const result2 = query.run({ rowId: messageId, content: content });
} }
events.emit('message-updated', messageId, content); events.emit('message-updated', messageId, content);
return result; return result;
} }
export const deleteMessage = async (messageId: string) => { export const deleteMessage = async (messageId: string) => {
const query = db.query(`DELETE FROM messages WHERE id = $id`); const query = db.prepare(`DELETE FROM messages WHERE id = $id`);
const result = query.run({ $id: messageId }); const result = query.run({ id: messageId });
// Remove from FTS table if enabled // Remove from FTS table if enabled
if (FTS5Enabled) { if (FTS5Enabled) {
const query2 = db.query(`DELETE FROM messages_fts WHERE rowid = $rowId`); const query2 = db.prepare(`DELETE FROM messages_fts WHERE rowid = $rowId`);
const result2 = query.run({ $rowId: messageId }); const result2 = query.run({ rowId: messageId });
} }
events.emit('message-deleted', messageId); events.emit('message-deleted', messageId);
return result; return result;
} }
export const getMessages = async (channelId: string) => { export const getMessages = async (channelId: string) => {
const query = db.query(` const query = db.prepare(`
SELECT SELECT
messages.id, messages.channelId, messages.content, messages.createdAt, messages.id, messages.channelId, messages.content, messages.createdAt,
files.id as fileId, files.filePath, files.fileType, files.createdAt as fileCreatedAt, files.originalName, files.fileSize files.id as fileId, files.filePath, files.fileType, files.createdAt as fileCreatedAt, files.originalName, files.fileSize
@ -60,12 +60,12 @@ export const getMessages = async (channelId: string) => {
WHERE WHERE
messages.channelId = $channelId messages.channelId = $channelId
`); `);
const rows = query.all({ $channelId: channelId }); const rows = query.all({ channelId: channelId });
return rows; return rows;
} }
export const getMessage = async (id: string) => { export const getMessage = async (id: string) => {
const query = db.query(` const query = db.prepare(`
SELECT SELECT
messages.id, messages.channelId, messages.content, messages.createdAt, messages.id, messages.channelId, messages.content, messages.createdAt,
files.id as fileId, files.filePath, files.fileType, files.createdAt as fileCreatedAt, files.originalName, files.fileSize files.id as fileId, files.filePath, files.fileType, files.createdAt as fileCreatedAt, files.originalName, files.fileSize
@ -78,6 +78,6 @@ export const getMessage = async (id: string) => {
WHERE WHERE
messages.id = $id messages.id = $id
`); `);
const row = query.get({ $id: id }); const row = query.get({ id: id });
return row; return row;
} }

View File

@ -12,7 +12,7 @@ export const search = async (query: string, channelId?: string) => {
JOIN messages ON messages_fts.rowid = messages.id JOIN messages ON messages_fts.rowid = messages.id
WHERE messages_fts MATCH lower($query) AND messages.channelId = $channelId WHERE messages_fts MATCH lower($query) AND messages.channelId = $channelId
`; `;
params = { $channelId: channelId, $query: (query || '').toString().toLowerCase() }; params = { channelId: channelId, query: (query || '').toString().toLowerCase() };
} else { } else {
sql = ` sql = `
SELECT messages.id, messages.channelId, messages.content, messages.createdAt SELECT messages.id, messages.channelId, messages.content, messages.createdAt
@ -20,7 +20,7 @@ export const search = async (query: string, channelId?: string) => {
JOIN messages ON messages_fts.rowid = messages.id JOIN messages ON messages_fts.rowid = messages.id
WHERE messages_fts MATCH lower($query) WHERE messages_fts MATCH lower($query)
`; `;
params = { $query: (query || '').toString().toLowerCase() }; params = { query: (query || '').toString().toLowerCase() };
} }
} else { } else {
console.log("Performing search without FTS5. This might be very slow."); console.log("Performing search without FTS5. This might be very slow.");
@ -28,16 +28,16 @@ export const search = async (query: string, channelId?: string) => {
sql = ` sql = `
SELECT * FROM messages WHERE LOWER(content) LIKE '%' || LOWER($query) || '%' AND channelId = $channelId SELECT * FROM messages WHERE LOWER(content) LIKE '%' || LOWER($query) || '%' AND channelId = $channelId
`; `;
params = { $channelId: channelId, $query: query }; params = { channelId: channelId, query: query };
} else { } else {
sql = ` sql = `
SELECT * FROM messages WHERE LOWER(content) LIKE '%' || LOWER($query) || '%' SELECT * FROM messages WHERE LOWER(content) LIKE '%' || LOWER($query) || '%'
`; `;
params = { $query: query }; params = { query: query };
} }
} }
const sqlquery = db.query(sql); const sqlquery = db.prepare(sql);
const rows = sqlquery.all(params); const rows = sqlquery.all(params);
return rows; return rows;

Binary file not shown.

4949
frontend/package-lock.json generated 100644

File diff suppressed because it is too large Load Diff