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:
@@ -65,6 +65,23 @@ func Run(t *testing.T, r Repos) {
|
||||
|
||||
func newID() string { return auth.NewID() }
|
||||
|
||||
// sameIDs reports whether got contains exactly the wanted ids (any order).
|
||||
func sameIDs(got []string, want ...string) bool {
|
||||
if len(got) != len(want) {
|
||||
return false
|
||||
}
|
||||
set := make(map[string]struct{}, len(got))
|
||||
for _, id := range got {
|
||||
set[id] = struct{}{}
|
||||
}
|
||||
for _, id := range want {
|
||||
if _, ok := set[id]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func seedUser(t *testing.T, ctx context.Context, r Repos, name string) *models.User {
|
||||
t.Helper()
|
||||
u := &models.User{ID: newID(), Email: newID()[:10] + "@test.local", PasswordHash: "x", Name: name}
|
||||
@@ -249,6 +266,9 @@ func testMembers(t *testing.T, ctx context.Context, r Repos) {
|
||||
if err != nil || len(list) != 1 || list[0].User.Name != "Member" {
|
||||
t.Fatalf("list: %v %+v", err, list)
|
||||
}
|
||||
if aud, err := r.Members.Audience(ctx, a.ID); err != nil || !sameIDs(aud, owner.ID, member.ID) {
|
||||
t.Fatalf("audience: %v %v", err, aud)
|
||||
}
|
||||
if err := r.Members.UpdateRole(ctx, a.ID, member.ID, "editor"); err != nil {
|
||||
t.Fatalf("update role: %v", err)
|
||||
}
|
||||
@@ -284,6 +304,9 @@ func testMembers(t *testing.T, ctx context.Context, r Repos) {
|
||||
if role, _ := r.Members.Role(ctx, a.ID, member.ID); role != "" {
|
||||
t.Fatalf("member not removed: %q", role)
|
||||
}
|
||||
if aud, _ := r.Members.Audience(ctx, a.ID); !sameIDs(aud, owner.ID) {
|
||||
t.Fatalf("audience after remove = %v", aud)
|
||||
}
|
||||
if err := r.Members.Remove(ctx, a.ID, member.ID); !errors.Is(err, members.ErrNotFound) {
|
||||
t.Fatalf("remove missing err = %v, want ErrNotFound", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user