Files
life-echo/api/app/features/memoir/repo.py
Kevin 9a1d31c71f feat: 章节软删除、对话左滑删除,移除已读状态
- 章节:详情页增加删除按钮,软删除(is_active=False),AI 不再修改但保留供参考
- 章节:get_chapter 增加 is_active 校验,已删除章节返回 404
- 章节:AI 生成时参考同类别已删除章节摘要
- 对话:左滑显示删除,调用 hard delete API,删除前二次确认
- 对话:根布局包裹 GestureHandlerRootView 以支持 Swipeable
- 对话:移除已读/未读状态展示及相关 i18n
2026-03-19 10:45:07 +08:00

89 lines
3.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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