Commit Graph
26 Commits
Author SHA1 Message Date
jake 84c4356a9f Add user avatar upload to account settings and nav bar
- Add `uploadAvatarAction` and `removeAvatarAction` server actions;
  store files under `DATA_DIR/avatars/{userId}.{ext}` via Bun.write
- Add `GET /api/avatars/[userId]` route that scans the avatar dir with
  Bun.Glob and serves with immutable Cache-Control (cache-busted via
  query param)
- Refactor AccountSection with hover overlay, AnimatePresence fade,
  and file input; click avatar to upload (no image) or remove (has image)
- Replace NavBar user initial badge with Avatar + DropdownMenu showing
  name, email, settings link, and sign-out
- Thread `userEmail` and `userImage` props through AuthenticatedShell
  → NavBar and SettingsPage → AccountSection
2026-03-08 11:02:33 -04:00
jake 383f0b819b Polish mobile layouts, extract ExpandableText, fix image sizing
- Stack title-hero poster/metadata vertically on mobile (flex-col md:flex-row),
  switch backdrop tall breakpoint from sm to md
- Extract bio expand/collapse logic into reusable ExpandableText component;
  use it in PersonHero and TitleHero
- Fix ContinueWatchingCard image to use fill + sizes instead of fixed
  width/height to avoid layout shift
- Add fade-out gradient on genre chip scrollbar edge on mobile
- Show scaled-down ambient glow on mobile instead of hiding it entirely
- Humanize knownForDepartment labels (Acting→Actor, Directing→Director, etc.)
- Change cast member name from truncate to line-clamp-2 for wrapping
- Hide tooltip arrow via [&>:last-child]:hidden instead of color-matching it
- Apply safe-area-inset padding to HeroBanner content on notched devices
2026-03-07 12:09:51 -05:00
jake 8d8c49a7f0 Replace API route handlers with server actions across the app
- Delete 10 API routes (stats, system-health, update-check, jobs/trigger,
  backup/restore, person, titles import/resolve, registration/status) and
  move logic into server actions in lib/actions/settings.ts and
  lib/actions/watchlist.ts
- Refactor SystemHealthCards to accept initialData prop and hydrate Jotai
  atoms via useHydrateAtoms; replace useSystemHealth SWR hook with a
  lightweight useSystemHealthRefresh that calls getSystemHealthAction
- Convert BackupRestoreSection to use restoreBackupAction + useTransition
  instead of a raw fetch call
- Split SystemStatusCard, BackgroundJobsCard, and StorageCard into
  standalone components reading from systemHealthDataAtom
- Make SetupPage async with connection() to opt into dynamic rendering
2026-03-06 17:31:14 -05:00
jake 49cea568a9 Add Sonarr and Radarr list integrations
- New `lists` service fetches Sonarr/Radarr library via their REST APIs
  and auto-imports matching titles from TMDB into the user's watchlist
- `app/api/lists/[token]/route.ts` webhook endpoint triggers a list sync
- Unified `IntegrationCard` component replaces `WebhookCard`, handling
  both webhook-style (Plex/Jellyfin/Emby) and list-style (Sonarr/Radarr)
  integrations with per-type config forms
- Schema migration adds `sonarr` and `radarr` to the integration type
  enum and a `listConnections` table for list-based integrations
- 212 tests added for the lists service covering import, deduplication,
  and error handling
2026-03-06 15:34:34 -05:00
jake fb24b25d34 Replace lib/version.ts with APP_VERSION build arg injected via Docker
Remove the static package.json import in lib/version.ts and instead
pass APP_VERSION as a Docker build arg extracted from package.json by
the CI workflow. Both the builder and runner stages receive APP_VERSION
and GIT_COMMIT_SHA as environment variables, making runtime version
info available via process.env without bundling package.json into the
standalone output.
2026-03-06 13:00:41 -05:00
jakeandClaude Opus 4.6 436f3d7fad Fix Select trigger display values and add TMDB attribution
Base UI Select.Value renders raw values by default — add children render
functions to map values to human-readable labels (e.g. "this_month" →
"This Month", "0" → "unlimited"). Switch inline select underlines from
border-bottom to text-decoration for proper baseline alignment. Add TMDB
attribution with logo and disclaimer to settings footer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 19:15:52 -05:00
jakeandClaude Opus 4.6 fc982bee1b Comprehensive Web Interface Guidelines audit: accessibility, motion, and typography
- Add global MotionConfig reducedMotion="user" in root layout
- Add color-scheme: dark, theme-color meta, skip-to-main-content link
- Add aria-hidden={true} to ~60+ decorative icons across 33 files
- Add aria-label to all icon-only buttons (logout, star rating, play, delete, etc.)
- Replace transition-all with specific properties (11 files)
- Add motion-safe: prefix for CSS hover transforms and animate-pulse
- Add prefers-reduced-motion: reduce override in globals.css
- Add useReducedMotion guard to status-dot pulsing animation
- Fix focus-visible styles on auth form inputs (focus → focus-visible, stronger ring)
- Add text-balance to all headings (13 files)
- Replace "..." with "…" (U+2026) in all loading/placeholder text
- Add autocomplete, spellCheck, role="alert" to auth form
- Add aria-label to Switch controls and filmography select
- Fix heading hierarchy (h3 → h2 where h2 was skipped)
- Add break-words to title overview, overscroll-contain to drawer
- Add confirmation dialog for destructive library removal
- Add group-focus-within:opacity-100 for keyboard-accessible backup actions
- Add role="radio" + aria-checked to star rating buttons
- Add aria-label to carousel region, role="img" to TMDB logo SVG
- Add text-foreground to native select for dark mode consistency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 18:20:25 -05:00
jakeandClaude Opus 4.6 f36ca0cbf8 Optimize performance: batch DB ops, N+1 fixes, Suspense streaming, and Jotai best practices
- Add composite indexes on userEpisodeWatches and userMovieWatches for hot queries
- Batch episode tracking: wrap season/batch watches in single transaction (~8 queries vs 8*N)
- Fix N+1 patterns in credits, recommendations, and filmography with batch prefetch+insert
- Stream TV season hydration via Suspense instead of blocking page render
- Optimize webhook logs (per-connection LIMIT 10) and system health queries
- Merge genre filter waterfalls into single Promise.all fetch
- Migrate deprecated Jotai loadable() to unwrap()
- Replace isolated createStore()+Provider with useHydrateAtoms on root store
- Remove unnecessary atomWithStorage SSR guards (handled by Jotai internally)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 17:33:27 -05:00
jakeandClaude Opus 4.6 fb6c88e8f5 Fix N+1 queries, add React.cache() session dedup, and parallelize async ops
Replace nested loop queries with batch fetches using inArray() across
discovery, tracking, metadata, settings, and system-health services.
The biggest win is getContinueWatchingFeed() going from ~375+ queries
to 5. Add a cached getSession() wrapper via React.cache() to deduplicate
auth lookups shared between layouts and pages. Parallelize independent
async operations in importTitle(), cacheImagesJob(), and the explore page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 12:07:54 -05:00
jake 107eb9a9e7 Overhaul system health section with job trigger controls and live timestamps
Rebuild the background jobs card as a sortable table showing each job's
schedule, last run time (live-updating via a new useTimeAgo hook), last
duration, and a manual trigger button backed by a new POST
/api/admin/jobs/trigger route. Extract StatusDot into a shared
component. Add cronToHuman() to display schedule patterns as readable
strings (e.g. "Every 6h", "Daily at 03:00"). Replace static
formatDistanceToNow calls throughout the health section with a
LiveTimeAgo component that refreshes every 30 seconds. Also swap a
handful of section icons for better visual matches across settings cards.
2026-03-05 11:52:37 -05:00
jakeandClaude Opus 4.6 a5791b150f Add version update check feature for admin users
Checks current version against latest GitHub release every 6 hours via
cron job, caches result in appSettings, and surfaces updates through a
toast notification (once per browser session) and an animated badge in
the settings footer. Includes an admin toggle to enable/disable checks
(enabled by default). Also renames ServerSection to RegistrationSection
now that update checks are in their own card.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:36:05 -05:00
jakeandClaude Opus 4.6 bafa45ec79 Add Emby webhook integration and remove unused username field
Add Emby as a third media server integration alongside Plex and Jellyfin.
Emby webhooks use a similar payload format (JSON with nested Item object
and ProviderIds). Also removes the mediaServerUsername field from all
webhook connections since it was never used for authentication — the
token-in-URL is the sole auth mechanism. This simplifies the connection
UX to a single "Connect" button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:50:32 -05:00
jakeandClaude Opus 4.6 7a64ced5e0 Add system health dashboard and split settings into separate cards
Add admin-only system health API and UI showing database stats, TMDB
connection status, background job history, storage usage, and environment
variables (with redacted secrets). Split monolithic backup card into
three focused cards (backups, schedule, restore) with dedicated section
headers for Server, Security, and Backups.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 18:30:22 -05:00
jakeandClaude Opus 4.6 6cfe3a0439 Add configurable backup schedule with admin UI
- Make backup frequency (6h/12h/1d/7d), time, and day-of-week
  configurable via settings, stored in appSettings key-value table
- Add rescheduleBackup() to dynamically update cron job at runtime
- Differentiate manual (sofa-manual-*) vs scheduled (sofa-scheduled-*)
  backup filenames with source icons and tooltips in the backup list
- Pruning now only targets scheduled backups, leaving manual ones intact
- Merge backup-actions.ts into actions.ts, use shadcn DropdownMenu for
  all selectors (consistent with dashboard stats cards)
- Fix hydration mismatch on relative backup timestamps

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 13:43:40 -05:00
jakeandClaude 83456c6a21 Migrate from Node.js built-ins to Bun-native APIs (#1)
Co-authored-by: Claude <noreply@anthropic.com>
2026-03-04 12:49:38 -05:00
jakeandClaude Opus 4.6 7d69262d0e Polish settings UI, add tooltips, replace timeAgo with date-fns
Clean up settings section design: consolidate backup cards, add
hover-reveal actions with tooltips, improve icon sizing across
components, and replace custom timeAgo helper with date-fns
formatDistanceToNow in webhook card.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 20:23:52 -05:00
jakeandClaude Opus 4.6 e59364e126 Add database backup/restore feature with admin UI
- Backup service using VACUUM INTO for WAL-safe atomic snapshots
- Server-side storage in DATA_DIR/backups with download/upload API routes
- Restore with integrity validation and automatic pre-restore safety backup
- Scheduled daily backups via cron with configurable retention
- Admin-only settings UI consolidated under single Server section
- Clean up account section sign-out button, fix switch sub-pixel rendering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 20:01:10 -05:00
jakeandClaude Opus 4.6 d9b408128b Remove unnecessary await/async from sync bun:sqlite db calls
drizzle-orm/bun-sqlite is fully synchronous — all queries return values
directly, not promises. Remove await from all db calls, drop async from
functions that no longer need it, simplify Promise.all patterns that
wrapped sync operations, and fix setSetting() which was missing .run()
(previously masked by await triggering execution via thenable).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 19:04:40 -05:00
jakeandClaude Opus 4.6 1a5a1e631c Use GIT_COMMIT_SHA env var instead of execSync for Docker compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 12:56:11 -05:00
jakeandClaude Opus 4.6 03917e0a1d Add version and commit info footer to settings page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 12:17:36 -05:00
jakeandClaude Opus 4.6 8487c3d7b1 Convert settings page to server component with server actions
Replace client-side data fetching with server-side queries and server
actions, eliminating 3 API route files. Extract page into granular
client components (account, integrations, server, webhook card) with
optimistic updates. Rename sections: Media Servers → Integrations,
Administration → Server. Add admin badge to account section.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 09:32:18 -05:00
jakeandClaude Opus 4.6 83e2bc5143 Make media server cards collapsible with matching input/button heights
Wrap Plex and Jellyfin webhook cards in collapsible sections with chevron
indicators. Fix input/button height mismatch by using default button sizes
(h-7) instead of lg (h-8). Add Plex Pass requirement info box and move
the enable/disable switch into the collapsible body.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 15:55:00 -05:00
jakeandClaude Opus 4.6 3455ff9c51 Replace custom CSS tooltips with shadcn Tooltip component
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 14:50:27 -05:00
jake f94ceaf233 Move auth protection to middleware and server layout
Replace client-side session checks (useSession + router.replace) with
a two-layer server-side approach: Next.js middleware performs a fast
cookie presence check for all protected routes, and the (pages) layout
does a full server-side session validation before rendering.

Move login/register into a dedicated (auth) route group with its own
minimal layout so they sit outside the authenticated shell. Lift the
Toaster to the root layout so it remains available across all route
groups.
2026-03-02 14:38:54 -05:00
jakeandClaude Opus 4.6 67356e04c6 Add Plex and Jellyfin webhook integration for automatic watch tracking
When a user finishes watching on their media server, a webhook fires and
Sofa logs it as watched, triggering all existing auto-transitions. Each
user configures their connection in settings and gets a unique webhook URL.

- Add webhookConnections and webhookEventLog schema tables
- Add TMDB findByExternalId for resolving IMDB/TVDB IDs
- Add source parameter to tracking functions (plex/jellyfin)
- Add webhook processing service with payload parsers, title resolution,
  and deduplication
- Add public webhook receiver route (token-based auth)
- Add authenticated settings API routes for managing connections
- Add Media Servers section to settings UI with Plex/Jellyfin cards

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 16:24:39 -05:00
jakeandClaude Opus 4.6 db3a6cc3d4 Add auto-close registration and admin settings page
First user to register automatically becomes admin and registration
closes. Admins can re-open registration from a new settings page.
Uses Better Auth admin plugin for role management and a new appSettings
table for the registration flag. Mobile tab bar now links to settings
instead of inline logout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 13:51:20 -05:00