diff --git a/server/package.json b/server/package.json index 39a3f78d0e..25dc71520b 100644 --- a/server/package.json +++ b/server/package.json @@ -24,10 +24,10 @@ "typeorm": "typeorm", "lifecycle": "node ./dist/utils/lifecycle.js", "typeorm:migrations:create": "typeorm migration:create", - "typeorm:migrations:generate": "typeorm migration:generate -d ./dist/database.config.js", - "typeorm:migrations:run": "typeorm migration:run -d ./dist/database.config.js", - "typeorm:migrations:revert": "typeorm migration:revert -d ./dist/database.config.js", - "typeorm:schema:drop": "typeorm query -d ./dist/database.config.js 'DROP schema public cascade; CREATE schema public;'", + "typeorm:migrations:generate": "typeorm migration:generate -d ./dist/bin/database.js", + "typeorm:migrations:run": "typeorm migration:run -d ./dist/bin/database.js", + "typeorm:migrations:revert": "typeorm migration:revert -d ./dist/bin/database.js", + "typeorm:schema:drop": "typeorm query -d ./dist/bin/database.js 'DROP schema public cascade; CREATE schema public;'", "typeorm:schema:reset": "npm run typeorm:schema:drop && npm run typeorm:migrations:run", "sync:open-api": "node ./dist/bin/sync-open-api.js", "sync:sql": "node ./dist/bin/sync-sql.js", diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 3c26faaca3..0a7f47959d 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -9,7 +9,6 @@ import { OpenTelemetryModule } from 'nestjs-otel'; import { commands } from 'src/commands'; import { clsConfig, immichAppConfig } from 'src/config'; import { controllers } from 'src/controllers'; -import { databaseConfig } from 'src/database.config'; import { entities } from 'src/entities'; import { ImmichWorker } from 'src/enum'; import { IEventRepository } from 'src/interfaces/event.interface'; @@ -38,7 +37,7 @@ const middleware = [ ]; const configRepository = new ConfigRepository(); -const { bull, otel } = configRepository.getEnv(); +const { bull, database, otel } = configRepository.getEnv(); const imports = [ BullModule.forRoot(bull.config), @@ -50,7 +49,7 @@ const imports = [ inject: [ModuleRef], useFactory: (moduleRef: ModuleRef) => { return { - ...databaseConfig, + ...database.config, poolErrorHandler: (error) => { moduleRef.get(DatabaseService, { strict: false }).handleConnectionError(error); }, diff --git a/server/src/bin/database.ts b/server/src/bin/database.ts new file mode 100644 index 0000000000..c861902b4e --- /dev/null +++ b/server/src/bin/database.ts @@ -0,0 +1,11 @@ +import { ConfigRepository } from 'src/repositories/config.repository'; +import { DataSource } from 'typeorm'; + +const { database } = new ConfigRepository().getEnv(); + +/** + * @deprecated - DO NOT USE THIS + * + * this export is ONLY to be used for TypeORM commands in package.json#scripts + */ +export const dataSource = new DataSource({ ...database.config, host: 'localhost' }); diff --git a/server/src/bin/sync-sql.ts b/server/src/bin/sync-sql.ts index e4f11cc692..98f26d879a 100644 --- a/server/src/bin/sync-sql.ts +++ b/server/src/bin/sync-sql.ts @@ -8,7 +8,6 @@ import { OpenTelemetryModule } from 'nestjs-otel'; import { mkdir, rm, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { format } from 'sql-formatter'; -import { databaseConfig } from 'src/database.config'; import { GENERATE_SQL_KEY, GenerateSqlQueries } from 'src/decorators'; import { entities } from 'src/entities'; import { ILoggerRepository } from 'src/interfaces/logger.interface'; @@ -74,12 +73,12 @@ class SqlGenerator { await rm(this.options.targetDir, { force: true, recursive: true }); await mkdir(this.options.targetDir); - const { otel } = new ConfigRepository().getEnv(); + const { database, otel } = new ConfigRepository().getEnv(); const moduleFixture = await Test.createTestingModule({ imports: [ TypeOrmModule.forRoot({ - ...databaseConfig, + ...database.config, host: 'localhost', entities, logging: ['query'], diff --git a/server/src/database.config.ts b/server/src/database.config.ts deleted file mode 100644 index 2a46067bc1..0000000000 --- a/server/src/database.config.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ConfigRepository } from 'src/repositories/config.repository'; -import { DataSource } from 'typeorm'; -import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js'; - -const { database } = new ConfigRepository().getEnv(); -const { url, host, port, username, password, name } = database; -const urlOrParts = url - ? { url } - : { - host, - port, - username, - password, - database: name, - }; - -/* eslint unicorn/prefer-module: "off" -- We can fix this when migrating to ESM*/ -export const databaseConfig: PostgresConnectionOptions = { - type: 'postgres', - entities: [__dirname + '/entities/*.entity.{js,ts}'], - migrations: [__dirname + '/migrations/*.{js,ts}'], - subscribers: [__dirname + '/subscribers/*.{js,ts}'], - migrationsRun: false, - synchronize: false, - connectTimeoutMS: 10_000, // 10 seconds - parseInt8: true, - ...urlOrParts, -}; - -/** - * @deprecated - DO NOT USE THIS - * - * this export is ONLY to be used for TypeORM commands in package.json#scripts - */ -export const dataSource = new DataSource({ ...databaseConfig, host: 'localhost' }); diff --git a/server/src/interfaces/config.interface.ts b/server/src/interfaces/config.interface.ts index e201241e82..cf1bc18e3e 100644 --- a/server/src/interfaces/config.interface.ts +++ b/server/src/interfaces/config.interface.ts @@ -4,6 +4,7 @@ import { RedisOptions } from 'ioredis'; import { OpenTelemetryModuleOptions } from 'nestjs-otel/lib/interfaces'; import { ImmichEnvironment, ImmichTelemetry, ImmichWorker, LogLevel } from 'src/enum'; import { VectorExtension } from 'src/interfaces/database.interface'; +import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js'; export const IConfigRepository = 'IConfigRepository'; @@ -36,12 +37,7 @@ export interface EnvData { }; database: { - url?: string; - host: string; - port: number; - username: string; - password: string; - name: string; + config: PostgresConnectionOptions; skipMigrations: boolean; vectorExtension: VectorExtension; }; diff --git a/server/src/repositories/config.repository.spec.ts b/server/src/repositories/config.repository.spec.ts index 516ceaaf82..d2777a2e46 100644 --- a/server/src/repositories/config.repository.spec.ts +++ b/server/src/repositories/config.repository.spec.ts @@ -66,12 +66,14 @@ describe('getEnv', () => { it('should use defaults', () => { const { database } = getEnv(); expect(database).toEqual({ - url: undefined, - host: 'database', - port: 5432, - name: 'immich', - username: 'postgres', - password: 'postgres', + config: expect.objectContaining({ + type: 'postgres', + host: 'database', + port: 5432, + database: 'immich', + username: 'postgres', + password: 'postgres', + }), skipMigrations: false, vectorExtension: 'vectors', }); diff --git a/server/src/repositories/config.repository.ts b/server/src/repositories/config.repository.ts index 0abee0f603..077a19175e 100644 --- a/server/src/repositories/config.repository.ts +++ b/server/src/repositories/config.repository.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { join } from 'node:path'; +import { join, resolve } from 'node:path'; import { citiesFile, excludePaths } from 'src/constants'; import { Telemetry } from 'src/decorators'; import { ImmichEnvironment, ImmichTelemetry, ImmichWorker, LogLevel } from 'src/enum'; @@ -46,10 +46,14 @@ const getEnv = (): EnvData => { const isProd = environment === ImmichEnvironment.PRODUCTION; const buildFolder = process.env.IMMICH_BUILD_DATA || '/build'; const folders = { + // eslint-disable-next-line unicorn/prefer-module + dist: resolve(`${__dirname}/..`), geodata: join(buildFolder, 'geodata'), web: join(buildFolder, 'www'), }; + const databaseUrl = process.env.DB_URL; + let redisConfig = { host: process.env.REDIS_HOSTNAME || 'redis', port: Number.parseInt(process.env.REDIS_PORT || '') || 6379, @@ -118,12 +122,25 @@ const getEnv = (): EnvData => { }, database: { - url: process.env.DB_URL, - host: process.env.DB_HOSTNAME || 'database', - port: Number(process.env.DB_PORT) || 5432, - username: process.env.DB_USERNAME || 'postgres', - password: process.env.DB_PASSWORD || 'postgres', - name: process.env.DB_DATABASE_NAME || 'immich', + config: { + type: 'postgres', + entities: [`${folders.dist}/entities` + '/*.entity.{js,ts}'], + migrations: [`${folders.dist}/migrations` + '/*.{js,ts}'], + subscribers: [`${folders.dist}/subscribers` + '/*.{js,ts}'], + migrationsRun: false, + synchronize: false, + connectTimeoutMS: 10_000, // 10 seconds + parseInt8: true, + ...(databaseUrl + ? { url: databaseUrl } + : { + host: process.env.DB_HOSTNAME || 'database', + port: Number(process.env.DB_PORT) || 5432, + username: process.env.DB_USERNAME || 'postgres', + password: process.env.DB_PASSWORD || 'postgres', + database: process.env.DB_DATABASE_NAME || 'immich', + }), + }, skipMigrations: process.env.DB_SKIP_MIGRATIONS === 'true', vectorExtension: diff --git a/server/src/services/database.service.spec.ts b/server/src/services/database.service.spec.ts index 96d94453c4..79845e4297 100644 --- a/server/src/services/database.service.spec.ts +++ b/server/src/services/database.service.spec.ts @@ -60,11 +60,14 @@ describe(DatabaseService.name, () => { configMock.getEnv.mockReturnValue( mockEnvData({ database: { - host: 'database', - port: 5432, - username: 'postgres', - password: 'postgres', - name: 'immich', + config: { + type: 'postgres', + host: 'database', + port: 5432, + username: 'postgres', + password: 'postgres', + database: 'immich', + }, skipMigrations: false, vectorExtension: extension, }, @@ -286,11 +289,14 @@ describe(DatabaseService.name, () => { configMock.getEnv.mockReturnValue( mockEnvData({ database: { - host: 'database', - port: 5432, - username: 'postgres', - password: 'postgres', - name: 'immich', + config: { + type: 'postgres', + host: 'database', + port: 5432, + username: 'postgres', + password: 'postgres', + database: 'immich', + }, skipMigrations: true, vectorExtension: DatabaseExtension.VECTORS, }, @@ -306,11 +312,14 @@ describe(DatabaseService.name, () => { configMock.getEnv.mockReturnValue( mockEnvData({ database: { - host: 'database', - port: 5432, - username: 'postgres', - password: 'postgres', - name: 'immich', + config: { + type: 'postgres', + host: 'database', + port: 5432, + username: 'postgres', + password: 'postgres', + database: 'immich', + }, skipMigrations: true, vectorExtension: DatabaseExtension.VECTOR, }, diff --git a/server/test/repositories/config.repository.mock.ts b/server/test/repositories/config.repository.mock.ts index 462e9f8327..08c2394e85 100644 --- a/server/test/repositories/config.repository.mock.ts +++ b/server/test/repositories/config.repository.mock.ts @@ -16,11 +16,16 @@ const envData: EnvData = { }, database: { - host: 'database', - port: 5432, - username: 'postgres', - password: 'postgres', - name: 'immich', + config: { + type: 'postgres', + host: 'database', + port: 5432, + username: 'postgres', + password: 'postgres', + name: 'immich', + synchronize: false, + migrationsRun: true, + }, skipMigrations: false, vectorExtension: DatabaseExtension.VECTORS,