Kairo-Jinkendo/backend/data_layer/actions.py
Lars db06af0489
All checks were successful
Deploy Development / deploy (push) Successful in 47s
Test Suite / pytest-backend (push) Successful in 42s
Test Suite / lint-backend (push) Successful in 2s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 18s
Test Suite / playwright-smoke (push) Successful in 12s
AP0.7: Tenant Hardening, Actor Directory und Data Layer Minimum
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-05 07:25:01 +02:00

86 lines
2.8 KiB
Python

"""Action read-models for workspace — tenant-scoped, actor-assignment based."""
from __future__ import annotations
from typing import Any
from psycopg2.extras import RealDictCursor
from db import get_connection
from services.actions import OPEN_ACTION_STATUSES, _attach_assignments, _serialize_row
from tenant_context import TenantContext
def _require_actor(ctx: TenantContext) -> str:
if not ctx.actor_id:
raise ValueError("Kein Actor im TenantContext")
return ctx.actor_id
def _list_assigned_actions(
*,
tenant_id: str,
actor_id: str,
statuses: list[str],
) -> list[dict[str, Any]]:
conn = get_connection()
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute(
"""
SELECT a.id, a.tenant_id, a.initiative_id, a.title, a.description,
a.status, a.priority, a.created_at, a.updated_at,
i.title AS initiative_title
FROM actions a
JOIN action_assignments aa
ON aa.action_id = a.id AND aa.tenant_id = a.tenant_id
JOIN initiatives i
ON i.id = a.initiative_id AND i.tenant_id = a.tenant_id
WHERE a.tenant_id = %s
AND aa.actor_id = %s
AND a.status = ANY(%s)
ORDER BY
CASE a.priority
WHEN 'high' THEN 0
WHEN 'normal' THEN 1
WHEN 'low' THEN 2
END,
a.updated_at DESC
""",
(tenant_id, actor_id, statuses),
)
actions = [_serialize_row(dict(row)) for row in cur.fetchall()]
finally:
conn.close()
return _attach_assignments(actions, tenant_id=tenant_id)
def get_my_open_actions(ctx: TenantContext) -> list[dict[str, Any]]:
"""Open + in_progress actions assigned to current actor (excludes blocked)."""
actor_id = _require_actor(ctx)
return _list_assigned_actions(
tenant_id=ctx.tenant_id,
actor_id=actor_id,
statuses=["open", "in_progress"],
)
def get_my_blocked_actions(ctx: TenantContext) -> list[dict[str, Any]]:
"""Blocked actions assigned to current actor."""
actor_id = _require_actor(ctx)
return _list_assigned_actions(
tenant_id=ctx.tenant_id,
actor_id=actor_id,
statuses=["blocked"],
)
def get_all_my_open_actions(ctx: TenantContext) -> list[dict[str, Any]]:
"""All non-terminal open actions (open, in_progress, blocked) — legacy compat."""
actor_id = _require_actor(ctx)
return _list_assigned_actions(
tenant_id=ctx.tenant_id,
actor_id=actor_id,
statuses=list(OPEN_ACTION_STATUSES),
)