This commit is contained in:
Michel Fedde 2025-06-18 22:53:54 +02:00
parent 441715675c
commit a79898b2e9
48 changed files with 2062 additions and 1503 deletions

View file

@ -5,38 +5,39 @@ import {Container} from "../Container/Container";
import {Logger} from "log4js";
export class DatabaseConnection {
private database: Sqlite3.Database;
constructor(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: unknown[]): 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;
}
}
private database: Sqlite3.Database;
public fetch<BindParameters extends unknown[] | {} = unknown[], Result = unknown>(query: string, ...args: unknown[]): 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: unknown[]): 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;
constructor(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: unknown[]): 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: unknown[]): 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: unknown[]): 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

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

View file

@ -4,78 +4,82 @@ 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<object, {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' : '',
]
constructor(private readonly database: DatabaseConnection) {
}
return values.join(' ');
}).join(', ');
const sql = `ALTER TABLE ${definition.name} ${columnsSQL}`;
this.database.execute(sql);
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 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})`;
this.database.execute(sql);
private ensureTableColumns(definition: DatabaseDefinition) {
const DBSQLColumns = this.database.fetchAll<object, { 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}
)`;
this.database.execute(sql);
}
}

View file

@ -4,9 +4,9 @@ import Playdate from "./tables/Playdate";
import GroupConfiguration from "./tables/GroupConfiguration";
const definitions = new Set<DatabaseDefinition>([
Groups,
Playdate,
GroupConfiguration
Groups,
Playdate,
GroupConfiguration
]);
export default definitions;

View file

@ -1,34 +1,34 @@
import {DatabaseDefinition} from "../DatabaseDefinition";
export type DBGroupConfiguration = {
id: number;
groupid: number;
key: string;
value: string;
id: number;
groupid: number;
key: string;
value: string;
}
const dbDefinition: DatabaseDefinition = {
name: "groupConfiguration",
columns: [
{
name: "id",
type: "INTEGER",
autoIncrement: true,
primaryKey: true,
},
{
name: "groupid",
type: "VARCHAR(32)",
},
{
name: "key",
type: "VARCHAR(32)",
},
{
name: "value",
type: "VARCHAR(128)",
}
]
name: "groupConfiguration",
columns: [
{
name: "id",
type: "INTEGER",
autoIncrement: true,
primaryKey: true,
},
{
name: "groupid",
type: "VARCHAR(32)",
},
{
name: "key",
type: "VARCHAR(32)",
},
{
name: "value",
type: "VARCHAR(128)",
}
]
}
export default dbDefinition;

View file

@ -1,39 +1,39 @@
import {DatabaseDefinition} from "../DatabaseDefinition";
export type DBGroup = {
id: number;
name: string;
server: string;
leader: string;
role: string;
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)",
}
]
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

@ -1,35 +1,35 @@
import {DatabaseDefinition} from "../DatabaseDefinition";
export type DBPlaydate = {
id: number;
groupid: number;
id: number;
groupid: number;
time_from: number;
time_to: 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",
}
]
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;