mirror of
https://github.com/immich-app/immich.git
synced 2026-03-01 18:19:10 +03:00
@@ -1,5 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Insertable, Kysely, NotNull, Selectable, UpdateResult, Updateable, sql } from 'kysely';
|
||||
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
||||
import { isEmpty, isUndefined, omitBy } from 'lodash';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { Stack } from 'src/database';
|
||||
@@ -335,6 +336,23 @@ export class AssetRepository {
|
||||
return count;
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
getFileSamples() {
|
||||
return this.db
|
||||
.selectFrom('asset')
|
||||
.select((eb) => [
|
||||
'asset.id',
|
||||
'asset.originalPath',
|
||||
'asset.sidecarPath',
|
||||
'asset.encodedVideoPath',
|
||||
jsonArrayFrom(eb.selectFrom('asset_file').select('path').whereRef('asset.id', '=', 'asset_file.assetId')).as(
|
||||
'files',
|
||||
),
|
||||
])
|
||||
.limit(sql.lit(3))
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getById(id: string, { exifInfo, faces, files, library, owner, smartSearch, stack, tags }: GetByIdsRelations = {}) {
|
||||
return this.db
|
||||
|
||||
@@ -13,6 +13,7 @@ const resetEnv = () => {
|
||||
'IMMICH_WORKERS_EXCLUDE',
|
||||
'IMMICH_TRUSTED_PROXIES',
|
||||
'IMMICH_API_METRICS_PORT',
|
||||
'IMMICH_MEDIA_LOCATION',
|
||||
'IMMICH_MICROSERVICES_METRICS_PORT',
|
||||
'IMMICH_TELEMETRY_INCLUDE',
|
||||
'IMMICH_TELEMETRY_EXCLUDE',
|
||||
@@ -76,6 +77,13 @@ describe('getEnv', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('IMMICH_MEDIA_LOCATION', () => {
|
||||
it('should throw an error for relative paths', () => {
|
||||
process.env.IMMICH_MEDIA_LOCATION = './relative/path';
|
||||
expect(() => getEnv()).toThrowError('IMMICH_MEDIA_LOCATION must be an absolute path');
|
||||
});
|
||||
});
|
||||
|
||||
describe('database', () => {
|
||||
it('should use defaults', () => {
|
||||
const { database } = getEnv();
|
||||
@@ -95,7 +103,7 @@ describe('getEnv', () => {
|
||||
|
||||
it('should validate DB_SSL_MODE', () => {
|
||||
process.env.DB_SSL_MODE = 'invalid';
|
||||
expect(() => getEnv()).toThrowError('Invalid environment variables: DB_SSL_MODE');
|
||||
expect(() => getEnv()).toThrowError('DB_SSL_MODE must be one of the following values:');
|
||||
});
|
||||
|
||||
it('should accept a valid DB_SSL_MODE', () => {
|
||||
@@ -239,7 +247,7 @@ describe('getEnv', () => {
|
||||
|
||||
it('should reject invalid trusted proxies', () => {
|
||||
process.env.IMMICH_TRUSTED_PROXIES = '10.1';
|
||||
expect(() => getEnv()).toThrowError('Invalid environment variables: IMMICH_TRUSTED_PROXIES');
|
||||
expect(() => getEnv()).toThrow('IMMICH_TRUSTED_PROXIES must be an ip address, or ip address range');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -131,9 +131,11 @@ const getEnv = (): EnvData => {
|
||||
const dto = plainToInstance(EnvDto, process.env);
|
||||
const errors = validateSync(dto);
|
||||
if (errors.length > 0) {
|
||||
throw new Error(
|
||||
`Invalid environment variables: ${errors.map((error) => `${error.property}=${error.value}`).join(', ')}`,
|
||||
);
|
||||
const messages = [`Invalid environment variables: `];
|
||||
for (const error of errors) {
|
||||
messages.push(` - ${error.property}=${error.value} (${Object.values(error.constraints || {}).join(', ')})`);
|
||||
}
|
||||
throw new Error(messages.join('\n'));
|
||||
}
|
||||
|
||||
const includedWorkers = asSet(dto.IMMICH_WORKERS_INCLUDE, [ImmichWorker.Api, ImmichWorker.Microservices]);
|
||||
|
||||
@@ -436,6 +436,39 @@ export class DatabaseRepository {
|
||||
this.logger.debug('Finished running kysely migrations');
|
||||
}
|
||||
|
||||
async migrateFilePaths(sourceFolder: string, targetFolder: string): Promise<void> {
|
||||
// escaping regex special characters with a backslash
|
||||
const sourceRegex = '^' + sourceFolder.replaceAll(/[-[\]{}()*+?.,\\^$|#\s]/g, String.raw`\$&`);
|
||||
const source = sql.raw(`'${sourceRegex}'`);
|
||||
const target = sql.lit(targetFolder);
|
||||
|
||||
await this.db.transaction().execute(async (tx) => {
|
||||
await tx
|
||||
.updateTable('asset')
|
||||
.set((eb) => ({
|
||||
originalPath: eb.fn('REGEXP_REPLACE', ['originalPath', source, target]),
|
||||
encodedVideoPath: eb.fn('REGEXP_REPLACE', ['encodedVideoPath', source, target]),
|
||||
sidecarPath: eb.fn('REGEXP_REPLACE', ['sidecarPath', source, target]),
|
||||
}))
|
||||
.execute();
|
||||
|
||||
await tx
|
||||
.updateTable('asset_file')
|
||||
.set((eb) => ({ path: eb.fn('REGEXP_REPLACE', ['path', source, target]) }))
|
||||
.execute();
|
||||
|
||||
await tx
|
||||
.updateTable('person')
|
||||
.set((eb) => ({ thumbnailPath: eb.fn('REGEXP_REPLACE', ['thumbnailPath', source, target]) }))
|
||||
.execute();
|
||||
|
||||
await tx
|
||||
.updateTable('user')
|
||||
.set((eb) => ({ profileImagePath: eb.fn('REGEXP_REPLACE', ['profileImagePath', source, target]) }))
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
|
||||
async withLock<R>(lock: DatabaseLock, callback: () => Promise<R>): Promise<R> {
|
||||
let res;
|
||||
await this.asyncLock.acquire(DatabaseLock[lock], async () => {
|
||||
|
||||
@@ -142,6 +142,16 @@ export class PersonRepository {
|
||||
.stream();
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
getFileSamples() {
|
||||
return this.db
|
||||
.selectFrom('person')
|
||||
.select(['id', 'thumbnailPath'])
|
||||
.where('thumbnailPath', '!=', sql.lit(''))
|
||||
.limit(sql.lit(3))
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [{ take: 1, skip: 0 }, DummyValue.UUID] })
|
||||
async getAllForUser(pagination: PaginationOptions, userId: string, options?: PersonSearchOptions) {
|
||||
const items = await this.db
|
||||
|
||||
@@ -79,6 +79,16 @@ export class UserRepository {
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
getFileSamples() {
|
||||
return this.db
|
||||
.selectFrom('user')
|
||||
.select(['id', 'profileImagePath'])
|
||||
.where('profileImagePath', '!=', sql.lit(''))
|
||||
.limit(sql.lit(3))
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
async hasAdmin(): Promise<boolean> {
|
||||
const admin = await this.db
|
||||
|
||||
Reference in New Issue
Block a user