Skip to content

Commit 3ac00b0

Browse files
authored
refactor(server): db env (immich-app#13167)
1 parent e2bf680 commit 3ac00b0

11 files changed

+84
-31
lines changed

server/src/database.config.ts

+8-10
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
import { DatabaseExtension } from 'src/interfaces/database.interface';
1+
import { ConfigRepository } from 'src/repositories/config.repository';
22
import { DataSource } from 'typeorm';
33
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js';
44

5-
const url = process.env.DB_URL;
5+
const { database } = new ConfigRepository().getEnv();
6+
const { url, host, port, username, password, name } = database;
67
const urlOrParts = url
78
? { url }
89
: {
9-
host: process.env.DB_HOSTNAME || 'database',
10-
port: Number.parseInt(process.env.DB_PORT || '5432'),
11-
username: process.env.DB_USERNAME || 'postgres',
12-
password: process.env.DB_PASSWORD || 'postgres',
13-
database: process.env.DB_DATABASE_NAME || 'immich',
10+
host,
11+
port,
12+
username,
13+
password,
14+
database: name,
1415
};
1516

1617
/* eslint unicorn/prefer-module: "off" -- We can fix this when migrating to ESM*/
@@ -32,6 +33,3 @@ export const databaseConfig: PostgresConnectionOptions = {
3233
* this export is ONLY to be used for TypeORM commands in package.json#scripts
3334
*/
3435
export const dataSource = new DataSource({ ...databaseConfig, host: 'localhost' });
35-
36-
export const getVectorExtension = () =>
37-
process.env.DB_VECTOR_EXTENSION === 'pgvector' ? DatabaseExtension.VECTOR : DatabaseExtension.VECTORS;

server/src/interfaces/config.interface.ts

+6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ export interface EnvData {
2626
};
2727

2828
database: {
29+
url?: string;
30+
host: string;
31+
port: number;
32+
username: string;
33+
password: string;
34+
name: string;
2935
skipMigrations: boolean;
3036
vectorExtension: VectorExtension;
3137
};

server/src/migrations/1700713871511-UsePgVectors.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import { getVectorExtension } from 'src/database.config';
1+
import { ConfigRepository } from 'src/repositories/config.repository';
22
import { getCLIPModelInfo } from 'src/utils/misc';
33
import { MigrationInterface, QueryRunner } from 'typeorm';
44

5+
const vectorExtension = new ConfigRepository().getEnv().database.vectorExtension;
6+
57
export class UsePgVectors1700713871511 implements MigrationInterface {
68
name = 'UsePgVectors1700713871511';
79

810
public async up(queryRunner: QueryRunner): Promise<void> {
911
await queryRunner.query(`SET search_path TO "$user", public, vectors`);
10-
await queryRunner.query(`CREATE EXTENSION IF NOT EXISTS ${getVectorExtension()}`);
12+
await queryRunner.query(`CREATE EXTENSION IF NOT EXISTS ${vectorExtension}`);
1113
const faceDimQuery = await queryRunner.query(`
1214
SELECT CARDINALITY(embedding::real[]) as dimsize
1315
FROM asset_faces

server/src/migrations/1700713994428-AddCLIPEmbeddingIndex.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { getVectorExtension } from 'src/database.config';
21
import { DatabaseExtension } from 'src/interfaces/database.interface';
2+
import { ConfigRepository } from 'src/repositories/config.repository';
33
import { MigrationInterface, QueryRunner } from 'typeorm';
44

5+
const vectorExtension = new ConfigRepository().getEnv().database.vectorExtension;
6+
57
export class AddCLIPEmbeddingIndex1700713994428 implements MigrationInterface {
68
name = 'AddCLIPEmbeddingIndex1700713994428';
79

810
public async up(queryRunner: QueryRunner): Promise<void> {
9-
if (getVectorExtension() === DatabaseExtension.VECTORS) {
11+
if (vectorExtension === DatabaseExtension.VECTORS) {
1012
await queryRunner.query(`SET vectors.pgvector_compatibility=on`);
1113
}
1214
await queryRunner.query(`SET search_path TO "$user", public, vectors`);

server/src/migrations/1700714033632-AddFaceEmbeddingIndex.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { getVectorExtension } from 'src/database.config';
21
import { DatabaseExtension } from 'src/interfaces/database.interface';
2+
import { ConfigRepository } from 'src/repositories/config.repository';
33
import { MigrationInterface, QueryRunner } from 'typeorm';
44

5+
const vectorExtension = new ConfigRepository().getEnv().database.vectorExtension;
6+
57
export class AddFaceEmbeddingIndex1700714033632 implements MigrationInterface {
68
name = 'AddFaceEmbeddingIndex1700714033632';
79

810
public async up(queryRunner: QueryRunner): Promise<void> {
9-
if (getVectorExtension() === DatabaseExtension.VECTORS) {
11+
if (vectorExtension === DatabaseExtension.VECTORS) {
1012
await queryRunner.query(`SET vectors.pgvector_compatibility=on`);
1113
}
1214
await queryRunner.query(`SET search_path TO "$user", public, vectors`);

server/src/migrations/1718486162779-AddFaceSearchRelation.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { getVectorExtension } from 'src/database.config';
21
import { DatabaseExtension } from 'src/interfaces/database.interface';
2+
import { ConfigRepository } from 'src/repositories/config.repository';
33
import { MigrationInterface, QueryRunner } from 'typeorm';
44

5+
const vectorExtension = new ConfigRepository().getEnv().database.vectorExtension;
6+
57
export class AddFaceSearchRelation1718486162779 implements MigrationInterface {
68
public async up(queryRunner: QueryRunner): Promise<void> {
7-
if (getVectorExtension() === DatabaseExtension.VECTORS) {
9+
if (vectorExtension === DatabaseExtension.VECTORS) {
810
await queryRunner.query(`SET search_path TO "$user", public, vectors`);
911
await queryRunner.query(`SET vectors.pgvector_compatibility=on`);
1012
}
@@ -13,9 +15,10 @@ export class AddFaceSearchRelation1718486162779 implements MigrationInterface {
1315
const columns = await queryRunner.query(
1416
`SELECT column_name as name
1517
FROM information_schema.columns
16-
WHERE table_name = '${tableName}'`);
18+
WHERE table_name = '${tableName}'`,
19+
);
1720
return columns.some((column: { name: string }) => column.name === 'embedding');
18-
}
21+
};
1922

2023
const hasAssetEmbeddings = await hasEmbeddings('smart_search');
2124
if (!hasAssetEmbeddings) {
@@ -31,7 +34,7 @@ export class AddFaceSearchRelation1718486162779 implements MigrationInterface {
3134
await queryRunner.query(`ALTER TABLE face_search ALTER COLUMN embedding SET STORAGE EXTERNAL`);
3235
await queryRunner.query(`ALTER TABLE smart_search ALTER COLUMN embedding SET STORAGE EXTERNAL`);
3336

34-
const hasFaceEmbeddings = await hasEmbeddings('asset_faces')
37+
const hasFaceEmbeddings = await hasEmbeddings('asset_faces');
3538
if (hasFaceEmbeddings) {
3639
await queryRunner.query(`
3740
INSERT INTO face_search("faceId", embedding)
@@ -56,7 +59,7 @@ export class AddFaceSearchRelation1718486162779 implements MigrationInterface {
5659
}
5760

5861
public async down(queryRunner: QueryRunner): Promise<void> {
59-
if (getVectorExtension() === DatabaseExtension.VECTORS) {
62+
if (vectorExtension === DatabaseExtension.VECTORS) {
6063
await queryRunner.query(`SET search_path TO "$user", public, vectors`);
6164
await queryRunner.query(`SET vectors.pgvector_compatibility=on`);
6265
}

server/src/repositories/config.repository.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Injectable } from '@nestjs/common';
2-
import { getVectorExtension } from 'src/database.config';
32
import { ImmichEnvironment, ImmichWorker, LogLevel } from 'src/enum';
43
import { EnvData, IConfigRepository } from 'src/interfaces/config.interface';
4+
import { DatabaseExtension } from 'src/interfaces/database.interface';
55
import { setDifference } from 'src/utils/set';
66

77
// TODO replace src/config validation with class-validator, here
@@ -65,8 +65,16 @@ export class ConfigRepository implements IConfigRepository {
6565
},
6666

6767
database: {
68+
url: process.env.DB_URL,
69+
host: process.env.DB_HOSTNAME || 'database',
70+
port: Number(process.env.DB_PORT) || 5432,
71+
username: process.env.DB_USERNAME || 'postgres',
72+
password: process.env.DB_PASSWORD || 'postgres',
73+
name: process.env.DB_DATABASE_NAME || 'immich',
74+
6875
skipMigrations: process.env.DB_SKIP_MIGRATIONS === 'true',
69-
vectorExtension: getVectorExtension(),
76+
vectorExtension:
77+
process.env.DB_VECTOR_EXTENSION === 'pgvector' ? DatabaseExtension.VECTOR : DatabaseExtension.VECTORS,
7078
},
7179

7280
licensePublicKey: isProd ? productionKeys : stagingKeys,

server/src/repositories/database.repository.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { InjectDataSource } from '@nestjs/typeorm';
33
import AsyncLock from 'async-lock';
44
import semver from 'semver';
55
import { POSTGRES_VERSION_RANGE, VECTOR_VERSION_RANGE, VECTORS_VERSION_RANGE } from 'src/constants';
6-
import { getVectorExtension } from 'src/database.config';
6+
import { IConfigRepository } from 'src/interfaces/config.interface';
77
import {
88
DatabaseExtension,
99
DatabaseLock,
@@ -22,12 +22,15 @@ import { DataSource, EntityManager, QueryRunner } from 'typeorm';
2222
@Instrumentation()
2323
@Injectable()
2424
export class DatabaseRepository implements IDatabaseRepository {
25+
private vectorExtension: VectorExtension;
2526
readonly asyncLock = new AsyncLock();
2627

2728
constructor(
2829
@InjectDataSource() private dataSource: DataSource,
2930
@Inject(ILoggerRepository) private logger: ILoggerRepository,
31+
@Inject(IConfigRepository) configRepository: IConfigRepository,
3032
) {
33+
this.vectorExtension = configRepository.getEnv().database.vectorExtension;
3134
this.logger.setContext(DatabaseRepository.name);
3235
}
3336

@@ -119,7 +122,7 @@ export class DatabaseRepository implements IDatabaseRepository {
119122
try {
120123
await this.dataSource.query(`REINDEX INDEX ${index}`);
121124
} catch (error) {
122-
if (getVectorExtension() !== DatabaseExtension.VECTORS) {
125+
if (this.vectorExtension !== DatabaseExtension.VECTORS) {
123126
throw error;
124127
}
125128
this.logger.warn(`Could not reindex index ${index}. Attempting to auto-fix.`);
@@ -141,7 +144,7 @@ export class DatabaseRepository implements IDatabaseRepository {
141144
}
142145

143146
async shouldReindex(name: VectorIndex): Promise<boolean> {
144-
if (getVectorExtension() !== DatabaseExtension.VECTORS) {
147+
if (this.vectorExtension !== DatabaseExtension.VECTORS) {
145148
return false;
146149
}
147150

server/src/repositories/search.repository.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Inject, Injectable } from '@nestjs/common';
22
import { InjectRepository } from '@nestjs/typeorm';
33
import { randomUUID } from 'node:crypto';
4-
import { getVectorExtension } from 'src/database.config';
54
import { DummyValue, GenerateSql } from 'src/decorators';
65
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
76
import { AssetEntity } from 'src/entities/asset.entity';
@@ -10,7 +9,8 @@ import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity';
109
import { SmartInfoEntity } from 'src/entities/smart-info.entity';
1110
import { SmartSearchEntity } from 'src/entities/smart-search.entity';
1211
import { AssetType, PaginationMode } from 'src/enum';
13-
import { DatabaseExtension } from 'src/interfaces/database.interface';
12+
import { IConfigRepository } from 'src/interfaces/config.interface';
13+
import { DatabaseExtension, VectorExtension } from 'src/interfaces/database.interface';
1414
import { ILoggerRepository } from 'src/interfaces/logger.interface';
1515
import {
1616
AssetDuplicateResult,
@@ -31,6 +31,7 @@ import { Repository, SelectQueryBuilder } from 'typeorm';
3131
@Instrumentation()
3232
@Injectable()
3333
export class SearchRepository implements ISearchRepository {
34+
private vectorExtension: VectorExtension;
3435
private faceColumns: string[];
3536
private assetsByCityQuery: string;
3637

@@ -42,7 +43,9 @@ export class SearchRepository implements ISearchRepository {
4243
@InjectRepository(SmartSearchEntity) private smartSearchRepository: Repository<SmartSearchEntity>,
4344
@InjectRepository(GeodataPlacesEntity) private geodataPlacesRepository: Repository<GeodataPlacesEntity>,
4445
@Inject(ILoggerRepository) private logger: ILoggerRepository,
46+
@Inject(IConfigRepository) configRepository: IConfigRepository,
4547
) {
48+
this.vectorExtension = configRepository.getEnv().database.vectorExtension;
4649
this.logger.setContext(SearchRepository.name);
4750
this.faceColumns = this.assetFaceRepository.manager.connection
4851
.getMetadata(AssetFaceEntity)
@@ -440,7 +443,7 @@ export class SearchRepository implements ISearchRepository {
440443
}
441444

442445
private getRuntimeConfig(numResults?: number): string {
443-
if (getVectorExtension() === DatabaseExtension.VECTOR) {
446+
if (this.vectorExtension === DatabaseExtension.VECTOR) {
444447
return 'SET LOCAL hnsw.ef_search = 1000;'; // mitigate post-filter recall
445448
}
446449

server/src/services/database.service.spec.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,17 @@ describe(DatabaseService.name, () => {
5757
])('should work with $extensionName', ({ extension, extensionName }) => {
5858
beforeEach(() => {
5959
configMock.getEnv.mockReturnValue(
60-
mockEnvData({ database: { skipMigrations: false, vectorExtension: extension } }),
60+
mockEnvData({
61+
database: {
62+
host: 'database',
63+
port: 5432,
64+
username: 'postgres',
65+
password: 'postgres',
66+
name: 'immich',
67+
skipMigrations: false,
68+
vectorExtension: extension,
69+
},
70+
}),
6171
);
6272
});
6373

@@ -245,6 +255,11 @@ describe(DatabaseService.name, () => {
245255
configMock.getEnv.mockReturnValue(
246256
mockEnvData({
247257
database: {
258+
host: 'database',
259+
port: 5432,
260+
username: 'postgres',
261+
password: 'postgres',
262+
name: 'immich',
248263
skipMigrations: true,
249264
vectorExtension: DatabaseExtension.VECTORS,
250265
},
@@ -260,6 +275,11 @@ describe(DatabaseService.name, () => {
260275
configMock.getEnv.mockReturnValue(
261276
mockEnvData({
262277
database: {
278+
host: 'database',
279+
port: 5432,
280+
username: 'postgres',
281+
password: 'postgres',
282+
name: 'immich',
263283
skipMigrations: true,
264284
vectorExtension: DatabaseExtension.VECTOR,
265285
},

server/test/repositories/config.repository.mock.ts

+6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ const envData: EnvData = {
1010
buildMetadata: {},
1111

1212
database: {
13+
host: 'database',
14+
port: 5432,
15+
username: 'postgres',
16+
password: 'postgres',
17+
name: 'immich',
18+
1319
skipMigrations: false,
1420
vectorExtension: DatabaseExtension.VECTORS,
1521
},

0 commit comments

Comments
 (0)