package handlers import ( "context" "net/http" "strconv" "time" "github.com/breakpilot/consent-service/internal/middleware" "github.com/breakpilot/consent-service/internal/models" "github.com/breakpilot/consent-service/internal/services" "github.com/gin-gonic/gin" "github.com/google/uuid" ) // DSRHandler handles Data Subject Request HTTP endpoints type DSRHandler struct { dsrService *services.DSRService } // NewDSRHandler creates a new DSR handler func NewDSRHandler(dsrService *services.DSRService) *DSRHandler { return &DSRHandler{ dsrService: dsrService, } } // ======================================== // USER ENDPOINTS // ======================================== // CreateDSR creates a new data subject request (user-facing) func (h *DSRHandler) CreateDSR(c *gin.Context) { userID, err := middleware.GetUserID(c) if err != nil || userID == uuid.Nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid user"}) return } var req models.CreateDSRRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()}) return } // Get user email if not provided if req.RequesterEmail == "" { var email string ctx := context.Background() h.dsrService.GetPool().QueryRow(ctx, "SELECT email FROM users WHERE id = $1", userID).Scan(&email) req.RequesterEmail = email } // Set source as API req.Source = "api" dsr, err := h.dsrService.CreateRequest(c.Request.Context(), req, &userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, gin.H{ "message": "Ihre Anfrage wurde erfolgreich eingereicht", "request_number": dsr.RequestNumber, "dsr": dsr, }) } // GetMyDSRs returns DSRs for the current user func (h *DSRHandler) GetMyDSRs(c *gin.Context) { userID, err := middleware.GetUserID(c) if err != nil || userID == uuid.Nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid user"}) return } dsrs, err := h.dsrService.ListByUser(c.Request.Context(), userID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch requests"}) return } c.JSON(http.StatusOK, gin.H{"requests": dsrs}) } // GetMyDSR returns a specific DSR for the current user func (h *DSRHandler) GetMyDSR(c *gin.Context) { userID, err := middleware.GetUserID(c) if err != nil || userID == uuid.Nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid user"}) return } dsrID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request ID"}) return } dsr, err := h.dsrService.GetByID(c.Request.Context(), dsrID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Request not found"}) return } // Verify ownership if dsr.UserID == nil || *dsr.UserID != userID { c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"}) return } c.JSON(http.StatusOK, dsr) } // CancelMyDSR cancels a user's own DSR func (h *DSRHandler) CancelMyDSR(c *gin.Context) { userID, err := middleware.GetUserID(c) if err != nil || userID == uuid.Nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid user"}) return } dsrID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request ID"}) return } err = h.dsrService.CancelRequest(c.Request.Context(), dsrID, userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Anfrage wurde storniert"}) } // ======================================== // ADMIN ENDPOINTS // ======================================== // AdminListDSR returns all DSRs with filters (admin only) func (h *DSRHandler) AdminListDSR(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } // Parse pagination limit := 20 offset := 0 if l := c.Query("limit"); l != "" { if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 { limit = parsed } } if o := c.Query("offset"); o != "" { if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 { offset = parsed } } // Parse filters filters := models.DSRListFilters{} if status := c.Query("status"); status != "" { filters.Status = &status } if reqType := c.Query("request_type"); reqType != "" { filters.RequestType = &reqType } if assignedTo := c.Query("assigned_to"); assignedTo != "" { filters.AssignedTo = &assignedTo } if priority := c.Query("priority"); priority != "" { filters.Priority = &priority } if c.Query("overdue_only") == "true" { filters.OverdueOnly = true } if search := c.Query("search"); search != "" { filters.Search = &search } if from := c.Query("from_date"); from != "" { if t, err := time.Parse("2006-01-02", from); err == nil { filters.FromDate = &t } } if to := c.Query("to_date"); to != "" { if t, err := time.Parse("2006-01-02", to); err == nil { filters.ToDate = &t } } dsrs, total, err := h.dsrService.List(c.Request.Context(), filters, limit, offset) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch requests"}) return } c.JSON(http.StatusOK, gin.H{ "requests": dsrs, "total": total, "limit": limit, "offset": offset, }) } // AdminGetDSR returns a specific DSR (admin only) func (h *DSRHandler) AdminGetDSR(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } dsrID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request ID"}) return } dsr, err := h.dsrService.GetByID(c.Request.Context(), dsrID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Request not found"}) return } c.JSON(http.StatusOK, dsr) } // AdminCreateDSR creates a DSR manually (admin only) func (h *DSRHandler) AdminCreateDSR(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } userID, _ := middleware.GetUserID(c) var req models.CreateDSRRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()}) return } // Set source as admin_panel if req.Source == "" { req.Source = "admin_panel" } dsr, err := h.dsrService.CreateRequest(c.Request.Context(), req, &userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, gin.H{ "message": "Anfrage wurde erstellt", "request_number": dsr.RequestNumber, "dsr": dsr, }) } // AdminUpdateDSR updates a DSR (admin only) func (h *DSRHandler) AdminUpdateDSR(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } dsrID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request ID"}) return } userID, _ := middleware.GetUserID(c) var req models.UpdateDSRRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } ctx := c.Request.Context() // Update status if provided if req.Status != nil { err = h.dsrService.UpdateStatus(ctx, dsrID, models.DSRStatus(*req.Status), "", &userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } } // Update processing notes if req.ProcessingNotes != nil { h.dsrService.GetPool().Exec(ctx, ` UPDATE data_subject_requests SET processing_notes = $1, updated_at = NOW() WHERE id = $2 `, *req.ProcessingNotes, dsrID) } // Update priority if req.Priority != nil { h.dsrService.GetPool().Exec(ctx, ` UPDATE data_subject_requests SET priority = $1, updated_at = NOW() WHERE id = $2 `, *req.Priority, dsrID) } // Get updated DSR dsr, _ := h.dsrService.GetByID(ctx, dsrID) c.JSON(http.StatusOK, gin.H{ "message": "Anfrage wurde aktualisiert", "dsr": dsr, }) } // AdminGetDSRStats returns dashboard statistics func (h *DSRHandler) AdminGetDSRStats(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } stats, err := h.dsrService.GetDashboardStats(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch statistics"}) return } c.JSON(http.StatusOK, stats) } // AdminVerifyIdentity verifies the identity of a requester func (h *DSRHandler) AdminVerifyIdentity(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } dsrID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request ID"}) return } userID, _ := middleware.GetUserID(c) var req models.VerifyDSRIdentityRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } err = h.dsrService.VerifyIdentity(c.Request.Context(), dsrID, req.Method, userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Identität wurde verifiziert"}) } // AdminAssignDSR assigns a DSR to a user func (h *DSRHandler) AdminAssignDSR(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } dsrID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request ID"}) return } userID, _ := middleware.GetUserID(c) var req struct { AssigneeID string `json:"assignee_id" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } assigneeID, err := uuid.Parse(req.AssigneeID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid assignee ID"}) return } err = h.dsrService.AssignRequest(c.Request.Context(), dsrID, assigneeID, userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Anfrage wurde zugewiesen"}) } // AdminExtendDSRDeadline extends the deadline for a DSR func (h *DSRHandler) AdminExtendDSRDeadline(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } dsrID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request ID"}) return } userID, _ := middleware.GetUserID(c) var req models.ExtendDSRDeadlineRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } err = h.dsrService.ExtendDeadline(c.Request.Context(), dsrID, req.Reason, req.Days, userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Frist wurde verlängert"}) } // AdminCompleteDSR marks a DSR as completed func (h *DSRHandler) AdminCompleteDSR(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } dsrID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request ID"}) return } userID, _ := middleware.GetUserID(c) var req models.CompleteDSRRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } err = h.dsrService.CompleteRequest(c.Request.Context(), dsrID, req.ResultSummary, req.ResultData, userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Anfrage wurde abgeschlossen"}) } // AdminRejectDSR rejects a DSR func (h *DSRHandler) AdminRejectDSR(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } dsrID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request ID"}) return } userID, _ := middleware.GetUserID(c) var req models.RejectDSRRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } err = h.dsrService.RejectRequest(c.Request.Context(), dsrID, req.Reason, req.LegalBasis, userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Anfrage wurde abgelehnt"}) } // AdminGetDSRHistory returns the status history for a DSR func (h *DSRHandler) AdminGetDSRHistory(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } dsrID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request ID"}) return } history, err := h.dsrService.GetStatusHistory(c.Request.Context(), dsrID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch history"}) return } c.JSON(http.StatusOK, gin.H{"history": history}) } // AdminGetDSRCommunications returns communications for a DSR func (h *DSRHandler) AdminGetDSRCommunications(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } dsrID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request ID"}) return } comms, err := h.dsrService.GetCommunications(c.Request.Context(), dsrID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch communications"}) return } c.JSON(http.StatusOK, gin.H{"communications": comms}) } // AdminSendDSRCommunication sends a communication for a DSR func (h *DSRHandler) AdminSendDSRCommunication(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } dsrID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request ID"}) return } userID, _ := middleware.GetUserID(c) var req models.SendDSRCommunicationRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } err = h.dsrService.SendCommunication(c.Request.Context(), dsrID, req, userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Kommunikation wurde gesendet"}) } // AdminUpdateDSRStatus updates the status of a DSR func (h *DSRHandler) AdminUpdateDSRStatus(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } dsrID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request ID"}) return } userID, _ := middleware.GetUserID(c) var req struct { Status string `json:"status" binding:"required"` Comment string `json:"comment"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } err = h.dsrService.UpdateStatus(c.Request.Context(), dsrID, models.DSRStatus(req.Status), req.Comment, &userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Status wurde aktualisiert"}) } // ======================================== // EXCEPTION CHECKS (Art. 17) // ======================================== // AdminGetExceptionChecks returns exception checks for an erasure DSR func (h *DSRHandler) AdminGetExceptionChecks(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } dsrID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request ID"}) return } checks, err := h.dsrService.GetExceptionChecks(c.Request.Context(), dsrID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch exception checks"}) return } c.JSON(http.StatusOK, gin.H{"exception_checks": checks}) } // AdminInitExceptionChecks initializes exception checks for an erasure DSR func (h *DSRHandler) AdminInitExceptionChecks(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } dsrID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request ID"}) return } err = h.dsrService.InitErasureExceptionChecks(c.Request.Context(), dsrID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to initialize exception checks"}) return } c.JSON(http.StatusOK, gin.H{"message": "Ausnahmeprüfungen wurden initialisiert"}) } // AdminUpdateExceptionCheck updates a single exception check func (h *DSRHandler) AdminUpdateExceptionCheck(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } checkID, err := uuid.Parse(c.Param("checkId")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid check ID"}) return } userID, _ := middleware.GetUserID(c) var req struct { Applies bool `json:"applies"` Notes *string `json:"notes"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } err = h.dsrService.UpdateExceptionCheck(c.Request.Context(), checkID, req.Applies, req.Notes, userID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update exception check"}) return } c.JSON(http.StatusOK, gin.H{"message": "Ausnahmeprüfung wurde aktualisiert"}) } // ======================================== // TEMPLATE ENDPOINTS // ======================================== // AdminGetDSRTemplates returns all DSR templates func (h *DSRHandler) AdminGetDSRTemplates(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } ctx := c.Request.Context() rows, err := h.dsrService.GetPool().Query(ctx, ` SELECT id, template_type, name, description, request_types, is_active, sort_order, created_at, updated_at FROM dsr_templates ORDER BY sort_order, name `) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch templates"}) return } defer rows.Close() var templates []map[string]interface{} for rows.Next() { var id uuid.UUID var templateType, name string var description *string var requestTypes []byte var isActive bool var sortOrder int var createdAt, updatedAt time.Time err := rows.Scan(&id, &templateType, &name, &description, &requestTypes, &isActive, &sortOrder, &createdAt, &updatedAt) if err != nil { continue } templates = append(templates, map[string]interface{}{ "id": id, "template_type": templateType, "name": name, "description": description, "request_types": string(requestTypes), "is_active": isActive, "sort_order": sortOrder, "created_at": createdAt, "updated_at": updatedAt, }) } c.JSON(http.StatusOK, gin.H{"templates": templates}) } // AdminGetDSRTemplateVersions returns versions for a template func (h *DSRHandler) AdminGetDSRTemplateVersions(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } templateID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid template ID"}) return } ctx := c.Request.Context() rows, err := h.dsrService.GetPool().Query(ctx, ` SELECT id, template_id, version, language, subject, body_html, body_text, status, published_at, created_by, approved_by, approved_at, created_at, updated_at FROM dsr_template_versions WHERE template_id = $1 ORDER BY created_at DESC `, templateID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch versions"}) return } defer rows.Close() var versions []map[string]interface{} for rows.Next() { var id, tempID uuid.UUID var version, language, subject, bodyHTML, bodyText, status string var publishedAt, approvedAt *time.Time var createdBy, approvedBy *uuid.UUID var createdAt, updatedAt time.Time err := rows.Scan(&id, &tempID, &version, &language, &subject, &bodyHTML, &bodyText, &status, &publishedAt, &createdBy, &approvedBy, &approvedAt, &createdAt, &updatedAt) if err != nil { continue } versions = append(versions, map[string]interface{}{ "id": id, "template_id": tempID, "version": version, "language": language, "subject": subject, "body_html": bodyHTML, "body_text": bodyText, "status": status, "published_at": publishedAt, "created_by": createdBy, "approved_by": approvedBy, "approved_at": approvedAt, "created_at": createdAt, "updated_at": updatedAt, }) } c.JSON(http.StatusOK, gin.H{"versions": versions}) } // AdminCreateDSRTemplateVersion creates a new template version func (h *DSRHandler) AdminCreateDSRTemplateVersion(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } templateID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid template ID"}) return } userID, _ := middleware.GetUserID(c) var req struct { Version string `json:"version" binding:"required"` Language string `json:"language"` Subject string `json:"subject" binding:"required"` BodyHTML string `json:"body_html" binding:"required"` BodyText string `json:"body_text"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } if req.Language == "" { req.Language = "de" } ctx := c.Request.Context() var versionID uuid.UUID err = h.dsrService.GetPool().QueryRow(ctx, ` INSERT INTO dsr_template_versions (template_id, version, language, subject, body_html, body_text, created_by) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id `, templateID, req.Version, req.Language, req.Subject, req.BodyHTML, req.BodyText, userID).Scan(&versionID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create version"}) return } c.JSON(http.StatusCreated, gin.H{ "message": "Version wurde erstellt", "id": versionID, }) } // AdminPublishDSRTemplateVersion publishes a template version func (h *DSRHandler) AdminPublishDSRTemplateVersion(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } versionID, err := uuid.Parse(c.Param("versionId")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid version ID"}) return } userID, _ := middleware.GetUserID(c) ctx := c.Request.Context() _, err = h.dsrService.GetPool().Exec(ctx, ` UPDATE dsr_template_versions SET status = 'published', published_at = NOW(), approved_by = $1, approved_at = NOW(), updated_at = NOW() WHERE id = $2 `, userID, versionID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to publish version"}) return } c.JSON(http.StatusOK, gin.H{"message": "Version wurde veröffentlicht"}) } // AdminGetPublishedDSRTemplates returns all published templates for selection func (h *DSRHandler) AdminGetPublishedDSRTemplates(c *gin.Context) { if !middleware.IsAdmin(c) && !middleware.IsDSB(c) { c.JSON(http.StatusForbidden, gin.H{"error": "Admin or DSB access required"}) return } requestType := c.Query("request_type") language := c.DefaultQuery("language", "de") ctx := c.Request.Context() query := ` SELECT t.id, t.template_type, t.name, t.description, v.id as version_id, v.version, v.subject, v.body_html, v.body_text FROM dsr_templates t JOIN dsr_template_versions v ON t.id = v.template_id WHERE t.is_active = TRUE AND v.status = 'published' AND v.language = $1 ` args := []interface{}{language} if requestType != "" { query += ` AND t.request_types @> $2::jsonb` args = append(args, `["`+requestType+`"]`) } query += " ORDER BY t.sort_order, t.name" rows, err := h.dsrService.GetPool().Query(ctx, query, args...) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch templates"}) return } defer rows.Close() var templates []map[string]interface{} for rows.Next() { var templateID, versionID uuid.UUID var templateType, name, version, subject, bodyHTML, bodyText string var description *string err := rows.Scan(&templateID, &templateType, &name, &description, &versionID, &version, &subject, &bodyHTML, &bodyText) if err != nil { continue } templates = append(templates, map[string]interface{}{ "template_id": templateID, "template_type": templateType, "name": name, "description": description, "version_id": versionID, "version": version, "subject": subject, "body_html": bodyHTML, "body_text": bodyText, }) } c.JSON(http.StatusOK, gin.H{"templates": templates}) } // ======================================== // DEADLINE PROCESSING // ======================================== // ProcessDeadlines triggers deadline checking (called by scheduler) func (h *DSRHandler) ProcessDeadlines(c *gin.Context) { err := h.dsrService.ProcessDeadlines(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to process deadlines"}) return } c.JSON(http.StatusOK, gin.H{"message": "Deadline processing completed"}) }