forked from yvv/decision
Compartimentation : isolation stricte des données par espace de travail
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
- Ajout clause else IS NULL sur tous les endpoints list (protocols, decisions, mandates, documents, groups, votes) — sans X-Organization → données globales seulement, jamais le contenu d'un autre espace - _get_protocol/_get_decision/_get_mandate : org_id propagé à tous les endpoints GET/PUT/DELETE/advance/assign/revoke/steps → 404 si UUID d'un autre espace - votes.py : list_vote_sessions filtre via JOIN VotingProtocol.organization_id - groups.py : suppression _org_id_from_header() mort, create_group assigne organization_id correctement Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -31,13 +31,20 @@ router = APIRouter()
|
||||
# ── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
async def _get_decision(db: AsyncSession, decision_id: uuid.UUID) -> Decision:
|
||||
"""Fetch a decision by ID with its steps eagerly loaded, or raise 404."""
|
||||
result = await db.execute(
|
||||
async def _get_decision(
|
||||
db: AsyncSession, decision_id: uuid.UUID, org_id: uuid.UUID | None = None
|
||||
) -> Decision:
|
||||
"""Fetch a decision by ID within the active org scope, or raise 404."""
|
||||
stmt = (
|
||||
select(Decision)
|
||||
.options(selectinload(Decision.steps))
|
||||
.where(Decision.id == decision_id)
|
||||
)
|
||||
if org_id is not None:
|
||||
stmt = stmt.where(Decision.organization_id == org_id)
|
||||
else:
|
||||
stmt = stmt.where(Decision.organization_id.is_(None))
|
||||
result = await db.execute(stmt)
|
||||
decision = result.scalar_one_or_none()
|
||||
if decision is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Decision introuvable")
|
||||
@@ -61,6 +68,8 @@ async def list_decisions(
|
||||
|
||||
if org_id is not None:
|
||||
stmt = stmt.where(Decision.organization_id == org_id)
|
||||
else:
|
||||
stmt = stmt.where(Decision.organization_id.is_(None))
|
||||
if decision_type is not None:
|
||||
stmt = stmt.where(Decision.decision_type == decision_type)
|
||||
if status_filter is not None:
|
||||
@@ -91,7 +100,7 @@ async def create_decision(
|
||||
await db.refresh(decision)
|
||||
|
||||
# Reload with steps (empty at creation)
|
||||
decision = await _get_decision(db, decision.id)
|
||||
decision = await _get_decision(db, decision.id, org_id)
|
||||
return DecisionOut.model_validate(decision)
|
||||
|
||||
|
||||
@@ -99,9 +108,10 @@ async def create_decision(
|
||||
async def get_decision(
|
||||
id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
org_id: uuid.UUID | None = Depends(get_active_org_id),
|
||||
) -> DecisionOut:
|
||||
"""Get a single decision with all its steps."""
|
||||
decision = await _get_decision(db, id)
|
||||
decision = await _get_decision(db, id, org_id)
|
||||
return DecisionOut.model_validate(decision)
|
||||
|
||||
|
||||
@@ -111,9 +121,10 @@ async def update_decision(
|
||||
payload: DecisionUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
identity: DuniterIdentity = Depends(get_current_identity),
|
||||
org_id: uuid.UUID | None = Depends(get_active_org_id),
|
||||
) -> DecisionOut:
|
||||
"""Update a decision's metadata (title, description, status, protocol)."""
|
||||
decision = await _get_decision(db, id)
|
||||
decision = await _get_decision(db, id, org_id)
|
||||
|
||||
update_data = payload.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
@@ -123,7 +134,7 @@ async def update_decision(
|
||||
await db.refresh(decision)
|
||||
|
||||
# Reload with steps
|
||||
decision = await _get_decision(db, decision.id)
|
||||
decision = await _get_decision(db, decision.id, org_id)
|
||||
return DecisionOut.model_validate(decision)
|
||||
|
||||
|
||||
@@ -136,10 +147,11 @@ async def add_step(
|
||||
payload: DecisionStepCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
identity: DuniterIdentity = Depends(get_current_identity),
|
||||
org_id: uuid.UUID | None = Depends(get_active_org_id),
|
||||
) -> DecisionStepOut:
|
||||
"""Add a step to a decision process."""
|
||||
# Verify decision exists
|
||||
decision = await _get_decision(db, id)
|
||||
decision = await _get_decision(db, id, org_id)
|
||||
|
||||
step = DecisionStep(
|
||||
decision_id=decision.id,
|
||||
@@ -160,6 +172,7 @@ async def advance_decision_endpoint(
|
||||
id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
identity: DuniterIdentity = Depends(get_current_identity),
|
||||
org_id: uuid.UUID | None = Depends(get_active_org_id),
|
||||
) -> DecisionAdvanceOut:
|
||||
"""Advance a decision to its next step or status."""
|
||||
try:
|
||||
@@ -168,7 +181,7 @@ async def advance_decision_endpoint(
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))
|
||||
|
||||
# Reload with steps for complete output
|
||||
decision = await _get_decision(db, decision.id)
|
||||
decision = await _get_decision(db, decision.id, org_id)
|
||||
data = DecisionOut.model_validate(decision).model_dump()
|
||||
data["message"] = f"Decision avancee au statut : {decision.status}"
|
||||
return DecisionAdvanceOut(**data)
|
||||
@@ -184,6 +197,7 @@ async def create_vote_session_for_step_endpoint(
|
||||
step_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
identity: DuniterIdentity = Depends(get_current_identity),
|
||||
org_id: uuid.UUID | None = Depends(get_active_org_id),
|
||||
) -> VoteSessionOut:
|
||||
"""Create a vote session linked to a decision step."""
|
||||
try:
|
||||
@@ -199,9 +213,10 @@ async def delete_decision(
|
||||
id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
identity: DuniterIdentity = Depends(get_current_identity),
|
||||
org_id: uuid.UUID | None = Depends(get_active_org_id),
|
||||
) -> None:
|
||||
"""Delete a decision (only if in draft status)."""
|
||||
decision = await _get_decision(db, id)
|
||||
decision = await _get_decision(db, id, org_id)
|
||||
|
||||
if decision.status != "draft":
|
||||
raise HTTPException(
|
||||
|
||||
Reference in New Issue
Block a user