refactor(vis): H4.5 step 1 — LibraryService.VisibleLibrarySet, the canonical resolver; media endpoints migrated
One core for "which libraries can this user see": family-lens default- allow stays where it always was (the GetUserVisibleLibraries SQL); the core adds the shared set+ids shaping that four endpoints each hand-rolled. Media's visibleLibrarySet is now a thin identity adapter (JWT user object -> resolved id) and the download-permission gate uses the set instead of a manual membership loop. Semantic diff (H4.5 step 1, in the plan): zero family-lens divergence found across all six sites — the rule lives in one query; the variation was shape-level (identity adapter, set-vs-ids, UUID idiom).
This commit is contained in:
1 parent
29a546229d
commit
1d12a64915
2 files changed
+30
-22
No files matched your search
@@ -654,25 +654,14 @@ func (h *MediaHandler) HandleBulkUpdate(c *echo.Context) error {
|
||||
|
||||
// visibleLibrarySet loads the requester's visible libraries — membership
|
||||
// map for param checks, id slice for SQL ANY() filters.
|
||||
// visibleLibrarySet adapts the media endpoints' identity source (the
|
||||
// JWT user object) to the canonical resolver (H4.5).
|
||||
func (mh *MediaHandler) visibleLibrarySet(c *echo.Context) (map[string]bool, []pgtype.UUID, error) {
|
||||
user, ok := c.Get("user").(database.Users)
|
||||
if !ok {
|
||||
return nil, nil, echo.NewHTTPError(http.StatusUnauthorized, "invalid user")
|
||||
}
|
||||
libraries, err := mh.libraryService.GetUserVisibleLibraries(c.Request().Context(), user.ID)
|
||||
if err != nil {
|
||||
return nil, nil, echo.NewHTTPError(http.StatusInternalServerError, "failed to check library access")
|
||||
}
|
||||
set := make(map[string]bool, len(libraries))
|
||||
ids := make([]pgtype.UUID, 0, len(libraries))
|
||||
for _, lib := range libraries {
|
||||
if !lib.ID.Valid {
|
||||
continue
|
||||
}
|
||||
set[uuid.UUID(lib.ID.Bytes).String()] = true
|
||||
ids = append(ids, lib.ID)
|
||||
}
|
||||
return set, ids, nil
|
||||
return mh.libraryService.VisibleLibrarySet(c.Request().Context(), user.ID)
|
||||
}
|
||||
|
||||
// mediaStorageState reports whether the library storage holding a media
|
||||
@@ -2517,17 +2506,11 @@ func (mh *MediaHandler) ServeFile(c *echo.Context) error {
|
||||
// Library visibility gate: the library is what grants permission to
|
||||
// see and download media.
|
||||
user := c.Get("user").(database.Users)
|
||||
visibleLibraries, err := mh.libraryService.GetUserVisibleLibraries(c.Request().Context(), user.ID)
|
||||
visibleSet, _, err := mh.libraryService.VisibleLibrarySet(c.Request().Context(), user.ID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to check library access"})
|
||||
}
|
||||
libraryVisible := false
|
||||
for _, lib := range visibleLibraries {
|
||||
if lib.ID.Valid && lib.ID.Bytes == libraryUUID.Bytes {
|
||||
libraryVisible = true
|
||||
break
|
||||
}
|
||||
}
|
||||
libraryVisible := visibleSet[uuid.UUID(libraryUUID.Bytes).String()]
|
||||
if !libraryVisible {
|
||||
return c.JSON(http.StatusForbidden, map[string]string{"error": "library not accessible"})
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bookhoard/internal/database"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -174,6 +175,30 @@ func (s *LibraryService) SetLibraryVisibility(ctx context.Context, userID, libra
|
||||
}
|
||||
|
||||
// GetUserVisibleLibraries retrieves all libraries visible to a user
|
||||
// VisibleLibrarySet is THE canonical library-visibility resolver (H4.5):
|
||||
// every endpoint that asks "which libraries can this user see" funnels
|
||||
// here. The family-lens rule itself lives in the
|
||||
// GetUserVisibleLibraries SQL (COALESCE(lv.is_visible, true) —
|
||||
// default-allow: an admin hides a library, everyone else still sees it).
|
||||
// Callers adapt their own identity source (JWT user object, device
|
||||
// token, SSR session); this core takes the resolved user id.
|
||||
func (s *LibraryService) VisibleLibrarySet(ctx context.Context, userID pgtype.UUID) (map[string]bool, []pgtype.UUID, error) {
|
||||
rows, err := s.GetUserVisibleLibraries(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to check library access: %w", err)
|
||||
}
|
||||
set := make(map[string]bool, len(rows))
|
||||
ids := make([]pgtype.UUID, 0, len(rows))
|
||||
for _, lib := range rows {
|
||||
if !lib.ID.Valid {
|
||||
continue
|
||||
}
|
||||
set[uuid.UUID(lib.ID.Bytes).String()] = true
|
||||
ids = append(ids, lib.ID)
|
||||
}
|
||||
return set, ids, nil
|
||||
}
|
||||
|
||||
func (s *LibraryService) GetUserVisibleLibraries(ctx context.Context, userID pgtype.UUID) ([]database.GetUserVisibleLibrariesRow, error) {
|
||||
return s.db.GetUserVisibleLibraries(ctx, userID)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user