summary refs log tree commit diff
path: root/util/src/util/Database.ts
diff options
context:
space:
mode:
Diffstat (limited to 'util/src/util/Database.ts')
-rw-r--r--util/src/util/Database.ts106
1 files changed, 67 insertions, 39 deletions
diff --git a/util/src/util/Database.ts b/util/src/util/Database.ts
index 31a30561..c4693108 100644
--- a/util/src/util/Database.ts
+++ b/util/src/util/Database.ts
@@ -1,72 +1,100 @@
 import path from "path";
 import "reflect-metadata";
-import { DataSource, createConnection } from "typeorm";
+import { DataSource, createConnection, DataSourceOptions, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm";
 import * as Models from "../entities";
 import { Migration } from "../entities/Migration";
 import { yellow, green, red } from "picocolors";
+import fs from "fs";
+import { exit } from "process";
+import { BaseClass, BaseClassWithoutId } from "../entities";
 
 // UUID extension option is only supported with postgres
 // We want to generate all id's with Snowflakes that's why we have our own BaseEntity class
 
 let promise: Promise<any>;
-let dataSource: DataSource | undefined;
-let dbConnectionString = process.env.DATABASE || path.join(process.cwd(), "database.db");
-let verbose_db = false;
+let dataSource: DataSource;
+
+
 
 export async function initDatabase(): Promise<DataSource> {
 	if (dataSource) return dataSource; // prevent initalizing multiple times
 
+	let dso = getDataSourceOptions();
+	console.log(`[Database] ${yellow(`Connecting to ${dso.type} database...`)}`);
+
+	//promise = dataSource.initialize();
+	//await promise;
+
+	console.log(`[Database] ${green("Connected!")}`);
+
+	return promise;
+}
+
+
+export function closeDatabase() {
+	dataSource?.destroy();
+}
+
+function getDataSourceOptions(): DataSourceOptions {
+	//get connection string and check for migrations
+	const dbConnectionString = process.env.DATABASE || path.join(process.cwd(), "database.db");
 	const type = dbConnectionString.includes("://") ? dbConnectionString.split(":")[0]?.replace("+srv", "") : "sqlite" as any;
 	const isSqlite = type.includes("sqlite");
-	if(process.env.DB_VERBOSE) verbose_db = true;
-
-	console.log(`[Database] ${yellow(`connecting to ${type} db`)}`);
+	const migrationsExist = fs.existsSync(path.join(__dirname, "..", "migrations", type));
+	//read env vars
+	const synchronizeInsteadOfMigrations = "DB_UNSAFE" in process.env;
+	const verboseDb = "DB_VERBOSE" in process.env;
+	
 	if(isSqlite)
 		console.log(`[Database] ${red(`You are running sqlite! Please keep in mind that we recommend setting up a dedicated database!`)}`);
-	if(verbose_db)
-		console.log(`[Database] ${red(`Verbose database logging is enabled, this might impact performance! Unset VERBOSE_DB to disable.`)}`);
-	// @ts-ignore
-	dataSource = new DataSource({
+	if(verboseDb)
+		console.log(`[Database] ${red(`Verbose database logging is enabled, this might impact performance! Unset DB_VERBOSE to disable.`)}`);
+
+	if(synchronizeInsteadOfMigrations){
+		console.log(`[Database] ${red(`Unsafe database upgrades are enabled! We are not responsible for broken databases! Unset DB_UNSAFE to disable.`)}`);
+	}
+	else if(!migrationsExist) {
+		console.log(`[Database] ${red(`Database engine not supported! Set UNSAFE_DB to bypass.`)}`);
+		console.log(`[Database] ${red(`Please mention this to Fosscord developers, and provide this info:`)}`);
+		console.log(`[Database]\n${red(JSON.stringify({
+			db_type: type,
+			migrations_exist: migrationsExist
+		}, null, 4))}`);
+
+		//exit(1);
+	}
+	
+	return {
 		type,
         charset: 'utf8mb4',
 		url: isSqlite ? undefined : dbConnectionString,
 		database: isSqlite ? dbConnectionString : undefined,
 		// @ts-ignore
-		entities: Object.values(Models).filter((x) => x.constructor.name !== "Object" && x.name),
-		synchronize: type !== "mongodb",
-		logging: verbose_db,
+		//entities: Object.values(Models).filter((x) => x.constructor.name !== "Object" && x.constructor.name !== "Array" && x.constructor.name !== "BigInt" && x).map(x=>x.name),
+		entities: Object.values(Models).filter((x) => x.constructor.name == "Function" && shouldIncludeEntity(x.name)),
+		synchronize: synchronizeInsteadOfMigrations,
+		logging: verboseDb,
 		cache: {
 			duration: 1000 * 3, // cache all find queries for 3 seconds
 		},
 		bigNumberStrings: false,
 		supportBigNumbers: true,
 		name: "default",
-		migrations: [path.join(__dirname, "..", "migrations", "*.js")],
-	});
-	promise = dataSource.initialize();
-	await promise;
-	// run migrations, and if it is a new fresh database, set it to the last migration
-	if (dataSource.migrations.length) {
-		if (!(await Migration.findOne({}))) {
-			let i = 0;
-
-			await Migration.insert(
-				dataSource.migrations.map((x) => ({
-					id: i++,
-					name: x.name,
-					timestamp: Date.now(),
-				}))
-			);
+		migrations: synchronizeInsteadOfMigrations ? [] : [path.join(__dirname, "..", "migrations", type, "*.js")],
+		migrationsRun: !synchronizeInsteadOfMigrations,
+		cli: {
+			migrationsDir: `src/migrations/${type}`
 		}
-	}
-	await dataSource.runMigrations();
-	console.log(`[Database] ${green("connected")}`);
-
-	return promise;
+	} as DataSourceOptions;
 }
 
-export { dataSource };
-
-export function closeDatabase() {
-	dataSource?.destroy();
+function shouldIncludeEntity(name: string): boolean {
+	return ![ 
+		BaseClassWithoutId,
+		PrimaryColumn,
+		BaseClass,
+		PrimaryGeneratedColumn
+	].map(x=>x.name).includes(name);
 }
+
+export default dataSource = new DataSource(getDataSourceOptions());
\ No newline at end of file