A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
328 lines
9.0 KiB
Go
328 lines
9.0 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// Checkpoint represents a checkpoint definition
|
|
type Checkpoint struct {
|
|
ID string `json:"id"`
|
|
Step string `json:"step"`
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
BlocksProgress bool `json:"blocksProgress"`
|
|
RequiresReview string `json:"requiresReview"`
|
|
AutoValidate bool `json:"autoValidate"`
|
|
Description string `json:"description"`
|
|
}
|
|
|
|
// CheckpointHandler handles checkpoint-related requests
|
|
type CheckpointHandler struct {
|
|
checkpoints map[string]Checkpoint
|
|
}
|
|
|
|
// NewCheckpointHandler creates a new checkpoint handler
|
|
func NewCheckpointHandler() *CheckpointHandler {
|
|
return &CheckpointHandler{
|
|
checkpoints: initCheckpoints(),
|
|
}
|
|
}
|
|
|
|
func initCheckpoints() map[string]Checkpoint {
|
|
return map[string]Checkpoint{
|
|
"CP-UC": {
|
|
ID: "CP-UC",
|
|
Step: "use-case-workshop",
|
|
Name: "Use Case Erfassung",
|
|
Type: "REQUIRED",
|
|
BlocksProgress: true,
|
|
RequiresReview: "NONE",
|
|
AutoValidate: true,
|
|
Description: "Mindestens ein Use Case muss erfasst sein",
|
|
},
|
|
"CP-SCAN": {
|
|
ID: "CP-SCAN",
|
|
Step: "screening",
|
|
Name: "System Screening",
|
|
Type: "REQUIRED",
|
|
BlocksProgress: true,
|
|
RequiresReview: "NONE",
|
|
AutoValidate: true,
|
|
Description: "SBOM und Security Scan müssen abgeschlossen sein",
|
|
},
|
|
"CP-MOD": {
|
|
ID: "CP-MOD",
|
|
Step: "modules",
|
|
Name: "Modul-Zuweisung",
|
|
Type: "REQUIRED",
|
|
BlocksProgress: true,
|
|
RequiresReview: "NONE",
|
|
AutoValidate: true,
|
|
Description: "Mindestens ein Compliance-Modul muss zugewiesen sein",
|
|
},
|
|
"CP-REQ": {
|
|
ID: "CP-REQ",
|
|
Step: "requirements",
|
|
Name: "Anforderungen",
|
|
Type: "REQUIRED",
|
|
BlocksProgress: true,
|
|
RequiresReview: "NONE",
|
|
AutoValidate: true,
|
|
Description: "Anforderungen müssen aus Regulierungen abgeleitet sein",
|
|
},
|
|
"CP-CTRL": {
|
|
ID: "CP-CTRL",
|
|
Step: "controls",
|
|
Name: "Controls",
|
|
Type: "REQUIRED",
|
|
BlocksProgress: true,
|
|
RequiresReview: "NONE",
|
|
AutoValidate: true,
|
|
Description: "Controls müssen den Anforderungen zugeordnet sein",
|
|
},
|
|
"CP-EVI": {
|
|
ID: "CP-EVI",
|
|
Step: "evidence",
|
|
Name: "Nachweise",
|
|
Type: "REQUIRED",
|
|
BlocksProgress: true,
|
|
RequiresReview: "NONE",
|
|
AutoValidate: true,
|
|
Description: "Nachweise für Controls müssen dokumentiert sein",
|
|
},
|
|
"CP-CHK": {
|
|
ID: "CP-CHK",
|
|
Step: "audit-checklist",
|
|
Name: "Audit Checklist",
|
|
Type: "REQUIRED",
|
|
BlocksProgress: true,
|
|
RequiresReview: "NONE",
|
|
AutoValidate: true,
|
|
Description: "Prüfliste muss generiert und überprüft sein",
|
|
},
|
|
"CP-RISK": {
|
|
ID: "CP-RISK",
|
|
Step: "risks",
|
|
Name: "Risikobewertung",
|
|
Type: "REQUIRED",
|
|
BlocksProgress: true,
|
|
RequiresReview: "NONE",
|
|
AutoValidate: true,
|
|
Description: "Kritische Risiken müssen Mitigationsmaßnahmen haben",
|
|
},
|
|
"CP-AI": {
|
|
ID: "CP-AI",
|
|
Step: "ai-act",
|
|
Name: "AI Act Klassifizierung",
|
|
Type: "REQUIRED",
|
|
BlocksProgress: true,
|
|
RequiresReview: "LEGAL",
|
|
AutoValidate: false,
|
|
Description: "KI-System muss klassifiziert sein",
|
|
},
|
|
"CP-OBL": {
|
|
ID: "CP-OBL",
|
|
Step: "obligations",
|
|
Name: "Pflichtenübersicht",
|
|
Type: "REQUIRED",
|
|
BlocksProgress: true,
|
|
RequiresReview: "NONE",
|
|
AutoValidate: true,
|
|
Description: "Rechtliche Pflichten müssen identifiziert sein",
|
|
},
|
|
"CP-DSFA": {
|
|
ID: "CP-DSFA",
|
|
Step: "dsfa",
|
|
Name: "DSFA",
|
|
Type: "RECOMMENDED",
|
|
BlocksProgress: false,
|
|
RequiresReview: "DSB",
|
|
AutoValidate: false,
|
|
Description: "Datenschutz-Folgenabschätzung muss erstellt und genehmigt sein",
|
|
},
|
|
"CP-TOM": {
|
|
ID: "CP-TOM",
|
|
Step: "tom",
|
|
Name: "TOMs",
|
|
Type: "REQUIRED",
|
|
BlocksProgress: true,
|
|
RequiresReview: "NONE",
|
|
AutoValidate: true,
|
|
Description: "Technische und organisatorische Maßnahmen müssen definiert sein",
|
|
},
|
|
"CP-VVT": {
|
|
ID: "CP-VVT",
|
|
Step: "vvt",
|
|
Name: "Verarbeitungsverzeichnis",
|
|
Type: "REQUIRED",
|
|
BlocksProgress: true,
|
|
RequiresReview: "DSB",
|
|
AutoValidate: false,
|
|
Description: "Verarbeitungsverzeichnis muss vollständig sein",
|
|
},
|
|
}
|
|
}
|
|
|
|
// GetAll returns all checkpoint definitions
|
|
func (h *CheckpointHandler) GetAll(c *gin.Context) {
|
|
tenantID := c.Query("tenantId")
|
|
|
|
checkpointList := make([]Checkpoint, 0, len(h.checkpoints))
|
|
for _, cp := range h.checkpoints {
|
|
checkpointList = append(checkpointList, cp)
|
|
}
|
|
|
|
SuccessResponse(c, gin.H{
|
|
"tenantId": tenantID,
|
|
"checkpoints": checkpointList,
|
|
})
|
|
}
|
|
|
|
// Validate validates a specific checkpoint
|
|
func (h *CheckpointHandler) Validate(c *gin.Context) {
|
|
var req struct {
|
|
TenantID string `json:"tenantId" binding:"required"`
|
|
CheckpointID string `json:"checkpointId" binding:"required"`
|
|
Data map[string]interface{} `json:"data"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
ErrorResponse(c, http.StatusBadRequest, err.Error(), "INVALID_REQUEST")
|
|
return
|
|
}
|
|
|
|
checkpoint, ok := h.checkpoints[req.CheckpointID]
|
|
if !ok {
|
|
ErrorResponse(c, http.StatusNotFound, "Checkpoint not found", "CHECKPOINT_NOT_FOUND")
|
|
return
|
|
}
|
|
|
|
// Perform validation based on checkpoint ID
|
|
result := h.validateCheckpoint(checkpoint, req.Data)
|
|
|
|
SuccessResponse(c, result)
|
|
}
|
|
|
|
func (h *CheckpointHandler) validateCheckpoint(checkpoint Checkpoint, data map[string]interface{}) CheckpointResult {
|
|
result := CheckpointResult{
|
|
CheckpointID: checkpoint.ID,
|
|
Passed: true,
|
|
ValidatedAt: now(),
|
|
ValidatedBy: "SYSTEM",
|
|
Errors: []ValidationError{},
|
|
Warnings: []ValidationError{},
|
|
}
|
|
|
|
// Validation logic based on checkpoint
|
|
switch checkpoint.ID {
|
|
case "CP-UC":
|
|
useCases, _ := data["useCases"].([]interface{})
|
|
if len(useCases) == 0 {
|
|
result.Passed = false
|
|
result.Errors = append(result.Errors, ValidationError{
|
|
RuleID: "uc-min-count",
|
|
Field: "useCases",
|
|
Message: "Mindestens ein Use Case muss erstellt werden",
|
|
Severity: "ERROR",
|
|
})
|
|
}
|
|
|
|
case "CP-SCAN":
|
|
screening, _ := data["screening"].(map[string]interface{})
|
|
if screening == nil || screening["status"] != "COMPLETED" {
|
|
result.Passed = false
|
|
result.Errors = append(result.Errors, ValidationError{
|
|
RuleID: "scan-complete",
|
|
Field: "screening",
|
|
Message: "Security Scan muss abgeschlossen sein",
|
|
Severity: "ERROR",
|
|
})
|
|
}
|
|
|
|
case "CP-MOD":
|
|
modules, _ := data["modules"].([]interface{})
|
|
if len(modules) == 0 {
|
|
result.Passed = false
|
|
result.Errors = append(result.Errors, ValidationError{
|
|
RuleID: "mod-min-count",
|
|
Field: "modules",
|
|
Message: "Mindestens ein Modul muss zugewiesen werden",
|
|
Severity: "ERROR",
|
|
})
|
|
}
|
|
|
|
case "CP-RISK":
|
|
risks, _ := data["risks"].([]interface{})
|
|
criticalUnmitigated := 0
|
|
for _, r := range risks {
|
|
risk, ok := r.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
severity, _ := risk["severity"].(string)
|
|
if severity == "CRITICAL" || severity == "HIGH" {
|
|
mitigations, _ := risk["mitigation"].([]interface{})
|
|
if len(mitigations) == 0 {
|
|
criticalUnmitigated++
|
|
}
|
|
}
|
|
}
|
|
if criticalUnmitigated > 0 {
|
|
result.Passed = false
|
|
result.Errors = append(result.Errors, ValidationError{
|
|
RuleID: "critical-risks-mitigated",
|
|
Field: "risks",
|
|
Message: "Kritische Risiken ohne Mitigationsmaßnahmen gefunden",
|
|
Severity: "ERROR",
|
|
})
|
|
}
|
|
|
|
case "CP-DSFA":
|
|
dsfa, _ := data["dsfa"].(map[string]interface{})
|
|
if dsfa == nil {
|
|
result.Passed = false
|
|
result.Errors = append(result.Errors, ValidationError{
|
|
RuleID: "dsfa-exists",
|
|
Field: "dsfa",
|
|
Message: "DSFA muss erstellt werden",
|
|
Severity: "ERROR",
|
|
})
|
|
} else if dsfa["status"] != "APPROVED" {
|
|
result.Warnings = append(result.Warnings, ValidationError{
|
|
RuleID: "dsfa-approved",
|
|
Field: "dsfa",
|
|
Message: "DSFA sollte vom DSB genehmigt werden",
|
|
Severity: "WARNING",
|
|
})
|
|
}
|
|
|
|
case "CP-TOM":
|
|
toms, _ := data["toms"].([]interface{})
|
|
if len(toms) == 0 {
|
|
result.Passed = false
|
|
result.Errors = append(result.Errors, ValidationError{
|
|
RuleID: "tom-min-count",
|
|
Field: "toms",
|
|
Message: "Mindestens eine TOM muss definiert werden",
|
|
Severity: "ERROR",
|
|
})
|
|
}
|
|
|
|
case "CP-VVT":
|
|
vvt, _ := data["vvt"].([]interface{})
|
|
if len(vvt) == 0 {
|
|
result.Passed = false
|
|
result.Errors = append(result.Errors, ValidationError{
|
|
RuleID: "vvt-min-count",
|
|
Field: "vvt",
|
|
Message: "Mindestens eine Verarbeitungstätigkeit muss dokumentiert werden",
|
|
Severity: "ERROR",
|
|
})
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|