First product
This commit is contained in:
parent
778da9e2fc
commit
4433d7d2c4
157 changed files with 23975 additions and 0 deletions
165
app/web/routers/publications.py
Normal file
165
app/web/routers/publications.py
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from urllib.parse import urlencode, urlparse
|
||||
|
||||
from fastapi import APIRouter, Depends, Form, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db.session import get_db_session
|
||||
from app.services import publications as publication_service
|
||||
from app.services import scholars as scholar_service
|
||||
from app.theme import resolve_theme
|
||||
from app.web import common
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
MODE_NEW = "new"
|
||||
MODE_ALL = "all"
|
||||
|
||||
|
||||
def _resolve_mode(raw_mode: str | None) -> str:
|
||||
return publication_service.resolve_mode(raw_mode)
|
||||
|
||||
|
||||
def _parse_scholar_profile_id(value: str | None) -> int | None:
|
||||
if not value:
|
||||
return None
|
||||
try:
|
||||
parsed = int(value)
|
||||
except ValueError:
|
||||
return None
|
||||
return parsed if parsed > 0 else None
|
||||
|
||||
|
||||
def _build_publications_url(*, mode: str, scholar_profile_id: int | None) -> str:
|
||||
params: dict[str, str] = {"mode": mode}
|
||||
if scholar_profile_id is not None:
|
||||
params["scholar_profile_id"] = str(scholar_profile_id)
|
||||
return f"/publications?{urlencode(params)}"
|
||||
|
||||
|
||||
def _is_safe_return_to(value: str | None) -> bool:
|
||||
if not value:
|
||||
return False
|
||||
parsed = urlparse(value)
|
||||
if parsed.scheme or parsed.netloc:
|
||||
return False
|
||||
if not value.startswith("/"):
|
||||
return False
|
||||
return value == "/" or value.startswith("/publications")
|
||||
|
||||
|
||||
@router.get("/publications", response_class=HTMLResponse)
|
||||
async def publications_page(
|
||||
request: Request,
|
||||
mode: str | None = None,
|
||||
scholar_profile_id: str | None = None,
|
||||
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()
|
||||
|
||||
resolved_mode = _resolve_mode(mode)
|
||||
scholar_id_filter = _parse_scholar_profile_id(scholar_profile_id)
|
||||
scholars = await scholar_service.list_scholars_for_user(
|
||||
db_session,
|
||||
user_id=current_user.id,
|
||||
)
|
||||
scholar_lookup = {scholar.id: scholar for scholar in scholars}
|
||||
if scholar_id_filter not in scholar_lookup:
|
||||
scholar_id_filter = None
|
||||
|
||||
publications = await publication_service.list_for_user(
|
||||
db_session,
|
||||
user_id=current_user.id,
|
||||
mode=resolved_mode,
|
||||
scholar_profile_id=scholar_id_filter,
|
||||
limit=500,
|
||||
)
|
||||
new_count = await publication_service.count_for_user(
|
||||
db_session,
|
||||
user_id=current_user.id,
|
||||
mode=MODE_NEW,
|
||||
scholar_profile_id=scholar_id_filter,
|
||||
)
|
||||
total_count = await publication_service.count_for_user(
|
||||
db_session,
|
||||
user_id=current_user.id,
|
||||
mode=MODE_ALL,
|
||||
scholar_profile_id=scholar_id_filter,
|
||||
)
|
||||
mode_new_url = _build_publications_url(
|
||||
mode=MODE_NEW,
|
||||
scholar_profile_id=scholar_id_filter,
|
||||
)
|
||||
mode_all_url = _build_publications_url(
|
||||
mode=MODE_ALL,
|
||||
scholar_profile_id=scholar_id_filter,
|
||||
)
|
||||
selected_scholar = scholar_lookup.get(scholar_id_filter) if scholar_id_filter else None
|
||||
current_publications_url = _build_publications_url(
|
||||
mode=resolved_mode,
|
||||
scholar_profile_id=scholar_id_filter,
|
||||
)
|
||||
|
||||
context = common.build_template_context(
|
||||
request,
|
||||
page_title="Publications",
|
||||
active_nav="publications",
|
||||
theme_name=resolve_theme(theme),
|
||||
session_user=common.to_session_user(current_user),
|
||||
notice=request.query_params.get("notice"),
|
||||
page_error=request.query_params.get("error"),
|
||||
)
|
||||
context["mode"] = resolved_mode
|
||||
context["publications"] = publications
|
||||
context["new_count"] = new_count
|
||||
context["total_count"] = total_count
|
||||
context["mode_new_url"] = mode_new_url
|
||||
context["mode_all_url"] = mode_all_url
|
||||
context["scholars"] = scholars
|
||||
context["selected_scholar_id"] = scholar_id_filter
|
||||
context["selected_scholar"] = selected_scholar
|
||||
context["current_publications_url"] = current_publications_url
|
||||
return common.templates.TemplateResponse(
|
||||
request=request,
|
||||
name="publications.html",
|
||||
context=context,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/publications/mark-all-read")
|
||||
async def mark_all_publications_read(
|
||||
request: Request,
|
||||
return_to: str | None = Form(default=None),
|
||||
db_session: AsyncSession = Depends(get_db_session),
|
||||
):
|
||||
current_user = await common.get_authenticated_user(request, db_session)
|
||||
if current_user is None:
|
||||
return common.redirect_to_login()
|
||||
|
||||
updated_count = await publication_service.mark_all_unread_as_read_for_user(
|
||||
db_session,
|
||||
user_id=current_user.id,
|
||||
)
|
||||
logger.info(
|
||||
"publications.mark_all_read",
|
||||
extra={
|
||||
"event": "publications.mark_all_read",
|
||||
"user_id": current_user.id,
|
||||
"updated_count": updated_count,
|
||||
},
|
||||
)
|
||||
redirect_target = "/publications?mode=new"
|
||||
if _is_safe_return_to(return_to):
|
||||
redirect_target = return_to
|
||||
return common.redirect_with_message(
|
||||
redirect_target,
|
||||
notice=f"Marked {updated_count} publication(s) as read.",
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue