Adds initial progress

This commit is contained in:
Michel Fedde 2025-03-28 23:19:54 +01:00
commit a0b668cb90
34 changed files with 2680 additions and 0 deletions

View file

@ -0,0 +1,44 @@
import {DatabaseEnvironment, Environment} from "../Environment";
import Sqlite3 from "better-sqlite3";
import Database from "better-sqlite3";
import {Container} from "../Container/Container";
import {Logger} from "log4js";
export class DatabaseConnection {
private static connection: DatabaseConnection;
private database: Sqlite3.Database;
constructor(private readonly env: DatabaseEnvironment) {
this.database = new Database(env.path, {
nativeBinding: "node_modules/better-sqlite3/build/Release/better_sqlite3.node",
})
this.database.pragma('journal_mode = WAL');
}
public execute(query: string, ...args: any[]): Sqlite3.RunResult {
try {
const preparedQuery = this.database.prepare(query);
return preparedQuery.run(args);
} catch (error) {
Container.get<Logger>("logger").error("Failed to execute database connection", error, query, args);
throw error;
}
}
public fetch<BindParameters extends unknown[] | {} = unknown[], Result = unknown>(query: string, ...args: any[]): Result|undefined {
const preparedQuery = this.database.prepare<BindParameters, Result>(query);
return preparedQuery.get(args);
}
public fetchAll<BindParameters extends unknown[] | {} = unknown[], Result = unknown>(query: string, ...args: any[]): Result[] {
const preparedQuery = this.database.prepare<BindParameters, Result>(query);
return preparedQuery.all(args);
}
public hasTable(tableName: string): boolean {
const sql = "SELECT COUNT(*) as tableCount FROM sqlite_master WHERE type='table' AND name=? LIMIT 1;";
const result = this.fetch<string[], {tableCount: number}>(sql, tableName);
return result != undefined && result.tableCount > 0;
}
}

View file

@ -0,0 +1,13 @@
export type DatabaseColumnDefinition = {
name: string;
type: string;
primaryKey?: boolean;
autoIncrement?: boolean;
notNull?: boolean;
options?: string;
}
export type DatabaseDefinition = {
name: string;
columns: DatabaseColumnDefinition[];
}

View file

@ -0,0 +1,81 @@
import {DatabaseConnection} from "./DatabaseConnection";
import {DatabaseColumnDefinition, DatabaseDefinition} from "./DatabaseDefinition";
import {Container} from "../Container/Container";
import {Logger} from "log4js";
export class DatabaseUpdater {
constructor(private readonly database: DatabaseConnection) {}
public ensureAvaliablity(definitions: Iterable<DatabaseDefinition>) {
for (const definition of definitions) {
this.ensureDefinition(definition);
}
}
private ensureDefinition(definition: DatabaseDefinition) {
if (this.database.hasTable(definition.name)) {
this.ensureTableColumns(definition);
return;
}
this.createTable(definition);
}
private ensureTableColumns(definition: DatabaseDefinition) {
const DBSQLColumns = this.database.fetchAll<{}, {name: string, type: string}>(
`PRAGMA table_info("${definition.name}")`
);
if (!DBSQLColumns) {
Container.get<Logger>("logger").log("Request failed...");
return;
}
const missingColumns = definition.columns.filter(
(column: DatabaseColumnDefinition) => {
return !DBSQLColumns.some((dbColumn: DatabaseColumnDefinition) => {
return column.name === dbColumn.name
});
}
)
if (missingColumns.length < 1) {
Container.get<Logger>("logger").log(`No new columns found for ${definition.name}`)
return;
}
const columnsSQL = missingColumns.map((column: DatabaseColumnDefinition) => {
const values = [
"ADD",
column.name,
column.type,
column.primaryKey ? `PRIMARY KEY` : '',
column.notNull ? 'NOT NULL' : '',
column.autoIncrement ? 'AUTOINCREMENT' : '',
]
return values.join(' ');
}).join(', ');
const sql = `ALTER TABLE ${definition.name} ${columnsSQL}`;
this.database.execute(sql);
}
private createTable(definition: DatabaseDefinition) {
const columnsSQL = definition.columns.map((column: DatabaseColumnDefinition) => {
const values = [
column.name,
column.type,
column.primaryKey ? `PRIMARY KEY` : '',
column.notNull ? 'NOT NULL' : '',
column.autoIncrement ? 'AUTOINCREMENT' : '',
]
return values.join(' ');
}).join(', ');
const sql = `CREATE TABLE IF NOT EXISTS ${definition.name} (${columnsSQL})`;
const result = this.database.execute(sql);
}
}

View file

@ -0,0 +1,10 @@
import Groups from "./tables/Groups";
import {DatabaseDefinition} from "./DatabaseDefinition";
import Playdate from "./tables/Playdate";
const definitions = new Set<DatabaseDefinition>([
Groups,
Playdate
]);
export default definitions;

View file

@ -0,0 +1,39 @@
import {DatabaseDefinition} from "../DatabaseDefinition";
export type DBGroup = {
id: number;
name: string;
server: string;
leader: string;
role: string;
}
const dbDefinition: DatabaseDefinition = {
name: "groups",
columns: [
{
name: "id",
type: "INTEGER",
autoIncrement: true,
primaryKey: true,
},
{
name: "server",
type: "VARCHAR(32)"
},
{
name: "name",
type: "VARCHAR(32)",
},
{
name: "leader",
type: "VARCHAR(32)",
},
{
name: "role",
type: "VARCHAR(32)",
}
]
}
export default dbDefinition;

View file

@ -0,0 +1,35 @@
import {DatabaseDefinition} from "../DatabaseDefinition";
export type DBPlaydate = {
id: number;
groupid: number;
time_from: number;
time_to: number;
}
const dbDefinition: DatabaseDefinition = {
name: "playdates",
columns: [
{
name: "id",
type: "INTEGER",
autoIncrement: true,
primaryKey: true,
},
{
name: "groupid",
type: "INTEGER",
},
{
name: "time_from",
type: "TIMESTAMP",
},
{
name: "time_to",
type: "TIMESTAMP",
}
]
}
export default dbDefinition;