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>
639 lines
19 KiB
Go
639 lines
19 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/breakpilot/ai-compliance-sdk/internal/funding"
|
|
"github.com/breakpilot/ai-compliance-sdk/internal/llm"
|
|
"github.com/breakpilot/ai-compliance-sdk/internal/rbac"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// FundingHandlers handles funding application API endpoints
|
|
type FundingHandlers struct {
|
|
store funding.Store
|
|
providerRegistry *llm.ProviderRegistry
|
|
wizardSchema *WizardSchema
|
|
bundeslandProfiles map[string]*BundeslandProfile
|
|
}
|
|
|
|
// WizardSchema represents the loaded wizard schema
|
|
type WizardSchema struct {
|
|
Metadata struct {
|
|
Version string `yaml:"version"`
|
|
Name string `yaml:"name"`
|
|
Description string `yaml:"description"`
|
|
TotalSteps int `yaml:"total_steps"`
|
|
} `yaml:"metadata"`
|
|
Steps []WizardStep `yaml:"steps"`
|
|
FundingAssistant struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
Model string `yaml:"model"`
|
|
SystemPrompt string `yaml:"system_prompt"`
|
|
StepContexts map[int]string `yaml:"step_contexts"`
|
|
QuickPrompts []QuickPrompt `yaml:"quick_prompts"`
|
|
} `yaml:"funding_assistant"`
|
|
Presets map[string]Preset `yaml:"presets"`
|
|
}
|
|
|
|
// WizardStep represents a step in the wizard
|
|
type WizardStep struct {
|
|
Number int `yaml:"number" json:"number"`
|
|
ID string `yaml:"id" json:"id"`
|
|
Title string `yaml:"title" json:"title"`
|
|
Subtitle string `yaml:"subtitle" json:"subtitle"`
|
|
Description string `yaml:"description" json:"description"`
|
|
Icon string `yaml:"icon" json:"icon"`
|
|
IsRequired bool `yaml:"is_required" json:"is_required"`
|
|
Fields []WizardField `yaml:"fields" json:"fields"`
|
|
AssistantContext string `yaml:"assistant_context" json:"assistant_context"`
|
|
}
|
|
|
|
// WizardField represents a field in the wizard
|
|
type WizardField struct {
|
|
ID string `yaml:"id" json:"id"`
|
|
Type string `yaml:"type" json:"type"`
|
|
Label string `yaml:"label" json:"label"`
|
|
Placeholder string `yaml:"placeholder,omitempty" json:"placeholder,omitempty"`
|
|
Required bool `yaml:"required,omitempty" json:"required,omitempty"`
|
|
Options []FieldOption `yaml:"options,omitempty" json:"options,omitempty"`
|
|
HelpText string `yaml:"help_text,omitempty" json:"help_text,omitempty"`
|
|
MaxLength int `yaml:"max_length,omitempty" json:"max_length,omitempty"`
|
|
Min *int `yaml:"min,omitempty" json:"min,omitempty"`
|
|
Max *int `yaml:"max,omitempty" json:"max,omitempty"`
|
|
Default interface{} `yaml:"default,omitempty" json:"default,omitempty"`
|
|
Conditional string `yaml:"conditional,omitempty" json:"conditional,omitempty"`
|
|
}
|
|
|
|
// FieldOption represents an option for select fields
|
|
type FieldOption struct {
|
|
Value string `yaml:"value" json:"value"`
|
|
Label string `yaml:"label" json:"label"`
|
|
Description string `yaml:"description,omitempty" json:"description,omitempty"`
|
|
}
|
|
|
|
// QuickPrompt represents a quick prompt for the assistant
|
|
type QuickPrompt struct {
|
|
Label string `yaml:"label" json:"label"`
|
|
Prompt string `yaml:"prompt" json:"prompt"`
|
|
}
|
|
|
|
// Preset represents a BreakPilot preset
|
|
type Preset struct {
|
|
ID string `yaml:"id" json:"id"`
|
|
Name string `yaml:"name" json:"name"`
|
|
Description string `yaml:"description" json:"description"`
|
|
BudgetItems []funding.BudgetItem `yaml:"budget_items" json:"budget_items"`
|
|
AutoFill map[string]interface{} `yaml:"auto_fill" json:"auto_fill"`
|
|
}
|
|
|
|
// BundeslandProfile represents a federal state profile
|
|
type BundeslandProfile struct {
|
|
Name string `yaml:"name" json:"name"`
|
|
Short string `yaml:"short" json:"short"`
|
|
FundingPrograms []string `yaml:"funding_programs" json:"funding_programs"`
|
|
DefaultFundingRate float64 `yaml:"default_funding_rate" json:"default_funding_rate"`
|
|
RequiresMEP bool `yaml:"requires_mep" json:"requires_mep"`
|
|
ContactAuthority ContactAuthority `yaml:"contact_authority" json:"contact_authority"`
|
|
SpecialRequirements []string `yaml:"special_requirements" json:"special_requirements"`
|
|
}
|
|
|
|
// ContactAuthority represents a contact authority
|
|
type ContactAuthority struct {
|
|
Name string `yaml:"name" json:"name"`
|
|
Department string `yaml:"department,omitempty" json:"department,omitempty"`
|
|
Website string `yaml:"website" json:"website"`
|
|
Email string `yaml:"email,omitempty" json:"email,omitempty"`
|
|
}
|
|
|
|
// NewFundingHandlers creates new funding handlers
|
|
func NewFundingHandlers(store funding.Store, providerRegistry *llm.ProviderRegistry) *FundingHandlers {
|
|
h := &FundingHandlers{
|
|
store: store,
|
|
providerRegistry: providerRegistry,
|
|
}
|
|
|
|
// Load wizard schema
|
|
if err := h.loadWizardSchema(); err != nil {
|
|
fmt.Printf("Warning: Could not load wizard schema: %v\n", err)
|
|
}
|
|
|
|
// Load bundesland profiles
|
|
if err := h.loadBundeslandProfiles(); err != nil {
|
|
fmt.Printf("Warning: Could not load bundesland profiles: %v\n", err)
|
|
}
|
|
|
|
return h
|
|
}
|
|
|
|
func (h *FundingHandlers) loadWizardSchema() error {
|
|
data, err := os.ReadFile("policies/funding/foerderantrag_wizard_v1.yaml")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
h.wizardSchema = &WizardSchema{}
|
|
return yaml.Unmarshal(data, h.wizardSchema)
|
|
}
|
|
|
|
func (h *FundingHandlers) loadBundeslandProfiles() error {
|
|
data, err := os.ReadFile("policies/funding/bundesland_profiles.yaml")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var profiles struct {
|
|
Bundeslaender map[string]*BundeslandProfile `yaml:"bundeslaender"`
|
|
}
|
|
if err := yaml.Unmarshal(data, &profiles); err != nil {
|
|
return err
|
|
}
|
|
|
|
h.bundeslandProfiles = profiles.Bundeslaender
|
|
return nil
|
|
}
|
|
|
|
// ============================================================================
|
|
// Application CRUD
|
|
// ============================================================================
|
|
|
|
// CreateApplication creates a new funding application
|
|
// POST /sdk/v1/funding/applications
|
|
func (h *FundingHandlers) CreateApplication(c *gin.Context) {
|
|
tenantID := rbac.GetTenantID(c)
|
|
userID := rbac.GetUserID(c)
|
|
if tenantID == uuid.Nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "tenant ID required"})
|
|
return
|
|
}
|
|
|
|
var req funding.CreateApplicationRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
app := &funding.FundingApplication{
|
|
TenantID: tenantID,
|
|
Title: req.Title,
|
|
FundingProgram: req.FundingProgram,
|
|
Status: funding.ApplicationStatusDraft,
|
|
CurrentStep: 1,
|
|
TotalSteps: 8,
|
|
WizardData: make(map[string]interface{}),
|
|
CreatedBy: userID,
|
|
UpdatedBy: userID,
|
|
}
|
|
|
|
// Initialize school profile with federal state
|
|
app.SchoolProfile = &funding.SchoolProfile{
|
|
FederalState: req.FederalState,
|
|
}
|
|
|
|
// Apply preset if specified
|
|
if req.PresetID != "" && h.wizardSchema != nil {
|
|
if preset, ok := h.wizardSchema.Presets[req.PresetID]; ok {
|
|
app.Budget = &funding.Budget{
|
|
BudgetItems: preset.BudgetItems,
|
|
}
|
|
app.WizardData["preset_id"] = req.PresetID
|
|
app.WizardData["preset_applied"] = true
|
|
for k, v := range preset.AutoFill {
|
|
app.WizardData[k] = v
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := h.store.CreateApplication(c.Request.Context(), app); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Add history entry
|
|
h.store.AddHistoryEntry(c.Request.Context(), &funding.ApplicationHistoryEntry{
|
|
ApplicationID: app.ID,
|
|
Action: "created",
|
|
PerformedBy: userID,
|
|
Notes: "Antrag erstellt",
|
|
})
|
|
|
|
c.JSON(http.StatusCreated, app)
|
|
}
|
|
|
|
// GetApplication retrieves a funding application
|
|
// GET /sdk/v1/funding/applications/:id
|
|
func (h *FundingHandlers) GetApplication(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid application ID"})
|
|
return
|
|
}
|
|
|
|
app, err := h.store.GetApplication(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, app)
|
|
}
|
|
|
|
// ListApplications returns a list of funding applications
|
|
// GET /sdk/v1/funding/applications
|
|
func (h *FundingHandlers) ListApplications(c *gin.Context) {
|
|
tenantID := rbac.GetTenantID(c)
|
|
if tenantID == uuid.Nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "tenant ID required"})
|
|
return
|
|
}
|
|
|
|
filter := funding.ApplicationFilter{
|
|
Page: 1,
|
|
PageSize: 20,
|
|
}
|
|
|
|
// Parse query parameters
|
|
if status := c.Query("status"); status != "" {
|
|
s := funding.ApplicationStatus(status)
|
|
filter.Status = &s
|
|
}
|
|
if program := c.Query("program"); program != "" {
|
|
p := funding.FundingProgram(program)
|
|
filter.FundingProgram = &p
|
|
}
|
|
|
|
result, err := h.store.ListApplications(c.Request.Context(), tenantID, filter)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// UpdateApplication updates a funding application
|
|
// PUT /sdk/v1/funding/applications/:id
|
|
func (h *FundingHandlers) UpdateApplication(c *gin.Context) {
|
|
userID := rbac.GetUserID(c)
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid application ID"})
|
|
return
|
|
}
|
|
|
|
app, err := h.store.GetApplication(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
var req funding.UpdateApplicationRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if req.Title != nil {
|
|
app.Title = *req.Title
|
|
}
|
|
if req.WizardData != nil {
|
|
for k, v := range req.WizardData {
|
|
app.WizardData[k] = v
|
|
}
|
|
}
|
|
if req.CurrentStep != nil {
|
|
app.CurrentStep = *req.CurrentStep
|
|
}
|
|
app.UpdatedBy = userID
|
|
|
|
if err := h.store.UpdateApplication(c.Request.Context(), app); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, app)
|
|
}
|
|
|
|
// DeleteApplication deletes a funding application
|
|
// DELETE /sdk/v1/funding/applications/:id
|
|
func (h *FundingHandlers) DeleteApplication(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid application ID"})
|
|
return
|
|
}
|
|
|
|
if err := h.store.DeleteApplication(c.Request.Context(), id); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "application archived"})
|
|
}
|
|
|
|
// ============================================================================
|
|
// Wizard Endpoints
|
|
// ============================================================================
|
|
|
|
// GetWizardSchema returns the wizard schema
|
|
// GET /sdk/v1/funding/wizard/schema
|
|
func (h *FundingHandlers) GetWizardSchema(c *gin.Context) {
|
|
if h.wizardSchema == nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "wizard schema not loaded"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"metadata": h.wizardSchema.Metadata,
|
|
"steps": h.wizardSchema.Steps,
|
|
"presets": h.wizardSchema.Presets,
|
|
"assistant": gin.H{
|
|
"enabled": h.wizardSchema.FundingAssistant.Enabled,
|
|
"quick_prompts": h.wizardSchema.FundingAssistant.QuickPrompts,
|
|
},
|
|
})
|
|
}
|
|
|
|
// SaveWizardStep saves wizard step data
|
|
// POST /sdk/v1/funding/applications/:id/wizard
|
|
func (h *FundingHandlers) SaveWizardStep(c *gin.Context) {
|
|
userID := rbac.GetUserID(c)
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid application ID"})
|
|
return
|
|
}
|
|
|
|
var req funding.SaveWizardStepRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Save step data
|
|
if err := h.store.SaveWizardStep(c.Request.Context(), id, req.Step, req.Data); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Get updated progress
|
|
progress, err := h.store.GetWizardProgress(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Add history entry
|
|
h.store.AddHistoryEntry(c.Request.Context(), &funding.ApplicationHistoryEntry{
|
|
ApplicationID: id,
|
|
Action: "wizard_step_saved",
|
|
PerformedBy: userID,
|
|
Notes: fmt.Sprintf("Schritt %d gespeichert", req.Step),
|
|
})
|
|
|
|
c.JSON(http.StatusOK, progress)
|
|
}
|
|
|
|
// AskAssistant handles LLM assistant queries
|
|
// POST /sdk/v1/funding/wizard/ask
|
|
func (h *FundingHandlers) AskAssistant(c *gin.Context) {
|
|
var req funding.AssistantRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if h.wizardSchema == nil || !h.wizardSchema.FundingAssistant.Enabled {
|
|
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "assistant not available"})
|
|
return
|
|
}
|
|
|
|
// Build system prompt with step context
|
|
systemPrompt := h.wizardSchema.FundingAssistant.SystemPrompt
|
|
if stepContext, ok := h.wizardSchema.FundingAssistant.StepContexts[req.CurrentStep]; ok {
|
|
systemPrompt += "\n\nKontext fuer diesen Schritt:\n" + stepContext
|
|
}
|
|
|
|
// Build messages
|
|
messages := []llm.Message{
|
|
{Role: "system", Content: systemPrompt},
|
|
}
|
|
for _, msg := range req.History {
|
|
messages = append(messages, llm.Message{
|
|
Role: msg.Role,
|
|
Content: msg.Content,
|
|
})
|
|
}
|
|
messages = append(messages, llm.Message{
|
|
Role: "user",
|
|
Content: req.Question,
|
|
})
|
|
|
|
// Generate response using registry
|
|
chatReq := &llm.ChatRequest{
|
|
Messages: messages,
|
|
Temperature: 0.3,
|
|
MaxTokens: 1000,
|
|
}
|
|
|
|
response, err := h.providerRegistry.Chat(c.Request.Context(), chatReq)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, funding.AssistantResponse{
|
|
Answer: response.Message.Content,
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// Status Endpoints
|
|
// ============================================================================
|
|
|
|
// SubmitApplication submits an application for review
|
|
// POST /sdk/v1/funding/applications/:id/submit
|
|
func (h *FundingHandlers) SubmitApplication(c *gin.Context) {
|
|
userID := rbac.GetUserID(c)
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid application ID"})
|
|
return
|
|
}
|
|
|
|
app, err := h.store.GetApplication(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Validate that all required steps are completed
|
|
progress, _ := h.store.GetWizardProgress(c.Request.Context(), id)
|
|
if progress == nil || len(progress.CompletedSteps) < app.TotalSteps {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "not all required steps completed"})
|
|
return
|
|
}
|
|
|
|
// Update status
|
|
app.Status = funding.ApplicationStatusSubmitted
|
|
now := time.Now()
|
|
app.SubmittedAt = &now
|
|
app.UpdatedBy = userID
|
|
|
|
if err := h.store.UpdateApplication(c.Request.Context(), app); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Add history entry
|
|
h.store.AddHistoryEntry(c.Request.Context(), &funding.ApplicationHistoryEntry{
|
|
ApplicationID: id,
|
|
Action: "submitted",
|
|
PerformedBy: userID,
|
|
Notes: "Antrag eingereicht",
|
|
})
|
|
|
|
c.JSON(http.StatusOK, app)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Export Endpoints
|
|
// ============================================================================
|
|
|
|
// ExportApplication exports all documents as ZIP
|
|
// GET /sdk/v1/funding/applications/:id/export
|
|
func (h *FundingHandlers) ExportApplication(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid application ID"})
|
|
return
|
|
}
|
|
|
|
app, err := h.store.GetApplication(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Generate export (this will be implemented in export.go)
|
|
// For now, return a placeholder response
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "Export generation initiated",
|
|
"application_id": app.ID,
|
|
"status": "processing",
|
|
})
|
|
}
|
|
|
|
// PreviewApplication generates a PDF preview
|
|
// GET /sdk/v1/funding/applications/:id/preview
|
|
func (h *FundingHandlers) PreviewApplication(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid application ID"})
|
|
return
|
|
}
|
|
|
|
app, err := h.store.GetApplication(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Generate PDF preview (placeholder)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "Preview generation initiated",
|
|
"application_id": app.ID,
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// Bundesland Profile Endpoints
|
|
// ============================================================================
|
|
|
|
// GetBundeslandProfiles returns all bundesland profiles
|
|
// GET /sdk/v1/funding/bundeslaender
|
|
func (h *FundingHandlers) GetBundeslandProfiles(c *gin.Context) {
|
|
if h.bundeslandProfiles == nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "bundesland profiles not loaded"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, h.bundeslandProfiles)
|
|
}
|
|
|
|
// GetBundeslandProfile returns a specific bundesland profile
|
|
// GET /sdk/v1/funding/bundeslaender/:state
|
|
func (h *FundingHandlers) GetBundeslandProfile(c *gin.Context) {
|
|
state := c.Param("state")
|
|
|
|
if h.bundeslandProfiles == nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "bundesland profiles not loaded"})
|
|
return
|
|
}
|
|
|
|
profile, ok := h.bundeslandProfiles[state]
|
|
if !ok {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "bundesland not found"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, profile)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Statistics Endpoint
|
|
// ============================================================================
|
|
|
|
// GetStatistics returns funding statistics
|
|
// GET /sdk/v1/funding/statistics
|
|
func (h *FundingHandlers) GetStatistics(c *gin.Context) {
|
|
tenantID := rbac.GetTenantID(c)
|
|
if tenantID == uuid.Nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "tenant ID required"})
|
|
return
|
|
}
|
|
|
|
stats, err := h.store.GetStatistics(c.Request.Context(), tenantID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, stats)
|
|
}
|
|
|
|
// ============================================================================
|
|
// History Endpoint
|
|
// ============================================================================
|
|
|
|
// GetApplicationHistory returns the audit trail
|
|
// GET /sdk/v1/funding/applications/:id/history
|
|
func (h *FundingHandlers) GetApplicationHistory(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid application ID"})
|
|
return
|
|
}
|
|
|
|
history, err := h.store.GetHistory(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, history)
|
|
}
|