feat(realtime): доставлять клиентам события жизненного цикла альбома и изменения состава фото
Публиковать album.created/updated/deleted и изменения состава фото в личный поток SSE, а также photo.created при загрузке, чтобы списки альбомов, счётчики и открытые страницы альбома обновлялись без перезагрузки. - members.Repository получил Audience (владелец + участники) для дешёвого веера - albums.Service рассылает события в личные потоки получателей, перечитывая альбом через GetByID и attachCover, чтобы payload совпадал с GET /api/albums - members.Service публикует при принятии инвайта, смене роли и исключении - photos.Service публикует photo.created при загрузке и album.updated при добавлении/копировании/перемещении/восстановлении/корзине/окончательном удалении - photos_count теперь считает фото вне корзины - web-клиент подписывается на /api/sse/me один раз на уровне layout'а (app) и применяет события к стору альбомов; страница альбома уходит при потере доступа
This commit is contained in:
@@ -26,6 +26,9 @@ import (
|
||||
|
||||
type AlbumAccess interface {
|
||||
Access(ctx context.Context, albumID, userID string) (*models.Album, string, error)
|
||||
// NotifyAlbumChanged announces a change to the album's photo composition on
|
||||
// the recipients' personal streams (album cards and counts update live).
|
||||
NotifyAlbumChanged(ctx context.Context, albumID string)
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
@@ -237,6 +240,10 @@ func (s *Service) Upload(ctx context.Context, in UploadInput) (*models.Photo, er
|
||||
if err := s.attachURLs(ctx, p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The photo appears in the album: subscribers insert it, and the album's
|
||||
// recipients see the new count (and cover, if this was the first photo).
|
||||
s.hub.Publish(in.AlbumID, "photo.created", p)
|
||||
s.albums.NotifyAlbumChanged(ctx, in.AlbumID)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
@@ -501,6 +508,7 @@ func (s *Service) Delete(ctx context.Context, photoID, userID string) error {
|
||||
// A trashed cover leaves the album without one.
|
||||
s.store.ClearCoverIfMatches(ctx, p.AlbumID, photoID)
|
||||
s.publishDeleted(p)
|
||||
s.albums.NotifyAlbumChanged(ctx, p.AlbumID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -583,6 +591,7 @@ func (s *Service) Restore(ctx context.Context, photoID, userID, targetAlbumID st
|
||||
}
|
||||
if p.AlbumID != "" {
|
||||
s.hub.Publish(p.AlbumID, "photo.created", p)
|
||||
s.albums.NotifyAlbumChanged(ctx, p.AlbumID)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
@@ -620,6 +629,7 @@ func (s *Service) PermanentDelete(ctx context.Context, photoID, userID string) e
|
||||
}
|
||||
if p.AlbumID != "" {
|
||||
s.publishDeleted(p)
|
||||
s.albums.NotifyAlbumChanged(ctx, p.AlbumID)
|
||||
}
|
||||
// Mark objects that no longer have any binding for garbage collection.
|
||||
// The background worker removes the blobs from storage.
|
||||
@@ -658,6 +668,7 @@ func (s *Service) MoveBatch(ctx context.Context, ids []string, userID, targetAlb
|
||||
return 0, httpx.Forbidden("forbidden", "you cannot add photos to the target album")
|
||||
}
|
||||
moved := 0
|
||||
changed := map[string]struct{}{}
|
||||
for _, id := range ids {
|
||||
p, err := s.store.GetByID(ctx, id, userID)
|
||||
if err != nil || p.DeletedAt != nil {
|
||||
@@ -686,8 +697,14 @@ func (s *Service) MoveBatch(ctx context.Context, ids []string, userID, targetAlb
|
||||
if err := s.attachURLs(ctx, p); err == nil {
|
||||
s.hub.Publish(targetAlbumID, "photo.created", p)
|
||||
}
|
||||
changed[oldAlbum] = struct{}{}
|
||||
changed[targetAlbumID] = struct{}{}
|
||||
moved++
|
||||
}
|
||||
// One album.updated per affected album, not per photo.
|
||||
for albumID := range changed {
|
||||
s.albums.NotifyAlbumChanged(ctx, albumID)
|
||||
}
|
||||
return moved, nil
|
||||
}
|
||||
|
||||
@@ -724,6 +741,9 @@ func (s *Service) CopyBatch(ctx context.Context, ids []string, userID, targetAlb
|
||||
s.hub.Publish(targetAlbumID, "photo.created", clone)
|
||||
out = append(out, *clone)
|
||||
}
|
||||
if len(out) > 0 {
|
||||
s.albums.NotifyAlbumChanged(ctx, targetAlbumID)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user