feat: lightroom-style workspace UI with album covers
Backend: - expose cover thumbnail URL on album responses (LEFT JOIN + presign) - auto-assign cover only when empty; FK cover_photo_id -> photos ON DELETE SET NULL - add photos_count / members_count to album responses; cover repo test Frontend: - split layouts into (app) shell (resizable rail + contextual panel) and (public) - album rail + create-album dialog; album panel with cover picker / make-cover - photo viewer route with filmstrip, replacing lightbox; share reuses it - grid density slider, toolbar + drag-and-drop upload, dark graphite default OpenSpec: add lightroom-workspace-ui change (proposal, design, specs, tasks)
This commit is contained in:
@@ -51,7 +51,24 @@ func (s *Service) Create(ctx context.Context, userID string, in CreateInput) (*m
|
||||
}
|
||||
|
||||
func (s *Service) List(ctx context.Context, userID string) ([]models.Album, error) {
|
||||
return s.store.ListForUser(ctx, userID)
|
||||
out, err := s.store.ListForUser(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range out {
|
||||
s.attachCover(ctx, &out[i])
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// attachCover fills CoverThumbURL from the presigned thumbnail key, when present.
|
||||
func (s *Service) attachCover(ctx context.Context, a *models.Album) {
|
||||
if s.storage == nil || a == nil || a.CoverThumbKey == "" {
|
||||
return
|
||||
}
|
||||
if url, err := s.storage.PresignGet(ctx, a.CoverThumbKey); err == nil {
|
||||
a.CoverThumbURL = url
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) Access(ctx context.Context, albumID, userID string) (*models.Album, string, error) {
|
||||
@@ -76,7 +93,12 @@ func (s *Service) Access(ctx context.Context, albumID, userID string) (*models.A
|
||||
}
|
||||
|
||||
func (s *Service) Get(ctx context.Context, albumID, userID string) (*models.Album, string, error) {
|
||||
return s.Access(ctx, albumID, userID)
|
||||
a, role, err := s.Access(ctx, albumID, userID)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
s.attachCover(ctx, a)
|
||||
return a, role, nil
|
||||
}
|
||||
|
||||
type UpdateInput struct {
|
||||
@@ -112,6 +134,10 @@ func (s *Service) Update(ctx context.Context, albumID, userID string, in UpdateI
|
||||
if err := s.store.Update(ctx, a); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if refreshed, err := s.store.GetByID(ctx, a.ID); err == nil {
|
||||
a = refreshed
|
||||
}
|
||||
s.attachCover(ctx, a)
|
||||
s.hub.Publish(albumID, "album.updated", a)
|
||||
return a, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user