feat(rulebook): service layer — subscriptions + get_applicable_rules
This commit is contained in:
@@ -343,3 +343,116 @@ async def delete_rule(rule_id: int, user_id: int) -> None:
|
||||
return
|
||||
await session.delete(rule)
|
||||
await session.commit()
|
||||
|
||||
|
||||
# ── Subscriptions + get_applicable_rules ───────────────────────────────
|
||||
|
||||
from sqlalchemy import insert, delete as sql_delete
|
||||
|
||||
|
||||
async def subscribe_project(
|
||||
project_id: int, rulebook_id: int, user_id: int,
|
||||
) -> None:
|
||||
"""Add a subscription. Idempotent — duplicates raise; we swallow."""
|
||||
from fabledassistant.models.rulebook import project_rulebook_subscriptions
|
||||
|
||||
async with async_session() as session:
|
||||
await _assert_rulebook_owned(session, rulebook_id, user_id)
|
||||
# ON CONFLICT DO NOTHING via try/except to keep dialect-agnostic.
|
||||
try:
|
||||
await session.execute(
|
||||
insert(project_rulebook_subscriptions).values(
|
||||
project_id=project_id, rulebook_id=rulebook_id,
|
||||
)
|
||||
)
|
||||
await session.commit()
|
||||
except Exception:
|
||||
await session.rollback() # PK collision = already subscribed; fine.
|
||||
|
||||
|
||||
async def unsubscribe_project(
|
||||
project_id: int, rulebook_id: int, user_id: int,
|
||||
) -> None:
|
||||
from fabledassistant.models.rulebook import project_rulebook_subscriptions
|
||||
|
||||
async with async_session() as session:
|
||||
await _assert_rulebook_owned(session, rulebook_id, user_id)
|
||||
await session.execute(
|
||||
sql_delete(project_rulebook_subscriptions).where(
|
||||
project_rulebook_subscriptions.c.project_id == project_id,
|
||||
project_rulebook_subscriptions.c.rulebook_id == rulebook_id,
|
||||
)
|
||||
)
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def get_applicable_rules(
|
||||
project_id: int, user_id: int, limit: int = 50,
|
||||
) -> dict:
|
||||
"""Return rules applicable to a project via its subscriptions.
|
||||
|
||||
Shape:
|
||||
{
|
||||
"rules": [{id, title, statement, topic_title, rulebook_title}, ...],
|
||||
"truncated": bool,
|
||||
"subscribed_rulebooks": [{id, title}, ...]
|
||||
}
|
||||
"""
|
||||
from fabledassistant.models.rulebook import project_rulebook_subscriptions
|
||||
|
||||
async with async_session() as session:
|
||||
# Subscribed rulebooks for the project (ownership-scoped).
|
||||
sub_q = (
|
||||
select(Rulebook.id, Rulebook.title)
|
||||
.join(
|
||||
project_rulebook_subscriptions,
|
||||
project_rulebook_subscriptions.c.rulebook_id == Rulebook.id,
|
||||
)
|
||||
.where(
|
||||
project_rulebook_subscriptions.c.project_id == project_id,
|
||||
Rulebook.owner_user_id == user_id,
|
||||
)
|
||||
.order_by(Rulebook.title)
|
||||
)
|
||||
sub_rows = (await session.execute(sub_q)).all()
|
||||
subscribed_rulebooks = [
|
||||
{"id": rb_id, "title": rb_title} for rb_id, rb_title in sub_rows
|
||||
]
|
||||
|
||||
# Applicable rules (limit + 1 so we can detect truncation).
|
||||
rules_q = (
|
||||
select(
|
||||
Rule.id, Rule.title, Rule.statement,
|
||||
RulebookTopic.title.label("topic_title"),
|
||||
Rulebook.title.label("rulebook_title"),
|
||||
)
|
||||
.join(RulebookTopic, Rule.topic_id == RulebookTopic.id)
|
||||
.join(Rulebook, RulebookTopic.rulebook_id == Rulebook.id)
|
||||
.join(
|
||||
project_rulebook_subscriptions,
|
||||
project_rulebook_subscriptions.c.rulebook_id == Rulebook.id,
|
||||
)
|
||||
.where(
|
||||
project_rulebook_subscriptions.c.project_id == project_id,
|
||||
Rulebook.owner_user_id == user_id,
|
||||
)
|
||||
.order_by(
|
||||
Rulebook.id, RulebookTopic.order_index, Rule.order_index, Rule.title,
|
||||
)
|
||||
.limit(limit + 1)
|
||||
)
|
||||
rule_rows = (await session.execute(rules_q)).all()
|
||||
truncated = len(rule_rows) > limit
|
||||
rules = [
|
||||
{
|
||||
"id": rid, "title": rtitle, "statement": stmt,
|
||||
"topic_title": tt, "rulebook_title": rbt,
|
||||
}
|
||||
for rid, rtitle, stmt, tt, rbt in rule_rows[:limit]
|
||||
]
|
||||
|
||||
return {
|
||||
"rules": rules,
|
||||
"truncated": truncated,
|
||||
"subscribed_rulebooks": subscribed_rulebooks,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user