refactor: optimize cron scheduling, DB maintenance, and TMDB reliability

- Skip recommendations refresh for titles fetched within the last 7 days via `getTitlesWithFreshRecommendations`; only stale titles are re-fetched
- Replace `Promise.allSettled` in `refreshTvChildren` with a `mapWithConcurrency` helper (concurrency = 5) to stay within TMDB's rate limit
- Add 30s request timeout middleware to TMDB client to prevent indefinitely hung fetches
- Expand `optimizeDb` weekly job to prune 30-day-old cron runs, integration events, expired sessions, and expired verifications
- Add `wal_checkpoint(TRUNCATE)` to `optimizeDatabase` so the WAL is flushed before `PRAGMA optimize`
- Add `recoverStaleImportJobs` called on server startup to mark any `pending`/`running` import jobs as errored after a crash/restart
- Return `false` from `triggerJob` immediately if the job is already running
This commit is contained in:
2026-03-23 13:59:31 -04:00
parent d9be566863
commit b289b7abed
17 changed files with 235 additions and 36 deletions
+1 -2
View File
@@ -1,7 +1,6 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"extends": ["../.oxlintrc.json"],
"plugins": ["eslint", "typescript", "unicorn", "oxc", "react", "nextjs", "import"],
"plugins": ["oxc", "eslint", "typescript", "react", "nextjs", "import", "unicorn"],
"categories": {
"correctness": "error",
"suspicious": "warn"
+5 -5
View File
@@ -10,11 +10,11 @@ Sofa includes optional, privacy-focused telemetry to help understand how the pro
Sofa has two separate telemetry systems depending on the platform:
| | Server | Mobile App | Web App |
| ----------- | ---------------- | ------------ | ------- |
| **System** | Custom reporting | PostHog | None |
| **Default** | Disabled | Disabled | N/A |
| **Opt-in** | Admin toggle | User toggle | N/A |
| | Server | Mobile App | Web App |
| ----------- | ---------------- | ----------- | ------- |
| **System** | Custom reporting | PostHog | None |
| **Default** | Disabled | Disabled | N/A |
| **Opt-in** | Admin toggle | User toggle | N/A |
The web app has **zero analytics** — no tracking scripts, no third-party services.
+23 -12
View File
@@ -8,8 +8,9 @@ icon: Compass
The dashboard is your home screen after logging in. It shows a personalized overview of your tracking activity.
- **Stats** — Cards showing movies watched, episodes watched, library size, and completed titles. Each card has a period selector (today, this week, this month, this year).
- **Stats** — Cards showing movies watched, episodes watched, library size, and completed titles. Each card has a period selector (today, this week, this month, this year) and a sparkline chart of your watch history.
- **Continue Watching** — TV shows you're currently watching, with the next unwatched episode highlighted. Click a show to jump to its detail page and pick up where you left off.
- **Upcoming** — Episodes and releases airing in the next 7 days for titles in your library. Click "View all" to open the full upcoming page with episodes grouped by date (Today, Tomorrow, This Week, Later).
- **In Your Library** — A grid of all titles you're tracking, regardless of status.
- **Recommended for You** — Personalized suggestions based on titles you've watched and rated highly.
@@ -19,23 +20,31 @@ If your library is empty, the dashboard shows an invitation to start exploring.
### The Status System
Every title in your library has one of three statuses:
Every title in your library has a status:
| Status | Meaning |
| --------------- | ------------------------------ |
| **Watchlist** | You plan to watch this |
| **In Progress** | You're currently watching this |
| **Completed** | You've finished watching this |
| Status | Meaning |
| ------------- | ------------------------------------------------------------------------- |
| **Watchlist** | You plan to watch this |
| **Watching** | You're currently watching this (TV shows only) |
| **Caught Up** | You've watched all aired episodes, but the show is still airing (TV only) |
| **Completed** | You've finished watching this |
Set a status from any title's detail page by clicking the status button, or press <kbd>W</kbd> to cycle through statuses.
Click the status button on a title page to add it to your watchlist. If the title is already in your library, hovering the button reveals a "Remove" action. Press <kbd>W</kbd> to toggle the title in or out of your watchlist.
<Callout type="info">
**Caught Up** vs **Completed** is automatic — when you've watched every episode of a TV show that
is still airing (Returning Series or In Production on TMDB), Sofa shows **Caught Up** instead of
**Completed**. Once the show ends, it switches to **Completed** automatically.
</Callout>
### Automatic Status Transitions
Sofa automatically updates statuses as you log watches so you don't have to manage them manually:
- **First episode watched** on a TV show → status changes to **In Progress** (if it was on your Watchlist or had no status)
- **All episodes watched** on a TV show → status changes to **Completed**
- **Unwatch an episode** on a completed show → status drops back to **In Progress**
- **First episode watched** on a TV show → status changes to **Watching** (if it was on your Watchlist or had no status)
- **All episodes watched** on a TV show → status changes to **Caught Up** (if the show is still airing) or **Completed** (if the show has ended)
- **Unwatch an episode** on a completed show → status drops back to **Watching**
- **Unwatch all episodes** on a show → status drops back to **Watchlist**
- **Mark a movie as watched** → status changes to **Completed**
These same transitions apply when watches are logged automatically via [webhook integrations](/docs/integrations) (Plex, Jellyfin, Emby) or [imported from another app](/docs/integrations/import) (Trakt, Simkl, Letterboxd).
@@ -118,7 +127,9 @@ Press <kbd>?</kbd> anywhere in the app to see the full shortcut reference. Here'
| Global | `?` | Show keyboard shortcuts |
| Navigation | `g` then `h` | Go to dashboard |
| Navigation | `g` then `e` | Go to explore |
| Title page | `w` | Cycle watch status |
| Navigation | `g` then `u` | Go to upcoming |
| Navigation | `g` then `s` | Go to settings |
| Title page | `w` | Toggle watchlist |
| Title page | `m` | Mark movie as watched |
| Title page | `Escape` | Go back |
| Title page | `1``5` | Rate 15 stars |
+1 -1
View File
@@ -31,7 +31,7 @@ const config = {
source: "/testflight",
destination: "https://testflight.apple.com/join/tjSddcaZ",
permanent: false,
}
},
];
},
async rewrites() {
+5 -5
View File
@@ -3,15 +3,15 @@
"version": "0.0.0",
"private": true,
"scripts": {
"build": "next build",
"dev": "next dev",
"start": "next start",
"build": "bun --bun next build",
"dev": "bun --bun next dev",
"start": "bun --bun next start",
"lint": "oxlint",
"format": "oxfmt",
"format:check": "oxfmt --check",
"check-types": "fumadocs-mdx && next typegen && tsc --noEmit",
"check-types": "bun --bun fumadocs-mdx && bun --bun next typegen && tsc --noEmit",
"generate:api-docs": "bun scripts/generate-api-docs.ts",
"postinstall": "fumadocs-mdx"
"postinstall": "bun --bun fumadocs-mdx"
},
"dependencies": {
"@takumi-rs/image-response": "^0.73.1",
+3
View File
@@ -0,0 +1,3 @@
{
"bunVersion": "1.x"
}