feat: option A — visibility gating + author submission system
Some checks failed
Release / Test backend (push) Successful in 1m1s
Release / Check ui (push) Successful in 1m0s
Release / Docker (push) Successful in 11m19s
Release / Deploy to prod (push) Successful in 2m10s
Release / Deploy to homelab (push) Failing after 7s
Release / Gitea Release (push) Successful in 2m25s

Content visibility:
- Add `visibility` field to books ("public" | "admin_only"); new migration
  backfills all existing scraped books to admin_only
- Meilisearch: add visibility as filterable attribute; catalogue/search
  endpoints filter to public-only for non-admin requests
- Admin users identified by bearer token bypass the filter and see all books
- All PocketBase discovery queries (trending, recommended, recently-updated,
  audio shelf, discover, subscription feed) now filter to visibility=public
- New scraped books default to admin_only; WriteMetadata preserves existing
  visibility on PATCH (never overwrites)

Author submission:
- POST /api/admin/books/submit — creates a public book with submitted_by
- PATCH /api/admin/books/{slug}/publish / unpublish — toggle visibility
- SvelteKit proxies: /api/admin/books/[slug]/publish|unpublish
- /api/books/[slug] endpoint for admin book lookup

Frontend:
- backendFetchAdmin() helper sends admin token on any path
- Catalogue server load uses admin fetch when user is admin
- /submit page: author submission form with genre picker and rights assertion
- "Publish" nav link shown to all logged-in users
- Admin catalogue-tools: visibility management panel (load book by slug, toggle)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Admin
2026-04-16 20:20:16 +05:00
parent 50a13447a4
commit da37b1be88
18 changed files with 769 additions and 23 deletions

View File

@@ -0,0 +1,75 @@
// Migration 3 — add visibility + submitted_by fields to books.
//
// visibility: "public" | "admin_only"
// All existing (scraped) books are backfilled to "admin_only".
// New author-submitted books are created with "public".
//
// submitted_by: optional app_users ID for books submitted by a registered author.
// Empty for scraped books.
//
// The backfill iterates books in pages of 200. It is idempotent: books whose
// visibility is already set are skipped.
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
coll, err := app.FindCollectionByNameOrId("books")
if err != nil {
return err
}
changed := false
if coll.Fields.GetByName("visibility") == nil {
coll.Fields.Add(&core.TextField{Name: "visibility"})
changed = true
}
if coll.Fields.GetByName("submitted_by") == nil {
coll.Fields.Add(&core.TextField{Name: "submitted_by"})
changed = true
}
if changed {
if err := app.Save(coll); err != nil {
return err
}
}
// Backfill: mark all existing books as admin_only where visibility is empty.
// These are scraped books that pre-date this migration.
const perPage = 200
for page := 1; ; page++ {
records, err := app.FindRecordsByFilter(
"books", `visibility=""`, "+id", perPage, (page-1)*perPage, nil,
)
if err != nil || len(records) == 0 {
break
}
for _, rec := range records {
rec.Set("visibility", "admin_only")
// Best-effort: ignore individual save errors (don't abort migration).
_ = app.Save(rec)
}
if len(records) < perPage {
break
}
}
return nil
}, func(app core.App) error {
coll, err := app.FindCollectionByNameOrId("books")
if err != nil {
return nil
}
for _, name := range []string{"visibility", "submitted_by"} {
f := coll.Fields.GetByName(name)
if f == nil {
continue
}
coll.Fields.RemoveById(f.GetId())
}
return app.Save(coll)
})
}