refactor(api): TOML 配置 SSOT、统一错误契约、Auth/事务加固与可观测性 (#33)
配置 SSOT(TOML + .env) 统一错误契约 Auth 与事务边界 Redis / Celery 可靠性:业务 Redis(DB/0)与 Celery broker/backend(DB/1)显式拆分;连接池、sync client 可观测性(OpenTelemetry + LGTM)
This commit is contained in:
@@ -10,6 +10,8 @@ from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.db import transactional
|
||||
from app.core.errors import NotFoundError
|
||||
from app.core.logging import get_logger
|
||||
from app.features.memoir import repo as memoir_repo
|
||||
from app.features.memoir.asset_resolver import strip_asset_image_refs_from_markdown
|
||||
@@ -124,45 +126,46 @@ class StoryService:
|
||||
) -> str:
|
||||
"""Create story, commit, return story_id."""
|
||||
md = strip_asset_image_refs_from_markdown(canonical_markdown or "")
|
||||
story = await create_story(
|
||||
self._db,
|
||||
user_id=user_id,
|
||||
title=title,
|
||||
stage=stage,
|
||||
story_type=story_type,
|
||||
summary=summary,
|
||||
canonical_markdown=md,
|
||||
)
|
||||
await self._db.flush()
|
||||
apply_infer_story_time_start_to_model(story)
|
||||
if md.strip():
|
||||
version = await create_story_version(
|
||||
async with transactional(self._db):
|
||||
story = await create_story(
|
||||
self._db,
|
||||
story_id=story.id,
|
||||
version_no=1,
|
||||
markdown_snapshot=md,
|
||||
actor_type="ai",
|
||||
source_type="generate",
|
||||
user_id=user_id,
|
||||
title=title,
|
||||
stage=stage,
|
||||
story_type=story_type,
|
||||
summary=summary,
|
||||
canonical_markdown=md,
|
||||
)
|
||||
await self._db.flush()
|
||||
story.current_version_id = version.id
|
||||
await _extract_and_store_image_intent(
|
||||
self._db,
|
||||
story=story,
|
||||
version=version,
|
||||
markdown=md,
|
||||
)
|
||||
if md.strip():
|
||||
await memoir_repo.mark_chapters_dirty_for_story(self._db, story.id)
|
||||
await self._db.commit()
|
||||
apply_infer_story_time_start_to_model(story)
|
||||
if md.strip():
|
||||
version = await create_story_version(
|
||||
self._db,
|
||||
story_id=story.id,
|
||||
version_no=1,
|
||||
markdown_snapshot=md,
|
||||
actor_type="ai",
|
||||
source_type="generate",
|
||||
)
|
||||
await self._db.flush()
|
||||
story.current_version_id = version.id
|
||||
await _extract_and_store_image_intent(
|
||||
self._db,
|
||||
story=story,
|
||||
version=version,
|
||||
markdown=md,
|
||||
)
|
||||
if md.strip():
|
||||
await memoir_repo.mark_chapters_dirty_for_story(self._db, story.id)
|
||||
story_id = story.id
|
||||
if md.strip():
|
||||
from app.features.memoir.repo import get_chapter_ids_linked_to_story
|
||||
from app.features.story.post_commit import enqueue_story_post_commit_effects
|
||||
|
||||
chapter_ids = set(await get_chapter_ids_linked_to_story(self._db, story.id))
|
||||
chapter_ids = set(await get_chapter_ids_linked_to_story(self._db, story_id))
|
||||
pc = enqueue_story_post_commit_effects(
|
||||
user_id=user_id,
|
||||
story_ids={story.id},
|
||||
story_ids={story_id},
|
||||
chapter_ids=chapter_ids,
|
||||
trigger_source="manual_api",
|
||||
need_compaction=False,
|
||||
@@ -176,7 +179,7 @@ class StoryService:
|
||||
pc.enqueued_chapter_recompose_count,
|
||||
pc.errors,
|
||||
)
|
||||
return story.id
|
||||
return story_id
|
||||
|
||||
async def append_version(
|
||||
self,
|
||||
@@ -191,32 +194,33 @@ class StoryService:
|
||||
"""Append new version, update canonical_markdown, return version_id."""
|
||||
story = await get_story_by_id(self._db, story_id)
|
||||
if not story:
|
||||
raise ValueError(f"Story {story_id} not found")
|
||||
raise NotFoundError(f"Story {story_id} not found")
|
||||
md = strip_asset_image_refs_from_markdown(markdown_snapshot or "")
|
||||
parent_id = story.current_version_id
|
||||
version_no = (await count_story_versions(self._db, story_id)) + 1
|
||||
version = await create_story_version(
|
||||
self._db,
|
||||
story_id=story_id,
|
||||
version_no=version_no,
|
||||
markdown_snapshot=md,
|
||||
actor_type=actor_type,
|
||||
source_type=source_type,
|
||||
parent_version_id=parent_id,
|
||||
prompt_meta=prompt_meta,
|
||||
)
|
||||
version.change_summary = change_summary
|
||||
story.current_version_id = version.id
|
||||
story.canonical_markdown = md
|
||||
apply_infer_story_time_start_to_model(story)
|
||||
await _extract_and_store_image_intent(
|
||||
self._db,
|
||||
story=story,
|
||||
version=version,
|
||||
markdown=md,
|
||||
)
|
||||
await memoir_repo.mark_chapters_dirty_for_story(self._db, story_id)
|
||||
await self._db.commit()
|
||||
async with transactional(self._db):
|
||||
version = await create_story_version(
|
||||
self._db,
|
||||
story_id=story_id,
|
||||
version_no=version_no,
|
||||
markdown_snapshot=md,
|
||||
actor_type=actor_type,
|
||||
source_type=source_type,
|
||||
parent_version_id=parent_id,
|
||||
prompt_meta=prompt_meta,
|
||||
)
|
||||
version.change_summary = change_summary
|
||||
story.current_version_id = version.id
|
||||
story.canonical_markdown = md
|
||||
apply_infer_story_time_start_to_model(story)
|
||||
await _extract_and_store_image_intent(
|
||||
self._db,
|
||||
story=story,
|
||||
version=version,
|
||||
markdown=md,
|
||||
)
|
||||
await memoir_repo.mark_chapters_dirty_for_story(self._db, story_id)
|
||||
version_id = version.id
|
||||
from app.features.memoir.repo import get_chapter_ids_linked_to_story
|
||||
from app.features.story.post_commit import enqueue_story_post_commit_effects
|
||||
|
||||
@@ -236,7 +240,7 @@ class StoryService:
|
||||
pc.enqueued_chapter_recompose_count,
|
||||
pc.errors,
|
||||
)
|
||||
return version.id
|
||||
return version_id
|
||||
|
||||
async def link_evidence(
|
||||
self,
|
||||
@@ -248,15 +252,15 @@ class StoryService:
|
||||
weight: float | None = None,
|
||||
) -> None:
|
||||
"""Add evidence link. Caller must ensure story exists."""
|
||||
await create_story_evidence_link(
|
||||
self._db,
|
||||
story_id=story_id,
|
||||
evidence_type=evidence_type,
|
||||
evidence_id=evidence_id,
|
||||
role=role,
|
||||
weight=weight,
|
||||
)
|
||||
await self._db.commit()
|
||||
async with transactional(self._db):
|
||||
await create_story_evidence_link(
|
||||
self._db,
|
||||
story_id=story_id,
|
||||
evidence_type=evidence_type,
|
||||
evidence_id=evidence_id,
|
||||
role=role,
|
||||
weight=weight,
|
||||
)
|
||||
|
||||
async def get_stories(
|
||||
self, user_id: str, *, status: str | None = "active"
|
||||
|
||||
Reference in New Issue
Block a user