Merge branch 'main' into main
This commit is contained in:
@@ -6,6 +6,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ClsModule } from 'nestjs-cls';
|
||||
import { OpenTelemetryModule } from 'nestjs-otel';
|
||||
import { commands } from 'src/commands';
|
||||
import { IWorker } from 'src/constants';
|
||||
import { controllers } from 'src/controllers';
|
||||
import { entities } from 'src/entities';
|
||||
import { ImmichWorker } from 'src/enum';
|
||||
@@ -57,12 +58,9 @@ const imports = [
|
||||
TypeOrmModule.forFeature(entities),
|
||||
];
|
||||
|
||||
abstract class BaseModule implements OnModuleInit, OnModuleDestroy {
|
||||
private get worker() {
|
||||
return this.getWorker();
|
||||
}
|
||||
|
||||
class BaseModule implements OnModuleInit, OnModuleDestroy {
|
||||
constructor(
|
||||
@Inject(IWorker) private worker: ImmichWorker,
|
||||
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
||||
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
||||
@@ -71,8 +69,6 @@ abstract class BaseModule implements OnModuleInit, OnModuleDestroy {
|
||||
logger.setAppName(this.worker);
|
||||
}
|
||||
|
||||
abstract getWorker(): ImmichWorker;
|
||||
|
||||
async onModuleInit() {
|
||||
this.telemetryRepository.setup({ repositories: repositories.map(({ useClass }) => useClass) });
|
||||
|
||||
@@ -94,23 +90,15 @@ abstract class BaseModule implements OnModuleInit, OnModuleDestroy {
|
||||
@Module({
|
||||
imports: [...imports, ScheduleModule.forRoot()],
|
||||
controllers: [...controllers],
|
||||
providers: [...common, ...middleware],
|
||||
providers: [...common, ...middleware, { provide: IWorker, useValue: ImmichWorker.API }],
|
||||
})
|
||||
export class ApiModule extends BaseModule {
|
||||
getWorker() {
|
||||
return ImmichWorker.API;
|
||||
}
|
||||
}
|
||||
export class ApiModule extends BaseModule {}
|
||||
|
||||
@Module({
|
||||
imports: [...imports],
|
||||
providers: [...common, SchedulerRegistry],
|
||||
providers: [...common, { provide: IWorker, useValue: ImmichWorker.MICROSERVICES }, SchedulerRegistry],
|
||||
})
|
||||
export class MicroservicesModule extends BaseModule {
|
||||
getWorker() {
|
||||
return ImmichWorker.MICROSERVICES;
|
||||
}
|
||||
}
|
||||
export class MicroservicesModule extends BaseModule {}
|
||||
|
||||
@Module({
|
||||
imports: [...imports],
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ImmichWorker } from 'src/enum';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
|
||||
const main = async () => {
|
||||
const { workers, port } = new ConfigRepository().getEnv();
|
||||
const { host, workers, port } = new ConfigRepository().getEnv();
|
||||
if (!workers.includes(ImmichWorker.API)) {
|
||||
process.exit();
|
||||
}
|
||||
@@ -11,7 +11,7 @@ const main = async () => {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 2000);
|
||||
try {
|
||||
const response = await fetch(`http://localhost:${port}/api/server/ping`, {
|
||||
const response = await fetch(`http://${host || 'localhost'}:${port}/api/server/ping`, {
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ export const ADDED_IN_PREFIX = 'This property was added in ';
|
||||
|
||||
export const SALT_ROUNDS = 10;
|
||||
|
||||
export const IWorker = 'IWorker';
|
||||
|
||||
const { version } = JSON.parse(readFileSync('./package.json', 'utf8'));
|
||||
export const serverVersion = new SemVer(version);
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { BadRequestException, Inject } from '@nestjs/common';
|
||||
import { BadRequestException, Inject, Optional } from '@nestjs/common';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import { SystemConfig } from 'src/config';
|
||||
import { SALT_ROUNDS } from 'src/constants';
|
||||
import { IWorker, SALT_ROUNDS } from 'src/constants';
|
||||
import { StorageCore } from 'src/cores/storage.core';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { ImmichWorker } from 'src/enum';
|
||||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||
import { IActivityRepository } from 'src/interfaces/activity.interface';
|
||||
import { IAlbumUserRepository } from 'src/interfaces/album-user.interface';
|
||||
@@ -49,6 +50,7 @@ export class BaseService {
|
||||
protected storageCore: StorageCore;
|
||||
|
||||
constructor(
|
||||
@Inject(IWorker) @Optional() protected worker: ImmichWorker | undefined,
|
||||
@Inject(ILoggerRepository) protected logger: ILoggerRepository,
|
||||
@Inject(IAccessRepository) protected accessRepository: IAccessRepository,
|
||||
@Inject(IActivityRepository) protected activityRepository: IActivityRepository,
|
||||
|
||||
@@ -31,11 +31,23 @@ describe(SearchService.name, () => {
|
||||
|
||||
describe('getDuplicates', () => {
|
||||
it('should get duplicates', async () => {
|
||||
assetMock.getDuplicates.mockResolvedValue([assetStub.hasDupe]);
|
||||
assetMock.getDuplicates.mockResolvedValue([assetStub.hasDupe, assetStub.hasDupe]);
|
||||
await expect(sut.getDuplicates(authStub.admin)).resolves.toEqual([
|
||||
{ duplicateId: assetStub.hasDupe.duplicateId, assets: [expect.objectContaining({ id: assetStub.hasDupe.id })] },
|
||||
{
|
||||
duplicateId: assetStub.hasDupe.duplicateId,
|
||||
assets: [
|
||||
expect.objectContaining({ id: assetStub.hasDupe.id }),
|
||||
expect.objectContaining({ id: assetStub.hasDupe.id }),
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should update assets with duplicateId', async () => {
|
||||
assetMock.getDuplicates.mockResolvedValue([assetStub.hasDupe]);
|
||||
await expect(sut.getDuplicates(authStub.admin)).resolves.toEqual([]);
|
||||
expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.hasDupe.id], { duplicateId: null });
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleQueueSearchDuplicates', () => {
|
||||
|
||||
@@ -16,8 +16,24 @@ import { usePagination } from 'src/utils/pagination';
|
||||
export class DuplicateService extends BaseService {
|
||||
async getDuplicates(auth: AuthDto): Promise<DuplicateResponseDto[]> {
|
||||
const res = await this.assetRepository.getDuplicates({ userIds: [auth.user.id] });
|
||||
|
||||
return mapDuplicateResponse(res.map((a) => mapAsset(a, { auth, withStack: true })));
|
||||
const uniqueAssetIds: string[] = [];
|
||||
const duplicates = mapDuplicateResponse(res.map((a) => mapAsset(a, { auth, withStack: true }))).filter(
|
||||
(duplicate) => {
|
||||
if (duplicate.assets.length === 1) {
|
||||
uniqueAssetIds.push(duplicate.assets[0].id);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
);
|
||||
if (uniqueAssetIds.length > 0) {
|
||||
try {
|
||||
await this.assetRepository.updateAll(uniqueAssetIds, { duplicateId: null });
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Failed to remove duplicateId from assets: ${error.message}`);
|
||||
}
|
||||
}
|
||||
return duplicates;
|
||||
}
|
||||
|
||||
@OnJob({ name: JobName.QUEUE_DUPLICATE_DETECTION, queue: QueueName.DUPLICATE_DETECTION })
|
||||
|
||||
@@ -18,7 +18,9 @@ describe(JobService.name, () => {
|
||||
let telemetryMock: Mocked<ITelemetryRepository>;
|
||||
|
||||
beforeEach(() => {
|
||||
({ sut, assetMock, jobMock, loggerMock, telemetryMock } = newTestService(JobService));
|
||||
({ sut, assetMock, jobMock, loggerMock, telemetryMock } = newTestService(JobService, {
|
||||
worker: ImmichWorker.MICROSERVICES,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should work', () => {
|
||||
@@ -27,7 +29,6 @@ describe(JobService.name, () => {
|
||||
|
||||
describe('onConfigUpdate', () => {
|
||||
it('should update concurrency', () => {
|
||||
sut.onBootstrap(ImmichWorker.MICROSERVICES);
|
||||
sut.onConfigUpdate({ oldConfig: defaults, newConfig: defaults });
|
||||
|
||||
expect(jobMock.setConcurrency).toHaveBeenCalledTimes(15);
|
||||
|
||||
@@ -38,16 +38,9 @@ const asJobItem = (dto: JobCreateDto): JobItem => {
|
||||
|
||||
@Injectable()
|
||||
export class JobService extends BaseService {
|
||||
private isMicroservices = false;
|
||||
|
||||
@OnEvent({ name: 'app.bootstrap' })
|
||||
onBootstrap(app: ArgOf<'app.bootstrap'>) {
|
||||
this.isMicroservices = app === ImmichWorker.MICROSERVICES;
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'config.update', server: true })
|
||||
onConfigUpdate({ newConfig: config }: ArgOf<'config.update'>) {
|
||||
if (!this.isMicroservices) {
|
||||
if (this.worker !== ImmichWorker.MICROSERVICES) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user