mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
Updated recent albums updates
This commit is contained in:
parent
7215e59809
commit
e3d49bfa86
7 changed files with 110 additions and 35 deletions
|
|
@ -49,7 +49,7 @@ type EventMap = {
|
||||||
ConfigValidate: [{ newConfig: SystemConfig; oldConfig: SystemConfig }];
|
ConfigValidate: [{ newConfig: SystemConfig; oldConfig: SystemConfig }];
|
||||||
|
|
||||||
// album events
|
// album events
|
||||||
AlbumUpdate: [{ id: string; recipientId: string }];
|
AlbumUpdate: [{ id: string; userId: string; notifyRecipients?: boolean }];
|
||||||
AlbumInvite: [{ id: string; userId: string }];
|
AlbumInvite: [{ id: string; userId: string }];
|
||||||
AlbumDelete: [{ id: string; userId: string }];
|
AlbumDelete: [{ id: string; userId: string }];
|
||||||
AlbumCreate: [{ id: string; userId: string }];
|
AlbumCreate: [{ id: string; userId: string }];
|
||||||
|
|
@ -114,6 +114,7 @@ export interface ClientEventMap {
|
||||||
on_session_delete: [string];
|
on_session_delete: [string];
|
||||||
on_album_delete: [string];
|
on_album_delete: [string];
|
||||||
on_album_create: [string];
|
on_album_create: [string];
|
||||||
|
on_album_update: [string];
|
||||||
|
|
||||||
AssetUploadReadyV1: [{ asset: SyncAssetV1; exif: SyncAssetExifV1 }];
|
AssetUploadReadyV1: [{ asset: SyncAssetV1; exif: SyncAssetExifV1 }];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,12 @@ export class AlbumService extends BaseService {
|
||||||
order: dto.order,
|
order: dto.order,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Emit AlbumUpdate event with notifyRecipients flag for notification service to handle recipient lookup
|
||||||
|
await this.eventRepository.emit('AlbumUpdate', {
|
||||||
|
id: album.id,
|
||||||
|
userId: auth.user.id,
|
||||||
|
});
|
||||||
|
|
||||||
return mapAlbumWithoutAssets({ ...updatedAlbum, assets: album.assets });
|
return mapAlbumWithoutAssets({ ...updatedAlbum, assets: album.assets });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,13 +183,12 @@ export class AlbumService extends BaseService {
|
||||||
albumThumbnailAssetId: album.albumThumbnailAssetId ?? firstNewAssetId,
|
albumThumbnailAssetId: album.albumThumbnailAssetId ?? firstNewAssetId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const allUsersExceptUs = [...album.albumUsers.map(({ user }) => user.id), album.owner.id].filter(
|
// Emit AlbumUpdate event with notifyRecipients flag for notification service to handle recipient lookup
|
||||||
(userId) => userId !== auth.user.id,
|
await this.eventRepository.emit('AlbumUpdate', {
|
||||||
);
|
id,
|
||||||
|
userId: auth.user.id,
|
||||||
for (const recipientId of allUsersExceptUs) {
|
notifyRecipients: true,
|
||||||
await this.eventRepository.emit('AlbumUpdate', { id, recipientId });
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
|
|
@ -204,6 +209,15 @@ export class AlbumService extends BaseService {
|
||||||
await this.albumRepository.updateThumbnails();
|
await this.albumRepository.updateThumbnails();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Emit AlbumUpdate event if any assets were successfully removed
|
||||||
|
if (removedIds.length > 0) {
|
||||||
|
await this.eventRepository.emit('AlbumUpdate', {
|
||||||
|
id,
|
||||||
|
userId: auth.user.id,
|
||||||
|
notifyRecipients: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,6 +245,12 @@ export class AlbumService extends BaseService {
|
||||||
await this.eventRepository.emit('AlbumInvite', { id, userId });
|
await this.eventRepository.emit('AlbumInvite', { id, userId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Emit AlbumUpdate event to notify all album members about new users being added
|
||||||
|
await this.eventRepository.emit('AlbumUpdate', {
|
||||||
|
id,
|
||||||
|
userId: auth.user.id,
|
||||||
|
});
|
||||||
|
|
||||||
return this.findOrFail(id, { withAssets: true }).then(mapAlbumWithoutAssets);
|
return this.findOrFail(id, { withAssets: true }).then(mapAlbumWithoutAssets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,11 +276,23 @@ export class AlbumService extends BaseService {
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.albumUserRepository.delete({ albumsId: id, usersId: userId });
|
await this.albumUserRepository.delete({ albumsId: id, usersId: userId });
|
||||||
|
|
||||||
|
// Emit AlbumUpdate event to notify remaining album members about user removal
|
||||||
|
await this.eventRepository.emit('AlbumUpdate', {
|
||||||
|
id,
|
||||||
|
userId: auth.user.id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateUser(auth: AuthDto, id: string, userId: string, dto: UpdateAlbumUserDto): Promise<void> {
|
async updateUser(auth: AuthDto, id: string, userId: string, dto: UpdateAlbumUserDto): Promise<void> {
|
||||||
await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] });
|
await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] });
|
||||||
await this.albumUserRepository.update({ albumsId: id, usersId: userId }, { role: dto.role });
|
await this.albumUserRepository.update({ albumsId: id, usersId: userId }, { role: dto.role });
|
||||||
|
|
||||||
|
// Emit AlbumUpdate event to notify all album members about role changes
|
||||||
|
await this.eventRepository.emit('AlbumUpdate', {
|
||||||
|
id,
|
||||||
|
userId: auth.user.id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async findOrFail(id: string, options: AlbumInfoOptions) {
|
private async findOrFail(id: string, options: AlbumInfoOptions) {
|
||||||
|
|
|
||||||
|
|
@ -198,12 +198,35 @@ export class NotificationService extends BaseService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'AlbumUpdate' })
|
@OnEvent({ name: 'AlbumUpdate' })
|
||||||
async onAlbumUpdate({ id, recipientId }: ArgOf<'AlbumUpdate'>) {
|
async onAlbumUpdate({ id, userId, notifyRecipients }: ArgOf<'AlbumUpdate'>) {
|
||||||
await this.jobRepository.removeJob(JobName.NotifyAlbumUpdate, `${id}/${recipientId}`);
|
if (notifyRecipients) {
|
||||||
await this.jobRepository.queue({
|
// Fetch album with users to get recipient list
|
||||||
name: JobName.NotifyAlbumUpdate,
|
const album = await this.albumRepository.getById(id, { withAssets: false });
|
||||||
data: { id, recipientId, delay: NotificationService.albumUpdateEmailDelayMs },
|
if (!album) {
|
||||||
});
|
this.logger.warn(`Album ${id} not found for update notification`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all users except the one who made the update
|
||||||
|
const allRecipients = [...album.albumUsers.map(({ user }) => user.id), album.owner.id].filter(
|
||||||
|
(recipientUserId) => recipientUserId !== userId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Send notifications and websocket events to all recipients
|
||||||
|
for (const recipient of allRecipients) {
|
||||||
|
await this.jobRepository.removeJob(JobName.NotifyAlbumUpdate, `${id}/${recipient}`);
|
||||||
|
await this.jobRepository.queue({
|
||||||
|
name: JobName.NotifyAlbumUpdate,
|
||||||
|
data: { id, recipientId: recipient, delay: NotificationService.albumUpdateEmailDelayMs },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send websocket event to the recipient
|
||||||
|
this.eventRepository.clientSend('on_album_update', recipient, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always send websocket event to the user who made the update
|
||||||
|
this.eventRepository.clientSend('on_album_update', userId, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'AlbumInvite' })
|
@OnEvent({ name: 'AlbumInvite' })
|
||||||
|
|
|
||||||
|
|
@ -249,11 +249,37 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add listener for album updates
|
||||||
|
const unsubscribeUpdate = websocketEvents.on('on_album_update', async (albumId) => {
|
||||||
|
console.log(`Album update event received for album ID: ${albumId}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch the updated album details
|
||||||
|
const updatedAlbum = await getAlbumInfo({ id: albumId });
|
||||||
|
|
||||||
|
// Update the album in the appropriate array
|
||||||
|
const ownedIndex = ownedAlbums.findIndex((album) => album.id === albumId);
|
||||||
|
if (ownedIndex !== -1) {
|
||||||
|
ownedAlbums[ownedIndex] = updatedAlbum;
|
||||||
|
ownedAlbums = [...ownedAlbums]; // Trigger reactivity
|
||||||
|
}
|
||||||
|
|
||||||
|
const sharedIndex = sharedAlbums.findIndex((album) => album.id === albumId);
|
||||||
|
if (sharedIndex !== -1) {
|
||||||
|
sharedAlbums[sharedIndex] = updatedAlbum;
|
||||||
|
sharedAlbums = [...sharedAlbums]; // Trigger reactivity
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch updated album details:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Return a combined unsubscribe function
|
// Return a combined unsubscribe function
|
||||||
const originalUnsubscribe = unsubscribeWebsocket;
|
const originalUnsubscribe = unsubscribeWebsocket;
|
||||||
unsubscribeWebsocket = () => {
|
unsubscribeWebsocket = () => {
|
||||||
originalUnsubscribe?.();
|
originalUnsubscribe?.();
|
||||||
unsubscribeCreate?.();
|
unsubscribeCreate?.();
|
||||||
|
unsubscribeUpdate?.();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,6 @@
|
||||||
let unsubscribeWebsocket: (() => void) | undefined;
|
let unsubscribeWebsocket: (() => void) | undefined;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (userInteraction.recentAlbums) {
|
|
||||||
albums = userInteraction.recentAlbums;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
allAlbums = await getAllAlbums({});
|
allAlbums = await getAllAlbums({});
|
||||||
albums = allAlbums.sort((a, b) => (a.updatedAt > b.updatedAt ? -1 : 1)).slice(0, 3);
|
albums = allAlbums.sort((a, b) => (a.updatedAt > b.updatedAt ? -1 : 1)).slice(0, 3);
|
||||||
|
|
@ -28,34 +24,30 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
unsubscribeWebsocket = websocketEvents.on('on_album_delete', (albumId) => {
|
const unsubscribeDelete = websocketEvents.on('on_album_delete', (albumId) => {
|
||||||
// Remove the deleted album from allAlbums
|
|
||||||
allAlbums = allAlbums.filter((album) => album.id !== albumId);
|
allAlbums = allAlbums.filter((album) => album.id !== albumId);
|
||||||
// Update the displayed albums with the filtered and sorted result
|
|
||||||
albums = allAlbums.sort((a, b) => (a.updatedAt > b.updatedAt ? -1 : 1)).slice(0, 3);
|
albums = allAlbums.sort((a, b) => (a.updatedAt > b.updatedAt ? -1 : 1)).slice(0, 3);
|
||||||
userInteraction.recentAlbums = albums;
|
userInteraction.recentAlbums = albums;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add listener for album creation
|
const unsubscribeUpdate = websocketEvents.on('on_album_update', async (albumId) => {
|
||||||
const unsubscribeCreate = websocketEvents.on('on_album_create', async (albumId) => {
|
|
||||||
try {
|
try {
|
||||||
// Fetch the newly created album details
|
const updatedAlbum = await getAlbumInfo({ id: albumId });
|
||||||
const newAlbum = await getAlbumInfo({ id: albumId });
|
|
||||||
// Add the new album to allAlbums
|
const index = allAlbums.findIndex((album) => album.id === albumId);
|
||||||
allAlbums = [newAlbum, ...allAlbums];
|
if (index !== -1) {
|
||||||
// Update the displayed albums with the new sorted result
|
allAlbums[index] = updatedAlbum;
|
||||||
albums = allAlbums.sort((a, b) => (a.updatedAt > b.updatedAt ? -1 : 1)).slice(0, 3);
|
albums = allAlbums.sort((a, b) => (a.updatedAt > b.updatedAt ? -1 : 1)).slice(0, 3);
|
||||||
userInteraction.recentAlbums = albums;
|
userInteraction.recentAlbums = albums;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch new album details:', error);
|
console.error('Failed to fetch updated album details:', error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Return a combined unsubscribe function
|
|
||||||
const originalUnsubscribe = unsubscribeWebsocket;
|
|
||||||
unsubscribeWebsocket = () => {
|
unsubscribeWebsocket = () => {
|
||||||
originalUnsubscribe?.();
|
unsubscribeDelete?.();
|
||||||
unsubscribeCreate?.();
|
unsubscribeUpdate?.();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ export interface Events {
|
||||||
on_notification: (notification: NotificationDto) => void;
|
on_notification: (notification: NotificationDto) => void;
|
||||||
on_album_delete: (albumId: string) => void;
|
on_album_delete: (albumId: string) => void;
|
||||||
on_album_create: (albumId: string) => void;
|
on_album_create: (albumId: string) => void;
|
||||||
|
on_album_update: (albumId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const websocket: Socket<Events> = io({
|
const websocket: Socket<Events> = io({
|
||||||
|
|
|
||||||
0
web/src/lib/utils/album-websocket-utils.ts
Normal file
0
web/src/lib/utils/album-websocket-utils.ts
Normal file
Loading…
Add table
Add a link
Reference in a new issue