feat: add album asset event handling (#29008)

* feat: add album asset event handling

* fix medium tests

* patch medium test

* collapse in AlbumUpdate
This commit is contained in:
Timon
2026-07-21 10:55:46 -05:00
committed by GitHub
parent 7a7303aceb
commit ee4bd3f833
11 changed files with 64 additions and 35 deletions
+6 -3
View File
@@ -841,7 +841,8 @@ describe(AlbumService.name, () => {
expect(mocks.album.addAssetIds).toHaveBeenCalledWith(album.id, [asset1.id, asset2.id, asset3.id]);
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', {
id: album.id,
recipientId: owner.id,
userIds: album.albumUsers.map(({ user }) => user.id),
recipientIds: [owner.id],
});
});
@@ -1091,11 +1092,13 @@ describe(AlbumService.name, () => {
]);
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', {
id: album1.id,
recipientId: owner1.id,
userIds: album1.albumUsers.map(({ user }) => user.id),
recipientIds: [owner1.id],
});
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', {
id: album2.id,
recipientId: owner2.id,
userIds: album2.albumUsers.map(({ user }) => user.id),
recipientIds: [owner2.id],
});
});
+18 -13
View File
@@ -190,11 +190,9 @@ export class AlbumService extends BaseService {
auth.user.id,
);
const allUsersExceptUs = album.albumUsers.map(({ user }) => user.id).filter((userId) => userId !== auth.user.id);
for (const recipientId of allUsersExceptUs) {
await this.eventRepository.emit('AlbumUpdate', { id, recipientId });
}
const userIds = album.albumUsers.map(({ user }) => user.id);
const recipientIds = userIds.filter((userId) => userId !== auth.user.id);
await this.eventRepository.emit('AlbumUpdate', { id, userIds, recipientIds });
}
return results;
@@ -223,7 +221,7 @@ export class AlbumService extends BaseService {
}
const albumAssetValues: { albumId: string; assetId: string }[] = [];
const events: { id: string; recipients: string[] }[] = [];
const events: { id: string; userIds: string[]; recipientIds: string[] }[] = [];
for (const albumId of allowedAlbumIds) {
const existingAssetIds = await this.albumRepository.getAssetIds(albumId, [...allowedAssetIds]);
const notPresentAssetIds = [...allowedAssetIds.difference(existingAssetIds)];
@@ -246,15 +244,14 @@ export class AlbumService extends BaseService {
},
auth.user.id,
);
const allUsersExceptUs = album.albumUsers.map(({ user }) => user.id).filter((userId) => userId !== auth.user.id);
events.push({ id: albumId, recipients: allUsersExceptUs });
const userIds = album.albumUsers.map(({ user }) => user.id);
const recipientIds = userIds.filter((userId) => userId !== auth.user.id);
events.push({ id: albumId, userIds, recipientIds });
}
await this.albumRepository.addAssetIdsToAlbums(albumAssetValues);
for (const event of events) {
for (const recipientId of event.recipients) {
await this.eventRepository.emit('AlbumUpdate', { id: event.id, recipientId });
}
await this.eventRepository.emit('AlbumUpdate', event);
}
return results;
@@ -271,8 +268,16 @@ export class AlbumService extends BaseService {
);
const removedIds = results.filter(({ success }) => success).map(({ id }) => id);
if (removedIds.length > 0 && album.albumThumbnailAssetId && removedIds.includes(album.albumThumbnailAssetId)) {
await this.albumRepository.updateThumbnails();
if (removedIds.length > 0) {
if (album.albumThumbnailAssetId && removedIds.includes(album.albumThumbnailAssetId)) {
await this.albumRepository.updateThumbnails();
}
await this.eventRepository.emit('AlbumUpdate', {
id,
userIds: album.albumUsers.map(({ user }) => user.id),
recipientIds: [],
});
}
return results;
@@ -2,7 +2,6 @@ import { defaults, SystemConfig } from 'src/config';
import { SystemConfigDto } from 'src/dtos/system-config.dto';
import { AssetFileType, JobName, JobStatus, UserMetadataKey } from 'src/enum';
import { NotificationService } from 'src/services/notification.service';
import { INotifyAlbumUpdateJob } from 'src/types';
import { AlbumFactory } from 'test/factories/album.factory';
import { AssetFileFactory } from 'test/factories/asset-file.factory';
import { AssetFactory } from 'test/factories/asset.factory';
@@ -157,13 +156,21 @@ describe(NotificationService.name, () => {
});
describe('onAlbumUpdateEvent', () => {
it('should queue notify album update event', async () => {
await sut.onAlbumUpdate({ id: 'album', recipientId: '42' });
expect(mocks.job.queue).toHaveBeenCalledWith({
it('should send a websocket event to every user and queue notify jobs for recipients', async () => {
await sut.onAlbumUpdate({ id: 'album', userIds: ['1', '42'], recipientIds: ['42'] });
expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_album_update', '1', 'album');
expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_album_update', '42', 'album');
expect(mocks.job.queue).toHaveBeenCalledExactlyOnceWith({
name: JobName.NotifyAlbumUpdate,
data: { id: 'album', recipientId: '42', delay: 300_000 },
});
});
it('should not queue email jobs when there are no recipients', async () => {
await sut.onAlbumUpdate({ id: 'album', userIds: ['1'], recipientIds: [] });
expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_album_update', '1', 'album');
expect(mocks.job.queue).not.toHaveBeenCalled();
});
});
describe('onAlbumInviteEvent', () => {
@@ -522,7 +529,7 @@ describe(NotificationService.name, () => {
});
it('should add new recipients for new images if job is already queued', async () => {
await sut.onAlbumUpdate({ id: '1', recipientId: '2' } as INotifyAlbumUpdateJob);
await sut.onAlbumUpdate({ id: '1', userIds: ['2'], recipientIds: ['2'] });
expect(mocks.job.removeJob).toHaveBeenCalledWith(JobName.NotifyAlbumUpdate, '1/2');
expect(mocks.job.queue).toHaveBeenCalledWith({
name: JobName.NotifyAlbumUpdate,
+12 -6
View File
@@ -217,12 +217,18 @@ export class NotificationService extends BaseService {
}
@OnEvent({ name: 'AlbumUpdate' })
async onAlbumUpdate({ id, recipientId }: ArgOf<'AlbumUpdate'>) {
await this.jobRepository.removeJob(JobName.NotifyAlbumUpdate, `${id}/${recipientId}`);
await this.jobRepository.queue({
name: JobName.NotifyAlbumUpdate,
data: { id, recipientId, delay: NotificationService.albumUpdateEmailDelayMs },
});
async onAlbumUpdate({ id, userIds, recipientIds }: ArgOf<'AlbumUpdate'>) {
for (const userId of userIds) {
this.websocketRepository.clientSend('on_album_update', userId, id);
}
for (const recipientId of recipientIds) {
await this.jobRepository.removeJob(JobName.NotifyAlbumUpdate, `${id}/${recipientId}`);
await this.jobRepository.queue({
name: JobName.NotifyAlbumUpdate,
data: { id, recipientId, delay: NotificationService.albumUpdateEmailDelayMs },
});
}
}
@OnEvent({ name: 'AlbumInvite' })