1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-04-21 12:16:59 +02:00
pigallery2/src/backend/model/database/SQLConnection.ts
2023-11-08 16:08:13 +01:00

292 lines
9.4 KiB
TypeScript

import 'reflect-metadata';
import {Connection, createConnection, DataSourceOptions, getConnection, LoggerOptions,} from 'typeorm';
import {UserEntity} from './enitites/UserEntity';
import {UserRoles} from '../../../common/entities/UserDTO';
import {PhotoEntity} from './enitites/PhotoEntity';
import {DirectoryEntity} from './enitites/DirectoryEntity';
import {Config} from '../../../common/config/private/Config';
import {SharingEntity} from './enitites/SharingEntity';
import {PasswordHelper} from '../PasswordHelper';
import {ProjectPath} from '../../ProjectPath';
import {VersionEntity} from './enitites/VersionEntity';
import {Logger} from '../../Logger';
import {MediaEntity} from './enitites/MediaEntity';
import {VideoEntity} from './enitites/VideoEntity';
import {DataStructureVersion} from '../../../common/DataStructureVersion';
import {FileEntity} from './enitites/FileEntity';
import {PersonEntry} from './enitites/PersonEntry';
import {Utils} from '../../../common/Utils';
import * as path from 'path';
import {DatabaseType, ServerDataBaseConfig, SQLLogLevel,} from '../../../common/config/private/PrivateConfig';
import {AlbumBaseEntity} from './enitites/album/AlbumBaseEntity';
import {SavedSearchEntity} from './enitites/album/SavedSearchEntity';
import {NotificationManager} from '../NotifocationManager';
import {PersonJunctionTable} from './enitites/PersonJunctionTable';
import {MDFileEntity} from './enitites/MDFileEntity';
const LOG_TAG = '[SQLConnection]';
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
export class SQLConnection {
// eslint-disable-next-line @typescript-eslint/ban-types
public static getEntries(): Function[] {
return this.entries;
}
// eslint-disable-next-line @typescript-eslint/ban-types
public static async addEntries(tables: Function[]) {
if (!tables?.length) {
return;
}
await this.close();
this.entries = Utils.getUnique(this.entries.concat(tables));
await (await this.getConnection()).synchronize();
}
// eslint-disable-next-line @typescript-eslint/ban-types
private static entries: Function[] = [
UserEntity,
FileEntity,
MDFileEntity,
PersonJunctionTable,
PersonEntry,
MediaEntity,
PhotoEntity,
VideoEntity,
DirectoryEntity,
SharingEntity,
AlbumBaseEntity,
SavedSearchEntity,
VersionEntity,
];
private static connection: Connection = null;
public static async getConnection(): Promise<Connection> {
if (this.connection == null) {
const options = this.getDriver(Config.Database);
Logger.debug(
LOG_TAG,
'Creating connection: ' + DatabaseType[Config.Database.type],
', with driver:',
options.type
);
this.connection = await this.createConnection(options);
await SQLConnection.schemeSync(this.connection);
}
return this.connection;
}
public static async tryConnection(
config: ServerDataBaseConfig
): Promise<boolean> {
try {
await getConnection('test').close();
// eslint-disable-next-line no-empty
} catch (err) {
}
const options = this.getDriver(config);
options.name = 'test';
const conn = await this.createConnection(options);
await SQLConnection.schemeSync(conn);
await conn.close();
return true;
}
public static async init(): Promise<void> {
const connection = await this.getConnection();
if (Config.Users.authenticationRequired !== true) {
return;
}
// Adding enforced users to the db
const userRepository = connection.getRepository(UserEntity);
if (
Array.isArray(Config.Users.enforcedUsers) &&
Config.Users.enforcedUsers.length > 0
) {
for (let i = 0; i < Config.Users.enforcedUsers.length; ++i) {
const uc = Config.Users.enforcedUsers[i];
const user = await userRepository.findOneBy({name: uc.name});
if (!user) {
Logger.info(LOG_TAG, 'Saving enforced user: ' + uc.name);
const a = new UserEntity();
a.name = uc.name;
a.password = uc.encryptedPassword;
a.role = uc.role;
await userRepository.save(a);
}
}
}
// Add dummy Admin to the db
const admins = await userRepository.findBy({role: UserRoles.Admin});
const devs = await userRepository.findBy({role: UserRoles.Developer});
if (admins.length === 0 && devs.length === 0) {
const a = new UserEntity();
a.name = 'admin';
a.password = PasswordHelper.cryptPassword('admin');
a.role = UserRoles.Admin;
await userRepository.save(a);
}
const defAdmin = await userRepository.findOneBy({
name: 'admin',
role: UserRoles.Admin,
});
if (
defAdmin &&
PasswordHelper.comparePassword('admin', defAdmin.password)
) {
NotificationManager.error(
'Using default admin user!',
'You are using the default admin/admin user/password, please change or remove it.'
);
}
}
public static async close(): Promise<void> {
try {
if (this.connection != null) {
await this.connection.close();
this.connection = null;
}
} catch (err) {
console.error('Error during closing sql db:');
console.error(err);
}
}
private static FIXED_SQL_TABLE = [
'sqlite_sequence'
];
/**
* Clears up the DB from unused tables. use it when the entities list are up-to-date (extensions won't add any new)
*/
public static async removeUnusedTables() {
const conn = await this.getConnection();
const validTableNames = this.entries.map(e => conn.getRepository(e).metadata.tableName).concat(this.FIXED_SQL_TABLE);
let currentTables: string[];
if (Config.Database.type === DatabaseType.sqlite) {
currentTables = (await conn.query('SELECT name FROM sqlite_master WHERE type=\'table\''))
.map((r: { name: string }) => r.name);
} else {
currentTables = (await conn.query(`SELECT table_name FROM information_schema.tables ` +
`WHERE table_schema = '${Config.Database.mysql.database}'`))
.map((r: { table_name: string }) => r.table_name);
}
const tableToDrop = currentTables.filter(ct => !validTableNames.includes(ct));
for (let i = 0; i < tableToDrop.length; ++i) {
await conn.query('DROP TABLE ' + tableToDrop[i]);
}
}
public static getSQLiteDB(config: ServerDataBaseConfig): string {
return path.join(ProjectPath.getAbsolutePath(config.dbFolder), 'sqlite.db');
}
private static async createConnection(
options: DataSourceOptions
): Promise<Connection> {
if (options.type === 'sqlite' || options.type === 'better-sqlite3') {
return await createConnection(options);
}
try {
return await createConnection(options);
} catch (e) {
if (e.sqlMessage === 'Unknown database \'' + options.database + '\'') {
Logger.debug(LOG_TAG, 'creating database: ' + options.database);
const tmpOption = Utils.clone(options);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete tmpOption.database;
const tmpConn = await createConnection(tmpOption);
await tmpConn.query(
'CREATE DATABASE IF NOT EXISTS ' + options.database
);
await tmpConn.close();
return await createConnection(options);
}
throw e;
}
}
private static async schemeSync(connection: Connection): Promise<void> {
let version = null;
try {
version = (await connection.getRepository(VersionEntity).find())[0];
// eslint-disable-next-line no-empty
} catch (ex) {
}
if (version && version.version === DataStructureVersion) {
return;
}
Logger.info(LOG_TAG, 'Updating database scheme');
if (!version) {
version = new VersionEntity();
}
version.version = DataStructureVersion;
let users: UserEntity[] = [];
try {
users = await connection
.getRepository(UserEntity)
.createQueryBuilder('user')
.getMany();
// eslint-disable-next-line no-empty
} catch (ex) {
}
await connection.dropDatabase();
await connection.synchronize();
await connection.getRepository(VersionEntity).save(version);
try {
await connection.getRepository(UserEntity).save(users);
} catch (e) {
await connection.dropDatabase();
await connection.synchronize();
await connection.getRepository(VersionEntity).save(version);
Logger.warn(
LOG_TAG,
'Could not move users to the new db scheme, deleting them. Details:' +
e.toString()
);
}
}
private static getDriver(config: ServerDataBaseConfig): Writeable<DataSourceOptions> {
let driver: Writeable<DataSourceOptions>;
if (config.type === DatabaseType.mysql) {
driver = {
type: 'mysql',
host: config.mysql.host,
port: config.mysql.port,
username: config.mysql.username,
password: config.mysql.password,
database: config.mysql.database,
charset: 'utf8mb4',
};
} else if (config.type === DatabaseType.sqlite) {
driver = {
type: 'better-sqlite3',
database: path.join(
ProjectPath.getAbsolutePath(config.dbFolder),
config.sqlite.DBFileName
),
};
}
driver.entities = this.entries;
driver.synchronize = false;
if (Config.Server.Log.sqlLevel !== SQLLogLevel.none) {
driver.logging = SQLLogLevel[Config.Server.Log.sqlLevel] as LoggerOptions;
}
return driver;
}
}