release prep
This commit is contained in:
parent
ca565e2530
commit
5499904cef
22 changed files with 111 additions and 119 deletions
63
app/api/media.py
Normal file
63
app/api/media.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import mimetypes
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi.responses import FileResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import get_api_current_user
|
||||
from app.api.errors import ApiException
|
||||
from app.db.models import User
|
||||
from app.db.session import get_db_session
|
||||
from app.services.domains.scholars import application as scholar_service
|
||||
from app.settings import settings
|
||||
|
||||
router = APIRouter(tags=["media"])
|
||||
|
||||
|
||||
@router.get("/scholar-images/{scholar_profile_id}/upload")
|
||||
async def get_uploaded_scholar_image(
|
||||
scholar_profile_id: int,
|
||||
db_session: AsyncSession = Depends(get_db_session),
|
||||
current_user: User = Depends(get_api_current_user),
|
||||
):
|
||||
profile = await scholar_service.get_user_scholar_by_id(
|
||||
db_session,
|
||||
user_id=current_user.id,
|
||||
scholar_profile_id=scholar_profile_id,
|
||||
)
|
||||
if profile is None:
|
||||
raise ApiException(
|
||||
status_code=404,
|
||||
code="scholar_not_found",
|
||||
message="Scholar not found.",
|
||||
)
|
||||
if not profile.profile_image_upload_path:
|
||||
raise ApiException(
|
||||
status_code=404,
|
||||
code="scholar_image_not_found",
|
||||
message="Scholar image not found.",
|
||||
)
|
||||
|
||||
try:
|
||||
image_path = scholar_service.resolve_upload_file_path(
|
||||
upload_dir=settings.scholar_image_upload_dir,
|
||||
relative_path=profile.profile_image_upload_path,
|
||||
)
|
||||
except scholar_service.ScholarServiceError as exc:
|
||||
raise ApiException(
|
||||
status_code=404,
|
||||
code="scholar_image_not_found",
|
||||
message="Scholar image not found.",
|
||||
) from exc
|
||||
|
||||
if not image_path.exists() or not image_path.is_file():
|
||||
raise ApiException(
|
||||
status_code=404,
|
||||
code="scholar_image_not_found",
|
||||
message="Scholar image not found.",
|
||||
)
|
||||
|
||||
media_type = mimetypes.guess_type(str(image_path))[0] or "application/octet-stream"
|
||||
return FileResponse(path=image_path, media_type=media_type)
|
||||
|
|
@ -2,10 +2,8 @@ from __future__ import annotations
|
|||
|
||||
import asyncio
|
||||
import logging
|
||||
import mimetypes
|
||||
|
||||
from fastapi import APIRouter, Depends, File, Query, Request, UploadFile
|
||||
from fastapi.responses import FileResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import get_api_current_user
|
||||
|
|
@ -35,14 +33,14 @@ logger = logging.getLogger(__name__)
|
|||
router = APIRouter(prefix="/scholars", tags=["api-scholars"])
|
||||
|
||||
|
||||
def _uploaded_image_api_path(scholar_profile_id: int) -> str:
|
||||
return f"/api/v1/scholars/{scholar_profile_id}/image/upload"
|
||||
def _uploaded_image_media_path(scholar_profile_id: int) -> str:
|
||||
return f"/scholar-images/{scholar_profile_id}/upload"
|
||||
|
||||
|
||||
def _serialize_scholar(profile) -> dict[str, object]:
|
||||
uploaded_image_url = None
|
||||
if profile.profile_image_upload_path:
|
||||
uploaded_image_url = _uploaded_image_api_path(int(profile.id))
|
||||
uploaded_image_url = _uploaded_image_media_path(int(profile.id))
|
||||
|
||||
profile_image_url, profile_image_source = scholar_service.resolve_profile_image(
|
||||
profile,
|
||||
|
|
@ -557,51 +555,3 @@ async def clear_scholar_image_customization(
|
|||
data=_serialize_scholar(updated),
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{scholar_profile_id}/image/upload",
|
||||
)
|
||||
async def get_uploaded_scholar_image(
|
||||
scholar_profile_id: int,
|
||||
db_session: AsyncSession = Depends(get_db_session),
|
||||
current_user: User = Depends(get_api_current_user),
|
||||
):
|
||||
profile = await scholar_service.get_user_scholar_by_id(
|
||||
db_session,
|
||||
user_id=current_user.id,
|
||||
scholar_profile_id=scholar_profile_id,
|
||||
)
|
||||
if profile is None:
|
||||
raise ApiException(
|
||||
status_code=404,
|
||||
code="scholar_not_found",
|
||||
message="Scholar not found.",
|
||||
)
|
||||
if not profile.profile_image_upload_path:
|
||||
raise ApiException(
|
||||
status_code=404,
|
||||
code="scholar_image_not_found",
|
||||
message="Scholar image not found.",
|
||||
)
|
||||
|
||||
try:
|
||||
image_path = scholar_service.resolve_upload_file_path(
|
||||
upload_dir=settings.scholar_image_upload_dir,
|
||||
relative_path=profile.profile_image_upload_path,
|
||||
)
|
||||
except scholar_service.ScholarServiceError as exc:
|
||||
raise ApiException(
|
||||
status_code=404,
|
||||
code="scholar_image_not_found",
|
||||
message="Scholar image not found.",
|
||||
) from exc
|
||||
|
||||
if not image_path.exists() or not image_path.is_file():
|
||||
raise ApiException(
|
||||
status_code=404,
|
||||
code="scholar_image_not_found",
|
||||
message="Scholar image not found.",
|
||||
)
|
||||
|
||||
media_type = mimetypes.guess_type(str(image_path))[0] or "application/octet-stream"
|
||||
return FileResponse(path=image_path, media_type=media_type)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from fastapi.staticfiles import StaticFiles
|
|||
from starlette.middleware.sessions import SessionMiddleware
|
||||
|
||||
from app.api.errors import register_api_exception_handlers
|
||||
from app.api.media import router as media_router
|
||||
from app.api.router import router as api_router
|
||||
from app.api.runtime_deps import get_ingestion_service, get_scholar_source
|
||||
from app.db.session import check_database
|
||||
|
|
@ -106,6 +107,7 @@ app.add_middleware(
|
|||
strict_transport_security_preload=settings.security_strict_transport_security_preload,
|
||||
)
|
||||
app.include_router(api_router)
|
||||
app.include_router(media_router)
|
||||
|
||||
|
||||
@app.get("/healthz")
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from app.services.domains.publications.counts import (
|
|||
)
|
||||
from app.services.domains.publications.listing import (
|
||||
list_for_user,
|
||||
list_new_for_latest_run_for_user,
|
||||
retry_pdf_for_user,
|
||||
list_unread_for_user,
|
||||
)
|
||||
|
|
@ -22,7 +21,6 @@ from app.services.domains.publications.modes import (
|
|||
MODE_LATEST,
|
||||
MODE_NEW,
|
||||
MODE_UNREAD,
|
||||
resolve_mode,
|
||||
resolve_publication_view_mode,
|
||||
)
|
||||
from app.services.domains.publications.queries import (
|
||||
|
|
@ -52,13 +50,11 @@ __all__ = [
|
|||
"PublicationListItem",
|
||||
"UnreadPublicationItem",
|
||||
"resolve_publication_view_mode",
|
||||
"resolve_mode",
|
||||
"get_latest_completed_run_id_for_user",
|
||||
"publications_query",
|
||||
"get_publication_item_for_user",
|
||||
"list_for_user",
|
||||
"list_unread_for_user",
|
||||
"list_new_for_latest_run_for_user",
|
||||
"retry_pdf_for_user",
|
||||
"hydrate_pdf_enrichment_state",
|
||||
"schedule_retry_pdf_enrichment_for_row",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
|
||||
from app.services.domains.publications.modes import (
|
||||
MODE_ALL,
|
||||
MODE_LATEST,
|
||||
MODE_UNREAD,
|
||||
resolve_publication_view_mode,
|
||||
)
|
||||
|
|
@ -81,34 +80,3 @@ async def list_unread_for_user(
|
|||
)
|
||||
)
|
||||
return [unread_item_from_row(row) for row in result.all()]
|
||||
|
||||
|
||||
async def list_new_for_latest_run_for_user(
|
||||
db_session: AsyncSession,
|
||||
*,
|
||||
user_id: int,
|
||||
limit: int = 100,
|
||||
) -> list[UnreadPublicationItem]:
|
||||
rows = await list_for_user(
|
||||
db_session,
|
||||
user_id=user_id,
|
||||
mode=MODE_LATEST,
|
||||
scholar_profile_id=None,
|
||||
limit=limit,
|
||||
)
|
||||
return [_to_unread_item(row) for row in rows]
|
||||
|
||||
|
||||
def _to_unread_item(row: PublicationListItem) -> UnreadPublicationItem:
|
||||
return UnreadPublicationItem(
|
||||
publication_id=row.publication_id,
|
||||
scholar_profile_id=row.scholar_profile_id,
|
||||
scholar_label=row.scholar_label,
|
||||
title=row.title,
|
||||
year=row.year,
|
||||
citation_count=row.citation_count,
|
||||
venue_text=row.venue_text,
|
||||
pub_url=row.pub_url,
|
||||
doi=row.doi,
|
||||
pdf_url=row.pdf_url,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,3 @@ def resolve_publication_view_mode(value: str | None) -> str:
|
|||
if value in {MODE_LATEST, MODE_NEW}:
|
||||
return MODE_LATEST
|
||||
return MODE_ALL
|
||||
|
||||
|
||||
def resolve_mode(value: str | None) -> str:
|
||||
return resolve_publication_view_mode(value)
|
||||
|
|
|
|||
|
|
@ -344,11 +344,6 @@ def _author_search_cooldown_remaining_seconds(
|
|||
return max(0, remaining_seconds)
|
||||
|
||||
|
||||
def _reset_author_search_runtime_state_for_tests() -> None:
|
||||
# Runtime state now lives in the database; tests should reset via DB fixtures.
|
||||
return None
|
||||
|
||||
|
||||
async def list_scholars_for_user(
|
||||
db_session: AsyncSession,
|
||||
*,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue