Commit Graph

24 Commits

Author SHA1 Message Date
Admin
83a5910a59 feat: book commenting system with upvote/downvote + fix profile SSR crash
Some checks failed
CI / Scraper / Test (push) Successful in 10s
CI / Scraper / Lint (push) Successful in 12s
CI / Scraper / Lint (pull_request) Successful in 8s
CI / Scraper / Test (pull_request) Successful in 19s
CI / UI / Build (push) Successful in 32s
CI / Scraper / Docker Push (pull_request) Has been skipped
CI / UI / Build (pull_request) Successful in 15s
CI / UI / Docker Push (pull_request) Has been skipped
CI / UI / Docker Push (push) Failing after 11s
CI / Scraper / Docker Push (push) Successful in 45s
iOS CI / Build (push) Failing after 2m44s
iOS CI / Test (push) Has been skipped
iOS CI / Build (pull_request) Failing after 5m46s
iOS CI / Test (pull_request) Has been skipped
- Add book_comments and comment_votes PocketBase collections (pb-init.sh + pocketbase.go EnsureCollections)
- Web: CommentsSection.svelte with post form, vote buttons, lazy-loaded per-book
- API routes: GET/POST /api/comments/[slug], POST /api/comments/[id]/vote
- iOS: BookComment + CommentsResponse models, fetchComments/postComment/voteComment in APIClient, CommentsView + CommentsViewModel wired into BookDetailView
- Fix profile page SSR crash (ERR_MODULE_NOT_FOUND cropperjs): lazy-load AvatarCropModal via dynamic import guarded by browser, move URL.createObjectURL into onMount
2026-03-10 18:05:41 +05:00
Admin
52f876d8e8 feat: avatar upload via presigned PUT URL flow
Some checks failed
CI / Scraper / Lint (push) Successful in 11s
CI / Scraper / Lint (pull_request) Successful in 9s
CI / Scraper / Test (push) Successful in 19s
CI / UI / Build (push) Successful in 21s
CI / Scraper / Test (pull_request) Successful in 9s
CI / UI / Build (pull_request) Successful in 27s
CI / Scraper / Docker Push (pull_request) Has been skipped
CI / Scraper / Docker Push (push) Successful in 47s
CI / UI / Docker Push (pull_request) Has been skipped
Release / UI / Build (push) Successful in 21s
Release / UI / Docker (push) Successful in 5m1s
iOS CI / Test (push) Has been cancelled
iOS CI / Build (push) Has been cancelled
CI / UI / Docker Push (push) Failing after 9m10s
iOS CI / Build (pull_request) Failing after 4m3s
iOS CI / Test (pull_request) Has been skipped
- Go scraper: add PresignAvatarUploadURL/PresignAvatarURL/DeleteAvatar to
  Store interface, implement on HybridStore+MinioClient, register
  GET /api/presign/avatar-upload/{userId} and /api/presign/avatar/{userId}
- SvelteKit: replace direct AWS S3 SDK in minio.ts with presign calls to
  the Go scraper; rewrite avatar +server.ts (POST=presign, PATCH=record key)
- iOS: rewrite uploadAvatar() as 3-step presigned PUT flow; refactor
  chip components into shared ChipButton in CommonViews.swift
2026-03-10 17:05:43 +05:00
Admin
fb8f1dfe25 feat: add avatar upload support (MinIO bucket, PocketBase field, SvelteKit API, iOS ProfileView)
Some checks failed
CI / Scraper / Lint (push) Successful in 15s
CI / Scraper / Test (push) Successful in 19s
CI / UI / Build (push) Successful in 21s
CI / Scraper / Lint (pull_request) Successful in 14s
iOS CI / Test (push) Has been cancelled
iOS CI / Build (push) Has been cancelled
CI / Scraper / Test (pull_request) Successful in 15s
CI / UI / Build (pull_request) Successful in 16s
CI / Scraper / Docker Push (pull_request) Has been skipped
CI / UI / Docker Push (pull_request) Has been skipped
iOS CI / Build (pull_request) Failing after 1m32s
iOS CI / Test (pull_request) Has been skipped
CI / UI / Docker Push (push) Successful in 6m42s
CI / Scraper / Docker Push (push) Successful in 7m12s
2026-03-10 16:08:38 +05:00
Admin
c6536d5b9f Add /admin/audio-jobs page for audio generation job history
Some checks failed
Deploy / Deploy Production (push) Has been skipped
Deploy / Cleanup Preview (push) Has been skipped
Deploy / Deploy Preview (push) Failing after 0s
CI / Scraper / Test (pull_request) Failing after 6s
CI / UI / Build (pull_request) Failing after 6s
CI / Scraper / Lint (pull_request) Failing after 11s
CI / Scraper / Build (pull_request) Has been skipped
iOS CI / Build (pull_request) Failing after 2s
iOS CI / Test (pull_request) Has been skipped
New page mirrors the scrape tasks pattern: loads audio_jobs from
PocketBase via a new listAudioJobs() helper, shows a filterable table
(slug, chapter, voice, status, started, duration, error), and live-polls
every 3s while any job is pending or generating. Also fixes the
/admin/audio nav active-state check (was startsWith, now exact match)
to prevent it from matching /admin/audio-jobs.
2026-03-07 20:20:48 +05:00
Admin
1eb70e9b9b Add session management: track active sessions, show on profile, allow revocation
Some checks failed
Deploy / Deploy Production (push) Has been skipped
Deploy / Deploy Preview (push) Failing after 1s
Deploy / Cleanup Preview (push) Has been skipped
CI / UI / Build (pull_request) Failing after 13s
CI / Scraper / Lint (pull_request) Successful in 19s
CI / Scraper / Test (pull_request) Successful in 20s
CI / Scraper / Build (pull_request) Successful in 12s
- Add user_sessions PocketBase collection (user_id, session_id, user_agent, ip, created/last_seen)
- Extend auth token format to include a per-login authSessionId (4th segment)
- Hook validates authSessionId against DB on each request; revoked sessions are cleared immediately
- Login/register create a session record capturing user-agent and IP
- Profile page shows all active sessions with current session highlighted; per-session End/Sign out buttons
- GET /api/sessions and DELETE /api/sessions/[id] endpoints for client-side revocation
- Backward compatible: legacy 3-segment tokens pass through without DB check
2026-03-07 11:53:16 +05:00
Admin
8f0a2f7e92 feat: profile page, admin pages, infinite scroll on browse
Some checks failed
Deploy / Deploy Production (push) Has been skipped
Deploy / Cleanup Preview (push) Has been skipped
Deploy / Deploy Preview (push) Failing after 1s
CI / UI / Build (pull_request) Successful in 16s
CI / Scraper / Test (pull_request) Successful in 16s
CI / Scraper / Lint (pull_request) Successful in 19s
CI / Scraper / Build (pull_request) Successful in 16s
- Add /profile page with reading settings (voice, speed, auto-next) and password change form
- Add /admin/scrape page showing scraping task history with live status polling and trigger controls
- Add /admin/audio page showing audio cache entries with client-side search filter
- Add changePassword(), listAudioCache(), listScrapingTasks() to pocketbase.ts
- Add /api/admin/scrape and /api/browse-page server-side proxy routes
- Replace browse page pagination with IntersectionObserver infinite scroll
- Update nav: username becomes a /profile link; admin users see Scrape and Audio cache links
2026-03-06 18:58:24 +05:00
Admin
08d4718245 feat: personal library — filter by read/saved books, add save button
Some checks failed
Deploy / Deploy Production (push) Has been skipped
Deploy / Deploy Preview (push) Failing after 1s
Deploy / Cleanup Preview (push) Has been skipped
CI / Scraper / Lint (pull_request) Successful in 13s
CI / Scraper / Test (pull_request) Successful in 19s
CI / UI / Build (pull_request) Successful in 20s
CI / Scraper / Build (pull_request) Successful in 18s
2026-03-06 18:41:30 +05:00
Admin
b0547c1b43 fix(ui): paginate listAll to fetch beyond 500 records from PocketBase
The previous implementation sent a single request with perPage=500 and
silently dropped any records beyond that. Books with 900+ chapters were
truncated to 500 in the chapter list and reader prev/next navigation.
Now loops through all pages until totalItems is exhausted.
2026-03-04 19:39:19 +05:00
Admin
acbfafb8cd fix(audio): remove speed from TTS/cache/MinIO keys; fix presigned URL host rewrite
- Speed is no longer part of Kokoro generation, in-memory cache keys, or
  MinIO object keys — audio is always generated at 1.0 and playback speed
  is applied client-side via audioEl.playbackRate
- presignAudio() now calls rewriteHost() so chapter audio URLs use the
  public MinIO endpoint (same as presignVoiceSample already did)
- docker-compose.yml: rename MINIO_PUBLIC_ENDPOINT → PUBLIC_MINIO_PUBLIC_URL
  for the ui service so SvelteKit's $env/dynamic/public picks it up
2026-03-04 19:30:46 +05:00
Admin
3899a96576 feat(audio): add voice selector, voice samples, MediaSession cover art, and fix speed/voice bug
- Fix speed/voice bug: AudioPlayer no longer accepts speed/voice as props;
  startPlayback() reads audioStore.voice/speed directly instead of overwriting them
- Add GET /api/voices endpoint (Go) proxying Kokoro, cached in-memory
- Add POST /api/audio/voice-samples endpoint (Go) to pre-generate sample clips
  for all voices and store them in MinIO under _voice-samples/{voice}.mp3
- Add GET /api/presign/voice-sample/{voice} endpoint (Go)
- Add SvelteKit proxy routes: /api/voices, /api/presign/voice-sample, /api/audio/voice-samples
- Add presignVoiceSample() helper in minio.ts with proper host rewrite
- Pass book.cover through +page.server.ts -> +page.svelte -> AudioPlayer
- Set navigator.mediaSession.metadata on playback start so cover art,
  book title, and chapter title appear on phone lock screen / notification
2026-03-04 19:05:08 +05:00
Admin
82186cfd6d feat(ui): persist user settings and audio position across sessions
- Replace double inline player controls with compact 'now playing' indicator when mini-bar is active
- Add user_settings PocketBase collection (auto_next, voice, speed) and audio_time field on progress
- Add getSettings/saveSettings/setAudioTime/getAudioTime server functions
- Add GET/PUT /api/settings and GET/PATCH /api/progress/audio-time API routes
- Load settings server-side in layout and apply to audioStore on mount
- Debounced 800ms effect persists settings changes to DB
- Save audio currentTime on pause/end; restore position when replaying a chapter
2026-03-04 16:21:17 +05:00
Admin
0d7b985469 fix(audio): return 404 from presign when audio object not yet uploaded
handlePresignAudio now checks AudioExists before presigning; previously it
would generate a valid-looking signed URL for a non-existent object, causing
the browser to get 404 from MinIO directly.

- Add AudioExists to Store interface and HybridStore
- Guard handlePresignAudio with AudioExists check, return 404 if missing
- Propagate 404 through presignAudio() in minio.ts (typed error.status=404)
- SvelteKit presign route forwards 404 to the browser instead of 500
2026-03-04 15:21:17 +05:00
Admin
06feb91f4f feat(ui): add homepage with Continue Reading, Recently Updated, and stats
Replace the single-button placeholder with a full homepage:
- Stats bar: total books, total chapters, books in progress
- Continue Reading grid (up to 6): links directly to last chapter read
- Recently Updated grid (up to 6): recent books not already in progress
- Empty state with Discover Novels CTA when library is empty

New pocketbase.ts helpers: recentlyAddedBooks(), getHomeStats(),
listN(), countCollection().
2026-03-04 12:55:03 +05:00
Admin
8edad54b10 feat(progress): associate progress with logged-in user for cross-device sync
Add optional user_id field to the progress collection. When a user is
authenticated, progress is keyed by user_id instead of session_id, making
it portable across devices and browsers. Anonymous reading still works via
session_id as before.

On login or registration, any progress accumulated under the anonymous
session is merged into the user's account (most-recent timestamp wins),
so chapters read before logging in are not lost.

- scripts/pb-init.sh: add user_id field to progress collection schema
- scraper/internal/storage/pocketbase.go: add user_id to EnsureCollections
- ui/src/lib/server/pocketbase.ts: Progress interface gains user_id;
  getProgress/allProgress/setProgress accept optional userId and query/write
  by user_id when present; add mergeSessionProgress() helper
- ui/src/routes/login/+page.server.ts: call mergeSessionProgress after
  successful login and registration (fire-and-forget, non-fatal)
- ui/src/routes/api/progress/+server.ts: pass locals.user?.id to setProgress
- ui/src/routes/books/+page.server.ts: pass locals.user?.id to allProgress
- ui/src/routes/books/[slug]/+page.server.ts: pass locals.user?.id to getProgress
2026-03-04 09:51:11 +05:00
Admin
1b05b6ebc6 fix(audio): sign presigned audio URLs with public MinIO endpoint
Add a second MinIO client (pub) initialized with MINIO_PUBLIC_ENDPOINT so
presigned audio URLs are signed against the public hostname from the start,
rather than signed internally and then rewritten. This avoids AWS4 signature
mismatch (SignatureDoesNotMatch 403) that occurred when the signed host was
substituted after signing.

- storage/minio.go: add PublicEndpoint/PublicUseSSL to MinioConfig; add pub
  client field; NewMinioClient creates pub client when public endpoint differs;
  PresignAudio uses pub, PresignChapter keeps internal client
- cmd/scraper/main.go: wire MINIO_PUBLIC_ENDPOINT and MINIO_PUBLIC_USE_SSL env vars
- docker-compose.yml: expose MINIO_PUBLIC_ENDPOINT and MINIO_PUBLIC_USE_SSL to scraper service
- ui/src/lib/server/minio.ts: remove rewriteHost() call from presignAudio
2026-03-04 01:15:46 +05:00
Admin
cabdd3ffdd fix(ui/minio): don't rewrite presigned URL host for server-side chapter fetches
The SvelteKit server fetches chapter markdown server-side using the presigned
URL. rewriteHost() was replacing the internal minio:9000 host with the public
PUBLIC_MINIO_PUBLIC_URL (localhost:9000), which is unreachable from inside
Docker, causing MinIO 403/connection errors.

presignChapter now takes an optional rewrite=false parameter — the server-side
load function gets the raw internal URL, while any future browser-facing use
can pass rewrite=true to get the public-facing URL.
2026-03-04 01:06:52 +05:00
Admin
353d7397eb fix(ui/books): guard null book fields in template; log null titles from PocketBase 2026-03-04 00:49:50 +05:00
Admin
89ff90629f fix(ui/books): guard against null items from PocketBase listAll 2026-03-04 00:46:40 +05:00
Admin
d89cefe975 fix(auth): add Bearer prefix to PocketBase Authorization header
PocketBase v0.23+ requires 'Bearer <token>' — sending the raw JWT without
the prefix results in 403 'Only superusers can perform this action' on all
collection record endpoints. Fix applied in three places:
- ui/src/lib/server/pocketbase.ts (pbGet, pbPost, pbPatch helpers)
- scraper/internal/storage/pocketbase.go (pbClient.do)
- scripts/pb-init.sh (wget --header in create_collection)
2026-03-03 20:15:44 +05:00
Admin
56bf4dde22 fix(auth): update PocketBase v0.23+ auth endpoint and rename users collection
- Replace deprecated /api/admins/auth-with-password with
  /api/collections/_superusers/auth-with-password in both the
  SvelteKit UI (pocketbase.ts) and Go scraper (pocketbase.go)
- Rename custom users collection to app_users to avoid name clash
  with PocketBase's built-in users auth collection
- Fix docker-compose volume mount path pb/pb_data -> pb_data so
  persisted data matches the entrypoint --dir flag
- Add PB_ADMIN_EMAIL/PB_ADMIN_PASSWORD env vars to pocketbase service
  so the superuser is auto-created on first boot
2026-03-03 16:59:25 +05:00
Admin
bf5774d8d0 feat(ui): add structured JSON logging to all server-side routes and lib
Introduces a logger.ts module emitting slog-compatible JSON lines to stderr.
Replaces silent catch blocks and console.error calls throughout minio.ts,
pocketbase.ts, hooks.server.ts, login, books, browse, and all API routes so
auth/registration failures, MinIO presign errors, and scraper proxy failures
are now visible in container logs.
2026-03-03 14:34:48 +05:00
Admin
3c26dfe2c0 feat(auth): add user authentication with roles, HMAC-signed cookies, and login/register UI
- Add `users` PocketBase collection (username, password_hash, role, created)
- Implement HMAC-SHA256 signed cookie auth in hooks.server.ts; token payload is userId:username:role
- Add User type, getUserByUsername, createUser (scrypt), loginUser (timing-safe) to pocketbase.ts
- Add login/register page with tabbed form UI and server actions
- Add logout route that clears the auth cookie
- Add layout.server.ts auth guard: redirect unauthenticated users to /login
- Extend App.Locals and App.PageData with role field
- Add AUTH_SECRET, POCKETBASE_ADMIN_EMAIL/PASSWORD to .env.example
- Install @types/node for Node crypto/scrypt types
2026-03-03 14:03:11 +05:00
Admin
33e2a4dc01 feat: add MinIO presign endpoints to scraper API and SvelteKit presign helper 2026-03-02 21:35:27 +05:00
Admin
cb4be0848f feat(ui): add server-side PocketBase client and session cookie hook 2026-03-02 21:32:20 +05:00