feat: refactor backend services and add direct PDF links, import/export, and dashboard sync
This commit is contained in:
parent
ba7976d935
commit
7f7a8ce2b0
26 changed files with 4170 additions and 2440 deletions
|
|
@ -94,6 +94,7 @@ async def list_publications(
|
|||
"citation_count": item.citation_count,
|
||||
"venue_text": item.venue_text,
|
||||
"pub_url": item.pub_url,
|
||||
"pdf_url": item.pdf_url,
|
||||
"is_read": item.is_read,
|
||||
"first_seen_at": item.first_seen_at,
|
||||
"is_new_in_latest_run": item.is_new_in_latest_run,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ from app.api.errors import ApiException
|
|||
from app.api.responses import success_payload
|
||||
from app.api.runtime_deps import get_scholar_source
|
||||
from app.api.schemas import (
|
||||
DataExportEnvelope,
|
||||
DataImportEnvelope,
|
||||
DataImportRequest,
|
||||
MessageEnvelope,
|
||||
ScholarCreateRequest,
|
||||
ScholarEnvelope,
|
||||
|
|
@ -22,6 +25,7 @@ from app.api.schemas import (
|
|||
)
|
||||
from app.db.models import User
|
||||
from app.db.session import get_db_session
|
||||
from app.services import import_export as import_export_service
|
||||
from app.services import scholars as scholar_service
|
||||
from app.services.scholar_source import ScholarSource
|
||||
from app.settings import settings
|
||||
|
|
@ -81,6 +85,68 @@ async def list_scholars(
|
|||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/export",
|
||||
response_model=DataExportEnvelope,
|
||||
)
|
||||
async def export_scholars_and_publications(
|
||||
request: Request,
|
||||
db_session: AsyncSession = Depends(get_db_session),
|
||||
current_user: User = Depends(get_api_current_user),
|
||||
):
|
||||
data = await import_export_service.export_user_data(
|
||||
db_session,
|
||||
user_id=current_user.id,
|
||||
)
|
||||
return success_payload(request, data=data)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/import",
|
||||
response_model=DataImportEnvelope,
|
||||
)
|
||||
async def import_scholars_and_publications(
|
||||
payload: DataImportRequest,
|
||||
request: Request,
|
||||
db_session: AsyncSession = Depends(get_db_session),
|
||||
current_user: User = Depends(get_api_current_user),
|
||||
):
|
||||
if (
|
||||
payload.schema_version is not None
|
||||
and int(payload.schema_version) != import_export_service.EXPORT_SCHEMA_VERSION
|
||||
):
|
||||
raise ApiException(
|
||||
status_code=400,
|
||||
code="invalid_import_schema_version",
|
||||
message=(
|
||||
"Import schema version is not supported. "
|
||||
f"Expected {import_export_service.EXPORT_SCHEMA_VERSION}."
|
||||
),
|
||||
)
|
||||
try:
|
||||
result = await import_export_service.import_user_data(
|
||||
db_session,
|
||||
user_id=current_user.id,
|
||||
scholars=[item.model_dump() for item in payload.scholars],
|
||||
publications=[item.model_dump() for item in payload.publications],
|
||||
)
|
||||
except import_export_service.ImportExportError as exc:
|
||||
raise ApiException(
|
||||
status_code=400,
|
||||
code="invalid_import_payload",
|
||||
message=str(exc),
|
||||
) from exc
|
||||
logger.info(
|
||||
"api.scholars.imported",
|
||||
extra={
|
||||
"event": "api.scholars.imported",
|
||||
"user_id": current_user.id,
|
||||
**result,
|
||||
},
|
||||
)
|
||||
return success_payload(request, data=result)
|
||||
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
response_model=ScholarEnvelope,
|
||||
|
|
|
|||
|
|
@ -186,6 +186,74 @@ class ScholarImageUrlUpdateRequest(BaseModel):
|
|||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class ScholarExportItemData(BaseModel):
|
||||
scholar_id: str
|
||||
display_name: str | None = None
|
||||
is_enabled: bool = True
|
||||
profile_image_override_url: str | None = None
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class PublicationExportItemData(BaseModel):
|
||||
scholar_id: str
|
||||
cluster_id: str | None = None
|
||||
fingerprint_sha256: str | None = None
|
||||
title: str
|
||||
year: int | None = None
|
||||
citation_count: int = 0
|
||||
author_text: str | None = None
|
||||
venue_text: str | None = None
|
||||
pub_url: str | None = None
|
||||
pdf_url: str | None = None
|
||||
is_read: bool = False
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class DataExportData(BaseModel):
|
||||
schema_version: int
|
||||
exported_at: str
|
||||
scholars: list[ScholarExportItemData]
|
||||
publications: list[PublicationExportItemData]
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class DataExportEnvelope(BaseModel):
|
||||
data: DataExportData
|
||||
meta: ApiMeta
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class DataImportRequest(BaseModel):
|
||||
schema_version: int | None = None
|
||||
scholars: list[ScholarExportItemData] = Field(default_factory=list)
|
||||
publications: list[PublicationExportItemData] = Field(default_factory=list)
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class DataImportResultData(BaseModel):
|
||||
scholars_created: int
|
||||
scholars_updated: int
|
||||
publications_created: int
|
||||
publications_updated: int
|
||||
links_created: int
|
||||
links_updated: int
|
||||
skipped_records: int
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class DataImportEnvelope(BaseModel):
|
||||
data: DataImportResultData
|
||||
meta: ApiMeta
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class RunListItemData(BaseModel):
|
||||
id: int
|
||||
trigger_type: str
|
||||
|
|
@ -505,6 +573,7 @@ class PublicationItemData(BaseModel):
|
|||
citation_count: int
|
||||
venue_text: str | None
|
||||
pub_url: str | None
|
||||
pdf_url: str | None
|
||||
is_read: bool
|
||||
first_seen_at: datetime
|
||||
is_new_in_latest_run: bool
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue