Compare commits

...

3 Commits

Author SHA1 Message Date
timonrieger 637525fbca patch medium test 2026-06-12 11:32:47 +02:00
timonrieger b9add5973a fix medium tests 2026-06-12 03:21:53 +02:00
timonrieger 78a79245b1 feat: add album asset event handling 2026-06-12 02:26:42 +02:00
9 changed files with 52 additions and 12 deletions
@@ -159,8 +159,8 @@ class RemoteAlbumService {
return updatedAlbum;
}
FutureOr<(DateTime, DateTime)> getDateRange(String albumId) {
return _repository.getDateRange(albumId);
Stream<(DateTime, DateTime)> watchDateRange(String albumId) {
return _repository.watchDateRange(albumId);
}
Future<List<UserDto>> getSharedUsers(String albumId) {
@@ -213,7 +213,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
});
}
FutureOr<(DateTime, DateTime)> getDateRange(String albumId) {
Stream<(DateTime, DateTime)> watchDateRange(String albumId) {
final query = _db.remoteAlbumAssetEntity.selectOnly()
..where(_db.remoteAlbumAssetEntity.albumId.equals(albumId))
..addColumns([_db.remoteAssetEntity.createdAt.min(), _db.remoteAssetEntity.createdAt.max()])
@@ -225,7 +225,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
final minDate = row.read(_db.remoteAssetEntity.createdAt.min());
final maxDate = row.read(_db.remoteAssetEntity.createdAt.max());
return (minDate ?? DateTime.now(), maxDate ?? DateTime.now());
}).getSingle();
}).watchSingle();
}
Future<List<UserDto>> getSharedUsers(String albumId) async {
@@ -313,9 +313,9 @@ class RemoteAlbumNotifier extends Notifier<RemoteAlbumState> {
}
}
final remoteAlbumDateRangeProvider = FutureProvider.family<(DateTime, DateTime), String>((ref, albumId) async {
final remoteAlbumDateRangeProvider = StreamProvider.autoDispose.family<(DateTime, DateTime), String>((ref, albumId) {
final service = ref.watch(remoteAlbumServiceProvider);
return service.getDateRange(albumId);
return service.watchDateRange(albumId);
});
final remoteAlbumSharedUsersProvider = FutureProvider.autoDispose.family<List<UserDto>, String>((ref, albumId) async {
@@ -103,6 +103,8 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
socket.on('AssetUploadReadyV2', _handleSyncAssetUploadReadyV2);
socket.on('AssetEditReadyV1', _handleSyncAssetEditReadyV1);
socket.on('AssetEditReadyV2', _handleSyncAssetEditReadyV2);
socket.on('on_album_asset_create', _handleAlbumAssetMutation);
socket.on('on_album_asset_delete', _handleAlbumAssetMutation);
socket.on('on_config_update', _handleOnConfigUpdate);
socket.on('on_new_release', _handleReleaseUpdates);
} catch (e) {
@@ -184,6 +186,10 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEditV1(data));
}
void _handleAlbumAssetMutation(dynamic _) {
unawaited(_ref.read(backgroundSyncProvider).syncRemote());
}
void _handleSyncAssetEditReadyV2(dynamic data) {
unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEditV2(data));
}
@@ -40,6 +40,8 @@ type EventMap = {
// album events
AlbumUpdate: [{ id: string; recipientId: string }];
AlbumInvite: [{ id: string; userId: string; senderName: string }];
AlbumAssetCreate: [{ albumId: string; userIds: string[] }];
AlbumAssetDelete: [{ albumId: string; userIds: string[] }];
// asset events
AssetCreate: [{ asset: Asset; file: UploadFile }];
@@ -37,6 +37,8 @@ export interface ClientEventMap {
on_asset_hidden: [string];
on_asset_restore: [string[]];
on_asset_stack_update: string[];
on_album_asset_create: [string];
on_album_asset_delete: [string];
on_person_thumbnail: [string];
on_server_version: [ServerVersionResponseDto];
on_config_update: [];
+19 -5
View File
@@ -195,6 +195,11 @@ export class AlbumService extends BaseService {
for (const recipientId of allUsersExceptUs) {
await this.eventRepository.emit('AlbumUpdate', { id, recipientId });
}
await this.eventRepository.emit('AlbumAssetCreate', {
albumId: id,
userIds: album.albumUsers.map(({ user }) => user.id),
});
}
return results;
@@ -223,7 +228,7 @@ export class AlbumService extends BaseService {
}
const albumAssetValues: { albumId: string; assetId: string }[] = [];
const events: { id: string; recipients: string[] }[] = [];
const events: { id: string; recipients: string[]; allUserIds: string[] }[] = [];
for (const albumId of allowedAlbumIds) {
const existingAssetIds = await this.albumRepository.getAssetIds(albumId, [...allowedAssetIds]);
const notPresentAssetIds = [...allowedAssetIds].filter((id) => !existingAssetIds.has(id));
@@ -246,8 +251,9 @@ 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 allUserIds = album.albumUsers.map(({ user }) => user.id);
const allUsersExceptUs = allUserIds.filter((userId) => userId !== auth.user.id);
events.push({ id: albumId, recipients: allUsersExceptUs, allUserIds });
}
await this.albumRepository.addAssetIdsToAlbums(albumAssetValues);
@@ -255,6 +261,7 @@ export class AlbumService extends BaseService {
for (const recipientId of event.recipients) {
await this.eventRepository.emit('AlbumUpdate', { id: event.id, recipientId });
}
await this.eventRepository.emit('AlbumAssetCreate', { albumId: event.id, userIds: event.allUserIds });
}
return results;
@@ -271,8 +278,15 @@ 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('AlbumAssetDelete', {
albumId: id,
userIds: album.albumUsers.map(({ user }) => user.id),
});
}
return results;
@@ -230,6 +230,20 @@ export class NotificationService extends BaseService {
await this.jobRepository.queue({ name: JobName.NotifyAlbumInvite, data: { id, recipientId: userId, senderName } });
}
@OnEvent({ name: 'AlbumAssetCreate' })
onAlbumAssetCreate({ albumId, userIds }: ArgOf<'AlbumAssetCreate'>) {
for (const userId of userIds) {
this.websocketRepository.clientSend('on_album_asset_create', userId, albumId);
}
}
@OnEvent({ name: 'AlbumAssetDelete' })
onAlbumAssetDelete({ albumId, userIds }: ArgOf<'AlbumAssetDelete'>) {
for (const userId of userIds) {
this.websocketRepository.clientSend('on_album_asset_delete', userId, albumId);
}
}
@OnEvent({ name: 'SessionDelete' })
onSessionDelete({ sessionId }: ArgOf<'SessionDelete'>) {
// after the response is sent
@@ -9,6 +9,7 @@ import { AssetRepository } from 'src/repositories/asset.repository';
import { ConfigRepository } from 'src/repositories/config.repository';
import { CryptoRepository } from 'src/repositories/crypto.repository';
import { DatabaseRepository } from 'src/repositories/database.repository';
import { EventRepository } from 'src/repositories/event.repository';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { PluginRepository } from 'src/repositories/plugin.repository';
import { StorageRepository } from 'src/repositories/storage.repository';
@@ -39,7 +40,7 @@ class WorkflowTestContext extends MediumTestContext<WorkflowExecutionService> {
UserRepository,
WorkflowRepository,
],
mock: [ConfigRepository],
mock: [ConfigRepository, EventRepository],
});
}
@@ -52,6 +53,7 @@ class WorkflowTestContext extends MediumTestContext<WorkflowExecutionService> {
mockData.resourcePaths.corePlugin = '../packages/plugin-core';
mockData.plugins.external.allow = false;
this.getMock(ConfigRepository).getEnv.mockReturnValue(mockData);
this.getMock(EventRepository).emit.mockResolvedValue();
this.get(LoggingRepository).setLogLevel(LogLevel.Verbose);
await this.sut.onPluginSync();