feat(rulebook): REST routes — rules, subscriptions, applicable rules
This commit is contained in:
@@ -119,3 +119,122 @@ async def update_topic(topic_id: int):
|
|||||||
async def delete_topic(topic_id: int):
|
async def delete_topic(topic_id: int):
|
||||||
await rulebooks_svc.delete_topic(topic_id, _uid())
|
await rulebooks_svc.delete_topic(topic_id, _uid())
|
||||||
return "", 204
|
return "", 204
|
||||||
|
|
||||||
|
|
||||||
|
# ── Rules ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@rulebooks_bp.get("/rules")
|
||||||
|
@login_required
|
||||||
|
async def list_rules():
|
||||||
|
def _opt_int(name):
|
||||||
|
raw = request.args.get(name)
|
||||||
|
return int(raw) if raw else None
|
||||||
|
|
||||||
|
try:
|
||||||
|
rulebook_id = _opt_int("rulebook_id")
|
||||||
|
topic_id = _opt_int("topic_id")
|
||||||
|
project_id = _opt_int("project_id")
|
||||||
|
except ValueError:
|
||||||
|
return jsonify({"error": "rulebook_id, topic_id, project_id must be integers"}), 400
|
||||||
|
|
||||||
|
rows = await rulebooks_svc.list_rules(
|
||||||
|
user_id=_uid(),
|
||||||
|
rulebook_id=rulebook_id,
|
||||||
|
topic_id=topic_id,
|
||||||
|
project_id=project_id,
|
||||||
|
)
|
||||||
|
return jsonify({"rules": [r.to_dict() for r in rows]})
|
||||||
|
|
||||||
|
|
||||||
|
@rulebooks_bp.post("/rulebook-topics/<int:topic_id>/rules")
|
||||||
|
@login_required
|
||||||
|
async def create_rule(topic_id: int):
|
||||||
|
data = await request.get_json() or {}
|
||||||
|
title = (data.get("title") or "").strip()
|
||||||
|
statement = (data.get("statement") or "").strip()
|
||||||
|
if not title or not statement:
|
||||||
|
return jsonify({"error": "title and statement are required"}), 400
|
||||||
|
try:
|
||||||
|
rule = await rulebooks_svc.create_rule(
|
||||||
|
topic_id=topic_id,
|
||||||
|
user_id=_uid(),
|
||||||
|
title=title,
|
||||||
|
statement=statement,
|
||||||
|
why=data.get("why", ""),
|
||||||
|
how_to_apply=data.get("how_to_apply", ""),
|
||||||
|
order_index=data.get("order_index", 0),
|
||||||
|
)
|
||||||
|
except ValueError as exc:
|
||||||
|
return jsonify({"error": str(exc)}), 404
|
||||||
|
return jsonify(rule.to_dict()), 201
|
||||||
|
|
||||||
|
|
||||||
|
@rulebooks_bp.get("/rules/<int:rule_id>")
|
||||||
|
@login_required
|
||||||
|
async def get_rule(rule_id: int):
|
||||||
|
rule = await rulebooks_svc.get_rule(rule_id, _uid())
|
||||||
|
if rule is None:
|
||||||
|
return jsonify({"error": "rule not found"}), 404
|
||||||
|
return jsonify(rule.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@rulebooks_bp.patch("/rules/<int:rule_id>")
|
||||||
|
@login_required
|
||||||
|
async def update_rule(rule_id: int):
|
||||||
|
data = await request.get_json() or {}
|
||||||
|
fields = {
|
||||||
|
k: v for k, v in data.items()
|
||||||
|
if k in ("title", "statement", "why", "how_to_apply", "order_index")
|
||||||
|
}
|
||||||
|
rule = await rulebooks_svc.update_rule(rule_id, _uid(), **fields)
|
||||||
|
if rule is None:
|
||||||
|
return jsonify({"error": "rule not found"}), 404
|
||||||
|
return jsonify(rule.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@rulebooks_bp.delete("/rules/<int:rule_id>")
|
||||||
|
@login_required
|
||||||
|
async def delete_rule(rule_id: int):
|
||||||
|
await rulebooks_svc.delete_rule(rule_id, _uid())
|
||||||
|
return "", 204
|
||||||
|
|
||||||
|
|
||||||
|
# ── Subscriptions ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@rulebooks_bp.post("/projects/<int:project_id>/rulebook-subscriptions")
|
||||||
|
@login_required
|
||||||
|
async def subscribe_project(project_id: int):
|
||||||
|
data = await request.get_json() or {}
|
||||||
|
rulebook_id = data.get("rulebook_id")
|
||||||
|
if not rulebook_id:
|
||||||
|
return jsonify({"error": "rulebook_id is required"}), 400
|
||||||
|
try:
|
||||||
|
await rulebooks_svc.subscribe_project(
|
||||||
|
project_id=project_id, rulebook_id=int(rulebook_id), user_id=_uid(),
|
||||||
|
)
|
||||||
|
except ValueError as exc:
|
||||||
|
return jsonify({"error": str(exc)}), 404
|
||||||
|
return "", 204
|
||||||
|
|
||||||
|
|
||||||
|
@rulebooks_bp.delete(
|
||||||
|
"/projects/<int:project_id>/rulebook-subscriptions/<int:rulebook_id>"
|
||||||
|
)
|
||||||
|
@login_required
|
||||||
|
async def unsubscribe_project(project_id: int, rulebook_id: int):
|
||||||
|
try:
|
||||||
|
await rulebooks_svc.unsubscribe_project(
|
||||||
|
project_id=project_id, rulebook_id=rulebook_id, user_id=_uid(),
|
||||||
|
)
|
||||||
|
except ValueError as exc:
|
||||||
|
return jsonify({"error": str(exc)}), 404
|
||||||
|
return "", 204
|
||||||
|
|
||||||
|
|
||||||
|
@rulebooks_bp.get("/projects/<int:project_id>/rules")
|
||||||
|
@login_required
|
||||||
|
async def get_project_rules(project_id: int):
|
||||||
|
result = await rulebooks_svc.get_applicable_rules(
|
||||||
|
project_id=project_id, user_id=_uid(),
|
||||||
|
)
|
||||||
|
return jsonify(result)
|
||||||
|
|||||||
@@ -44,6 +44,17 @@ def test_service_signatures_require_user_id():
|
|||||||
"create_rulebook", "list_rulebooks", "get_rulebook",
|
"create_rulebook", "list_rulebooks", "get_rulebook",
|
||||||
"update_rulebook", "delete_rulebook", "find_rulebook_by_title",
|
"update_rulebook", "delete_rulebook", "find_rulebook_by_title",
|
||||||
"create_topic", "list_topics", "get_topic", "update_topic", "delete_topic",
|
"create_topic", "list_topics", "get_topic", "update_topic", "delete_topic",
|
||||||
|
"create_rule", "list_rules", "get_rule", "update_rule", "delete_rule",
|
||||||
|
"subscribe_project", "unsubscribe_project", "get_applicable_rules",
|
||||||
):
|
):
|
||||||
sig = inspect.signature(getattr(svc, fn_name))
|
sig = inspect.signature(getattr(svc, fn_name))
|
||||||
assert "user_id" in sig.parameters, f"{fn_name} missing user_id param"
|
assert "user_id" in sig.parameters, f"{fn_name} missing user_id param"
|
||||||
|
|
||||||
|
|
||||||
|
def test_rule_and_subscription_handlers_callable():
|
||||||
|
from fabledassistant.routes import rulebooks as rb_routes
|
||||||
|
for name in (
|
||||||
|
"list_rules", "create_rule", "get_rule", "update_rule", "delete_rule",
|
||||||
|
"subscribe_project", "unsubscribe_project", "get_project_rules",
|
||||||
|
):
|
||||||
|
assert callable(getattr(rb_routes, name))
|
||||||
|
|||||||
Reference in New Issue
Block a user