fix: Vorbereitung-Module auf 100% — Feld-Fixes, Backend-Persistenz, Endpoints
- ScopeExportTab: 11 Feldnamen-Mismatches gegen ScopeDecision Interface korrigiert (level→determinedLevel, riskScore→risk_score, hardTriggers→triggeredHardTriggers, depthDescription→depth, effortEstimate→estimatedEffort, isMandatory→required, triggeredByHardTrigger→triggeredBy, effortDays→estimatedEffort) - Company Profile: GET vom Backend beim Mount, snake_case→camelCase, SDK State Fallback - Modules: Aktivierung/Deaktivierung ans Backend schreiben (activate/deactivate Endpoints) - Obligations: Explizites Fehler-Banner statt stiller Fallback bei Backend-Fehler - Source Policy: BlockedContentDB Model + GET /api/v1/admin/blocked-content Endpoint - Import: Offline-Modus Label fuer Backend-Fallback Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -198,6 +198,42 @@ async def seed_modules(
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/modules/{module_id}/activate")
|
||||
async def activate_module(module_id: str, db: Session = Depends(get_db)):
|
||||
"""Activate a service module."""
|
||||
from ..db.repository import ServiceModuleRepository
|
||||
|
||||
repo = ServiceModuleRepository(db)
|
||||
module = repo.get_by_id(module_id)
|
||||
if not module:
|
||||
module = repo.get_by_name(module_id)
|
||||
if not module:
|
||||
raise HTTPException(status_code=404, detail=f"Module {module_id} not found")
|
||||
|
||||
module.is_active = True
|
||||
db.commit()
|
||||
|
||||
return {"status": "activated", "id": module.id, "name": module.name}
|
||||
|
||||
|
||||
@router.post("/modules/{module_id}/deactivate")
|
||||
async def deactivate_module(module_id: str, db: Session = Depends(get_db)):
|
||||
"""Deactivate a service module."""
|
||||
from ..db.repository import ServiceModuleRepository
|
||||
|
||||
repo = ServiceModuleRepository(db)
|
||||
module = repo.get_by_id(module_id)
|
||||
if not module:
|
||||
module = repo.get_by_name(module_id)
|
||||
if not module:
|
||||
raise HTTPException(status_code=404, detail=f"Module {module_id} not found")
|
||||
|
||||
module.is_active = False
|
||||
db.commit()
|
||||
|
||||
return {"status": "deactivated", "id": module.id, "name": module.name}
|
||||
|
||||
|
||||
@router.post("/modules/{module_id}/regulations", response_model=ModuleRegulationMappingResponse)
|
||||
async def add_module_regulation(
|
||||
module_id: str,
|
||||
|
||||
@@ -32,6 +32,7 @@ from sqlalchemy.orm import Session
|
||||
from database import get_db
|
||||
from compliance.db.source_policy_models import (
|
||||
AllowedSourceDB,
|
||||
BlockedContentDB,
|
||||
SourceOperationDB,
|
||||
PIIRuleDB,
|
||||
SourcePolicyAuditDB,
|
||||
@@ -398,6 +399,43 @@ async def delete_pii_rule(rule_id: str, db: Session = Depends(get_db)):
|
||||
return {"status": "deleted", "id": rule_id}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Blocked Content
|
||||
# =============================================================================
|
||||
|
||||
@router.get("/blocked-content")
|
||||
async def list_blocked_content(
|
||||
limit: int = Query(50, ge=1, le=500),
|
||||
offset: int = Query(0, ge=0),
|
||||
domain: Optional[str] = None,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""List blocked content entries."""
|
||||
query = db.query(BlockedContentDB)
|
||||
|
||||
if domain:
|
||||
query = query.filter(BlockedContentDB.domain == domain)
|
||||
|
||||
total = query.count()
|
||||
entries = query.order_by(BlockedContentDB.created_at.desc()).offset(offset).limit(limit).all()
|
||||
|
||||
return {
|
||||
"blocked": [
|
||||
{
|
||||
"id": str(e.id),
|
||||
"url": e.url,
|
||||
"domain": e.domain,
|
||||
"block_reason": e.block_reason,
|
||||
"rule_id": str(e.rule_id) if e.rule_id else None,
|
||||
"details": e.details,
|
||||
"created_at": e.created_at.isoformat() if e.created_at else None,
|
||||
}
|
||||
for e in entries
|
||||
],
|
||||
"total": total,
|
||||
}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Audit Trail
|
||||
# =============================================================================
|
||||
|
||||
@@ -85,6 +85,28 @@ class PIIRuleDB(Base):
|
||||
)
|
||||
|
||||
|
||||
class BlockedContentDB(Base):
|
||||
"""Blocked content entries tracked by source policy enforcement."""
|
||||
|
||||
__tablename__ = 'compliance_blocked_content'
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
url = Column(Text, nullable=True)
|
||||
domain = Column(String(255), nullable=False)
|
||||
block_reason = Column(String(100), nullable=False) # unlicensed, pii, blacklisted, etc.
|
||||
rule_id = Column(UUID(as_uuid=True), nullable=True) # PII rule or source that triggered block
|
||||
details = Column(JSON, nullable=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
|
||||
__table_args__ = (
|
||||
Index('idx_blocked_content_domain', 'domain'),
|
||||
Index('idx_blocked_content_created', 'created_at'),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<BlockedContent {self.domain}: {self.block_reason}>"
|
||||
|
||||
|
||||
class SourcePolicyAuditDB(Base):
|
||||
"""Audit trail for source policy changes."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user