diff --git a/.env.example b/.env.example index 5c796e5..3b2e0c6 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,9 @@ # ---- Copy this file to .env and fill in the values ---- # Postgres (used by docker-compose for the db service and the DATABASE_URL) -POSTGRES_USER=subfeed +POSTGRES_USER=siftlode POSTGRES_PASSWORD=change-this-password -POSTGRES_DB=subfeed +POSTGRES_DB=siftlode # Host port the app is exposed on (http://localhost:) APP_PORT=8080 @@ -51,7 +51,7 @@ SMTP_FROM= # --- Deployment role --- # DATABASE_URL is set automatically by docker-compose.yml / docker-compose.server.yml to the # bundled `db` service. Override it only for local dev against the central server DB, e.g.: -# DATABASE_URL=postgresql+psycopg://subfeed:@your-db-host:5432/subfeed +# DATABASE_URL=postgresql+psycopg://siftlode:@your-db-host:5432/siftlode # Exactly one instance may run the background scheduler. The central server keeps it on; every # other instance (local dev, etc.) must set SCHEDULER_ENABLED=false. The compose files set this # for you (server=true via docker-compose.server.yml, local=false via docker-compose.localdev.yml). diff --git a/README.md b/README.md index 694f2eb..9c9c218 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ sync. depend on a refresh token (which expires after 7 days while the OAuth screen is in *Testing*). - **Local dev** (`docker-compose.localdev.yml`): webapp only, no local DB, no scheduler. In `.env` - set `DATABASE_URL` to the central DB (e.g. `…@your-db-host:5432/subfeed`), then + set `DATABASE_URL` to the central DB (e.g. `…@your-db-host:5432/siftlode`), then `docker compose -f docker-compose.localdev.yml up --build` and browse http://localhost:8080. **Exactly one instance may run the scheduler.** The server keeps `SCHEDULER_ENABLED=true`; every diff --git a/backend/app/auth.py b/backend/app/auth.py index 88f4549..1ac8bee 100644 --- a/backend/app/auth.py +++ b/backend/app/auth.py @@ -71,7 +71,7 @@ BASE_SCOPES = "openid email profile" READ_SCOPES = f"{BASE_SCOPES} {READ_SCOPE}" WRITE_SCOPES = f"{BASE_SCOPES} {READ_SCOPE} {WRITE_SCOPE}" -log = logging.getLogger("subfeed.auth") +log = logging.getLogger("siftlode.auth") router = APIRouter(prefix="/auth", tags=["auth"]) diff --git a/backend/app/config.py b/backend/app/config.py index 7d454a6..cce9b1c 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -30,7 +30,7 @@ class Settings(BaseSettings): git_sha: str = "unknown" build_date: str = "" - database_url: str = "postgresql+psycopg://subfeed:subfeed@db:5432/subfeed" + database_url: str = "postgresql+psycopg://siftlode:siftlode@db:5432/siftlode" # Session cookie signing key. secret_key: str = _DEFAULT_SECRET_KEY diff --git a/backend/app/email.py b/backend/app/email.py index 8e1e653..da0eea4 100644 --- a/backend/app/email.py +++ b/backend/app/email.py @@ -14,7 +14,7 @@ from app import sysconfig from app.config import settings from app.db import SessionLocal -log = logging.getLogger("subfeed.email") +log = logging.getLogger("siftlode.email") def _smtp() -> dict: diff --git a/backend/app/main.py b/backend/app/main.py index d000c16..4fc3803 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -5,20 +5,20 @@ from pathlib import Path from fastapi import FastAPI, HTTPException -# When started via uvicorn --log-config the "subfeed" logger is already configured +# When started via uvicorn --log-config the "siftlode" logger is already configured # (see log_config.json). This block is a timestamped fallback for other entrypoints # (tests, scripts) so our logs are never silently dropped. -_subfeed_logger = logging.getLogger("subfeed") -if not _subfeed_logger.handlers: +_siftlode_logger = logging.getLogger("siftlode") +if not _siftlode_logger.handlers: _handler = logging.StreamHandler(sys.stdout) _handler.setFormatter( logging.Formatter("%(asctime)s %(levelname)-5s [%(name)s] %(message)s") ) - _subfeed_logger.addHandler(_handler) - _subfeed_logger.setLevel(logging.INFO) - _subfeed_logger.propagate = False + _siftlode_logger.addHandler(_handler) + _siftlode_logger.setLevel(logging.INFO) + _siftlode_logger.propagate = False -log = logging.getLogger("subfeed.app") +log = logging.getLogger("siftlode.app") from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse, JSONResponse from fastapi.staticfiles import StaticFiles diff --git a/backend/app/routes/channels.py b/backend/app/routes/channels.py index 3045e04..f1d00eb 100644 --- a/backend/app/routes/channels.py +++ b/backend/app/routes/channels.py @@ -28,7 +28,7 @@ from app.youtube.client import YouTubeClient, YouTubeError router = APIRouter(prefix="/api/channels", tags=["channels"]) -log = logging.getLogger("subfeed.api") +log = logging.getLogger("siftlode.api") @router.get("") diff --git a/backend/app/scheduler.py b/backend/app/scheduler.py index e726929..fd8de99 100644 --- a/backend/app/scheduler.py +++ b/backend/app/scheduler.py @@ -27,7 +27,7 @@ from app.sync.runner import ( run_subscription_resync, ) -logger = logging.getLogger("subfeed.scheduler") +logger = logging.getLogger("siftlode.scheduler") _scheduler: BackgroundScheduler | None = None diff --git a/backend/app/state.py b/backend/app/state.py index 80466c5..c82f3dc 100644 --- a/backend/app/state.py +++ b/backend/app/state.py @@ -8,7 +8,7 @@ from sqlalchemy.orm import Session from app.config import settings from app.models import AppState -log = logging.getLogger("subfeed.state") +log = logging.getLogger("siftlode.state") # Bounds for the admin-set maintenance re-validation batch (videos re-checked per run). MAINTENANCE_BATCH_MIN = 100 diff --git a/backend/app/sync/autotag.py b/backend/app/sync/autotag.py index 0a1359e..ec36d24 100644 --- a/backend/app/sync/autotag.py +++ b/backend/app/sync/autotag.py @@ -11,7 +11,7 @@ import re from sqlalchemy import and_, exists, func, select from sqlalchemy.orm import Session -log = logging.getLogger("subfeed.autotag") +log = logging.getLogger("siftlode.autotag") from app import progress, sysconfig from app.models import Channel, ChannelTag, Tag, Video diff --git a/backend/app/sync/maintenance.py b/backend/app/sync/maintenance.py index 06262e0..80c0129 100644 --- a/backend/app/sync/maintenance.py +++ b/backend/app/sync/maintenance.py @@ -34,7 +34,7 @@ from app.sync.runner import get_service_user from app.sync.videos import apply_video_details, parse_dt from app.youtube.client import YouTubeClient -log = logging.getLogger("subfeed.maintenance") +log = logging.getLogger("siftlode.maintenance") # Reasons recorded on Video.unavailable_reason. "removed" = absent from videos.list # (deleted or made private); "abandoned" = an upcoming premiere that never went live. diff --git a/backend/app/sync/playlists.py b/backend/app/sync/playlists.py index a5fe3a7..4dcbaac 100644 --- a/backend/app/sync/playlists.py +++ b/backend/app/sync/playlists.py @@ -18,7 +18,7 @@ from app.models import Channel, OAuthToken, Playlist, PlaylistItem, User, Video from app.sync.videos import apply_video_details, parse_dt from app.youtube.client import YouTubeClient, YouTubeError -log = logging.getLogger("subfeed.sync") +log = logging.getLogger("siftlode.sync") # Cost of a single YouTube write op (playlists/playlistItems insert/update/delete). WRITE_UNIT_COST = 50 diff --git a/backend/app/sync/runner.py b/backend/app/sync/runner.py index 6f5faf4..7239a5d 100644 --- a/backend/app/sync/runner.py +++ b/backend/app/sync/runner.py @@ -12,7 +12,7 @@ from sqlalchemy.orm import Session from app import progress, quota, sysconfig -log = logging.getLogger("subfeed.sync") +log = logging.getLogger("siftlode.sync") from app.models import Channel, OAuthToken, Subscription, User from app.sync.subscriptions import import_subscriptions from app.sync.videos import ( diff --git a/backend/app/sync/subscriptions.py b/backend/app/sync/subscriptions.py index 9c139e3..adafce3 100644 --- a/backend/app/sync/subscriptions.py +++ b/backend/app/sync/subscriptions.py @@ -8,7 +8,7 @@ from sqlalchemy.orm import Session from app.models import Channel, Subscription, User from app.youtube.client import YouTubeClient, best_thumbnail -log = logging.getLogger("subfeed.sync") +log = logging.getLogger("siftlode.sync") def _to_int(value) -> int | None: diff --git a/backend/app/youtube/client.py b/backend/app/youtube/client.py index 0692171..aecd93b 100644 --- a/backend/app/youtube/client.py +++ b/backend/app/youtube/client.py @@ -16,7 +16,7 @@ from app.config import settings from app.models import User from app.security import decrypt -log = logging.getLogger("subfeed.youtube") +log = logging.getLogger("siftlode.youtube") GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token" API_BASE = "https://www.googleapis.com/youtube/v3" diff --git a/backend/log_config.json b/backend/log_config.json index 47f8c1b..8b1a481 100644 --- a/backend/log_config.json +++ b/backend/log_config.json @@ -35,6 +35,6 @@ "uvicorn": { "handlers": ["default"], "level": "INFO", "propagate": false }, "uvicorn.error": { "handlers": ["default"], "level": "INFO", "propagate": false }, "uvicorn.access": { "handlers": ["access"], "level": "INFO", "propagate": false }, - "subfeed": { "handlers": ["plain"], "level": "INFO", "propagate": false } + "siftlode": { "handlers": ["plain"], "level": "INFO", "propagate": false } } } diff --git a/docker-compose.localdev.yml b/docker-compose.localdev.yml index a9e1c28..d7974af 100644 --- a/docker-compose.localdev.yml +++ b/docker-compose.localdev.yml @@ -15,15 +15,15 @@ services: db: image: postgres:16-alpine environment: - POSTGRES_USER: ${POSTGRES_USER:-subfeed} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-subfeed} - POSTGRES_DB: ${POSTGRES_DB:-subfeed} + POSTGRES_USER: ${POSTGRES_USER:-siftlode} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-siftlode} + POSTGRES_DB: ${POSTGRES_DB:-siftlode} volumes: - pgdata:/var/lib/postgresql/data security_opt: - apparmor:unconfined healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-subfeed} -d ${POSTGRES_DB:-subfeed}"] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-siftlode} -d ${POSTGRES_DB:-siftlode}"] interval: 5s timeout: 5s retries: 12 @@ -40,7 +40,7 @@ services: env_file: .env environment: # Own local database (overrides whatever DATABASE_URL is in .env). - DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-subfeed}:${POSTGRES_PASSWORD:-subfeed}@db:5432/${POSTGRES_DB:-subfeed} + DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-siftlode}:${POSTGRES_PASSWORD:-siftlode}@db:5432/${POSTGRES_DB:-siftlode} # This instance owns its scheduler now (its own DB, so no double-write concern). SCHEDULER_ENABLED: "true" depends_on: diff --git a/docker-compose.selfhost.yml b/docker-compose.selfhost.yml index a646d11..758e6fc 100644 --- a/docker-compose.selfhost.yml +++ b/docker-compose.selfhost.yml @@ -14,16 +14,16 @@ services: db: image: postgres:16-alpine environment: - POSTGRES_USER: ${POSTGRES_USER:-subfeed} + POSTGRES_USER: ${POSTGRES_USER:-siftlode} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set (run install.sh)} - POSTGRES_DB: ${POSTGRES_DB:-subfeed} + POSTGRES_DB: ${POSTGRES_DB:-siftlode} volumes: - siftlode_pgdata:/var/lib/postgresql/data networks: [internal] security_opt: - no-new-privileges:true healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-subfeed} -d ${POSTGRES_DB:-subfeed}"] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-siftlode} -d ${POSTGRES_DB:-siftlode}"] interval: 10s timeout: 5s retries: 12 @@ -34,7 +34,7 @@ services: env_file: - .env environment: - DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-subfeed}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-subfeed} + DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-siftlode}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-siftlode} # This instance owns the background scheduler (single writer). SCHEDULER_ENABLED: "true" depends_on: diff --git a/docker-compose.yml b/docker-compose.yml index cf9c976..70ad302 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,13 +2,13 @@ services: db: image: postgres:16-alpine environment: - POSTGRES_USER: ${POSTGRES_USER:-subfeed} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-subfeed} - POSTGRES_DB: ${POSTGRES_DB:-subfeed} + POSTGRES_USER: ${POSTGRES_USER:-siftlode} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-siftlode} + POSTGRES_DB: ${POSTGRES_DB:-siftlode} volumes: - pgdata:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-subfeed} -d ${POSTGRES_DB:-subfeed}"] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-siftlode} -d ${POSTGRES_DB:-siftlode}"] interval: 5s timeout: 5s retries: 12 @@ -24,7 +24,7 @@ services: BUILD_DATE: ${BUILD_DATE:-} env_file: .env environment: - DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-subfeed}:${POSTGRES_PASSWORD:-subfeed}@db:5432/${POSTGRES_DB:-subfeed} + DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-siftlode}:${POSTGRES_PASSWORD:-siftlode}@db:5432/${POSTGRES_DB:-siftlode} depends_on: db: condition: service_healthy diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d6523ad..d76b819 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -57,9 +57,9 @@ const DEFAULT_FILTERS: FeedFilters = { show: "unwatched", }; -const FILTERS_KEY = "subfeed.filters"; +const FILTERS_KEY = "siftlode.filters"; const PAGE_KEY = "siftlode.page"; -const PERF_KEY = "subfeed.perfMode"; +const PERF_KEY = "siftlode.perfMode"; // The preferences edited on the Settings page. They apply locally for instant preview but // persist to the server only on an explicit Save — so App holds both the live "draft" (the diff --git a/frontend/src/lib/hints.ts b/frontend/src/lib/hints.ts index 4b8bd1e..cb3b274 100644 --- a/frontend/src/lib/hints.ts +++ b/frontend/src/lib/hints.ts @@ -1,7 +1,7 @@ // App-wide "hints" toggle: when on, Tooltip components reveal short explanatory // captions on hover so the UI is somewhat self-documenting for new users. Experienced // users can turn it off in Settings. Persisted to localStorage (+ server preferences). -const KEY = "subfeed.hints"; +const KEY = "siftlode.hints"; let enabled = load(); let listeners: Array<() => void> = []; diff --git a/frontend/src/lib/notifications.ts b/frontend/src/lib/notifications.ts index 3df64d5..9e6ff0c 100644 --- a/frontend/src/lib/notifications.ts +++ b/frontend/src/lib/notifications.ts @@ -71,8 +71,8 @@ export interface NotifyInput { transient?: boolean; // live status: sticky toast, not persisted (see Notification.transient) } -const HISTORY_KEY = "subfeed.notifications"; -const SETTINGS_KEY = "subfeed.notifSettings"; +const HISTORY_KEY = "siftlode.notifications"; +const SETTINGS_KEY = "siftlode.notifSettings"; const MAX_HISTORY = 100; const ERROR_TTL = 15000; diff --git a/frontend/src/lib/onboarding.ts b/frontend/src/lib/onboarding.ts index 1becf83..496e915 100644 --- a/frontend/src/lib/onboarding.ts +++ b/frontend/src/lib/onboarding.ts @@ -12,8 +12,8 @@ // suppresses the auto-popup on future logins (they // can still reopen it from Settings → Account). -export const ONBOARD_ACTIVE = "subfeed.onboarding.active"; -export const ONBOARD_DISMISSED = "subfeed.onboarding.dismissed"; +export const ONBOARD_ACTIVE = "siftlode.onboarding.active"; +export const ONBOARD_DISMISSED = "siftlode.onboarding.dismissed"; export function shouldAutoOpenOnboarding(me: { can_read: boolean; diff --git a/frontend/src/lib/sidebarLayout.ts b/frontend/src/lib/sidebarLayout.ts index 31e8335..fe98f9c 100644 --- a/frontend/src/lib/sidebarLayout.ts +++ b/frontend/src/lib/sidebarLayout.ts @@ -27,7 +27,7 @@ export const DEFAULT_LAYOUT: SidebarLayout = { hidden: {}, }; -const KEY = "subfeed.sidebarLayout"; +const KEY = "siftlode.sidebarLayout"; // Tolerate stale/partial data: keep only known widgets and append any that are missing // (e.g. a widget added in a later version) so nothing silently disappears. diff --git a/frontend/src/lib/theme.ts b/frontend/src/lib/theme.ts index 436cad4..ccfdfca 100644 --- a/frontend/src/lib/theme.ts +++ b/frontend/src/lib/theme.ts @@ -27,7 +27,7 @@ export function applyTheme(t: ThemePrefs): void { el.style.setProperty("--font-scale", String(t.fontScale)); } -const KEY = "subfeed.theme"; +const KEY = "siftlode.theme"; export function loadLocalTheme(): ThemePrefs { try { diff --git a/scripts/backup.ps1 b/scripts/backup.ps1 index e282da0..0445e34 100644 --- a/scripts/backup.ps1 +++ b/scripts/backup.ps1 @@ -1,8 +1,8 @@ # Dumps the Siftlode Postgres database to backupssiftlode-.dump (custom format). # Run from the project root: .\scripts\backup.ps1 $ErrorActionPreference = "Stop" -$user = if ($env:POSTGRES_USER) { $env:POSTGRES_USER } else { "subfeed" } -$dbname = if ($env:POSTGRES_DB) { $env:POSTGRES_DB } else { "subfeed" } +$user = if ($env:POSTGRES_USER) { $env:POSTGRES_USER } else { "siftlode" } +$dbname = if ($env:POSTGRES_DB) { $env:POSTGRES_DB } else { "siftlode" } New-Item -ItemType Directory -Force -Path "backups" | Out-Null $ts = Get-Date -Format "yyyyMMdd-HHmmss" $out = "backups\siftlode-$ts.dump" diff --git a/scripts/backup.sh b/scripts/backup.sh index b28bdc7..1efb444 100644 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -2,8 +2,8 @@ # Dumps the Siftlode Postgres database to backups/siftlode-.dump (custom format). # Run from the project root: ./scripts/backup.sh set -e -USER_NAME="${POSTGRES_USER:-subfeed}" -DB_NAME="${POSTGRES_DB:-subfeed}" +USER_NAME="${POSTGRES_USER:-siftlode}" +DB_NAME="${POSTGRES_DB:-siftlode}" mkdir -p backups TS=$(date +%Y%m%d-%H%M%S) OUT="backups/siftlode-$TS.dump" diff --git a/scripts/restore.ps1 b/scripts/restore.ps1 index cc21553..eeed8e9 100644 --- a/scripts/restore.ps1 +++ b/scripts/restore.ps1 @@ -2,8 +2,8 @@ # Usage: .\scripts\restore.ps1 backupssiftlode-YYYYMMDD-HHMMSS.dump param([Parameter(Mandatory = $true)][string]$DumpFile) $ErrorActionPreference = "Stop" -$user = if ($env:POSTGRES_USER) { $env:POSTGRES_USER } else { "subfeed" } -$dbname = if ($env:POSTGRES_DB) { $env:POSTGRES_DB } else { "subfeed" } +$user = if ($env:POSTGRES_USER) { $env:POSTGRES_USER } else { "siftlode" } +$dbname = if ($env:POSTGRES_DB) { $env:POSTGRES_DB } else { "siftlode" } if (-not (Test-Path $DumpFile)) { throw "Dump file not found: $DumpFile" } Get-Content -Encoding Byte $DumpFile | docker compose exec -T db pg_restore -U $user -d $dbname --clean --if-exists Write-Output "Restored $DumpFile" diff --git a/scripts/restore.sh b/scripts/restore.sh index 2bbff0e..eddf38c 100644 --- a/scripts/restore.sh +++ b/scripts/restore.sh @@ -5,7 +5,7 @@ set -e FILE="$1" if [ -z "$FILE" ]; then echo "Usage: restore.sh "; exit 1; fi if [ ! -f "$FILE" ]; then echo "Dump file not found: $FILE"; exit 1; fi -USER_NAME="${POSTGRES_USER:-subfeed}" -DB_NAME="${POSTGRES_DB:-subfeed}" +USER_NAME="${POSTGRES_USER:-siftlode}" +DB_NAME="${POSTGRES_DB:-siftlode}" docker compose exec -T db pg_restore -U "$USER_NAME" -d "$DB_NAME" --clean --if-exists < "$FILE" echo "Restored $FILE"