mirror of
https://github.com/immich-app/immich.git
synced 2024-12-28 22:51:59 +00:00
refactor(server): access env via repository (#12987)
This commit is contained in:
parent
12da250028
commit
36ee72cd87
6 changed files with 83 additions and 19 deletions
14
server/src/interfaces/config.interface.ts
Normal file
14
server/src/interfaces/config.interface.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { VectorExtension } from 'src/interfaces/database.interface';
|
||||
|
||||
export const IConfigRepository = 'IConfigRepository';
|
||||
|
||||
export interface EnvData {
|
||||
database: {
|
||||
skipMigrations: boolean;
|
||||
vectorExtension: VectorExtension;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IConfigRepository {
|
||||
getEnv(): EnvData;
|
||||
}
|
15
server/src/repositories/config.repository.ts
Normal file
15
server/src/repositories/config.repository.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { getVectorExtension } from 'src/database.config';
|
||||
import { EnvData, IConfigRepository } from 'src/interfaces/config.interface';
|
||||
|
||||
@Injectable()
|
||||
export class ConfigRepository implements IConfigRepository {
|
||||
getEnv(): EnvData {
|
||||
return {
|
||||
database: {
|
||||
skipMigrations: process.env.DB_SKIP_MIGRATIONS === 'true',
|
||||
vectorExtension: getVectorExtension(),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import { IAlbumRepository } from 'src/interfaces/album.interface';
|
|||
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||
import { IAuditRepository } from 'src/interfaces/audit.interface';
|
||||
import { IConfigRepository } from 'src/interfaces/config.interface';
|
||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||
import { IDatabaseRepository } from 'src/interfaces/database.interface';
|
||||
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||
|
@ -39,6 +40,7 @@ import { AlbumRepository } from 'src/repositories/album.repository';
|
|||
import { ApiKeyRepository } from 'src/repositories/api-key.repository';
|
||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||
import { AuditRepository } from 'src/repositories/audit.repository';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import { CryptoRepository } from 'src/repositories/crypto.repository';
|
||||
import { DatabaseRepository } from 'src/repositories/database.repository';
|
||||
import { EventRepository } from 'src/repositories/event.repository';
|
||||
|
@ -74,6 +76,7 @@ export const repositories = [
|
|||
{ provide: IAlbumUserRepository, useClass: AlbumUserRepository },
|
||||
{ provide: IAssetRepository, useClass: AssetRepository },
|
||||
{ provide: IAuditRepository, useClass: AuditRepository },
|
||||
{ provide: IConfigRepository, useClass: ConfigRepository },
|
||||
{ provide: ICryptoRepository, useClass: CryptoRepository },
|
||||
{ provide: IDatabaseRepository, useClass: DatabaseRepository },
|
||||
{ provide: IEventRepository, useClass: EventRepository },
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
import { DatabaseExtension, EXTENSION_NAMES, IDatabaseRepository } from 'src/interfaces/database.interface';
|
||||
import { IConfigRepository } from 'src/interfaces/config.interface';
|
||||
import {
|
||||
DatabaseExtension,
|
||||
EXTENSION_NAMES,
|
||||
IDatabaseRepository,
|
||||
VectorExtension,
|
||||
} from 'src/interfaces/database.interface';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { DatabaseService } from 'src/services/database.service';
|
||||
import { newConfigRepositoryMock } from 'test/repositories/config.repository.mock';
|
||||
import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock';
|
||||
import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
|
||||
import { Mocked } from 'vitest';
|
||||
|
||||
describe(DatabaseService.name, () => {
|
||||
let sut: DatabaseService;
|
||||
let configMock: Mocked<IConfigRepository>;
|
||||
let databaseMock: Mocked<IDatabaseRepository>;
|
||||
let loggerMock: Mocked<ILoggerRepository>;
|
||||
let extensionRange: string;
|
||||
|
@ -16,9 +24,11 @@ describe(DatabaseService.name, () => {
|
|||
let versionAboveRange: string;
|
||||
|
||||
beforeEach(() => {
|
||||
configMock = newConfigRepositoryMock();
|
||||
databaseMock = newDatabaseRepositoryMock();
|
||||
loggerMock = newLoggerRepositoryMock();
|
||||
sut = new DatabaseService(databaseMock, loggerMock);
|
||||
|
||||
sut = new DatabaseService(configMock, databaseMock, loggerMock);
|
||||
|
||||
extensionRange = '0.2.x';
|
||||
databaseMock.getExtensionVersionRange.mockReturnValue(extensionRange);
|
||||
|
@ -33,11 +43,6 @@ describe(DatabaseService.name, () => {
|
|||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.DB_SKIP_MIGRATIONS;
|
||||
delete process.env.DB_VECTOR_EXTENSION;
|
||||
});
|
||||
|
||||
it('should work', () => {
|
||||
expect(sut).toBeDefined();
|
||||
});
|
||||
|
@ -50,12 +55,12 @@ describe(DatabaseService.name, () => {
|
|||
expect(databaseMock.getPostgresVersion).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
describe.each([
|
||||
describe.each(<Array<{ extension: VectorExtension; extensionName: string }>>[
|
||||
{ extension: DatabaseExtension.VECTOR, extensionName: EXTENSION_NAMES[DatabaseExtension.VECTOR] },
|
||||
{ extension: DatabaseExtension.VECTORS, extensionName: EXTENSION_NAMES[DatabaseExtension.VECTORS] },
|
||||
])('should work with $extensionName', ({ extension, extensionName }) => {
|
||||
beforeEach(() => {
|
||||
process.env.DB_VECTOR_EXTENSION = extensionName;
|
||||
configMock.getEnv.mockReturnValue({ database: { skipMigrations: false, vectorExtension: extension } });
|
||||
});
|
||||
|
||||
it(`should start up successfully with ${extension}`, async () => {
|
||||
|
@ -236,18 +241,28 @@ describe(DatabaseService.name, () => {
|
|||
expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
|
||||
expect(loggerMock.fatal).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should skip migrations if DB_SKIP_MIGRATIONS=true', async () => {
|
||||
process.env.DB_SKIP_MIGRATIONS = 'true';
|
||||
|
||||
await expect(sut.onBootstrap()).resolves.toBeUndefined();
|
||||
|
||||
expect(databaseMock.runMigrations).not.toHaveBeenCalled();
|
||||
it('should skip migrations if DB_SKIP_MIGRATIONS=true', async () => {
|
||||
configMock.getEnv.mockReturnValue({
|
||||
database: {
|
||||
skipMigrations: true,
|
||||
vectorExtension: DatabaseExtension.VECTORS,
|
||||
},
|
||||
});
|
||||
|
||||
await expect(sut.onBootstrap()).resolves.toBeUndefined();
|
||||
|
||||
expect(databaseMock.runMigrations).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it(`should throw error if pgvector extension could not be created`, async () => {
|
||||
process.env.DB_VECTOR_EXTENSION = 'pgvector';
|
||||
configMock.getEnv.mockReturnValue({
|
||||
database: {
|
||||
skipMigrations: true,
|
||||
vectorExtension: DatabaseExtension.VECTOR,
|
||||
},
|
||||
});
|
||||
databaseMock.getExtensionVersion.mockResolvedValue({
|
||||
installedVersion: null,
|
||||
availableVersion: minVersionInRange,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Duration } from 'luxon';
|
||||
import semver from 'semver';
|
||||
import { getVectorExtension } from 'src/database.config';
|
||||
import { OnEmit } from 'src/decorators';
|
||||
import { IConfigRepository } from 'src/interfaces/config.interface';
|
||||
import {
|
||||
DatabaseExtension,
|
||||
DatabaseLock,
|
||||
|
@ -67,6 +67,7 @@ export class DatabaseService {
|
|||
private reconnection?: NodeJS.Timeout;
|
||||
|
||||
constructor(
|
||||
@Inject(IConfigRepository) private configRepository: IConfigRepository,
|
||||
@Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository,
|
||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
||||
) {
|
||||
|
@ -85,7 +86,8 @@ export class DatabaseService {
|
|||
}
|
||||
|
||||
await this.databaseRepository.withLock(DatabaseLock.Migrations, async () => {
|
||||
const extension = getVectorExtension();
|
||||
const envData = this.configRepository.getEnv();
|
||||
const extension = envData.database.vectorExtension;
|
||||
const name = EXTENSION_NAMES[extension];
|
||||
const extensionRange = this.databaseRepository.getExtensionVersionRange(extension);
|
||||
|
||||
|
@ -116,7 +118,8 @@ export class DatabaseService {
|
|||
|
||||
await this.checkReindexing();
|
||||
|
||||
if (process.env.DB_SKIP_MIGRATIONS !== 'true') {
|
||||
const { database } = this.configRepository.getEnv();
|
||||
if (!database.skipMigrations) {
|
||||
await this.databaseRepository.runMigrations();
|
||||
}
|
||||
});
|
||||
|
|
14
server/test/repositories/config.repository.mock.ts
Normal file
14
server/test/repositories/config.repository.mock.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { IConfigRepository } from 'src/interfaces/config.interface';
|
||||
import { DatabaseExtension } from 'src/interfaces/database.interface';
|
||||
import { Mocked, vitest } from 'vitest';
|
||||
|
||||
export const newConfigRepositoryMock = (): Mocked<IConfigRepository> => {
|
||||
return {
|
||||
getEnv: vitest.fn().mockReturnValue({
|
||||
database: {
|
||||
skipMigration: false,
|
||||
vectorExtension: DatabaseExtension.VECTORS,
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
Loading…
Reference in a new issue