refactor(backend/api): extract DSFA schemas + services (Step 4 — file 14 of 18)
- Create compliance/schemas/dsfa.py (161 LOC) — extract DSFACreate, DSFAUpdate, DSFAStatusUpdate, DSFASectionUpdate, DSFAApproveRequest - Create compliance/services/dsfa_service.py (386 LOC) — CRUD + helpers + stats + audit-log + CSV export; uses domain errors - Create compliance/services/dsfa_workflow_service.py (347 LOC) — status update, section update, submit-for-review, approve, export JSON, versions - Rewrite compliance/api/dsfa_routes.py (339 LOC) as thin handlers with Depends + translate_domain_errors(); re-export legacy symbols via __all__ - Add [mypy-compliance.api.dsfa_routes] ignore_errors = False to mypy.ini - Update tests: 422 -> 400 for domain ValidationError (6 assertions) - Regenerate OpenAPI baseline (360 paths / 484 operations — unchanged) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
161
backend-compliance/compliance/schemas/dsfa.py
Normal file
161
backend-compliance/compliance/schemas/dsfa.py
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
"""
|
||||||
|
DSFA — Datenschutz-Folgenabschaetzung schemas (Art. 35 DSGVO).
|
||||||
|
|
||||||
|
Phase 1 Step 4: extracted from ``compliance.api.dsfa_routes``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class DSFACreate(BaseModel):
|
||||||
|
title: str
|
||||||
|
description: str = ""
|
||||||
|
status: str = "draft"
|
||||||
|
risk_level: str = "low"
|
||||||
|
processing_activity: str = ""
|
||||||
|
data_categories: List[str] = []
|
||||||
|
recipients: List[str] = []
|
||||||
|
measures: List[str] = []
|
||||||
|
created_by: str = "system"
|
||||||
|
# Section 1
|
||||||
|
processing_description: Optional[str] = None
|
||||||
|
processing_purpose: Optional[str] = None
|
||||||
|
legal_basis: Optional[str] = None
|
||||||
|
legal_basis_details: Optional[str] = None
|
||||||
|
# Section 2
|
||||||
|
necessity_assessment: Optional[str] = None
|
||||||
|
proportionality_assessment: Optional[str] = None
|
||||||
|
data_minimization: Optional[str] = None
|
||||||
|
alternatives_considered: Optional[str] = None
|
||||||
|
retention_justification: Optional[str] = None
|
||||||
|
# Section 3
|
||||||
|
involves_ai: Optional[bool] = None
|
||||||
|
overall_risk_level: Optional[str] = None
|
||||||
|
risk_score: Optional[int] = None
|
||||||
|
# Section 6
|
||||||
|
dpo_consulted: Optional[bool] = None
|
||||||
|
dpo_name: Optional[str] = None
|
||||||
|
dpo_opinion: Optional[str] = None
|
||||||
|
dpo_approved: Optional[bool] = None
|
||||||
|
authority_consulted: Optional[bool] = None
|
||||||
|
authority_reference: Optional[str] = None
|
||||||
|
authority_decision: Optional[str] = None
|
||||||
|
# Metadata
|
||||||
|
version: Optional[int] = None
|
||||||
|
conclusion: Optional[str] = None
|
||||||
|
federal_state: Optional[str] = None
|
||||||
|
authority_resource_id: Optional[str] = None
|
||||||
|
submitted_by: Optional[str] = None
|
||||||
|
# JSONB Arrays
|
||||||
|
data_subjects: Optional[List[str]] = None
|
||||||
|
affected_rights: Optional[List[str]] = None
|
||||||
|
triggered_rule_codes: Optional[List[str]] = None
|
||||||
|
ai_trigger_ids: Optional[List[str]] = None
|
||||||
|
wp248_criteria_met: Optional[List[str]] = None
|
||||||
|
art35_abs3_triggered: Optional[List[str]] = None
|
||||||
|
tom_references: Optional[List[str]] = None
|
||||||
|
risks: Optional[List[dict]] = None # type: ignore[type-arg]
|
||||||
|
mitigations: Optional[List[dict]] = None # type: ignore[type-arg]
|
||||||
|
stakeholder_consultations: Optional[List[dict]] = None # type: ignore[type-arg]
|
||||||
|
review_triggers: Optional[List[dict]] = None # type: ignore[type-arg]
|
||||||
|
review_comments: Optional[List[dict]] = None # type: ignore[type-arg]
|
||||||
|
ai_use_case_modules: Optional[List[dict]] = None # type: ignore[type-arg]
|
||||||
|
section_8_complete: Optional[bool] = None
|
||||||
|
# JSONB Objects
|
||||||
|
threshold_analysis: Optional[dict] = None # type: ignore[type-arg]
|
||||||
|
consultation_requirement: Optional[dict] = None # type: ignore[type-arg]
|
||||||
|
review_schedule: Optional[dict] = None # type: ignore[type-arg]
|
||||||
|
section_progress: Optional[dict] = None # type: ignore[type-arg]
|
||||||
|
metadata: Optional[dict] = None # type: ignore[type-arg]
|
||||||
|
|
||||||
|
|
||||||
|
class DSFAUpdate(BaseModel):
|
||||||
|
title: Optional[str] = None
|
||||||
|
description: Optional[str] = None
|
||||||
|
status: Optional[str] = None
|
||||||
|
risk_level: Optional[str] = None
|
||||||
|
processing_activity: Optional[str] = None
|
||||||
|
data_categories: Optional[List[str]] = None
|
||||||
|
recipients: Optional[List[str]] = None
|
||||||
|
measures: Optional[List[str]] = None
|
||||||
|
approved_by: Optional[str] = None
|
||||||
|
# Section 1
|
||||||
|
processing_description: Optional[str] = None
|
||||||
|
processing_purpose: Optional[str] = None
|
||||||
|
legal_basis: Optional[str] = None
|
||||||
|
legal_basis_details: Optional[str] = None
|
||||||
|
# Section 2
|
||||||
|
necessity_assessment: Optional[str] = None
|
||||||
|
proportionality_assessment: Optional[str] = None
|
||||||
|
data_minimization: Optional[str] = None
|
||||||
|
alternatives_considered: Optional[str] = None
|
||||||
|
retention_justification: Optional[str] = None
|
||||||
|
# Section 3
|
||||||
|
involves_ai: Optional[bool] = None
|
||||||
|
overall_risk_level: Optional[str] = None
|
||||||
|
risk_score: Optional[int] = None
|
||||||
|
# Section 6
|
||||||
|
dpo_consulted: Optional[bool] = None
|
||||||
|
dpo_name: Optional[str] = None
|
||||||
|
dpo_opinion: Optional[str] = None
|
||||||
|
dpo_approved: Optional[bool] = None
|
||||||
|
authority_consulted: Optional[bool] = None
|
||||||
|
authority_reference: Optional[str] = None
|
||||||
|
authority_decision: Optional[str] = None
|
||||||
|
# Metadata
|
||||||
|
version: Optional[int] = None
|
||||||
|
conclusion: Optional[str] = None
|
||||||
|
federal_state: Optional[str] = None
|
||||||
|
authority_resource_id: Optional[str] = None
|
||||||
|
submitted_by: Optional[str] = None
|
||||||
|
# JSONB Arrays
|
||||||
|
data_subjects: Optional[List[str]] = None
|
||||||
|
affected_rights: Optional[List[str]] = None
|
||||||
|
triggered_rule_codes: Optional[List[str]] = None
|
||||||
|
ai_trigger_ids: Optional[List[str]] = None
|
||||||
|
wp248_criteria_met: Optional[List[str]] = None
|
||||||
|
art35_abs3_triggered: Optional[List[str]] = None
|
||||||
|
tom_references: Optional[List[str]] = None
|
||||||
|
risks: Optional[List[dict]] = None # type: ignore[type-arg]
|
||||||
|
mitigations: Optional[List[dict]] = None # type: ignore[type-arg]
|
||||||
|
stakeholder_consultations: Optional[List[dict]] = None # type: ignore[type-arg]
|
||||||
|
review_triggers: Optional[List[dict]] = None # type: ignore[type-arg]
|
||||||
|
review_comments: Optional[List[dict]] = None # type: ignore[type-arg]
|
||||||
|
ai_use_case_modules: Optional[List[dict]] = None # type: ignore[type-arg]
|
||||||
|
section_8_complete: Optional[bool] = None
|
||||||
|
# JSONB Objects
|
||||||
|
threshold_analysis: Optional[dict] = None # type: ignore[type-arg]
|
||||||
|
consultation_requirement: Optional[dict] = None # type: ignore[type-arg]
|
||||||
|
review_schedule: Optional[dict] = None # type: ignore[type-arg]
|
||||||
|
section_progress: Optional[dict] = None # type: ignore[type-arg]
|
||||||
|
metadata: Optional[dict] = None # type: ignore[type-arg]
|
||||||
|
|
||||||
|
|
||||||
|
class DSFAStatusUpdate(BaseModel):
|
||||||
|
status: str
|
||||||
|
approved_by: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class DSFASectionUpdate(BaseModel):
|
||||||
|
"""Body for PUT /dsfa/{id}/sections/{section_number}."""
|
||||||
|
content: Optional[str] = None
|
||||||
|
# Allow arbitrary extra fields so the frontend can send any section-specific data
|
||||||
|
extra: Optional[dict] = None # type: ignore[type-arg]
|
||||||
|
|
||||||
|
|
||||||
|
class DSFAApproveRequest(BaseModel):
|
||||||
|
"""Body for POST /dsfa/{id}/approve."""
|
||||||
|
approved: bool
|
||||||
|
comments: Optional[str] = None
|
||||||
|
approved_by: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"DSFACreate",
|
||||||
|
"DSFAUpdate",
|
||||||
|
"DSFAStatusUpdate",
|
||||||
|
"DSFASectionUpdate",
|
||||||
|
"DSFAApproveRequest",
|
||||||
|
]
|
||||||
386
backend-compliance/compliance/services/dsfa_service.py
Normal file
386
backend-compliance/compliance/services/dsfa_service.py
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
# mypy: disable-error-code="arg-type,assignment,union-attr,no-any-return,call-overload,index,no-untyped-call"
|
||||||
|
"""
|
||||||
|
DSFA service — CRUD + helpers + stats + audit + CSV export.
|
||||||
|
|
||||||
|
Phase 1 Step 4: extracted from ``compliance.api.dsfa_routes``. The workflow
|
||||||
|
side (status update, section update, submit, approve, export, versions) lives
|
||||||
|
in ``compliance.services.dsfa_workflow_service``.
|
||||||
|
|
||||||
|
Module-level helpers (_dsfa_to_response, _get_tenant_id, _log_audit) are
|
||||||
|
shared by both service modules and re-exported from
|
||||||
|
``compliance.api.dsfa_routes`` for legacy test imports.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from sqlalchemy import text
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from compliance.domain import NotFoundError, ValidationError
|
||||||
|
from compliance.schemas.dsfa import DSFACreate, DSFAUpdate
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DEFAULT_TENANT_ID = "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e"
|
||||||
|
VALID_STATUSES = {"draft", "in-review", "approved", "needs-update"}
|
||||||
|
VALID_RISK_LEVELS = {"low", "medium", "high", "critical"}
|
||||||
|
JSONB_FIELDS = {
|
||||||
|
"data_categories", "recipients", "measures", "data_subjects",
|
||||||
|
"affected_rights", "triggered_rule_codes", "ai_trigger_ids",
|
||||||
|
"wp248_criteria_met", "art35_abs3_triggered", "tom_references",
|
||||||
|
"risks", "mitigations", "stakeholder_consultations", "review_triggers",
|
||||||
|
"review_comments", "ai_use_case_modules", "threshold_analysis",
|
||||||
|
"consultation_requirement", "review_schedule", "section_progress",
|
||||||
|
"metadata",
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Module-level helpers (re-exported by compliance.api.dsfa_routes) -----
|
||||||
|
|
||||||
|
|
||||||
|
def _get_tenant_id(tenant_id: Optional[str]) -> str:
|
||||||
|
return tenant_id or DEFAULT_TENANT_ID
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_arr(val: Any) -> Any:
|
||||||
|
"""Parse a JSONB array field -> list."""
|
||||||
|
if val is None:
|
||||||
|
return []
|
||||||
|
if isinstance(val, list):
|
||||||
|
return val
|
||||||
|
if isinstance(val, str):
|
||||||
|
try:
|
||||||
|
parsed = json.loads(val)
|
||||||
|
return parsed if isinstance(parsed, list) else []
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_obj(val: Any) -> Any:
|
||||||
|
"""Parse a JSONB object field -> dict."""
|
||||||
|
if val is None:
|
||||||
|
return {}
|
||||||
|
if isinstance(val, dict):
|
||||||
|
return val
|
||||||
|
if isinstance(val, str):
|
||||||
|
try:
|
||||||
|
parsed = json.loads(val)
|
||||||
|
return parsed if isinstance(parsed, dict) else {}
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
def _ts(val: Any) -> Any:
|
||||||
|
"""Timestamp -> ISO string or None."""
|
||||||
|
if not val:
|
||||||
|
return None
|
||||||
|
return val if isinstance(val, str) else val.isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
def _get(row: Any, key: str, default: Any = None) -> Any:
|
||||||
|
"""Safe row access — returns default if key missing."""
|
||||||
|
try:
|
||||||
|
v = row[key]
|
||||||
|
return default if v is None and default is not None else v
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def _dsfa_to_response(row: Any) -> dict[str, Any]:
|
||||||
|
"""Convert a DB row to a JSON-serializable dict."""
|
||||||
|
g = lambda k, d=None: _get(row, k, d) # noqa: E731
|
||||||
|
prev = g("previous_version_id")
|
||||||
|
return {
|
||||||
|
"id": str(row["id"]),
|
||||||
|
"tenant_id": row["tenant_id"],
|
||||||
|
"title": row["title"],
|
||||||
|
"description": row["description"] or "",
|
||||||
|
"status": row["status"] or "draft",
|
||||||
|
"risk_level": row["risk_level"] or "low",
|
||||||
|
"processing_activity": row["processing_activity"] or "",
|
||||||
|
"data_categories": _parse_arr(row["data_categories"]),
|
||||||
|
"recipients": _parse_arr(row["recipients"]),
|
||||||
|
"measures": _parse_arr(row["measures"]),
|
||||||
|
"approved_by": row["approved_by"],
|
||||||
|
"approved_at": _ts(row["approved_at"]),
|
||||||
|
"created_by": row["created_by"] or "system",
|
||||||
|
"created_at": _ts(row["created_at"]),
|
||||||
|
"updated_at": _ts(row["updated_at"]),
|
||||||
|
"processing_description": g("processing_description"),
|
||||||
|
"processing_purpose": g("processing_purpose"),
|
||||||
|
"legal_basis": g("legal_basis"),
|
||||||
|
"legal_basis_details": g("legal_basis_details"),
|
||||||
|
"necessity_assessment": g("necessity_assessment"),
|
||||||
|
"proportionality_assessment": g("proportionality_assessment"),
|
||||||
|
"data_minimization": g("data_minimization"),
|
||||||
|
"alternatives_considered": g("alternatives_considered"),
|
||||||
|
"retention_justification": g("retention_justification"),
|
||||||
|
"involves_ai": g("involves_ai", False),
|
||||||
|
"overall_risk_level": g("overall_risk_level"),
|
||||||
|
"risk_score": g("risk_score", 0),
|
||||||
|
"dpo_consulted": g("dpo_consulted", False),
|
||||||
|
"dpo_consulted_at": _ts(g("dpo_consulted_at")),
|
||||||
|
"dpo_name": g("dpo_name"),
|
||||||
|
"dpo_opinion": g("dpo_opinion"),
|
||||||
|
"dpo_approved": g("dpo_approved"),
|
||||||
|
"authority_consulted": g("authority_consulted", False),
|
||||||
|
"authority_consulted_at": _ts(g("authority_consulted_at")),
|
||||||
|
"authority_reference": g("authority_reference"),
|
||||||
|
"authority_decision": g("authority_decision"),
|
||||||
|
"version": g("version", 1),
|
||||||
|
"previous_version_id": str(prev) if prev else None,
|
||||||
|
"conclusion": g("conclusion"),
|
||||||
|
"federal_state": g("federal_state"),
|
||||||
|
"authority_resource_id": g("authority_resource_id"),
|
||||||
|
"submitted_for_review_at": _ts(g("submitted_for_review_at")),
|
||||||
|
"submitted_by": g("submitted_by"),
|
||||||
|
"data_subjects": _parse_arr(g("data_subjects")),
|
||||||
|
"affected_rights": _parse_arr(g("affected_rights")),
|
||||||
|
"triggered_rule_codes": _parse_arr(g("triggered_rule_codes")),
|
||||||
|
"ai_trigger_ids": _parse_arr(g("ai_trigger_ids")),
|
||||||
|
"wp248_criteria_met": _parse_arr(g("wp248_criteria_met")),
|
||||||
|
"art35_abs3_triggered": _parse_arr(g("art35_abs3_triggered")),
|
||||||
|
"tom_references": _parse_arr(g("tom_references")),
|
||||||
|
"risks": _parse_arr(g("risks")),
|
||||||
|
"mitigations": _parse_arr(g("mitigations")),
|
||||||
|
"stakeholder_consultations": _parse_arr(g("stakeholder_consultations")),
|
||||||
|
"review_triggers": _parse_arr(g("review_triggers")),
|
||||||
|
"review_comments": _parse_arr(g("review_comments")),
|
||||||
|
"ai_use_case_modules": _parse_arr(g("ai_use_case_modules")),
|
||||||
|
"section_8_complete": g("section_8_complete", False),
|
||||||
|
"threshold_analysis": _parse_obj(g("threshold_analysis")),
|
||||||
|
"consultation_requirement": _parse_obj(g("consultation_requirement")),
|
||||||
|
"review_schedule": _parse_obj(g("review_schedule")),
|
||||||
|
"section_progress": _parse_obj(g("section_progress")),
|
||||||
|
"metadata": _parse_obj(g("metadata")),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _log_audit(
|
||||||
|
db: Session, tenant_id: str, dsfa_id: Any, action: str,
|
||||||
|
changed_by: str = "system", old_values: Any = None,
|
||||||
|
new_values: Any = None,
|
||||||
|
) -> None:
|
||||||
|
db.execute(
|
||||||
|
text("""
|
||||||
|
INSERT INTO compliance_dsfa_audit_log
|
||||||
|
(tenant_id, dsfa_id, action, changed_by, old_values, new_values)
|
||||||
|
VALUES
|
||||||
|
(:tenant_id, :dsfa_id, :action, :changed_by,
|
||||||
|
CAST(:old_values AS jsonb), CAST(:new_values AS jsonb))
|
||||||
|
"""),
|
||||||
|
{
|
||||||
|
"tenant_id": tenant_id,
|
||||||
|
"dsfa_id": str(dsfa_id) if dsfa_id else None,
|
||||||
|
"action": action, "changed_by": changed_by,
|
||||||
|
"old_values": json.dumps(old_values) if old_values else None,
|
||||||
|
"new_values": json.dumps(new_values) if new_values else None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---- Service ---------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
class DSFAService:
|
||||||
|
"""CRUD + stats + audit-log + CSV export."""
|
||||||
|
|
||||||
|
def __init__(self, db: Session) -> None:
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
def list_dsfas(
|
||||||
|
self, tenant_id: Optional[str], status: Optional[str],
|
||||||
|
risk_level: Optional[str], skip: int, limit: int,
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
tid = _get_tenant_id(tenant_id)
|
||||||
|
sql = "SELECT * FROM compliance_dsfas WHERE tenant_id = :tid"
|
||||||
|
params: dict[str, Any] = {"tid": tid}
|
||||||
|
if status:
|
||||||
|
sql += " AND status = :status"; params["status"] = status
|
||||||
|
if risk_level:
|
||||||
|
sql += " AND risk_level = :risk_level"; params["risk_level"] = risk_level
|
||||||
|
sql += " ORDER BY created_at DESC LIMIT :limit OFFSET :skip"
|
||||||
|
params["limit"] = limit; params["skip"] = skip
|
||||||
|
rows = self.db.execute(text(sql), params).fetchall()
|
||||||
|
return [_dsfa_to_response(r) for r in rows]
|
||||||
|
|
||||||
|
def create(
|
||||||
|
self, tenant_id: Optional[str], body: DSFACreate,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
if body.status not in VALID_STATUSES:
|
||||||
|
raise ValidationError(f"Ungültiger Status: {body.status}")
|
||||||
|
if body.risk_level not in VALID_RISK_LEVELS:
|
||||||
|
raise ValidationError(f"Ungültiges Risiko-Level: {body.risk_level}")
|
||||||
|
tid = _get_tenant_id(tenant_id)
|
||||||
|
row = self.db.execute(
|
||||||
|
text("""
|
||||||
|
INSERT INTO compliance_dsfas
|
||||||
|
(tenant_id, title, description, status, risk_level,
|
||||||
|
processing_activity, data_categories, recipients,
|
||||||
|
measures, created_by)
|
||||||
|
VALUES
|
||||||
|
(:tenant_id, :title, :description, :status, :risk_level,
|
||||||
|
:processing_activity,
|
||||||
|
CAST(:data_categories AS jsonb),
|
||||||
|
CAST(:recipients AS jsonb),
|
||||||
|
CAST(:measures AS jsonb),
|
||||||
|
:created_by)
|
||||||
|
RETURNING *
|
||||||
|
"""),
|
||||||
|
{
|
||||||
|
"tenant_id": tid, "title": body.title,
|
||||||
|
"description": body.description, "status": body.status,
|
||||||
|
"risk_level": body.risk_level,
|
||||||
|
"processing_activity": body.processing_activity,
|
||||||
|
"data_categories": json.dumps(body.data_categories),
|
||||||
|
"recipients": json.dumps(body.recipients),
|
||||||
|
"measures": json.dumps(body.measures),
|
||||||
|
"created_by": body.created_by,
|
||||||
|
},
|
||||||
|
).fetchone()
|
||||||
|
self.db.flush()
|
||||||
|
_log_audit(
|
||||||
|
self.db, tid, row["id"], "CREATE", body.created_by,
|
||||||
|
new_values={"title": body.title, "status": body.status},
|
||||||
|
)
|
||||||
|
self.db.commit()
|
||||||
|
return _dsfa_to_response(row)
|
||||||
|
|
||||||
|
def get(self, dsfa_id: str, tenant_id: Optional[str]) -> dict[str, Any]:
|
||||||
|
tid = _get_tenant_id(tenant_id)
|
||||||
|
row = self.db.execute(
|
||||||
|
text("SELECT * FROM compliance_dsfas WHERE id = :id AND tenant_id = :tid"),
|
||||||
|
{"id": dsfa_id, "tid": tid},
|
||||||
|
).fetchone()
|
||||||
|
if not row:
|
||||||
|
raise NotFoundError(f"DSFA {dsfa_id} nicht gefunden")
|
||||||
|
return _dsfa_to_response(row)
|
||||||
|
|
||||||
|
def update(
|
||||||
|
self, dsfa_id: str, tenant_id: Optional[str], body: DSFAUpdate,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
tid = _get_tenant_id(tenant_id)
|
||||||
|
existing = self.db.execute(
|
||||||
|
text("SELECT * FROM compliance_dsfas WHERE id = :id AND tenant_id = :tid"),
|
||||||
|
{"id": dsfa_id, "tid": tid},
|
||||||
|
).fetchone()
|
||||||
|
if not existing:
|
||||||
|
raise NotFoundError(f"DSFA {dsfa_id} nicht gefunden")
|
||||||
|
updates = body.model_dump(exclude_none=True)
|
||||||
|
if "status" in updates and updates["status"] not in VALID_STATUSES:
|
||||||
|
raise ValidationError(f"Ungültiger Status: {updates['status']}")
|
||||||
|
if "risk_level" in updates and updates["risk_level"] not in VALID_RISK_LEVELS:
|
||||||
|
raise ValidationError(f"Ungültiges Risiko-Level: {updates['risk_level']}")
|
||||||
|
if not updates:
|
||||||
|
return _dsfa_to_response(existing)
|
||||||
|
set_clauses: list[str] = []
|
||||||
|
params: dict[str, Any] = {"id": dsfa_id, "tid": tid}
|
||||||
|
for field, value in updates.items():
|
||||||
|
if field in JSONB_FIELDS:
|
||||||
|
set_clauses.append(f"{field} = CAST(:{field} AS jsonb)")
|
||||||
|
params[field] = json.dumps(value)
|
||||||
|
else:
|
||||||
|
set_clauses.append(f"{field} = :{field}")
|
||||||
|
params[field] = value
|
||||||
|
set_clauses.append("updated_at = NOW()")
|
||||||
|
sql = (
|
||||||
|
f"UPDATE compliance_dsfas SET {', '.join(set_clauses)} "
|
||||||
|
f"WHERE id = :id AND tenant_id = :tid RETURNING *"
|
||||||
|
)
|
||||||
|
old_values = {"title": existing["title"], "status": existing["status"]}
|
||||||
|
row = self.db.execute(text(sql), params).fetchone()
|
||||||
|
_log_audit(self.db, tid, dsfa_id, "UPDATE",
|
||||||
|
new_values=updates, old_values=old_values)
|
||||||
|
self.db.commit()
|
||||||
|
return _dsfa_to_response(row)
|
||||||
|
|
||||||
|
def delete(self, dsfa_id: str, tenant_id: Optional[str]) -> dict[str, Any]:
|
||||||
|
tid = _get_tenant_id(tenant_id)
|
||||||
|
existing = self.db.execute(
|
||||||
|
text("SELECT id, title FROM compliance_dsfas WHERE id = :id AND tenant_id = :tid"),
|
||||||
|
{"id": dsfa_id, "tid": tid},
|
||||||
|
).fetchone()
|
||||||
|
if not existing:
|
||||||
|
raise NotFoundError(f"DSFA {dsfa_id} nicht gefunden")
|
||||||
|
_log_audit(self.db, tid, dsfa_id, "DELETE",
|
||||||
|
old_values={"title": existing["title"]})
|
||||||
|
self.db.execute(
|
||||||
|
text("DELETE FROM compliance_dsfas WHERE id = :id AND tenant_id = :tid"),
|
||||||
|
{"id": dsfa_id, "tid": tid},
|
||||||
|
)
|
||||||
|
self.db.commit()
|
||||||
|
return {"success": True, "message": f"DSFA {dsfa_id} gelöscht"}
|
||||||
|
|
||||||
|
def stats(self, tenant_id: Optional[str]) -> dict[str, Any]:
|
||||||
|
tid = _get_tenant_id(tenant_id)
|
||||||
|
rows = self.db.execute(
|
||||||
|
text("SELECT status, risk_level FROM compliance_dsfas WHERE tenant_id = :tid"),
|
||||||
|
{"tid": tid},
|
||||||
|
).fetchall()
|
||||||
|
by_status: dict[str, int] = {}
|
||||||
|
by_risk: dict[str, int] = {}
|
||||||
|
for row in rows:
|
||||||
|
s = row["status"] or "draft"
|
||||||
|
r = row["risk_level"] or "low"
|
||||||
|
by_status[s] = by_status.get(s, 0) + 1
|
||||||
|
by_risk[r] = by_risk.get(r, 0) + 1
|
||||||
|
return {
|
||||||
|
"total": len(rows), "by_status": by_status, "by_risk_level": by_risk,
|
||||||
|
"draft_count": by_status.get("draft", 0),
|
||||||
|
"in_review_count": by_status.get("in-review", 0),
|
||||||
|
"approved_count": by_status.get("approved", 0),
|
||||||
|
"needs_update_count": by_status.get("needs-update", 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
def audit_log(
|
||||||
|
self, tenant_id: Optional[str], limit: int, offset: int,
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
tid = _get_tenant_id(tenant_id)
|
||||||
|
rows = self.db.execute(
|
||||||
|
text("""
|
||||||
|
SELECT id, tenant_id, dsfa_id, action, changed_by,
|
||||||
|
old_values, new_values, created_at
|
||||||
|
FROM compliance_dsfa_audit_log
|
||||||
|
WHERE tenant_id = :tid
|
||||||
|
ORDER BY created_at DESC LIMIT :limit OFFSET :offset
|
||||||
|
"""),
|
||||||
|
{"tid": tid, "limit": limit, "offset": offset},
|
||||||
|
).fetchall()
|
||||||
|
result: list[dict[str, Any]] = []
|
||||||
|
for r in rows:
|
||||||
|
ca = r["created_at"]
|
||||||
|
result.append({
|
||||||
|
"id": str(r["id"]),
|
||||||
|
"tenant_id": r["tenant_id"],
|
||||||
|
"dsfa_id": str(r["dsfa_id"]) if r["dsfa_id"] else None,
|
||||||
|
"action": r["action"],
|
||||||
|
"changed_by": r["changed_by"],
|
||||||
|
"old_values": r["old_values"],
|
||||||
|
"new_values": r["new_values"],
|
||||||
|
"created_at": ca if isinstance(ca, str) else (ca.isoformat() if ca else None),
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
|
||||||
|
def export_csv(self, tenant_id: Optional[str]) -> str:
|
||||||
|
tid = _get_tenant_id(tenant_id)
|
||||||
|
rows = self.db.execute(
|
||||||
|
text("SELECT * FROM compliance_dsfas WHERE tenant_id = :tid ORDER BY created_at DESC"),
|
||||||
|
{"tid": tid},
|
||||||
|
).fetchall()
|
||||||
|
output = io.StringIO()
|
||||||
|
writer = csv.writer(output, delimiter=";")
|
||||||
|
writer.writerow(["ID", "Titel", "Status", "Risiko-Level", "Erstellt", "Aktualisiert"])
|
||||||
|
for r in rows:
|
||||||
|
ca = r["created_at"]
|
||||||
|
ua = r["updated_at"]
|
||||||
|
writer.writerow([
|
||||||
|
str(r["id"]), r["title"], r["status"] or "draft", r["risk_level"] or "low",
|
||||||
|
ca if isinstance(ca, str) else (ca.isoformat() if ca else ""),
|
||||||
|
ua if isinstance(ua, str) else (ua.isoformat() if ua else ""),
|
||||||
|
])
|
||||||
|
return output.getvalue()
|
||||||
347
backend-compliance/compliance/services/dsfa_workflow_service.py
Normal file
347
backend-compliance/compliance/services/dsfa_workflow_service.py
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
# mypy: disable-error-code="arg-type,assignment,union-attr,no-any-return,call-overload,index"
|
||||||
|
"""
|
||||||
|
DSFA workflow service — status, section update, submit, approve, export, versions.
|
||||||
|
|
||||||
|
Phase 1 Step 4: extracted from ``compliance.api.dsfa_routes``. CRUD + helpers
|
||||||
|
live in ``compliance.services.dsfa_service``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from sqlalchemy import text
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from compliance.api.versioning_utils import get_version, list_versions
|
||||||
|
from compliance.domain import NotFoundError, ValidationError
|
||||||
|
from compliance.schemas.dsfa import (
|
||||||
|
DSFAApproveRequest,
|
||||||
|
DSFASectionUpdate,
|
||||||
|
DSFAStatusUpdate,
|
||||||
|
)
|
||||||
|
from compliance.services.dsfa_service import (
|
||||||
|
VALID_STATUSES,
|
||||||
|
_dsfa_to_response,
|
||||||
|
_get_tenant_id,
|
||||||
|
_log_audit,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SECTION_FIELD_MAP: dict[int, str] = {
|
||||||
|
1: "processing_description",
|
||||||
|
2: "necessity_assessment",
|
||||||
|
3: "risk_assessment",
|
||||||
|
4: "stakeholder_consultations",
|
||||||
|
5: "measures",
|
||||||
|
6: "dpo_opinion",
|
||||||
|
7: "conclusion",
|
||||||
|
8: "ai_use_case_modules",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DSFAWorkflowService:
|
||||||
|
"""Status update, section update, submit, approve, export, versions."""
|
||||||
|
|
||||||
|
def __init__(self, db: Session) -> None:
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Status update
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def update_status(
|
||||||
|
self,
|
||||||
|
dsfa_id: str,
|
||||||
|
tenant_id: Optional[str],
|
||||||
|
body: DSFAStatusUpdate,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
if body.status not in VALID_STATUSES:
|
||||||
|
raise ValidationError(f"Ungültiger Status: {body.status}")
|
||||||
|
|
||||||
|
tid = _get_tenant_id(tenant_id)
|
||||||
|
existing = self.db.execute(
|
||||||
|
text(
|
||||||
|
"SELECT id, status FROM compliance_dsfas "
|
||||||
|
"WHERE id = :id AND tenant_id = :tid"
|
||||||
|
),
|
||||||
|
{"id": dsfa_id, "tid": tid},
|
||||||
|
).fetchone()
|
||||||
|
if not existing:
|
||||||
|
raise NotFoundError(f"DSFA {dsfa_id} nicht gefunden")
|
||||||
|
|
||||||
|
params: dict[str, Any] = {
|
||||||
|
"id": dsfa_id,
|
||||||
|
"tid": tid,
|
||||||
|
"status": body.status,
|
||||||
|
"approved_at": (
|
||||||
|
datetime.now(timezone.utc)
|
||||||
|
if body.status == "approved"
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
"approved_by": body.approved_by,
|
||||||
|
}
|
||||||
|
row = self.db.execute(
|
||||||
|
text("""
|
||||||
|
UPDATE compliance_dsfas
|
||||||
|
SET status = :status, approved_at = :approved_at,
|
||||||
|
approved_by = :approved_by, updated_at = NOW()
|
||||||
|
WHERE id = :id AND tenant_id = :tid
|
||||||
|
RETURNING *
|
||||||
|
"""),
|
||||||
|
params,
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
_log_audit(
|
||||||
|
self.db, tid, dsfa_id, "STATUS_CHANGE",
|
||||||
|
old_values={"status": existing["status"]},
|
||||||
|
new_values={"status": body.status},
|
||||||
|
)
|
||||||
|
self.db.commit()
|
||||||
|
return _dsfa_to_response(row)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Section update
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def update_section(
|
||||||
|
self,
|
||||||
|
dsfa_id: str,
|
||||||
|
section_number: int,
|
||||||
|
tenant_id: Optional[str],
|
||||||
|
body: DSFASectionUpdate,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
if section_number < 1 or section_number > 8:
|
||||||
|
raise ValidationError(
|
||||||
|
f"Section must be 1-8, got {section_number}"
|
||||||
|
)
|
||||||
|
|
||||||
|
tid = _get_tenant_id(tenant_id)
|
||||||
|
existing = self.db.execute(
|
||||||
|
text(
|
||||||
|
"SELECT * FROM compliance_dsfas "
|
||||||
|
"WHERE id = :id AND tenant_id = :tid"
|
||||||
|
),
|
||||||
|
{"id": dsfa_id, "tid": tid},
|
||||||
|
).fetchone()
|
||||||
|
if not existing:
|
||||||
|
raise NotFoundError(f"DSFA {dsfa_id} nicht gefunden")
|
||||||
|
|
||||||
|
field = SECTION_FIELD_MAP[section_number]
|
||||||
|
jsonb_sections = {4, 5, 8}
|
||||||
|
|
||||||
|
params: dict[str, Any] = {"id": dsfa_id, "tid": tid}
|
||||||
|
|
||||||
|
if section_number in jsonb_sections:
|
||||||
|
value = (
|
||||||
|
body.extra
|
||||||
|
if body.extra is not None
|
||||||
|
else ([] if section_number != 4 else [])
|
||||||
|
)
|
||||||
|
params["val"] = json.dumps(value)
|
||||||
|
set_clause = f"{field} = CAST(:val AS jsonb)"
|
||||||
|
else:
|
||||||
|
params["val"] = body.content or ""
|
||||||
|
set_clause = f"{field} = :val"
|
||||||
|
|
||||||
|
# Update section_progress
|
||||||
|
progress = (
|
||||||
|
existing["section_progress"]
|
||||||
|
if existing["section_progress"]
|
||||||
|
else {}
|
||||||
|
)
|
||||||
|
if isinstance(progress, str):
|
||||||
|
progress = json.loads(progress)
|
||||||
|
progress[f"section_{section_number}"] = True
|
||||||
|
params["progress"] = json.dumps(progress)
|
||||||
|
|
||||||
|
row = self.db.execute(
|
||||||
|
text(f"""
|
||||||
|
UPDATE compliance_dsfas
|
||||||
|
SET {set_clause},
|
||||||
|
section_progress = CAST(:progress AS jsonb),
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = :id AND tenant_id = :tid
|
||||||
|
RETURNING *
|
||||||
|
"""),
|
||||||
|
params,
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
_log_audit(
|
||||||
|
self.db, tid, dsfa_id, "SECTION_UPDATE",
|
||||||
|
new_values={"section": section_number, "field": field},
|
||||||
|
)
|
||||||
|
self.db.commit()
|
||||||
|
return _dsfa_to_response(row)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Submit for review
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def submit_for_review(
|
||||||
|
self, dsfa_id: str, tenant_id: Optional[str]
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
tid = _get_tenant_id(tenant_id)
|
||||||
|
existing = self.db.execute(
|
||||||
|
text(
|
||||||
|
"SELECT id, status FROM compliance_dsfas "
|
||||||
|
"WHERE id = :id AND tenant_id = :tid"
|
||||||
|
),
|
||||||
|
{"id": dsfa_id, "tid": tid},
|
||||||
|
).fetchone()
|
||||||
|
if not existing:
|
||||||
|
raise NotFoundError(f"DSFA {dsfa_id} nicht gefunden")
|
||||||
|
|
||||||
|
if existing["status"] not in ("draft", "needs-update"):
|
||||||
|
raise ValidationError(
|
||||||
|
f"Kann nur aus Status 'draft' oder 'needs-update' "
|
||||||
|
f"eingereicht werden, aktuell: {existing['status']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
row = self.db.execute(
|
||||||
|
text("""
|
||||||
|
UPDATE compliance_dsfas
|
||||||
|
SET status = 'in-review',
|
||||||
|
submitted_for_review_at = NOW(),
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = :id AND tenant_id = :tid
|
||||||
|
RETURNING *
|
||||||
|
"""),
|
||||||
|
{"id": dsfa_id, "tid": tid},
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
_log_audit(
|
||||||
|
self.db, tid, dsfa_id, "SUBMIT_FOR_REVIEW",
|
||||||
|
old_values={"status": existing["status"]},
|
||||||
|
new_values={"status": "in-review"},
|
||||||
|
)
|
||||||
|
self.db.commit()
|
||||||
|
return {
|
||||||
|
"message": "DSFA zur Prüfung eingereicht",
|
||||||
|
"status": "in-review",
|
||||||
|
"dsfa": _dsfa_to_response(row),
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Approve / reject
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def approve(
|
||||||
|
self,
|
||||||
|
dsfa_id: str,
|
||||||
|
tenant_id: Optional[str],
|
||||||
|
body: DSFAApproveRequest,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
tid = _get_tenant_id(tenant_id)
|
||||||
|
existing = self.db.execute(
|
||||||
|
text(
|
||||||
|
"SELECT id, status FROM compliance_dsfas "
|
||||||
|
"WHERE id = :id AND tenant_id = :tid"
|
||||||
|
),
|
||||||
|
{"id": dsfa_id, "tid": tid},
|
||||||
|
).fetchone()
|
||||||
|
if not existing:
|
||||||
|
raise NotFoundError(f"DSFA {dsfa_id} nicht gefunden")
|
||||||
|
|
||||||
|
if existing["status"] != "in-review":
|
||||||
|
raise ValidationError(
|
||||||
|
f"Nur DSFAs im Status 'in-review' können genehmigt werden, "
|
||||||
|
f"aktuell: {existing['status']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if body.approved:
|
||||||
|
new_status = "approved"
|
||||||
|
self.db.execute(
|
||||||
|
text("""
|
||||||
|
UPDATE compliance_dsfas
|
||||||
|
SET status = 'approved',
|
||||||
|
approved_by = :approved_by,
|
||||||
|
approved_at = NOW(),
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = :id AND tenant_id = :tid
|
||||||
|
RETURNING *
|
||||||
|
"""),
|
||||||
|
{
|
||||||
|
"id": dsfa_id,
|
||||||
|
"tid": tid,
|
||||||
|
"approved_by": body.approved_by or "system",
|
||||||
|
},
|
||||||
|
).fetchone()
|
||||||
|
else:
|
||||||
|
new_status = "needs-update"
|
||||||
|
self.db.execute(
|
||||||
|
text("""
|
||||||
|
UPDATE compliance_dsfas
|
||||||
|
SET status = 'needs-update', updated_at = NOW()
|
||||||
|
WHERE id = :id AND tenant_id = :tid
|
||||||
|
RETURNING *
|
||||||
|
"""),
|
||||||
|
{"id": dsfa_id, "tid": tid},
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
_log_audit(
|
||||||
|
self.db, tid, dsfa_id,
|
||||||
|
"APPROVE" if body.approved else "REJECT",
|
||||||
|
old_values={"status": existing["status"]},
|
||||||
|
new_values={"status": new_status, "comments": body.comments},
|
||||||
|
)
|
||||||
|
self.db.commit()
|
||||||
|
return {
|
||||||
|
"message": (
|
||||||
|
"DSFA genehmigt"
|
||||||
|
if body.approved
|
||||||
|
else "DSFA zurückgewiesen"
|
||||||
|
),
|
||||||
|
"status": new_status,
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Export JSON
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def export_json(
|
||||||
|
self, dsfa_id: str, tenant_id: Optional[str], fmt: str
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
tid = _get_tenant_id(tenant_id)
|
||||||
|
row = self.db.execute(
|
||||||
|
text(
|
||||||
|
"SELECT * FROM compliance_dsfas "
|
||||||
|
"WHERE id = :id AND tenant_id = :tid"
|
||||||
|
),
|
||||||
|
{"id": dsfa_id, "tid": tid},
|
||||||
|
).fetchone()
|
||||||
|
if not row:
|
||||||
|
raise NotFoundError(f"DSFA {dsfa_id} nicht gefunden")
|
||||||
|
|
||||||
|
dsfa_data = _dsfa_to_response(row)
|
||||||
|
return {
|
||||||
|
"exported_at": datetime.now(timezone.utc).isoformat(),
|
||||||
|
"format": fmt,
|
||||||
|
"dsfa": dsfa_data,
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Versions
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def list_versions(
|
||||||
|
self, dsfa_id: str, tenant_id: Optional[str]
|
||||||
|
) -> Any:
|
||||||
|
tid = _get_tenant_id(tenant_id)
|
||||||
|
return list_versions(self.db, "dsfa", dsfa_id, tid)
|
||||||
|
|
||||||
|
def get_version(
|
||||||
|
self,
|
||||||
|
dsfa_id: str,
|
||||||
|
version_number: int,
|
||||||
|
tenant_id: Optional[str],
|
||||||
|
) -> Any:
|
||||||
|
tid = _get_tenant_id(tenant_id)
|
||||||
|
v = get_version(self.db, "dsfa", dsfa_id, version_number, tid)
|
||||||
|
if not v:
|
||||||
|
raise NotFoundError(
|
||||||
|
f"Version {version_number} not found"
|
||||||
|
)
|
||||||
|
return v
|
||||||
@@ -95,5 +95,7 @@ ignore_errors = False
|
|||||||
ignore_errors = False
|
ignore_errors = False
|
||||||
[mypy-compliance.api.legal_document_routes]
|
[mypy-compliance.api.legal_document_routes]
|
||||||
ignore_errors = False
|
ignore_errors = False
|
||||||
|
[mypy-compliance.api.dsfa_routes]
|
||||||
|
ignore_errors = False
|
||||||
[mypy-compliance.api._http_errors]
|
[mypy-compliance.api._http_errors]
|
||||||
ignore_errors = False
|
ignore_errors = False
|
||||||
|
|||||||
@@ -374,59 +374,6 @@
|
|||||||
"title": "ApprovalCommentRequest",
|
"title": "ApprovalCommentRequest",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"ApprovalHistoryEntry": {
|
|
||||||
"properties": {
|
|
||||||
"action": {
|
|
||||||
"title": "Action",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"approver": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Approver"
|
|
||||||
},
|
|
||||||
"comment": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Comment"
|
|
||||||
},
|
|
||||||
"created_at": {
|
|
||||||
"format": "date-time",
|
|
||||||
"title": "Created At",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"title": "Id",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"version_id": {
|
|
||||||
"title": "Version Id",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"version_id",
|
|
||||||
"action",
|
|
||||||
"approver",
|
|
||||||
"comment",
|
|
||||||
"created_at"
|
|
||||||
],
|
|
||||||
"title": "ApprovalHistoryEntry",
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"AssignRequest": {
|
"AssignRequest": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"assignee_id": {
|
"assignee_id": {
|
||||||
@@ -19563,122 +19510,6 @@
|
|||||||
"title": "ConsentCreate",
|
"title": "ConsentCreate",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"compliance__api__legal_document_routes__VersionCreate": {
|
|
||||||
"properties": {
|
|
||||||
"content": {
|
|
||||||
"title": "Content",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"created_by": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Created By"
|
|
||||||
},
|
|
||||||
"document_id": {
|
|
||||||
"title": "Document Id",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"language": {
|
|
||||||
"default": "de",
|
|
||||||
"title": "Language",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"summary": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Summary"
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"title": "Title",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"version": {
|
|
||||||
"title": "Version",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"document_id",
|
|
||||||
"version",
|
|
||||||
"title",
|
|
||||||
"content"
|
|
||||||
],
|
|
||||||
"title": "VersionCreate",
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"compliance__api__legal_document_routes__VersionUpdate": {
|
|
||||||
"properties": {
|
|
||||||
"content": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Content"
|
|
||||||
},
|
|
||||||
"language": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Language"
|
|
||||||
},
|
|
||||||
"summary": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Summary"
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Title"
|
|
||||||
},
|
|
||||||
"version": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Version"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "VersionUpdate",
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"compliance__api__notfallplan_routes__IncidentCreate": {
|
"compliance__api__notfallplan_routes__IncidentCreate": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"affected_data_categories": {
|
"affected_data_categories": {
|
||||||
@@ -20361,6 +20192,122 @@
|
|||||||
],
|
],
|
||||||
"title": "StatusUpdate",
|
"title": "StatusUpdate",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
},
|
||||||
|
"compliance__schemas__legal_document__VersionCreate": {
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"title": "Content",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"created_by": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Created By"
|
||||||
|
},
|
||||||
|
"document_id": {
|
||||||
|
"title": "Document Id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"default": "de",
|
||||||
|
"title": "Language",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Summary"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"title": "Title",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"title": "Version",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"document_id",
|
||||||
|
"version",
|
||||||
|
"title",
|
||||||
|
"content"
|
||||||
|
],
|
||||||
|
"title": "VersionCreate",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"compliance__schemas__legal_document__VersionUpdate": {
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Content"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Language"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Summary"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Title"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Version"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "VersionUpdate",
|
||||||
|
"type": "object"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -23428,7 +23375,7 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/controls/paginated": {
|
"/api/compliance/controls/paginated": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "List controls with pagination and eager-loaded relationships.\n\nThis endpoint is optimized for large datasets with:\n- Eager loading to prevent N+1 queries\n- Server-side pagination\n- Full-text search support",
|
"description": "List controls with pagination.",
|
||||||
"operationId": "list_controls_paginated_api_compliance_controls_paginated_get",
|
"operationId": "list_controls_paginated_api_compliance_controls_paginated_get",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -23708,13 +23655,17 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/create-indexes": {
|
"/api/compliance/create-indexes": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Create additional performance indexes for large datasets.\n\nThese indexes are optimized for:\n- Pagination queries (1000+ requirements)\n- Full-text search\n- Filtering by status/priority",
|
"description": "Create additional performance indexes.",
|
||||||
"operationId": "create_performance_indexes_api_compliance_create_indexes_post",
|
"operationId": "create_performance_indexes_api_compliance_create_indexes_post",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Create Performance Indexes Api Compliance Create Indexes Post",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -23821,7 +23772,7 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/dsfa": {
|
"/api/compliance/dsfa": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Liste aller DSFAs f\u00fcr einen Tenant.",
|
"description": "Liste aller DSFAs fuer einen Tenant.",
|
||||||
"operationId": "list_dsfas_api_compliance_dsfa_get",
|
"operationId": "list_dsfas_api_compliance_dsfa_get",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -23900,7 +23851,14 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"title": "Response List Dsfas Api Compliance Dsfa Get",
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -23957,7 +23915,11 @@
|
|||||||
"201": {
|
"201": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Create Dsfa Api Compliance Dsfa Post",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -24029,7 +23991,14 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"title": "Response Get Audit Log Api Compliance Dsfa Audit Log Get",
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -24081,7 +24050,13 @@
|
|||||||
"501": {
|
"501": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": "Response Get By Assessment Api Compliance Dsfa By Assessment Assessment Id Get",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -24145,7 +24120,7 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/dsfa/from-assessment/{assessment_id}": {
|
"/api/compliance/dsfa/from-assessment/{assessment_id}": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Stub: Create DSFA from UCCA assessment. Requires cross-service communication.",
|
"description": "Stub: Create DSFA from UCCA assessment.",
|
||||||
"operationId": "create_from_assessment_api_compliance_dsfa_from_assessment__assessment_id__post",
|
"operationId": "create_from_assessment_api_compliance_dsfa_from_assessment__assessment_id__post",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -24172,7 +24147,13 @@
|
|||||||
"501": {
|
"501": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": "Response Create From Assessment Api Compliance Dsfa From Assessment Assessment Id Post",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -24187,7 +24168,7 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/dsfa/stats": {
|
"/api/compliance/dsfa/stats": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Z\u00e4hler nach Status und Risiko-Level.",
|
"description": "Zaehler nach Status und Risiko-Level.",
|
||||||
"operationId": "get_stats_api_compliance_dsfa_stats_get",
|
"operationId": "get_stats_api_compliance_dsfa_stats_get",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -24211,7 +24192,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Get Stats Api Compliance Dsfa Stats Get",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -24236,7 +24221,7 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/dsfa/{dsfa_id}": {
|
"/api/compliance/dsfa/{dsfa_id}": {
|
||||||
"delete": {
|
"delete": {
|
||||||
"description": "DSFA l\u00f6schen (Art. 17 DSGVO).",
|
"description": "DSFA loeschen (Art. 17 DSGVO).",
|
||||||
"operationId": "delete_dsfa_api_compliance_dsfa__dsfa_id__delete",
|
"operationId": "delete_dsfa_api_compliance_dsfa__dsfa_id__delete",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -24269,7 +24254,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Delete Dsfa Api Compliance Dsfa Dsfa Id Delete",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -24325,7 +24314,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Get Dsfa Api Compliance Dsfa Dsfa Id Get",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -24391,7 +24384,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Update Dsfa Api Compliance Dsfa Dsfa Id Put",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -24459,7 +24456,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Approve Dsfa Api Compliance Dsfa Dsfa Id Approve Post",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -24527,7 +24528,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Export Dsfa Json Api Compliance Dsfa Dsfa Id Export Get",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -24604,7 +24609,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Update Section Api Compliance Dsfa Dsfa Id Sections Section Number Put",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -24672,7 +24681,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Update Dsfa Status Api Compliance Dsfa Dsfa Id Status Patch",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -24697,7 +24710,7 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/dsfa/{dsfa_id}/submit-for-review": {
|
"/api/compliance/dsfa/{dsfa_id}/submit-for-review": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Submit a DSFA for DPO review (draft \u2192 in-review).",
|
"description": "Submit a DSFA for DPO review (draft -> in-review).",
|
||||||
"operationId": "submit_for_review_api_compliance_dsfa__dsfa_id__submit_for_review_post",
|
"operationId": "submit_for_review_api_compliance_dsfa__dsfa_id__submit_for_review_post",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -24730,7 +24743,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Submit For Review Api Compliance Dsfa Dsfa Id Submit For Review Post",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -24788,7 +24805,9 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"title": "Response List Dsfa Versions Api Compliance Dsfa Dsfa Id Versions Get"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -24855,7 +24874,9 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"title": "Response Get Dsfa Version Api Compliance Dsfa Dsfa Id Versions Version Number Get"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -30989,7 +31010,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Init Tables Api Compliance Init Tables Post",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -33186,7 +33211,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/audit-log": {
|
"/api/compliance/legal-documents/audit-log": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Consent audit trail (paginated).",
|
|
||||||
"operationId": "get_audit_log_api_compliance_legal_documents_audit_log_get",
|
"operationId": "get_audit_log_api_compliance_legal_documents_audit_log_get",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -33265,7 +33289,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Get Audit Log Api Compliance Legal Documents Audit Log Get",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -33290,7 +33318,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/consents": {
|
"/api/compliance/legal-documents/consents": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Record user consent for a legal document.",
|
|
||||||
"operationId": "record_consent_api_compliance_legal_documents_consents_post",
|
"operationId": "record_consent_api_compliance_legal_documents_consents_post",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -33324,7 +33351,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Record Consent Api Compliance Legal Documents Consents Post",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -33349,7 +33380,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/consents/check/{document_type}": {
|
"/api/compliance/legal-documents/consents/check/{document_type}": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Check if user has active consent for a document type.",
|
|
||||||
"operationId": "check_consent_api_compliance_legal_documents_consents_check__document_type__get",
|
"operationId": "check_consent_api_compliance_legal_documents_consents_check__document_type__get",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -33391,7 +33421,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Check Consent Api Compliance Legal Documents Consents Check Document Type Get",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -33416,7 +33450,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/consents/my": {
|
"/api/compliance/legal-documents/consents/my": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Get all consents for a specific user.",
|
|
||||||
"operationId": "get_my_consents_api_compliance_legal_documents_consents_my_get",
|
"operationId": "get_my_consents_api_compliance_legal_documents_consents_my_get",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -33449,7 +33482,14 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"title": "Response Get My Consents Api Compliance Legal Documents Consents My Get",
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -33474,7 +33514,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/consents/{consent_id}": {
|
"/api/compliance/legal-documents/consents/{consent_id}": {
|
||||||
"delete": {
|
"delete": {
|
||||||
"description": "Withdraw a consent (DSGVO Art. 7 Abs. 3).",
|
|
||||||
"operationId": "withdraw_consent_api_compliance_legal_documents_consents__consent_id__delete",
|
"operationId": "withdraw_consent_api_compliance_legal_documents_consents__consent_id__delete",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -33507,7 +33546,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Withdraw Consent Api Compliance Legal Documents Consents Consent Id Delete",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -33532,7 +33575,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/cookie-categories": {
|
"/api/compliance/legal-documents/cookie-categories": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "List all cookie categories.",
|
|
||||||
"operationId": "list_cookie_categories_api_compliance_legal_documents_cookie_categories_get",
|
"operationId": "list_cookie_categories_api_compliance_legal_documents_cookie_categories_get",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -33556,7 +33598,14 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"title": "Response List Cookie Categories Api Compliance Legal Documents Cookie Categories Get",
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -33579,7 +33628,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Create a cookie category.",
|
|
||||||
"operationId": "create_cookie_category_api_compliance_legal_documents_cookie_categories_post",
|
"operationId": "create_cookie_category_api_compliance_legal_documents_cookie_categories_post",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -33613,7 +33661,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Create Cookie Category Api Compliance Legal Documents Cookie Categories Post",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -33638,7 +33690,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/cookie-categories/{category_id}": {
|
"/api/compliance/legal-documents/cookie-categories/{category_id}": {
|
||||||
"delete": {
|
"delete": {
|
||||||
"description": "Delete a cookie category.",
|
|
||||||
"operationId": "delete_cookie_category_api_compliance_legal_documents_cookie_categories__category_id__delete",
|
"operationId": "delete_cookie_category_api_compliance_legal_documents_cookie_categories__category_id__delete",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -33689,7 +33740,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"put": {
|
"put": {
|
||||||
"description": "Update a cookie category.",
|
|
||||||
"operationId": "update_cookie_category_api_compliance_legal_documents_cookie_categories__category_id__put",
|
"operationId": "update_cookie_category_api_compliance_legal_documents_cookie_categories__category_id__put",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -33732,7 +33782,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Update Cookie Category Api Compliance Legal Documents Cookie Categories Category Id Put",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -33757,7 +33811,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/documents": {
|
"/api/compliance/legal-documents/documents": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "List all legal documents, optionally filtered by tenant or type.",
|
|
||||||
"operationId": "list_documents_api_compliance_legal_documents_documents_get",
|
"operationId": "list_documents_api_compliance_legal_documents_documents_get",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -33824,7 +33877,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Create a new legal document type.",
|
|
||||||
"operationId": "create_document_api_compliance_legal_documents_documents_post",
|
"operationId": "create_document_api_compliance_legal_documents_documents_post",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"content": {
|
"content": {
|
||||||
@@ -33867,7 +33919,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/documents/{document_id}": {
|
"/api/compliance/legal-documents/documents/{document_id}": {
|
||||||
"delete": {
|
"delete": {
|
||||||
"description": "Delete a legal document and all its versions.",
|
|
||||||
"operationId": "delete_document_api_compliance_legal_documents_documents__document_id__delete",
|
"operationId": "delete_document_api_compliance_legal_documents_documents__document_id__delete",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -33902,7 +33953,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Get a single legal document by ID.",
|
|
||||||
"operationId": "get_document_api_compliance_legal_documents_documents__document_id__get",
|
"operationId": "get_document_api_compliance_legal_documents_documents__document_id__get",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -33946,7 +33996,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/documents/{document_id}/versions": {
|
"/api/compliance/legal-documents/documents/{document_id}/versions": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "List all versions for a legal document.",
|
|
||||||
"operationId": "list_versions_api_compliance_legal_documents_documents__document_id__versions_get",
|
"operationId": "list_versions_api_compliance_legal_documents_documents__document_id__versions_get",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -33994,7 +34043,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/public": {
|
"/api/compliance/legal-documents/public": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Active documents for end-user display.",
|
|
||||||
"operationId": "list_public_documents_api_compliance_legal_documents_public_get",
|
"operationId": "list_public_documents_api_compliance_legal_documents_public_get",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -34018,7 +34066,14 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"title": "Response List Public Documents Api Compliance Legal Documents Public Get",
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -34043,7 +34098,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/public/{document_type}/latest": {
|
"/api/compliance/legal-documents/public/{document_type}/latest": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Get the latest published version of a document type.",
|
|
||||||
"operationId": "get_latest_published_api_compliance_legal_documents_public__document_type__latest_get",
|
"operationId": "get_latest_published_api_compliance_legal_documents_public__document_type__latest_get",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -34086,7 +34140,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Get Latest Published Api Compliance Legal Documents Public Document Type Latest Get",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -34111,7 +34169,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/stats/consents": {
|
"/api/compliance/legal-documents/stats/consents": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Consent statistics for dashboard.",
|
|
||||||
"operationId": "get_consent_stats_api_compliance_legal_documents_stats_consents_get",
|
"operationId": "get_consent_stats_api_compliance_legal_documents_stats_consents_get",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -34135,7 +34192,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Get Consent Stats Api Compliance Legal Documents Stats Consents Get",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -34160,13 +34221,12 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/versions": {
|
"/api/compliance/legal-documents/versions": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Create a new version for a legal document.",
|
|
||||||
"operationId": "create_version_api_compliance_legal_documents_versions_post",
|
"operationId": "create_version_api_compliance_legal_documents_versions_post",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/compliance__api__legal_document_routes__VersionCreate"
|
"$ref": "#/components/schemas/compliance__schemas__legal_document__VersionCreate"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -34203,7 +34263,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/versions/upload-word": {
|
"/api/compliance/legal-documents/versions/upload-word": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Convert DOCX to HTML using mammoth (if available) or return raw text.",
|
|
||||||
"operationId": "upload_word_api_compliance_legal_documents_versions_upload_word_post",
|
"operationId": "upload_word_api_compliance_legal_documents_versions_upload_word_post",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"content": {
|
"content": {
|
||||||
@@ -34248,7 +34307,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/versions/{version_id}": {
|
"/api/compliance/legal-documents/versions/{version_id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Get a single version by ID.",
|
|
||||||
"operationId": "get_version_api_compliance_legal_documents_versions__version_id__get",
|
"operationId": "get_version_api_compliance_legal_documents_versions__version_id__get",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -34290,7 +34348,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"put": {
|
"put": {
|
||||||
"description": "Update a draft legal document version.",
|
|
||||||
"operationId": "update_version_api_compliance_legal_documents_versions__version_id__put",
|
"operationId": "update_version_api_compliance_legal_documents_versions__version_id__put",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -34307,7 +34364,7 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/compliance__api__legal_document_routes__VersionUpdate"
|
"$ref": "#/components/schemas/compliance__schemas__legal_document__VersionUpdate"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -34344,7 +34401,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/versions/{version_id}/approval-history": {
|
"/api/compliance/legal-documents/versions/{version_id}/approval-history": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Get the full approval audit trail for a version.",
|
|
||||||
"operationId": "get_approval_history_api_compliance_legal_documents_versions__version_id__approval_history_get",
|
"operationId": "get_approval_history_api_compliance_legal_documents_versions__version_id__approval_history_get",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -34363,7 +34419,8 @@
|
|||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/ApprovalHistoryEntry"
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
},
|
},
|
||||||
"title": "Response Get Approval History Api Compliance Legal Documents Versions Version Id Approval History Get",
|
"title": "Response Get Approval History Api Compliance Legal Documents Versions Version Id Approval History Get",
|
||||||
"type": "array"
|
"type": "array"
|
||||||
@@ -34392,7 +34449,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/versions/{version_id}/approve": {
|
"/api/compliance/legal-documents/versions/{version_id}/approve": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Approve a version under review.",
|
|
||||||
"operationId": "approve_version_api_compliance_legal_documents_versions__version_id__approve_post",
|
"operationId": "approve_version_api_compliance_legal_documents_versions__version_id__approve_post",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -34446,7 +34502,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/versions/{version_id}/publish": {
|
"/api/compliance/legal-documents/versions/{version_id}/publish": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Publish an approved version.",
|
|
||||||
"operationId": "publish_version_api_compliance_legal_documents_versions__version_id__publish_post",
|
"operationId": "publish_version_api_compliance_legal_documents_versions__version_id__publish_post",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -34500,7 +34555,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/versions/{version_id}/reject": {
|
"/api/compliance/legal-documents/versions/{version_id}/reject": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Reject a version under review.",
|
|
||||||
"operationId": "reject_version_api_compliance_legal_documents_versions__version_id__reject_post",
|
"operationId": "reject_version_api_compliance_legal_documents_versions__version_id__reject_post",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -34554,7 +34608,6 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/legal-documents/versions/{version_id}/submit-review": {
|
"/api/compliance/legal-documents/versions/{version_id}/submit-review": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Submit a draft version for review.",
|
|
||||||
"operationId": "submit_review_api_compliance_legal_documents_versions__version_id__submit_review_post",
|
"operationId": "submit_review_api_compliance_legal_documents_versions__version_id__submit_review_post",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -39445,7 +39498,7 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/requirements": {
|
"/api/compliance/requirements": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "List requirements with pagination and eager-loaded relationships.\n\nThis endpoint is optimized for large datasets (1000+ requirements) with:\n- Eager loading to prevent N+1 queries\n- Server-side pagination\n- Full-text search support",
|
"description": "List requirements with pagination.",
|
||||||
"operationId": "list_requirements_paginated_api_compliance_requirements_get",
|
"operationId": "list_requirements_paginated_api_compliance_requirements_get",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -39635,7 +39688,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Delete Requirement Api Compliance Requirements Requirement Id Delete",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -39686,7 +39743,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Get Requirement Api Compliance Requirements Requirement Id Get",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -39737,7 +39798,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Update Requirement Api Compliance Requirements Requirement Id Put",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
@@ -40741,13 +40806,17 @@
|
|||||||
},
|
},
|
||||||
"/api/compliance/seed-risks": {
|
"/api/compliance/seed-risks": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Seed only risks (incremental update for existing databases).",
|
"description": "Seed only risks.",
|
||||||
"operationId": "seed_risks_only_api_compliance_seed_risks_post",
|
"operationId": "seed_risks_only_api_compliance_seed_risks_post",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {}
|
"schema": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Response Seed Risks Only Api Compliance Seed Risks Post",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Successful Response"
|
"description": "Successful Response"
|
||||||
|
|||||||
@@ -712,11 +712,11 @@ class TestDSFARouteCRUD:
|
|||||||
|
|
||||||
def test_create_invalid_status(self):
|
def test_create_invalid_status(self):
|
||||||
resp = client.post("/api/compliance/dsfa", json={"title": "Bad", "status": "invalid"})
|
resp = client.post("/api/compliance/dsfa", json={"title": "Bad", "status": "invalid"})
|
||||||
assert resp.status_code == 422
|
assert resp.status_code == 400 # ValidationError -> 400
|
||||||
|
|
||||||
def test_create_invalid_risk_level(self):
|
def test_create_invalid_risk_level(self):
|
||||||
resp = client.post("/api/compliance/dsfa", json={"title": "Bad", "risk_level": "extreme"})
|
resp = client.post("/api/compliance/dsfa", json={"title": "Bad", "risk_level": "extreme"})
|
||||||
assert resp.status_code == 422
|
assert resp.status_code == 400 # ValidationError -> 400
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -760,7 +760,7 @@ class TestDSFARouteStatusPatch:
|
|||||||
f"/api/compliance/dsfa/{created['id']}/status",
|
f"/api/compliance/dsfa/{created['id']}/status",
|
||||||
json={"status": "bogus"},
|
json={"status": "bogus"},
|
||||||
)
|
)
|
||||||
assert resp.status_code == 422
|
assert resp.status_code == 400 # ValidationError -> 400
|
||||||
|
|
||||||
def test_patch_status_not_found(self):
|
def test_patch_status_not_found(self):
|
||||||
resp = client.patch(
|
resp = client.patch(
|
||||||
@@ -810,7 +810,7 @@ class TestDSFARouteSectionUpdate:
|
|||||||
f"/api/compliance/dsfa/{created['id']}/sections/9",
|
f"/api/compliance/dsfa/{created['id']}/sections/9",
|
||||||
json={"content": "X"},
|
json={"content": "X"},
|
||||||
)
|
)
|
||||||
assert resp.status_code == 422
|
assert resp.status_code == 400 # ValidationError -> 400
|
||||||
|
|
||||||
def test_update_section_not_found(self):
|
def test_update_section_not_found(self):
|
||||||
resp = client.put(
|
resp = client.put(
|
||||||
@@ -839,7 +839,7 @@ class TestDSFARouteWorkflow:
|
|||||||
client.post(f"/api/compliance/dsfa/{created['id']}/submit-for-review")
|
client.post(f"/api/compliance/dsfa/{created['id']}/submit-for-review")
|
||||||
# Try to submit again (already in-review)
|
# Try to submit again (already in-review)
|
||||||
resp = client.post(f"/api/compliance/dsfa/{created['id']}/submit-for-review")
|
resp = client.post(f"/api/compliance/dsfa/{created['id']}/submit-for-review")
|
||||||
assert resp.status_code == 422
|
assert resp.status_code == 400 # ValidationError -> 400
|
||||||
|
|
||||||
def test_submit_not_found(self):
|
def test_submit_not_found(self):
|
||||||
resp = client.post(f"/api/compliance/dsfa/{uuid.uuid4()}/submit-for-review")
|
resp = client.post(f"/api/compliance/dsfa/{uuid.uuid4()}/submit-for-review")
|
||||||
@@ -871,7 +871,7 @@ class TestDSFARouteWorkflow:
|
|||||||
f"/api/compliance/dsfa/{created['id']}/approve",
|
f"/api/compliance/dsfa/{created['id']}/approve",
|
||||||
json={"approved": True},
|
json={"approved": True},
|
||||||
)
|
)
|
||||||
assert resp.status_code == 422
|
assert resp.status_code == 400 # ValidationError -> 400
|
||||||
|
|
||||||
def test_approve_not_found(self):
|
def test_approve_not_found(self):
|
||||||
resp = client.post(
|
resp = client.post(
|
||||||
|
|||||||
Reference in New Issue
Block a user