- Python 82.2%
- HTML 7.3%
- CSS 5.7%
- JavaScript 4.2%
- Shell 0.4%
- Other 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
CI / test (push) Successful in 3m27s
Reviewed-on: #11 |
||
| .forgejo/workflows | ||
| app | ||
| config | ||
| deploy | ||
| docs | ||
| scripts | ||
| tests | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| package-lock.json | ||
| package.json | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
TILens
Public English technology-news aggregator covering Python, Rust, PostgreSQL, FastAPI, AWS, AI/ML, and adjacent topics. The server-rendered FastAPI site reads from a local SQLite database; a separate ingestion worker fetches verified RSS, Atom, and API endpoints, normalizes entries, ranks them by freshness and trust, and persists indefinitely.
Stack
- Web: FastAPI + Jinja2, server-rendered with progressive
enhancement. Reads SQLite with
mode=roandquery_only=ON. - Worker: long-running process that owns migrations, scheduled fetches, normalization, and writes. Sole writer to the database.
- Storage: SQLite WAL on a local volume. One database file, one worker, multiple read-only FastAPI workers.
- Search: SQLite FTS5 with bounded literal-term queries.
- Frontend assets: Tailwind CSS v4 and MathJax (compiled/copied, self-hosted).
- Tests: pytest, Ruff, Playwright (visual capture).
Frontend policy
- Light/dark themes with
systemdefault and cookie-backed override. - Pastel semantic palette for surfaces and topic identity.
- Zero border radius on first-party UI.
- Editorial layout (no card grids for the digest stream).
- English-only interface.
Local development
Install runtime + dev dependencies:
uv sync --extra dev
npm install
Build frontend assets once, or watch CSS changes:
npm run build:assets # CSS + MathJax
npm run watch:css # watch mode
Initialize the database and seed sources once:
mkdir -p data
uv run python scripts/audit_sources.py
uv run python -c "from app.backend.core.db import init_database; from pathlib import Path; init_database(Path('data/tilens.sqlite3'))"
Run the worker (separate terminal):
TILENS_DB_PATH=$(pwd)/data/tilens.sqlite3 \
uv run python -m app.backend.services.worker_service
Run the web process:
TILENS_DB_PATH=$(pwd)/data/tilens.sqlite3 \
uv run --extra dev uvicorn app.main:app --reload --port 8000
Open http://127.0.0.1:8000/.
Docker
Production image builds frontend assets and Python in one Dockerfile:
cp .env.example .env # only required if you want to override defaults
docker compose up --build
The Compose stack runs web and worker against a shared
tilens-data volume. Both services run as UID 10001 with a read-only
root filesystem, dropped capabilities, and no-new-privileges. The
worker exits nonzero if its supervisor task dies so Docker restarts it.
A worker heartbeat probe (file mtime under 3 * TILENS_WORKER_INTERVAL_SEC + 120s)
prevents acceptance while the supervisor is alive but the ingestion
loop is hung.
The Compose file binds the web service to 127.0.0.1:8000 so it
sits behind a reverse proxy. A reference Caddy configuration sits in
deploy/Caddyfile. Configure the proxy, then set TILENS_COOKIE_SECURE=1
and re-deploy the web service for HTTPS-only cookies.
Tests
uv run --extra dev pytest
uv run --extra dev ruff check
Operations
See docs/operations.md for environment variables, backup procedure,
worker lifecycle, single-writer guarantee, and public status policy.
Architecture
See docs/architecture.md for the data flow, schema, and security
boundaries.
Source catalog
The set of trusted ingestion endpoints lives in config/sources.csv.
Audit the catalog with:
uv run python scripts/audit_sources.py
The report is written to artifacts/source-audit/report.json. The
artifacts/ directory is local generated output and is not committed.
Backups
Online SQLite backup via the backup API:
uv run python scripts/backup.py data/tilens.sqlite3 backups/snap.sqlite3