7.9 KiB
7.9 KiB
scholarr
Container-first, self-hosted scholar tracking in the spirit of the *arr ecosystem, with strict multi-user isolation and an intentionally small operational footprint.
Current Scope
- Admin-managed users only (no public signup)
- Session auth with CSRF protection and login rate limiting
- Per-user scholar tracking list (add, enable/disable, delete)
- Per-user automation settings
- Manual per-user ingestion runs from dashboard
- Per-user run history and "new since last run" publication stream
- Publications library view with
newandallmodes plus scholar filtering - Dedicated run diagnostics pages (
/runs,/runs/{id}) with failed-only filtering - Continuation queue visibility + controls on runs page (
retry,drop,clear) - Baseline-aware discovery tracking (
new= discovered in latest completed run) - Read state is user-controlled (
Mark All Read), including baseline items - Multi-page profile ingestion (
cstart) to capture full publication lists - Dashboard rendered via a presentation-layer view model and section template registry
- Structured application logging with request IDs and field redaction
- Ingestion overlap guard via Postgres advisory locks
- Network-error retry support for ingestion attempts
- Automatic continuation queue for resumable/limit-hit scholar runs
- Background scheduler for per-user automatic runs
- User self-service password changes
- Admin user management (create users, activate/deactivate, reset passwords)
- PostgreSQL + Alembic migrations + containerized test workflow
- Versioned backend API (
/api/v1) for frontend decoupling (same-origin cookie session)
Tech Stack
- Python 3.12+
- FastAPI
- PostgreSQL 15+
- SQLAlchemy 2 + Alembic
- Jinja2 templates + modular theme tokens
uvfor dependency and runtime workflow- Docker / Docker Compose for development and CI parity
Quick Start
- Copy environment defaults:
cp .env.example .env
- Bootstrap the first admin on startup (recommended for first run):
export BOOTSTRAP_ADMIN_ON_START=1
export BOOTSTRAP_ADMIN_EMAIL=admin@example.com
export BOOTSTRAP_ADMIN_PASSWORD=change-me-now
- Start the stack:
docker compose up --build
- Open:
- Login:
http://localhost:8000/login - Dashboard:
http://localhost:8000/ - Healthcheck:
http://localhost:8000/healthz
- After first successful admin login, disable auto-bootstrap:
export BOOTSTRAP_ADMIN_ON_START=0
Admin Bootstrap (Manual)
You can create/update an admin account without restarting the app:
docker compose run --rm app uv run python scripts/bootstrap_admin.py \
--email admin@example.com \
--password change-me-now \
--force-password
Notes:
- If the user does not exist, this creates an active admin account.
- If the user exists, it ensures admin + active flags.
--force-passwordresets the password for existing users.
Test Workflow
-
Integration/smoke tests run against
TEST_DATABASE_URL. -
If
TEST_DATABASE_URLis empty, test runs derive an isolated database fromDATABASE_URLby appending_test. -
This keeps app data in
DATABASE_URLuntouched during test runs. -
Parser regression tests are pinned to captured real-world fixture pages in
tests/fixtures/scholar/regression. -
Unit tests:
docker compose run --rm app uv run pytest tests/unit
- Integration tests:
docker compose run --rm app uv run pytest -m integration
- Smoke checks (container + DB + migration sanity):
./scripts/smoke_compose.sh
CI runs migration, unit, and integration checks on push/PR via .github/workflows/ci.yml.
Scholar Ingestion Notes
- Current ingestion targets public profile pages:
/citations?user=<id>. - Runs are intentionally conservative and prioritize reliability over aggressive scraping.
- Ingestion follows pagination (
cstart) to collect all reachable profile pages. - If pagination cannot be completed (max pages hit, cursor stall, or page-state failure), the scholar result is marked partial with explicit debug context.
- Resumable partial/failure states are automatically queued and retried by the scheduler with bounded backoff.
- Queue items keep explicit status (
queued,retrying,dropped) and last-error context for UI diagnostics. - Failed scholar attempts persist structured debug context in
crawl_runs.error_log(state reason, fetch metadata, marker evidence, and compact response excerpt). - Only resumable states are retried automatically (
network_errorand continuation-eligible pagination truncations); blocked/layout states are recorded immediately as failures.
API v1 (Frontend Prep)
- Base path:
/api/v1 - Auth model: same-origin cookie session (no token auth added)
- CSRF model: required for unsafe methods (
POST,PUT,PATCH,DELETE) viaX-CSRF-Tokenheader. - Bootstrap CSRF via
GET /api/v1/auth/csrf(works for anonymous and authenticated sessions). - You can also fetch
csrf_tokenfromGET /api/v1/auth/meafter login. - Manual run idempotency is supported via
Idempotency-Keyheader onPOST /api/v1/runs/manual. - Response envelopes:
- Success:
{"data": ..., "meta": {"request_id": "..."}} - Error:
{"error": {"code": "...", "message": "...", "details": ...}, "meta": {"request_id": "..."}}
- Success:
- Initial API domains exposed:
auth:/auth/csrf,/auth/login,/auth/me,/auth/change-password,/auth/logoutadmin users:/admin/users(list/create),/admin/users/{id}/active,/admin/users/{id}/reset-passwordscholars: list/create/toggle/deletesettings: get/updateruns: list/detail/manual trigger + queue list/retry/drop/clearpublications: list + mark-all-read
- Queue action semantics:
retryis valid only for dropped items; retrying/queued states return409.dropreturns the updated queue item (statusdropped); dropping an already dropped item returns409.clearonly works for dropped items and returns{queue_item_id, previous_status, status="cleared"}.
Logging
- Default output is concise console logs to stdout.
- Timestamp format is compact UTC:
YYYY-MM-DD HH:MM:SSZ. - Every request gets an
X-Request-ID(propagated if caller provides one). - Sensitive fields are redacted before log emission (
password,csrf_token,cookie, etc.). - Configure via env:
LOG_LEVEL(defaultINFO)LOG_FORMAT(consoleorjson, defaultconsole)LOG_REQUESTS(1/0, default1)LOG_UVICORN_ACCESS(1/0, default0)LOG_REQUEST_SKIP_PATHS(comma-separated prefixes, default/healthz,/static/)LOG_REDACT_FIELDS(comma-separated additional keys)SCHEDULER_ENABLED(1/0, default1)SCHEDULER_TICK_SECONDS(default60)INGESTION_NETWORK_ERROR_RETRIES(default1)INGESTION_RETRY_BACKOFF_SECONDS(default1.0)INGESTION_MAX_PAGES_PER_SCHOLAR(default30)INGESTION_PAGE_SIZE(default100)INGESTION_CONTINUATION_QUEUE_ENABLED(1/0, default1)INGESTION_CONTINUATION_BASE_DELAY_SECONDS(default120)INGESTION_CONTINUATION_MAX_DELAY_SECONDS(default3600)INGESTION_CONTINUATION_MAX_ATTEMPTS(default6)SCHEDULER_QUEUE_BATCH_SIZE(default10)
Optional Local uv Workflow
uv sync --extra dev
uv run pytest
uv run uvicorn app.main:app --reload
Update the lockfile after dependency changes:
uv lock
Theming
- Theme registry:
app/theme.py - Semantic tokens:
app/static/theme.css - App styling:
app/static/app.css - Theme persistence:
app/static/theme.js - Dashboard presentation contract:
app/presentation/dashboard.py
Project Layout
app/ FastAPI app, auth/security modules, templates, services, DB wiring, presentation view-models
app/web/ Router modules, shared web helpers, and request middleware
alembic/ Migration environment and versions
scripts/ Entrypoint, DB wait, bootstrap, smoke automation
tests/ Unit, integration, and smoke suites