mirror of
https://github.com/immich-app/immich.git
synced 2024-11-28 09:33:27 +02:00
refactor(server): notification events (#10754)
This commit is contained in:
parent
0b88bef157
commit
81d12c0586
@ -4,17 +4,36 @@ import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.d
|
|||||||
|
|
||||||
export const IEventRepository = 'IEventRepository';
|
export const IEventRepository = 'IEventRepository';
|
||||||
|
|
||||||
|
export type SystemConfigUpdateEvent = { newConfig: SystemConfig; oldConfig: SystemConfig };
|
||||||
|
export type AlbumUpdateEvent = {
|
||||||
|
id: string;
|
||||||
|
/** user id */
|
||||||
|
updatedBy: string;
|
||||||
|
};
|
||||||
|
export type AlbumInviteEvent = { id: string; userId: string };
|
||||||
|
export type UserSignupEvent = { notify: boolean; id: string; tempPassword?: string };
|
||||||
|
|
||||||
type MaybePromise<T> = Promise<T> | T;
|
type MaybePromise<T> = Promise<T> | T;
|
||||||
|
type Handler<T = undefined> = (data: T) => MaybePromise<void>;
|
||||||
|
|
||||||
const noop = () => {};
|
const noop = () => {};
|
||||||
const dummyHandlers = {
|
const dummyHandlers = {
|
||||||
onBootstrapEvent: noop as (app: 'api' | 'microservices') => MaybePromise<void>,
|
// app events
|
||||||
|
onBootstrapEvent: noop as Handler<'api' | 'microservices'>,
|
||||||
onShutdownEvent: noop as () => MaybePromise<void>,
|
onShutdownEvent: noop as () => MaybePromise<void>,
|
||||||
onConfigUpdateEvent: noop as (update: SystemConfigUpdate) => MaybePromise<void>,
|
|
||||||
onConfigValidateEvent: noop as (update: SystemConfigUpdate) => MaybePromise<void>,
|
// config events
|
||||||
|
onConfigUpdateEvent: noop as Handler<SystemConfigUpdateEvent>,
|
||||||
|
onConfigValidateEvent: noop as Handler<SystemConfigUpdateEvent>,
|
||||||
|
|
||||||
|
// album events
|
||||||
|
onAlbumUpdateEvent: noop as Handler<AlbumUpdateEvent>,
|
||||||
|
onAlbumInviteEvent: noop as Handler<AlbumInviteEvent>,
|
||||||
|
|
||||||
|
// user events
|
||||||
|
onUserSignupEvent: noop as Handler<UserSignupEvent>,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SystemConfigUpdate = { newConfig: SystemConfig; oldConfig: SystemConfig };
|
|
||||||
export type EventHandlers = typeof dummyHandlers;
|
export type EventHandlers = typeof dummyHandlers;
|
||||||
export type EmitEvent = keyof EventHandlers;
|
export type EmitEvent = keyof EventHandlers;
|
||||||
export type EmitEventHandler<T extends EmitEvent> = (...args: Parameters<EventHandlers[T]>) => MaybePromise<void>;
|
export type EmitEventHandler<T extends EmitEvent> = (...args: Parameters<EventHandlers[T]>) => MaybePromise<void>;
|
||||||
|
@ -5,7 +5,7 @@ import { AlbumUserRole } from 'src/entities/album-user.entity';
|
|||||||
import { IAlbumUserRepository } from 'src/interfaces/album-user.interface';
|
import { IAlbumUserRepository } from 'src/interfaces/album-user.interface';
|
||||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||||
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
import { AlbumService } from 'src/services/album.service';
|
import { AlbumService } from 'src/services/album.service';
|
||||||
import { albumStub } from 'test/fixtures/album.stub';
|
import { albumStub } from 'test/fixtures/album.stub';
|
||||||
@ -15,7 +15,7 @@ import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositorie
|
|||||||
import { newAlbumUserRepositoryMock } from 'test/repositories/album-user.repository.mock';
|
import { newAlbumUserRepositoryMock } from 'test/repositories/album-user.repository.mock';
|
||||||
import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock';
|
import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock';
|
||||||
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
|
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
|
||||||
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
|
import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
|
||||||
import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
|
import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
|
||||||
import { Mocked } from 'vitest';
|
import { Mocked } from 'vitest';
|
||||||
|
|
||||||
@ -24,19 +24,19 @@ describe(AlbumService.name, () => {
|
|||||||
let accessMock: IAccessRepositoryMock;
|
let accessMock: IAccessRepositoryMock;
|
||||||
let albumMock: Mocked<IAlbumRepository>;
|
let albumMock: Mocked<IAlbumRepository>;
|
||||||
let assetMock: Mocked<IAssetRepository>;
|
let assetMock: Mocked<IAssetRepository>;
|
||||||
|
let eventMock: Mocked<IEventRepository>;
|
||||||
let userMock: Mocked<IUserRepository>;
|
let userMock: Mocked<IUserRepository>;
|
||||||
let albumUserMock: Mocked<IAlbumUserRepository>;
|
let albumUserMock: Mocked<IAlbumUserRepository>;
|
||||||
let jobMock: Mocked<IJobRepository>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
accessMock = newAccessRepositoryMock();
|
accessMock = newAccessRepositoryMock();
|
||||||
albumMock = newAlbumRepositoryMock();
|
albumMock = newAlbumRepositoryMock();
|
||||||
assetMock = newAssetRepositoryMock();
|
assetMock = newAssetRepositoryMock();
|
||||||
|
eventMock = newEventRepositoryMock();
|
||||||
userMock = newUserRepositoryMock();
|
userMock = newUserRepositoryMock();
|
||||||
albumUserMock = newAlbumUserRepositoryMock();
|
albumUserMock = newAlbumUserRepositoryMock();
|
||||||
jobMock = newJobRepositoryMock();
|
|
||||||
|
|
||||||
sut = new AlbumService(accessMock, albumMock, assetMock, userMock, albumUserMock, jobMock);
|
sut = new AlbumService(accessMock, albumMock, assetMock, eventMock, userMock, albumUserMock);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
@ -381,14 +381,10 @@ describe(AlbumService.name, () => {
|
|||||||
userId: authStub.user2.user.id,
|
userId: authStub.user2.user.id,
|
||||||
albumId: albumStub.sharedWithAdmin.id,
|
albumId: albumStub.sharedWithAdmin.id,
|
||||||
});
|
});
|
||||||
expect(jobMock.queue.mock.calls).toEqual([
|
expect(eventMock.emit).toHaveBeenCalledWith('onAlbumInviteEvent', {
|
||||||
[
|
id: albumStub.sharedWithAdmin.id,
|
||||||
{
|
userId: userStub.user2.id,
|
||||||
name: JobName.NOTIFY_ALBUM_INVITE,
|
});
|
||||||
data: { id: albumStub.sharedWithAdmin.id, recipientId: authStub.user2.user.id },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -573,14 +569,10 @@ describe(AlbumService.name, () => {
|
|||||||
albumThumbnailAssetId: 'asset-1',
|
albumThumbnailAssetId: 'asset-1',
|
||||||
});
|
});
|
||||||
expect(albumMock.addAssetIds).toHaveBeenCalledWith('album-123', ['asset-1', 'asset-2', 'asset-3']);
|
expect(albumMock.addAssetIds).toHaveBeenCalledWith('album-123', ['asset-1', 'asset-2', 'asset-3']);
|
||||||
expect(jobMock.queue.mock.calls).toEqual([
|
expect(eventMock.emit).toHaveBeenCalledWith('onAlbumUpdateEvent', {
|
||||||
[
|
id: 'album-123',
|
||||||
{
|
updatedBy: authStub.admin.user.id,
|
||||||
name: JobName.NOTIFY_ALBUM_UPDATE,
|
});
|
||||||
data: { id: 'album-123', senderId: authStub.admin.user.id },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not set the thumbnail if the album has one already', async () => {
|
it('should not set the thumbnail if the album has one already', async () => {
|
||||||
@ -621,14 +613,10 @@ describe(AlbumService.name, () => {
|
|||||||
albumThumbnailAssetId: 'asset-1',
|
albumThumbnailAssetId: 'asset-1',
|
||||||
});
|
});
|
||||||
expect(albumMock.addAssetIds).toHaveBeenCalledWith('album-123', ['asset-1', 'asset-2', 'asset-3']);
|
expect(albumMock.addAssetIds).toHaveBeenCalledWith('album-123', ['asset-1', 'asset-2', 'asset-3']);
|
||||||
expect(jobMock.queue.mock.calls).toEqual([
|
expect(eventMock.emit).toHaveBeenCalledWith('onAlbumUpdateEvent', {
|
||||||
[
|
id: 'album-123',
|
||||||
{
|
updatedBy: authStub.user1.user.id,
|
||||||
name: JobName.NOTIFY_ALBUM_UPDATE,
|
});
|
||||||
data: { id: 'album-123', senderId: authStub.user1.user.id },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow a shared user with viewer access to add assets', async () => {
|
it('should not allow a shared user with viewer access to add assets', async () => {
|
||||||
|
@ -21,7 +21,7 @@ import { IAccessRepository } from 'src/interfaces/access.interface';
|
|||||||
import { IAlbumUserRepository } from 'src/interfaces/album-user.interface';
|
import { IAlbumUserRepository } from 'src/interfaces/album-user.interface';
|
||||||
import { AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from 'src/interfaces/album.interface';
|
import { AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from 'src/interfaces/album.interface';
|
||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||||
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
import { addAssets, removeAssets } from 'src/utils/asset.util';
|
import { addAssets, removeAssets } from 'src/utils/asset.util';
|
||||||
|
|
||||||
@ -32,9 +32,9 @@ export class AlbumService {
|
|||||||
@Inject(IAccessRepository) private accessRepository: IAccessRepository,
|
@Inject(IAccessRepository) private accessRepository: IAccessRepository,
|
||||||
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
||||||
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
||||||
|
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
||||||
@Inject(IUserRepository) private userRepository: IUserRepository,
|
@Inject(IUserRepository) private userRepository: IUserRepository,
|
||||||
@Inject(IAlbumUserRepository) private albumUserRepository: IAlbumUserRepository,
|
@Inject(IAlbumUserRepository) private albumUserRepository: IAlbumUserRepository,
|
||||||
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
|
||||||
) {
|
) {
|
||||||
this.access = AccessCore.create(accessRepository);
|
this.access = AccessCore.create(accessRepository);
|
||||||
}
|
}
|
||||||
@ -188,12 +188,9 @@ export class AlbumService {
|
|||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
albumThumbnailAssetId: album.albumThumbnailAssetId ?? firstNewAssetId,
|
albumThumbnailAssetId: album.albumThumbnailAssetId ?? firstNewAssetId,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
await this.jobRepository.queue({
|
await this.eventRepository.emit('onAlbumUpdateEvent', { id, updatedBy: auth.user.id });
|
||||||
name: JobName.NOTIFY_ALBUM_UPDATE,
|
}
|
||||||
data: { id, senderId: auth.user.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
@ -240,11 +237,7 @@ export class AlbumService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.albumUserRepository.create({ userId: userId, albumId: id, role });
|
await this.albumUserRepository.create({ userId: userId, albumId: id, role });
|
||||||
|
await this.eventRepository.emit('onAlbumInviteEvent', { id, userId });
|
||||||
await this.jobRepository.queue({
|
|
||||||
name: JobName.NOTIFY_ALBUM_INVITE,
|
|
||||||
data: { id: album.id, recipientId: user.id },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.findOrFail(id, { withAssets: true }).then(mapAlbumWithoutAssets);
|
return this.findOrFail(id, { withAssets: true }).then(mapAlbumWithoutAssets);
|
||||||
|
@ -22,7 +22,7 @@ import { LibraryEntity } from 'src/entities/library.entity';
|
|||||||
import { IAssetRepository, WithProperty } from 'src/interfaces/asset.interface';
|
import { IAssetRepository, WithProperty } from 'src/interfaces/asset.interface';
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface';
|
import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface';
|
||||||
import { OnEvents, SystemConfigUpdate } from 'src/interfaces/event.interface';
|
import { OnEvents, SystemConfigUpdateEvent } from 'src/interfaces/event.interface';
|
||||||
import {
|
import {
|
||||||
IBaseJob,
|
IBaseJob,
|
||||||
IEntityJob,
|
IEntityJob,
|
||||||
@ -102,7 +102,7 @@ export class LibraryService implements OnEvents {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onConfigValidateEvent({ newConfig }: SystemConfigUpdate) {
|
onConfigValidateEvent({ newConfig }: SystemConfigUpdateEvent) {
|
||||||
const { scan } = newConfig.library;
|
const { scan } = newConfig.library;
|
||||||
if (!validateCronExpression(scan.cronExpression)) {
|
if (!validateCronExpression(scan.cronExpression)) {
|
||||||
throw new Error(`Invalid cron expression ${scan.cronExpression}`);
|
throw new Error(`Invalid cron expression ${scan.cronExpression}`);
|
||||||
|
@ -5,7 +5,13 @@ import { SystemConfigSmtpDto } from 'src/dtos/system-config.dto';
|
|||||||
import { AlbumEntity } from 'src/entities/album.entity';
|
import { AlbumEntity } from 'src/entities/album.entity';
|
||||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||||
import { OnEvents, SystemConfigUpdate } from 'src/interfaces/event.interface';
|
import {
|
||||||
|
AlbumInviteEvent,
|
||||||
|
AlbumUpdateEvent,
|
||||||
|
OnEvents,
|
||||||
|
SystemConfigUpdateEvent,
|
||||||
|
UserSignupEvent,
|
||||||
|
} from 'src/interfaces/event.interface';
|
||||||
import {
|
import {
|
||||||
IEmailJob,
|
IEmailJob,
|
||||||
IJobRepository,
|
IJobRepository,
|
||||||
@ -38,7 +44,7 @@ export class NotificationService implements OnEvents {
|
|||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, logger);
|
this.configCore = SystemConfigCore.create(systemMetadataRepository, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onConfigValidateEvent({ newConfig }: SystemConfigUpdate) {
|
async onConfigValidateEvent({ newConfig }: SystemConfigUpdateEvent) {
|
||||||
try {
|
try {
|
||||||
if (newConfig.notifications.smtp.enabled) {
|
if (newConfig.notifications.smtp.enabled) {
|
||||||
await this.notificationRepository.verifySmtp(newConfig.notifications.smtp.transport);
|
await this.notificationRepository.verifySmtp(newConfig.notifications.smtp.transport);
|
||||||
@ -49,6 +55,20 @@ export class NotificationService implements OnEvents {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async onUserSignupEvent({ notify, id, tempPassword }: UserSignupEvent) {
|
||||||
|
if (notify) {
|
||||||
|
await this.jobRepository.queue({ name: JobName.NOTIFY_SIGNUP, data: { id, tempPassword } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onAlbumUpdateEvent({ id, updatedBy }: AlbumUpdateEvent) {
|
||||||
|
await this.jobRepository.queue({ name: JobName.NOTIFY_ALBUM_UPDATE, data: { id, senderId: updatedBy } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async onAlbumInviteEvent({ id, userId }: AlbumInviteEvent) {
|
||||||
|
await this.jobRepository.queue({ name: JobName.NOTIFY_ALBUM_INVITE, data: { id, recipientId: userId } });
|
||||||
|
}
|
||||||
|
|
||||||
async sendTestEmail(id: string, dto: SystemConfigSmtpDto) {
|
async sendTestEmail(id: string, dto: SystemConfigSmtpDto) {
|
||||||
const user = await this.userRepository.get(id, { withDeleted: false });
|
const user = await this.userRepository.get(id, { withDeleted: false });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
import { SystemConfigCore } from 'src/cores/system-config.core';
|
||||||
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
|
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
|
||||||
import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface';
|
import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface';
|
||||||
import { OnEvents, SystemConfigUpdate } from 'src/interfaces/event.interface';
|
import { OnEvents, SystemConfigUpdateEvent } from 'src/interfaces/event.interface';
|
||||||
import {
|
import {
|
||||||
IBaseJob,
|
IBaseJob,
|
||||||
IEntityJob,
|
IEntityJob,
|
||||||
@ -50,7 +50,7 @@ export class SmartInfoService implements OnEvents {
|
|||||||
await this.jobRepository.resume(QueueName.SMART_SEARCH);
|
await this.jobRepository.resume(QueueName.SMART_SEARCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onConfigUpdateEvent({ oldConfig, newConfig }: SystemConfigUpdate) {
|
async onConfigUpdateEvent({ oldConfig, newConfig }: SystemConfigUpdateEvent) {
|
||||||
if (oldConfig.machineLearning.clip.modelName !== newConfig.machineLearning.clip.modelName) {
|
if (oldConfig.machineLearning.clip.modelName !== newConfig.machineLearning.clip.modelName) {
|
||||||
await this.repository.init(newConfig.machineLearning.clip.modelName);
|
await this.repository.init(newConfig.machineLearning.clip.modelName);
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ import { IAlbumRepository } from 'src/interfaces/album.interface';
|
|||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface';
|
import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface';
|
||||||
import { OnEvents, SystemConfigUpdate } from 'src/interfaces/event.interface';
|
import { OnEvents, SystemConfigUpdateEvent } from 'src/interfaces/event.interface';
|
||||||
import { IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobStatus } from 'src/interfaces/job.interface';
|
import { IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobStatus } from 'src/interfaces/job.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { IMoveRepository } from 'src/interfaces/move.interface';
|
import { IMoveRepository } from 'src/interfaces/move.interface';
|
||||||
@ -87,7 +87,7 @@ export class StorageTemplateService implements OnEvents {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onConfigValidateEvent({ newConfig }: SystemConfigUpdate) {
|
onConfigValidateEvent({ newConfig }: SystemConfigUpdateEvent) {
|
||||||
try {
|
try {
|
||||||
const { compiled } = this.compile(newConfig.storageTemplate.template);
|
const { compiled } = this.compile(newConfig.storageTemplate.template);
|
||||||
this.render(compiled, {
|
this.render(compiled, {
|
||||||
|
@ -20,7 +20,7 @@ import {
|
|||||||
IEventRepository,
|
IEventRepository,
|
||||||
OnEvents,
|
OnEvents,
|
||||||
ServerEvent,
|
ServerEvent,
|
||||||
SystemConfigUpdate,
|
SystemConfigUpdateEvent,
|
||||||
} from 'src/interfaces/event.interface';
|
} from 'src/interfaces/event.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
@ -42,11 +42,7 @@ export class SystemConfigService implements OnEvents {
|
|||||||
@EventHandlerOptions({ priority: -100 })
|
@EventHandlerOptions({ priority: -100 })
|
||||||
async onBootstrapEvent() {
|
async onBootstrapEvent() {
|
||||||
const config = await this.core.getConfig({ withCache: false });
|
const config = await this.core.getConfig({ withCache: false });
|
||||||
this.config$.next(config);
|
this.core.config$.next(config);
|
||||||
}
|
|
||||||
|
|
||||||
get config$() {
|
|
||||||
return this.core.config$;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getConfig(): Promise<SystemConfigDto> {
|
async getConfig(): Promise<SystemConfigDto> {
|
||||||
@ -58,7 +54,7 @@ export class SystemConfigService implements OnEvents {
|
|||||||
return mapConfig(defaults);
|
return mapConfig(defaults);
|
||||||
}
|
}
|
||||||
|
|
||||||
onConfigValidateEvent({ newConfig, oldConfig }: SystemConfigUpdate) {
|
onConfigValidateEvent({ newConfig, oldConfig }: SystemConfigUpdateEvent) {
|
||||||
if (!_.isEqual(instanceToPlain(newConfig.logging), oldConfig.logging) && this.getEnvLogLevel()) {
|
if (!_.isEqual(instanceToPlain(newConfig.logging), oldConfig.logging) && this.getEnvLogLevel()) {
|
||||||
throw new Error('Logging cannot be changed while the environment variable IMMICH_LOG_LEVEL is set.');
|
throw new Error('Logging cannot be changed while the environment variable IMMICH_LOG_LEVEL is set.');
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import { mapUserAdmin } from 'src/dtos/user.dto';
|
|||||||
import { UserStatus } from 'src/entities/user.entity';
|
import { UserStatus } from 'src/entities/user.entity';
|
||||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
|
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
@ -11,6 +12,7 @@ import { authStub } from 'test/fixtures/auth.stub';
|
|||||||
import { userStub } from 'test/fixtures/user.stub';
|
import { userStub } from 'test/fixtures/user.stub';
|
||||||
import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock';
|
import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock';
|
||||||
import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
|
import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
|
||||||
|
import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
|
||||||
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
|
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
|
||||||
import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
|
import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
|
||||||
import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
|
import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
|
||||||
@ -18,21 +20,22 @@ import { Mocked, describe } from 'vitest';
|
|||||||
|
|
||||||
describe(UserAdminService.name, () => {
|
describe(UserAdminService.name, () => {
|
||||||
let sut: UserAdminService;
|
let sut: UserAdminService;
|
||||||
let userMock: Mocked<IUserRepository>;
|
|
||||||
let cryptoRepositoryMock: Mocked<ICryptoRepository>;
|
|
||||||
|
|
||||||
let albumMock: Mocked<IAlbumRepository>;
|
let albumMock: Mocked<IAlbumRepository>;
|
||||||
|
let cryptoMock: Mocked<ICryptoRepository>;
|
||||||
|
let eventMock: Mocked<IEventRepository>;
|
||||||
let jobMock: Mocked<IJobRepository>;
|
let jobMock: Mocked<IJobRepository>;
|
||||||
let loggerMock: Mocked<ILoggerRepository>;
|
let loggerMock: Mocked<ILoggerRepository>;
|
||||||
|
let userMock: Mocked<IUserRepository>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
albumMock = newAlbumRepositoryMock();
|
albumMock = newAlbumRepositoryMock();
|
||||||
cryptoRepositoryMock = newCryptoRepositoryMock();
|
cryptoMock = newCryptoRepositoryMock();
|
||||||
|
eventMock = newEventRepositoryMock();
|
||||||
jobMock = newJobRepositoryMock();
|
jobMock = newJobRepositoryMock();
|
||||||
userMock = newUserRepositoryMock();
|
userMock = newUserRepositoryMock();
|
||||||
loggerMock = newLoggerRepositoryMock();
|
loggerMock = newLoggerRepositoryMock();
|
||||||
|
|
||||||
sut = new UserAdminService(albumMock, cryptoRepositoryMock, jobMock, userMock, loggerMock);
|
sut = new UserAdminService(albumMock, cryptoMock, eventMock, jobMock, userMock, loggerMock);
|
||||||
|
|
||||||
userMock.get.mockImplementation((userId) =>
|
userMock.get.mockImplementation((userId) =>
|
||||||
Promise.resolve([userStub.admin, userStub.user1].find((user) => user.id === userId) ?? null),
|
Promise.resolve([userStub.admin, userStub.user1].find((user) => user.id === userId) ?? null),
|
||||||
|
@ -15,6 +15,7 @@ import { UserMetadataKey } from 'src/entities/user-metadata.entity';
|
|||||||
import { UserStatus } from 'src/entities/user.entity';
|
import { UserStatus } from 'src/entities/user.entity';
|
||||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
|
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { IUserRepository, UserFindOptions } from 'src/interfaces/user.interface';
|
import { IUserRepository, UserFindOptions } from 'src/interfaces/user.interface';
|
||||||
@ -27,6 +28,7 @@ export class UserAdminService {
|
|||||||
constructor(
|
constructor(
|
||||||
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
||||||
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
||||||
|
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
||||||
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
||||||
@Inject(IUserRepository) private userRepository: IUserRepository,
|
@Inject(IUserRepository) private userRepository: IUserRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
||||||
@ -44,10 +46,12 @@ export class UserAdminService {
|
|||||||
const { notify, ...rest } = dto;
|
const { notify, ...rest } = dto;
|
||||||
const user = await this.userCore.createUser(rest);
|
const user = await this.userCore.createUser(rest);
|
||||||
|
|
||||||
const tempPassword = user.shouldChangePassword ? rest.password : undefined;
|
await this.eventRepository.emit('onUserSignupEvent', {
|
||||||
if (notify) {
|
notify: !!notify,
|
||||||
await this.jobRepository.queue({ name: JobName.NOTIFY_SIGNUP, data: { id: user.id, tempPassword } });
|
id: user.id,
|
||||||
}
|
tempPassword: user.shouldChangePassword ? rest.password : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
return mapUserAdmin(user);
|
return mapUserAdmin(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user