Add FastAPI web/API layer and static site
- queries.py: shared read-only query helpers (feed, brief, category counts) returning plain dicts, used by the API and available to the CLI. - api.py: FastAPI service with Pydantic response models (the companion-app contract), CORS, and endpoints for categories, feed, brief, and health; mounts a static site at /. - static/index.html: minimal dependency-free site rendering the daily five and topic/flavor category browsing. - 'goodnews serve' command launches uvicorn (lazy import; core CLI stays pure-stdlib). Web deps live behind the optional [web] extra. - Dockerfile + .dockerignore + build-system metadata so the service installs and deploys cleanly, with the DB mounted as a shared volume. - README: web/API and deployment docs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
@@ -71,7 +72,17 @@ def main() -> None:
|
||||
show_brief_parser.add_argument("--date", help="Brief date in YYYY-MM-DD format; defaults to latest brief")
|
||||
show_brief_parser.add_argument("--limit", type=int, default=10)
|
||||
|
||||
serve_parser = subparsers.add_parser("serve", help="Run the web/API server (requires the 'web' extra)")
|
||||
serve_parser.add_argument("--host", default="127.0.0.1", help="Bind host; use 0.0.0.0 to expose")
|
||||
serve_parser.add_argument("--port", type=int, default=8000)
|
||||
serve_parser.add_argument("--reload", action="store_true", help="Auto-reload on code changes (dev)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "serve":
|
||||
serve(args)
|
||||
return
|
||||
|
||||
conn = connect(args.db)
|
||||
|
||||
if args.command == "init-db":
|
||||
@@ -190,6 +201,20 @@ def list_recent(conn: sqlite3.Connection, limit: int, accepted_only: bool) -> No
|
||||
print(f" {row['canonical_url']}")
|
||||
|
||||
|
||||
def serve(args: argparse.Namespace) -> None:
|
||||
try:
|
||||
import uvicorn
|
||||
except ModuleNotFoundError:
|
||||
raise SystemExit(
|
||||
"The web server needs the optional 'web' extra. Install it with:\n"
|
||||
" pip install -e '.[web]'"
|
||||
)
|
||||
# Make sure the API reads the same database the CLI was pointed at.
|
||||
os.environ.setdefault("GOODNEWS_DB", str(args.db))
|
||||
print(f"Serving goodNews on http://{args.host}:{args.port} (docs at /docs)")
|
||||
uvicorn.run("goodnews.api:app", host=args.host, port=args.port, reload=args.reload)
|
||||
|
||||
|
||||
def list_category(
|
||||
conn: sqlite3.Connection,
|
||||
topic: str | None,
|
||||
|
||||
Reference in New Issue
Block a user