Docker Compose with 24+ services: - PostgreSQL (PostGIS), Valkey, MinIO, Qdrant - Vault (PKI/TLS), Nginx (Reverse Proxy) - Backend Core API, Consent Service, Billing Service - RAG Service, Embedding Service - Gitea, Woodpecker CI/CD - Night Scheduler, Health Aggregator - Jitsi (Web/XMPP/JVB/Jicofo), Mailpit Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
949 lines
26 KiB
Go
949 lines
26 KiB
Go
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"})
|
|
}
|