import * as path from 'path'; import { DataSource, type DataSourceOptions, type EntityManager, type EntityTarget, type ObjectLiteral, type Repository, } from 'typeorm'; import { assert, makeLogger, panic } from '../helpers'; import AccessField from './entities/access-fields'; import Access from './entities/accesses'; import Account from './entities/accounts'; import Alert from './entities/alerts'; import AppliedRecurringTransaction from './entities/applied-recurring-transactions'; import Budget from './entities/budgets'; import Category from './entities/categories'; import DuplicatesIgnored from './entities/duplicates-ignored'; import RecurringTransaction from './entities/recurring-transactions'; import Setting from './entities/settings'; import TransactionRule from './entities/transaction-rule'; import TransactionRuleAction from './entities/transaction-rule-action'; import TransactionRuleCondition from './entities/transaction-rule-condition'; import Transaction from './entities/transactions'; import User from './entities/users'; import View from './entities/views'; type MinimalTransaction = Partial & Pick; export { Access, AccessField, Account, Alert, AppliedRecurringTransaction, Budget, Category, DuplicatesIgnored, type MinimalTransaction, RecurringTransaction, Setting, Transaction, TransactionRule, TransactionRuleAction, TransactionRuleCondition, User, View, }; const log = makeLogger('models/index'); let dataSource: DataSource | null; function makeOrmConfig(): DataSourceOptions { let ormConfig: DataSourceOptions; // Keep this switch in sync with ../config.ts! switch (process.kresus.dbType) { case 'sqlite': assert(process.kresus.sqlitePath !== null, 'missing db path in server/models'); ormConfig = { type: 'better-sqlite3', database: process.kresus.sqlitePath, logging: process.kresus.dbLog, }; break; case 'postgres': assert(typeof process.kresus.dbHost === 'string', 'missing db host in server/models'); assert(typeof process.kresus.dbPort === 'number', 'missing db port in server/models'); assert( typeof process.kresus.dbUsername === 'string', 'missing db username in server/models' ); assert( typeof process.kresus.dbPassword === 'string', 'missing db password in server/models' ); assert(typeof process.kresus.dbName === 'string', 'missing db name in server/models'); ormConfig = { type: process.kresus.dbType, host: process.kresus.dbHost, port: process.kresus.dbPort, username: process.kresus.dbUsername, password: process.kresus.dbPassword, database: process.kresus.dbName, logging: process.kresus.dbLog, }; break; default: panic('unexpected db type in server/models'); } return ormConfig; } export async function setupOrm(): Promise { const ormConfig = Object.assign(makeOrmConfig(), { // Automatically run migrations. migrationsRun: true, // Entity models. entities: [path.join(__dirname, 'entities/*')], // Subscribers. subscribers: [path.join(__dirname, 'subscribers/*')], // Migration files. migrations: [path.join(__dirname, 'migrations/*')], // Use one transaction by migration, avoiding issues on promises hanging after alter table. migrationsTransactionMode: 'each', // Automatically synchronize the database schema on startup. Very // unsafe, use only to look at queries generated by the ORM. synchronize: false, }); dataSource = new DataSource(ormConfig); await dataSource.initialize(); log.info('database is ready'); return dataSource; } export function getRepository(x: EntityTarget): Repository { if (dataSource === null || typeof dataSource === 'undefined') { panic('Expected data source to be initialized'); } return dataSource.getRepository(x); } export function getManager(): EntityManager { if (dataSource === null || typeof dataSource === 'undefined') { panic('Expected data source to be initialized'); } return dataSource.manager; } export async function initModels() { const { userLoginHttpHeader } = process.kresus; dataSource = await setupOrm(); // Create default user. let defaultUser: User | null; if (process.kresus.providedUserId !== null) { const userId = process.kresus.providedUserId; // Check that the user actually exists already. defaultUser = await User.find(userId); if (!defaultUser) { throw new Error( `The user with provided ID ${userId} doesn't exist. Did you run "kresus create:user" first?` ); } } else { const users: User[] = await User.all(); if (!users.length) { const { login } = process.kresus.defaultUser; assert(!!login, 'There should be a default login set!'); log.info('Creating default user as administrator...'); defaultUser = await User.create({ login, isAdmin: true }); } else if (users.length > 1 && !userLoginHttpHeader) { throw new Error( 'Several users in database but "userLoginHttpHeader" was not configured. Please configure the userLoginHttpHeader variable in config.ini and setup a reverse proxy accordingly.' ); } else { defaultUser = users[0]; } } process.kresus.defaultUser = defaultUser; log.info( `Default user “${defaultUser.login}“ is set with id ${defaultUser.id} (admin: ${defaultUser.isAdmin})` ); }