- 章节:详情页增加删除按钮,软删除(is_active=False),AI 不再修改但保留供参考 - 章节:get_chapter 增加 is_active 校验,已删除章节返回 404 - 章节:AI 生成时参考同类别已删除章节摘要 - 对话:左滑显示删除,调用 hard delete API,删除前二次确认 - 对话:根布局包裹 GestureHandlerRootView 以支持 Swipeable - 对话:移除已读/未读状态展示及相关 i18n
89 lines
3.0 KiB
Python
89 lines
3.0 KiB
Python
"""Memoir repository — Book, Chapter, MemoirState data access."""
|
||
|
||
from sqlalchemy import select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from sqlalchemy.orm import Session, joinedload
|
||
|
||
from app.features.memoir.models import Book, Chapter, ChapterSection, MemoirState
|
||
|
||
|
||
async def get_current_book(user_id: str, db: AsyncSession) -> Book | None:
|
||
stmt = select(Book).where(Book.user_id == user_id).order_by(Book.updated_at.desc()).limit(1)
|
||
result = await db.execute(stmt)
|
||
return result.scalar_one_or_none()
|
||
|
||
|
||
async def get_chapters_with_sections(
|
||
user_id: str,
|
||
db: AsyncSession,
|
||
*,
|
||
active_only: bool = True,
|
||
is_new_only: bool | None = None,
|
||
) -> list[Chapter]:
|
||
stmt = (
|
||
select(Chapter)
|
||
.where(Chapter.user_id == user_id)
|
||
.options(
|
||
joinedload(Chapter.sections),
|
||
joinedload(Chapter.images),
|
||
joinedload(Chapter.sections).joinedload(ChapterSection.image_record),
|
||
)
|
||
.order_by(Chapter.order_index)
|
||
)
|
||
if active_only:
|
||
stmt = stmt.where(Chapter.is_active == True) # noqa: E712
|
||
if is_new_only is True:
|
||
stmt = stmt.where(Chapter.is_new == True) # noqa: E712
|
||
result = await db.execute(stmt)
|
||
return list(result.unique().scalars().all())
|
||
|
||
|
||
async def get_chapter_by_id(chapter_id: str, db: AsyncSession) -> Chapter | None:
|
||
stmt = (
|
||
select(Chapter)
|
||
.where(Chapter.id == chapter_id)
|
||
.options(
|
||
joinedload(Chapter.sections),
|
||
joinedload(Chapter.images),
|
||
joinedload(Chapter.sections).joinedload(ChapterSection.image_record),
|
||
)
|
||
)
|
||
result = await db.execute(stmt)
|
||
return result.unique().scalars().one_or_none()
|
||
|
||
|
||
async def get_memoir_state(user_id: str, db: AsyncSession) -> MemoirState | None:
|
||
stmt = select(MemoirState).where(MemoirState.user_id == user_id)
|
||
result = await db.execute(stmt)
|
||
return result.scalar_one_or_none()
|
||
|
||
|
||
def get_archived_chapter_summaries_sync(
|
||
session: Session, user_id: str, category: str
|
||
) -> list[tuple[str, str]]:
|
||
"""获取已删除(is_active=False)的同类别章节的标题与内容摘要,供 AI 参考。"""
|
||
stmt = (
|
||
select(Chapter)
|
||
.where(
|
||
Chapter.user_id == user_id,
|
||
Chapter.category == category,
|
||
Chapter.is_active == False, # noqa: E712
|
||
)
|
||
.options(joinedload(Chapter.sections))
|
||
.order_by(Chapter.updated_at.desc())
|
||
)
|
||
result = session.execute(stmt)
|
||
chapters = list(result.unique().scalars().all())
|
||
summaries: list[tuple[str, str]] = []
|
||
for ch in chapters:
|
||
sections = getattr(ch, "sections", None) or []
|
||
parts = [
|
||
(s.content or "").strip()
|
||
for s in sorted(sections, key=lambda x: getattr(x, "order_index", 0))
|
||
]
|
||
combined = "".join(parts)
|
||
preview = (combined[:200] + "...") if len(combined) > 200 else combined
|
||
if preview.strip():
|
||
summaries.append((ch.title or "", preview))
|
||
return summaries
|