Skip to content

Commit

Permalink
refactor(server): client emit events (#12606)
Browse files Browse the repository at this point in the history
* refactor(server): client emit events

* chore: test coverage
  • Loading branch information
jrasm91 authored Sep 12, 2024
1 parent 7b73778 commit ba57646
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 20 deletions.
14 changes: 14 additions & 0 deletions server/src/interfaces/event.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,24 @@ type EmitEventMap = {
'asset.untag': [{ assetId: string }];
'asset.hide': [{ assetId: string; userId: string }];
'asset.show': [{ assetId: string; userId: string }];
'asset.trash': [{ assetId: string; userId: string }];
'asset.delete': [{ assetId: string; userId: string }];

// asset bulk events
'assets.trash': [{ assetIds: string[]; userId: string }];
'assets.restore': [{ assetIds: string[]; userId: string }];

// session events
'session.delete': [{ sessionId: string }];

// stack events
'stack.create': [{ stackId: string; userId: string }];
'stack.update': [{ stackId: string; userId: string }];
'stack.delete': [{ stackId: string; userId: string }];

// stack bulk events
'stacks.delete': [{ stackIds: string[]; userId: string }];

// user events
'user.signup': [{ notify: boolean; id: string; tempPassword?: string }];
};
Expand Down
5 changes: 2 additions & 3 deletions server/src/services/asset-media.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity } from 'src/entities/asset.entit
import { AssetType, Permission } from 'src/enum';
import { IAccessRepository } from 'src/interfaces/access.interface';
import { IAssetRepository } from 'src/interfaces/asset.interface';
import { ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
import { IEventRepository } from 'src/interfaces/event.interface';
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface';
Expand Down Expand Up @@ -194,8 +194,7 @@ export class AssetMediaService {
const copiedPhoto = await this.createCopy(asset);
// and immediate trash it
await this.assetRepository.softDeleteAll([copiedPhoto.id]);

this.eventRepository.clientSend(ClientEvent.ASSET_TRASH, auth.user.id, [copiedPhoto.id]);
await this.eventRepository.emit('asset.trash', { assetId: copiedPhoto.id, userId: auth.user.id });

await this.userRepository.updateUsage(auth.user.id, file.size);

Expand Down
7 changes: 4 additions & 3 deletions server/src/services/asset.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { AssetEntity } from 'src/entities/asset.entity';
import { Permission } from 'src/enum';
import { IAccessRepository } from 'src/interfaces/access.interface';
import { IAssetRepository } from 'src/interfaces/asset.interface';
import { ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
import { IEventRepository } from 'src/interfaces/event.interface';
import {
IAssetDeleteJob,
IJobRepository,
Expand Down Expand Up @@ -273,7 +273,8 @@ export class AssetService {
if (!asset.libraryId) {
await this.userRepository.updateUsage(asset.ownerId, -(asset.exifInfo?.fileSizeInByte || 0));
}
this.eventRepository.clientSend(ClientEvent.ASSET_DELETE, asset.ownerId, id);

await this.eventRepository.emit('asset.delete', { assetId: id, userId: asset.ownerId });

// delete the motion if it is not used by another asset
if (asset.livePhotoVideoId) {
Expand Down Expand Up @@ -311,7 +312,7 @@ export class AssetService {
);
} else {
await this.assetRepository.softDeleteAll(ids);
this.eventRepository.clientSend(ClientEvent.ASSET_TRASH, auth.user.id, ids);
await this.eventRepository.emit('assets.trash', { assetIds: ids, userId: auth.user.id });
}
}

Expand Down
73 changes: 73 additions & 0 deletions server/src/services/notification.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,23 @@ describe(NotificationService.name, () => {
});
});

describe('onAssetHide', () => {
it('should send connected clients an event', () => {
sut.onAssetHide({ assetId: 'asset-id', userId: 'user-id' });
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_hidden', 'user-id', 'asset-id');
});
});

describe('onAssetShow', () => {
it('should queue the generate thumbnail job', async () => {
await sut.onAssetShow({ assetId: 'asset-id', userId: 'user-id' });
expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.GENERATE_THUMBNAIL,
data: { id: 'asset-id', notify: true },
});
});
});

describe('onUserSignupEvent', () => {
it('skips when notify is false', async () => {
await sut.onUserSignup({ id: '', notify: false });
Expand Down Expand Up @@ -179,6 +196,62 @@ describe(NotificationService.name, () => {
});
});

describe('onAssetTrash', () => {
it('should send connected clients an event', () => {
sut.onAssetTrash({ assetId: 'asset-id', userId: 'user-id' });
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_trash', 'user-id', ['asset-id']);
});
});

describe('onAssetDelete', () => {
it('should send connected clients an event', () => {
sut.onAssetDelete({ assetId: 'asset-id', userId: 'user-id' });
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_delete', 'user-id', 'asset-id');
});
});

describe('onAssetsTrash', () => {
it('should send connected clients an event', () => {
sut.onAssetsTrash({ assetIds: ['asset-id'], userId: 'user-id' });
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_trash', 'user-id', ['asset-id']);
});
});

describe('onAssetsRestore', () => {
it('should send connected clients an event', () => {
sut.onAssetsRestore({ assetIds: ['asset-id'], userId: 'user-id' });
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_restore', 'user-id', ['asset-id']);
});
});

describe('onStackCreate', () => {
it('should send connected clients an event', () => {
sut.onStackCreate({ stackId: 'stack-id', userId: 'user-id' });
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id', []);
});
});

describe('onStackUpdate', () => {
it('should send connected clients an event', () => {
sut.onStackUpdate({ stackId: 'stack-id', userId: 'user-id' });
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id', []);
});
});

describe('onStackDelete', () => {
it('should send connected clients an event', () => {
sut.onStackDelete({ stackId: 'stack-id', userId: 'user-id' });
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id', []);
});
});

describe('onStacksDelete', () => {
it('should send connected clients an event', () => {
sut.onStacksDelete({ stackIds: ['stack-id'], userId: 'user-id' });
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id', []);
});
});

describe('sendTestEmail', () => {
it('should throw error if user could not be found', async () => {
await expect(sut.sendTestEmail('', configs.smtpTransport.notifications.smtp)).rejects.toThrow('User not found');
Expand Down
41 changes: 40 additions & 1 deletion server/src/services/notification.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ export class NotificationService {

@OnEmit({ event: 'asset.hide' })
onAssetHide({ assetId, userId }: ArgOf<'asset.hide'>) {
// Notify clients to hide the linked live photo asset
this.eventRepository.clientSend(ClientEvent.ASSET_HIDDEN, userId, assetId);
}

Expand All @@ -69,6 +68,46 @@ export class NotificationService {
await this.jobRepository.queue({ name: JobName.GENERATE_THUMBNAIL, data: { id: assetId, notify: true } });
}

@OnEmit({ event: 'asset.trash' })
onAssetTrash({ assetId, userId }: ArgOf<'asset.trash'>) {
this.eventRepository.clientSend(ClientEvent.ASSET_TRASH, userId, [assetId]);
}

@OnEmit({ event: 'asset.delete' })
onAssetDelete({ assetId, userId }: ArgOf<'asset.delete'>) {
this.eventRepository.clientSend(ClientEvent.ASSET_DELETE, userId, assetId);
}

@OnEmit({ event: 'assets.trash' })
onAssetsTrash({ assetIds, userId }: ArgOf<'assets.trash'>) {
this.eventRepository.clientSend(ClientEvent.ASSET_TRASH, userId, assetIds);
}

@OnEmit({ event: 'assets.restore' })
onAssetsRestore({ assetIds, userId }: ArgOf<'assets.restore'>) {
this.eventRepository.clientSend(ClientEvent.ASSET_RESTORE, userId, assetIds);
}

@OnEmit({ event: 'stack.create' })
onStackCreate({ userId }: ArgOf<'stack.create'>) {
this.eventRepository.clientSend(ClientEvent.ASSET_STACK_UPDATE, userId, []);
}

@OnEmit({ event: 'stack.update' })
onStackUpdate({ userId }: ArgOf<'stack.update'>) {
this.eventRepository.clientSend(ClientEvent.ASSET_STACK_UPDATE, userId, []);
}

@OnEmit({ event: 'stack.delete' })
onStackDelete({ userId }: ArgOf<'stack.delete'>) {
this.eventRepository.clientSend(ClientEvent.ASSET_STACK_UPDATE, userId, []);
}

@OnEmit({ event: 'stacks.delete' })
onStacksDelete({ userId }: ArgOf<'stacks.delete'>) {
this.eventRepository.clientSend(ClientEvent.ASSET_STACK_UPDATE, userId, []);
}

@OnEmit({ event: 'user.signup' })
async onUserSignup({ notify, id, tempPassword }: ArgOf<'user.signup'>) {
if (notify) {
Expand Down
12 changes: 5 additions & 7 deletions server/src/services/stack.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { AuthDto } from 'src/dtos/auth.dto';
import { StackCreateDto, StackResponseDto, StackSearchDto, StackUpdateDto, mapStack } from 'src/dtos/stack.dto';
import { Permission } from 'src/enum';
import { IAccessRepository } from 'src/interfaces/access.interface';
import { ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
import { IEventRepository } from 'src/interfaces/event.interface';
import { IStackRepository } from 'src/interfaces/stack.interface';
import { requireAccess } from 'src/utils/access';

Expand All @@ -30,7 +30,7 @@ export class StackService {

const stack = await this.stackRepository.create({ ownerId: auth.user.id, assetIds: dto.assetIds });

this.eventRepository.clientSend(ClientEvent.ASSET_STACK_UPDATE, auth.user.id, []);
await this.eventRepository.emit('stack.create', { stackId: stack.id, userId: auth.user.id });

return mapStack(stack, { auth });
}
Expand All @@ -50,23 +50,21 @@ export class StackService {

const updatedStack = await this.stackRepository.update({ id, primaryAssetId: dto.primaryAssetId });

this.eventRepository.clientSend(ClientEvent.ASSET_STACK_UPDATE, auth.user.id, []);
await this.eventRepository.emit('stack.update', { stackId: id, userId: auth.user.id });

return mapStack(updatedStack, { auth });
}

async delete(auth: AuthDto, id: string): Promise<void> {
await requireAccess(this.access, { auth, permission: Permission.STACK_DELETE, ids: [id] });
await this.stackRepository.delete(id);

this.eventRepository.clientSend(ClientEvent.ASSET_STACK_UPDATE, auth.user.id, []);
await this.eventRepository.emit('stack.delete', { stackId: id, userId: auth.user.id });
}

async deleteAll(auth: AuthDto, dto: BulkIdsDto): Promise<void> {
await requireAccess(this.access, { auth, permission: Permission.STACK_DELETE, ids: dto.ids });
await this.stackRepository.deleteAll(dto.ids);

this.eventRepository.clientSend(ClientEvent.ASSET_STACK_UPDATE, auth.user.id, []);
await this.eventRepository.emit('stacks.delete', { stackIds: dto.ids, userId: auth.user.id });
}

private async findOrFail(id: string) {
Expand Down
6 changes: 2 additions & 4 deletions server/src/services/trash.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BadRequestException } from '@nestjs/common';
import { IAssetRepository } from 'src/interfaces/asset.interface';
import { ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
import { IEventRepository } from 'src/interfaces/event.interface';
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
import { TrashService } from 'src/services/trash.service';
import { assetStub } from 'test/fixtures/asset.stub';
Expand Down Expand Up @@ -62,9 +62,7 @@ describe(TrashService.name, () => {
assetMock.getByUserId.mockResolvedValue({ items: [assetStub.image], hasNextPage: false });
await expect(sut.restore(authStub.user1)).resolves.toBeUndefined();
expect(assetMock.restoreAll).toHaveBeenCalledWith([assetStub.image.id]);
expect(eventMock.clientSend).toHaveBeenCalledWith(ClientEvent.ASSET_RESTORE, authStub.user1.user.id, [
assetStub.image.id,
]);
expect(eventMock.emit).toHaveBeenCalledWith('assets.restore', { assetIds: ['asset-id'], userId: 'user-id' });
});
});

Expand Down
4 changes: 2 additions & 2 deletions server/src/services/trash.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { AuthDto } from 'src/dtos/auth.dto';
import { Permission } from 'src/enum';
import { IAccessRepository } from 'src/interfaces/access.interface';
import { IAssetRepository } from 'src/interfaces/asset.interface';
import { ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
import { IEventRepository } from 'src/interfaces/event.interface';
import { IJobRepository, JOBS_ASSET_PAGINATION_SIZE, JobName } from 'src/interfaces/job.interface';
import { requireAccess } from 'src/utils/access';
import { usePagination } from 'src/utils/pagination';
Expand Down Expand Up @@ -64,6 +64,6 @@ export class TrashService {
}

await this.assetRepository.restoreAll(ids);
this.eventRepository.clientSend(ClientEvent.ASSET_RESTORE, auth.user.id, ids);
await this.eventRepository.emit('assets.restore', { assetIds: ids, userId: auth.user.id });
}
}

0 comments on commit ba57646

Please sign in to comment.