feat(rulebook): service layer — Topic CRUD
This commit is contained in:
@@ -108,3 +108,106 @@ async def find_rulebook_by_title(
|
||||
)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
# ── Topic CRUD ──────────────────────────────────────────────────────────
|
||||
|
||||
from fabledassistant.models.rulebook import RulebookTopic
|
||||
|
||||
|
||||
async def _assert_rulebook_owned(session, rulebook_id: int, user_id: int) -> None:
|
||||
"""Raise ValueError if rulebook doesn't exist or isn't owned by user.
|
||||
Centralizes ownership check used by all topic/rule operations.
|
||||
"""
|
||||
result = await session.execute(
|
||||
select(Rulebook).where(
|
||||
Rulebook.id == rulebook_id,
|
||||
Rulebook.owner_user_id == user_id,
|
||||
)
|
||||
)
|
||||
if result.scalar_one_or_none() is None:
|
||||
raise ValueError(f"rulebook {rulebook_id} not found")
|
||||
|
||||
|
||||
async def create_topic(
|
||||
rulebook_id: int, user_id: int, title: str,
|
||||
description: str = "", order_index: int = 0,
|
||||
) -> RulebookTopic:
|
||||
async with async_session() as session:
|
||||
await _assert_rulebook_owned(session, rulebook_id, user_id)
|
||||
topic = RulebookTopic(
|
||||
rulebook_id=rulebook_id,
|
||||
title=title,
|
||||
description=description or None,
|
||||
order_index=order_index,
|
||||
)
|
||||
session.add(topic)
|
||||
await session.commit()
|
||||
await session.refresh(topic)
|
||||
return topic
|
||||
|
||||
|
||||
async def list_topics(rulebook_id: int, user_id: int) -> list[RulebookTopic]:
|
||||
async with async_session() as session:
|
||||
await _assert_rulebook_owned(session, rulebook_id, user_id)
|
||||
result = await session.execute(
|
||||
select(RulebookTopic)
|
||||
.where(RulebookTopic.rulebook_id == rulebook_id)
|
||||
.order_by(RulebookTopic.order_index, RulebookTopic.title)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
async def get_topic(topic_id: int, user_id: int) -> Optional[RulebookTopic]:
|
||||
"""Get a topic, scoped via the rulebook owner."""
|
||||
async with async_session() as session:
|
||||
result = await session.execute(
|
||||
select(RulebookTopic)
|
||||
.join(Rulebook, RulebookTopic.rulebook_id == Rulebook.id)
|
||||
.where(
|
||||
RulebookTopic.id == topic_id,
|
||||
Rulebook.owner_user_id == user_id,
|
||||
)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def update_topic(
|
||||
topic_id: int, user_id: int, **fields,
|
||||
) -> Optional[RulebookTopic]:
|
||||
async with async_session() as session:
|
||||
result = await session.execute(
|
||||
select(RulebookTopic)
|
||||
.join(Rulebook, RulebookTopic.rulebook_id == Rulebook.id)
|
||||
.where(
|
||||
RulebookTopic.id == topic_id,
|
||||
Rulebook.owner_user_id == user_id,
|
||||
)
|
||||
)
|
||||
topic = result.scalar_one_or_none()
|
||||
if topic is None:
|
||||
return None
|
||||
allowed = {"title", "description", "order_index"}
|
||||
for key, value in fields.items():
|
||||
if key in allowed and value is not None:
|
||||
setattr(topic, key, value)
|
||||
await session.commit()
|
||||
await session.refresh(topic)
|
||||
return topic
|
||||
|
||||
|
||||
async def delete_topic(topic_id: int, user_id: int) -> None:
|
||||
async with async_session() as session:
|
||||
result = await session.execute(
|
||||
select(RulebookTopic)
|
||||
.join(Rulebook, RulebookTopic.rulebook_id == Rulebook.id)
|
||||
.where(
|
||||
RulebookTopic.id == topic_id,
|
||||
Rulebook.owner_user_id == user_id,
|
||||
)
|
||||
)
|
||||
topic = result.scalar_one_or_none()
|
||||
if topic is None:
|
||||
return
|
||||
await session.delete(topic)
|
||||
await session.commit()
|
||||
|
||||
Reference in New Issue
Block a user