First product

This commit is contained in:
Justin Visser 2026-02-17 14:51:25 +01:00
parent 778da9e2fc
commit 4433d7d2c4
157 changed files with 23975 additions and 0 deletions

View file

@ -0,0 +1,167 @@
from __future__ import annotations
import logging
from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.models import RunTriggerType
from app.db.session import get_db_session
from app.presentation import dashboard as dashboard_presenter
from app.services import ingestion as ingestion_service
from app.services import publications as publication_service
from app.services import runs as run_service
from app.services import user_settings as user_settings_service
from app.settings import settings
from app.theme import resolve_theme
from app.web import common
from app.web.deps import get_ingestion_service
logger = logging.getLogger(__name__)
router = APIRouter()
async def _render_dashboard_page(
request: Request,
*,
db_session: AsyncSession,
current_user,
theme_name: str,
notice: str | None = None,
page_error: str | None = None,
) -> HTMLResponse:
unread_publications = await publication_service.list_new_for_latest_run_for_user(
db_session,
user_id=current_user.id,
limit=50,
)
recent_runs = await run_service.list_recent_runs_for_user(
db_session,
user_id=current_user.id,
limit=20,
)
user_settings = await user_settings_service.get_or_create_settings(
db_session,
user_id=current_user.id,
)
queue_counts = await run_service.queue_status_counts_for_user(
db_session,
user_id=current_user.id,
)
context = common.build_template_context(
request,
page_title="Dashboard",
active_nav="home",
theme_name=theme_name,
session_user=common.to_session_user(current_user),
notice=notice,
page_error=page_error,
)
context["dashboard"] = dashboard_presenter.build_dashboard_view_model(
unread_publications=unread_publications,
recent_runs=recent_runs,
request_delay_seconds=user_settings.request_delay_seconds,
queue_counts=queue_counts,
)
return common.templates.TemplateResponse(
request=request,
name="index.html",
context=context,
)
@router.get("/", response_class=HTMLResponse)
async def home(
request: Request,
theme: str | None = None,
db_session: AsyncSession = Depends(get_db_session),
) -> HTMLResponse:
current_user = await common.get_authenticated_user(request, db_session)
if current_user is None:
return common.redirect_to_login()
return await _render_dashboard_page(
request,
db_session=db_session,
current_user=current_user,
theme_name=resolve_theme(theme),
notice=request.query_params.get("notice"),
page_error=request.query_params.get("error"),
)
@router.post("/runs/manual")
async def run_manual_ingestion(
request: Request,
db_session: AsyncSession = Depends(get_db_session),
ingest_service: ingestion_service.ScholarIngestionService = Depends(get_ingestion_service),
):
current_user = await common.get_authenticated_user(request, db_session)
if current_user is None:
return common.redirect_to_login()
user_settings = await user_settings_service.get_or_create_settings(
db_session,
user_id=current_user.id,
)
logger.info(
"runs.manual_started",
extra={
"event": "runs.manual_started",
"user_id": current_user.id,
"request_delay_seconds": user_settings.request_delay_seconds,
"network_error_retries": settings.ingestion_network_error_retries,
"max_pages_per_scholar": settings.ingestion_max_pages_per_scholar,
"page_size": settings.ingestion_page_size,
},
)
try:
run_summary = await ingest_service.run_for_user(
db_session,
user_id=current_user.id,
trigger_type=RunTriggerType.MANUAL,
request_delay_seconds=user_settings.request_delay_seconds,
network_error_retries=settings.ingestion_network_error_retries,
retry_backoff_seconds=settings.ingestion_retry_backoff_seconds,
max_pages_per_scholar=settings.ingestion_max_pages_per_scholar,
page_size=settings.ingestion_page_size,
auto_queue_continuations=settings.ingestion_continuation_queue_enabled,
queue_delay_seconds=settings.ingestion_continuation_base_delay_seconds,
)
except ingestion_service.RunAlreadyInProgressError:
await db_session.rollback()
return common.redirect_with_message(
"/",
error="A run is already in progress for this account.",
)
except Exception:
await db_session.rollback()
logger.exception(
"runs.manual_failed",
extra={
"event": "runs.manual_failed",
"user_id": current_user.id,
},
)
return common.redirect_with_message("/", error="Manual run failed. Check logs for details.")
logger.info(
"runs.manual_completed",
extra={
"event": "runs.manual_completed",
"user_id": current_user.id,
"run_id": run_summary.crawl_run_id,
"status": run_summary.status.value,
"scholar_count": run_summary.scholar_count,
"new_publication_count": run_summary.new_publication_count,
},
)
return common.redirect_with_message(
"/",
notice=(
f"Run #{run_summary.crawl_run_id} complete ({run_summary.status.value}). "
f"Scholars: {run_summary.scholar_count}, "
f"new publications: {run_summary.new_publication_count}."
),
)