feat: add admin dashboard, pipeline service, lightbox, and update dependencies
This commit is contained in:
+34
-31
@@ -2,23 +2,24 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy import or_, select
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
|
||||
from app.models import Paper, PaperTag, UserBookmark, UserNote, UserReadingStatus
|
||||
from app.models import PAPER_FULL_LOAD, Paper, PaperTag, UserBookmark, UserNote, UserReadingStatus
|
||||
from app.utils import utc_now
|
||||
|
||||
# ── 收藏 ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def toggle_bookmark(db: Session, arxiv_id: str) -> dict:
|
||||
"""切换收藏状态。返回 {"bookmarked": bool, "arxiv_id": str}。"""
|
||||
paper = db.query(Paper).filter(Paper.arxiv_id == arxiv_id).first()
|
||||
paper = db.execute(select(Paper).where(Paper.arxiv_id == arxiv_id)).scalar_one_or_none()
|
||||
if not paper:
|
||||
return {"error": "not_found"}
|
||||
|
||||
existing = db.query(UserBookmark).filter(UserBookmark.paper_id == paper.id).first()
|
||||
existing = db.execute(
|
||||
select(UserBookmark).where(UserBookmark.paper_id == paper.id)
|
||||
).scalar_one_or_none()
|
||||
if existing:
|
||||
db.delete(existing)
|
||||
db.commit()
|
||||
@@ -26,7 +27,7 @@ def toggle_bookmark(db: Session, arxiv_id: str) -> dict:
|
||||
else:
|
||||
bookmark = UserBookmark(
|
||||
paper_id=paper.id,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
created_at=utc_now(),
|
||||
)
|
||||
db.add(bookmark)
|
||||
db.commit()
|
||||
@@ -43,16 +44,14 @@ def set_reading_status(db: Session, arxiv_id: str, status: str) -> dict:
|
||||
if status not in VALID_STATUSES:
|
||||
return {"error": "invalid_status", "valid": sorted(VALID_STATUSES)}
|
||||
|
||||
paper = db.query(Paper).filter(Paper.arxiv_id == arxiv_id).first()
|
||||
paper = db.execute(select(Paper).where(Paper.arxiv_id == arxiv_id)).scalar_one_or_none()
|
||||
if not paper:
|
||||
return {"error": "not_found"}
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
existing = (
|
||||
db.query(UserReadingStatus)
|
||||
.filter(UserReadingStatus.paper_id == paper.id)
|
||||
.first()
|
||||
)
|
||||
now = utc_now()
|
||||
existing = db.execute(
|
||||
select(UserReadingStatus).where(UserReadingStatus.paper_id == paper.id)
|
||||
).scalar_one_or_none()
|
||||
if existing:
|
||||
existing.status = status
|
||||
existing.updated_at = now
|
||||
@@ -73,11 +72,13 @@ def set_reading_status(db: Session, arxiv_id: str, status: str) -> dict:
|
||||
|
||||
def get_note(db: Session, arxiv_id: str) -> dict | None:
|
||||
"""获取笔记。返回 {"arxiv_id", "content", "updated_at"} 或 None(论文不存在时)。"""
|
||||
paper = db.query(Paper).filter(Paper.arxiv_id == arxiv_id).first()
|
||||
paper = db.execute(select(Paper).where(Paper.arxiv_id == arxiv_id)).scalar_one_or_none()
|
||||
if not paper:
|
||||
return None
|
||||
|
||||
note = db.query(UserNote).filter(UserNote.paper_id == paper.id).first()
|
||||
note = db.execute(
|
||||
select(UserNote).where(UserNote.paper_id == paper.id)
|
||||
).scalar_one_or_none()
|
||||
if not note:
|
||||
return {"arxiv_id": arxiv_id, "content": "", "updated_at": None}
|
||||
|
||||
@@ -90,12 +91,14 @@ def get_note(db: Session, arxiv_id: str) -> dict | None:
|
||||
|
||||
def save_note(db: Session, arxiv_id: str, content: str) -> dict:
|
||||
"""创建或更新笔记。返回 {"arxiv_id", "content", "updated_at"}。"""
|
||||
paper = db.query(Paper).filter(Paper.arxiv_id == arxiv_id).first()
|
||||
paper = db.execute(select(Paper).where(Paper.arxiv_id == arxiv_id)).scalar_one_or_none()
|
||||
if not paper:
|
||||
return {"error": "not_found"}
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
existing = db.query(UserNote).filter(UserNote.paper_id == paper.id).first()
|
||||
now = utc_now()
|
||||
existing = db.execute(
|
||||
select(UserNote).where(UserNote.paper_id == paper.id)
|
||||
).scalar_one_or_none()
|
||||
if existing:
|
||||
existing.content = content
|
||||
existing.updated_at = now
|
||||
@@ -126,7 +129,7 @@ def query_reading_list(
|
||||
) -> list[Paper]:
|
||||
"""根据筛选条件查询阅读列表。"""
|
||||
# 基础:有任意用户数据的论文
|
||||
base = db.query(Paper).filter(
|
||||
stmt = select(Paper).where(
|
||||
or_(
|
||||
Paper.bookmark.has(),
|
||||
Paper.reading_status.has(),
|
||||
@@ -136,25 +139,25 @@ def query_reading_list(
|
||||
|
||||
# 应用筛选
|
||||
if filter_type == "has_note":
|
||||
base = base.filter(Paper.note.has())
|
||||
stmt = stmt.where(Paper.note.has())
|
||||
elif filter_type in ("unread", "skimmed", "read_summary", "read_full"):
|
||||
base = base.filter(
|
||||
stmt = stmt.where(
|
||||
Paper.reading_status.has(UserReadingStatus.status == filter_type)
|
||||
)
|
||||
|
||||
# 应用标签
|
||||
if tag:
|
||||
base = base.filter(Paper.tags.any(PaperTag.tag == tag))
|
||||
stmt = stmt.where(Paper.tags.any(PaperTag.tag == tag))
|
||||
|
||||
return (
|
||||
base.options(
|
||||
joinedload(Paper.authors),
|
||||
joinedload(Paper.tags),
|
||||
joinedload(Paper.summary_status),
|
||||
joinedload(Paper.bookmark),
|
||||
joinedload(Paper.reading_status),
|
||||
joinedload(Paper.note),
|
||||
db.execute(
|
||||
stmt.options(
|
||||
joinedload(Paper.note),
|
||||
*PAPER_FULL_LOAD,
|
||||
)
|
||||
.order_by(Paper.paper_date.desc(), Paper.upvotes.desc())
|
||||
)
|
||||
.order_by(Paper.paper_date.desc(), Paper.upvotes.desc())
|
||||
.unique()
|
||||
.scalars()
|
||||
.all()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user