This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/consent-service/internal/handlers/email_template_handlers.go
Benjamin Admin bfdaf63ba9 fix: Restore all files lost during destructive rebase
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>
2026-02-09 09:51:32 +01:00

529 lines
17 KiB
Go

package handlers
import (
"net/http"
"strconv"
"time"
"github.com/breakpilot/consent-service/internal/models"
"github.com/breakpilot/consent-service/internal/services"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// EmailTemplateHandler handles email template operations
type EmailTemplateHandler struct {
service *services.EmailTemplateService
}
// NewEmailTemplateHandler creates a new email template handler
func NewEmailTemplateHandler(service *services.EmailTemplateService) *EmailTemplateHandler {
return &EmailTemplateHandler{service: service}
}
// GetAllTemplateTypes returns all available email template types with their variables
// GET /api/v1/admin/email-templates/types
func (h *EmailTemplateHandler) GetAllTemplateTypes(c *gin.Context) {
types := h.service.GetAllTemplateTypes()
c.JSON(http.StatusOK, gin.H{"types": types})
}
// GetAllTemplates returns all email templates with their latest published versions
// GET /api/v1/admin/email-templates
func (h *EmailTemplateHandler) GetAllTemplates(c *gin.Context) {
templates, err := h.service.GetAllTemplates(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"templates": templates})
}
// GetTemplate returns a single template by ID
// GET /api/v1/admin/email-templates/:id
func (h *EmailTemplateHandler) GetTemplate(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid template ID"})
return
}
template, err := h.service.GetTemplateByID(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "template not found"})
return
}
c.JSON(http.StatusOK, template)
}
// CreateTemplate creates a new email template type
// POST /api/v1/admin/email-templates
func (h *EmailTemplateHandler) CreateTemplate(c *gin.Context) {
var req models.CreateEmailTemplateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
template, err := h.service.CreateEmailTemplate(c.Request.Context(), &req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, template)
}
// GetTemplateVersions returns all versions for a template
// GET /api/v1/admin/email-templates/:id/versions
func (h *EmailTemplateHandler) GetTemplateVersions(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid template ID"})
return
}
versions, err := h.service.GetVersionsByTemplateID(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"versions": versions})
}
// GetVersion returns a single version by ID
// GET /api/v1/admin/email-template-versions/:id
func (h *EmailTemplateHandler) GetVersion(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid version ID"})
return
}
version, err := h.service.GetVersionByID(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "version not found"})
return
}
c.JSON(http.StatusOK, version)
}
// CreateVersion creates a new version of an email template
// POST /api/v1/admin/email-template-versions
func (h *EmailTemplateHandler) CreateVersion(c *gin.Context) {
var req models.CreateEmailTemplateVersionRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Get user ID from context
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"})
return
}
uid, _ := uuid.Parse(userID.(string))
version, err := h.service.CreateTemplateVersion(c.Request.Context(), &req, uid)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, version)
}
// UpdateVersion updates a version
// PUT /api/v1/admin/email-template-versions/:id
func (h *EmailTemplateHandler) UpdateVersion(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid version ID"})
return
}
var req models.UpdateEmailTemplateVersionRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.service.UpdateVersion(c.Request.Context(), id, &req); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "version updated"})
}
// SubmitForReview submits a version for review
// POST /api/v1/admin/email-template-versions/:id/submit
func (h *EmailTemplateHandler) SubmitForReview(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid version ID"})
return
}
var req struct {
Comment *string `json:"comment"`
}
c.ShouldBindJSON(&req)
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"})
return
}
uid, _ := uuid.Parse(userID.(string))
if err := h.service.SubmitForReview(c.Request.Context(), id, uid, req.Comment); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "version submitted for review"})
}
// ApproveVersion approves a version (DSB only)
// POST /api/v1/admin/email-template-versions/:id/approve
func (h *EmailTemplateHandler) ApproveVersion(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid version ID"})
return
}
// Check role
role, exists := c.Get("user_role")
if !exists || (role != "data_protection_officer" && role != "admin" && role != "super_admin") {
c.JSON(http.StatusForbidden, gin.H{"error": "insufficient permissions"})
return
}
var req struct {
Comment *string `json:"comment"`
ScheduledPublishAt *string `json:"scheduled_publish_at"`
}
c.ShouldBindJSON(&req)
userID, _ := c.Get("user_id")
uid, _ := uuid.Parse(userID.(string))
var scheduledAt *time.Time
if req.ScheduledPublishAt != nil {
t, err := time.Parse(time.RFC3339, *req.ScheduledPublishAt)
if err == nil {
scheduledAt = &t
}
}
if err := h.service.ApproveVersion(c.Request.Context(), id, uid, req.Comment, scheduledAt); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "version approved"})
}
// RejectVersion rejects a version
// POST /api/v1/admin/email-template-versions/:id/reject
func (h *EmailTemplateHandler) RejectVersion(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid version ID"})
return
}
role, exists := c.Get("user_role")
if !exists || (role != "data_protection_officer" && role != "admin" && role != "super_admin") {
c.JSON(http.StatusForbidden, gin.H{"error": "insufficient permissions"})
return
}
var req struct {
Comment string `json:"comment" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "comment is required"})
return
}
userID, _ := c.Get("user_id")
uid, _ := uuid.Parse(userID.(string))
if err := h.service.RejectVersion(c.Request.Context(), id, uid, req.Comment); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "version rejected"})
}
// PublishVersion publishes an approved version
// POST /api/v1/admin/email-template-versions/:id/publish
func (h *EmailTemplateHandler) PublishVersion(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid version ID"})
return
}
role, exists := c.Get("user_role")
if !exists || (role != "data_protection_officer" && role != "admin" && role != "super_admin") {
c.JSON(http.StatusForbidden, gin.H{"error": "insufficient permissions"})
return
}
userID, _ := c.Get("user_id")
uid, _ := uuid.Parse(userID.(string))
if err := h.service.PublishVersion(c.Request.Context(), id, uid); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "version published"})
}
// GetApprovals returns approval history for a version
// GET /api/v1/admin/email-template-versions/:id/approvals
func (h *EmailTemplateHandler) GetApprovals(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid version ID"})
return
}
approvals, err := h.service.GetApprovals(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"approvals": approvals})
}
// PreviewVersion renders a preview of an email template version
// POST /api/v1/admin/email-template-versions/:id/preview
func (h *EmailTemplateHandler) PreviewVersion(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid version ID"})
return
}
var req struct {
Variables map[string]string `json:"variables"`
}
c.ShouldBindJSON(&req)
version, err := h.service.GetVersionByID(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "version not found"})
return
}
// Use default test values if not provided
if req.Variables == nil {
req.Variables = map[string]string{
"user_name": "Max Mustermann",
"user_email": "max@example.com",
"login_url": "https://breakpilot.app/login",
"support_email": "support@breakpilot.app",
"verification_url": "https://breakpilot.app/verify?token=abc123",
"verification_code": "123456",
"expires_in": "24 Stunden",
"reset_url": "https://breakpilot.app/reset?token=xyz789",
"reset_code": "RESET123",
"ip_address": "192.168.1.1",
"device_info": "Chrome auf Windows 11",
"changed_at": time.Now().Format("02.01.2006 15:04"),
"enabled_at": time.Now().Format("02.01.2006 15:04"),
"disabled_at": time.Now().Format("02.01.2006 15:04"),
"support_url": "https://breakpilot.app/support",
"security_url": "https://breakpilot.app/account/security",
"login_time": time.Now().Format("02.01.2006 15:04"),
"location": "Berlin, Deutschland",
"activity_type": "Mehrere fehlgeschlagene Login-Versuche",
"activity_time": time.Now().Format("02.01.2006 15:04"),
"locked_at": time.Now().Format("02.01.2006 15:04"),
"reason": "Zu viele fehlgeschlagene Login-Versuche",
"unlock_time": time.Now().Add(30 * time.Minute).Format("02.01.2006 15:04"),
"unlocked_at": time.Now().Format("02.01.2006 15:04"),
"requested_at": time.Now().Format("02.01.2006"),
"deletion_date": time.Now().AddDate(0, 0, 30).Format("02.01.2006"),
"cancel_url": "https://breakpilot.app/cancel-deletion?token=cancel123",
"data_info": "Benutzerdaten, Zustimmungshistorie, Audit-Logs",
"deleted_at": time.Now().Format("02.01.2006"),
"feedback_url": "https://breakpilot.app/feedback",
"download_url": "https://breakpilot.app/export/download?token=export123",
"file_size": "2.3 MB",
"old_email": "alt@example.com",
"new_email": "neu@example.com",
"document_name": "Datenschutzerklärung",
"document_type": "privacy",
"version": "2.0.0",
"consent_url": "https://breakpilot.app/consent",
"deadline": time.Now().AddDate(0, 0, 14).Format("02.01.2006"),
"days_left": "7",
"hours_left": "24 Stunden",
"consequences": "Ohne Ihre Zustimmung wird Ihr Konto suspendiert.",
"suspended_at": time.Now().Format("02.01.2006 15:04"),
"documents": "- Datenschutzerklärung v2.0.0\n- AGB v1.5.0",
}
}
preview, err := h.service.RenderTemplate(version, req.Variables)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, preview)
}
// SendTestEmail sends a test email
// POST /api/v1/admin/email-template-versions/:id/send-test
func (h *EmailTemplateHandler) SendTestEmail(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid version ID"})
return
}
var req models.SendTestEmailRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
req.VersionID = idStr
version, err := h.service.GetVersionByID(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "version not found"})
return
}
// Get template to find type
template, err := h.service.GetTemplateByID(c.Request.Context(), version.TemplateID)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "template not found"})
return
}
userID, _ := c.Get("user_id")
uid, _ := uuid.Parse(userID.(string))
// Send test email
if err := h.service.SendEmail(c.Request.Context(), template.Type, version.Language, req.Recipient, req.Variables, &uid); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "test email sent"})
}
// GetSettings returns global email settings
// GET /api/v1/admin/email-templates/settings
func (h *EmailTemplateHandler) GetSettings(c *gin.Context) {
settings, err := h.service.GetSettings(c.Request.Context())
if err != nil {
// Return default settings if none exist
c.JSON(http.StatusOK, gin.H{
"company_name": "BreakPilot",
"sender_name": "BreakPilot",
"sender_email": "noreply@breakpilot.app",
"primary_color": "#2563eb",
"secondary_color": "#64748b",
})
return
}
c.JSON(http.StatusOK, settings)
}
// UpdateSettings updates global email settings
// PUT /api/v1/admin/email-templates/settings
func (h *EmailTemplateHandler) UpdateSettings(c *gin.Context) {
var req models.UpdateEmailTemplateSettingsRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
userID, _ := c.Get("user_id")
uid, _ := uuid.Parse(userID.(string))
if err := h.service.UpdateSettings(c.Request.Context(), &req, uid); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "settings updated"})
}
// GetEmailStats returns email statistics
// GET /api/v1/admin/email-templates/stats
func (h *EmailTemplateHandler) GetEmailStats(c *gin.Context) {
stats, err := h.service.GetEmailStats(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, stats)
}
// GetSendLogs returns email send logs
// GET /api/v1/admin/email-templates/logs
func (h *EmailTemplateHandler) GetSendLogs(c *gin.Context) {
limitStr := c.DefaultQuery("limit", "50")
offsetStr := c.DefaultQuery("offset", "0")
limit, _ := strconv.Atoi(limitStr)
offset, _ := strconv.Atoi(offsetStr)
if limit > 100 {
limit = 100
}
logs, total, err := h.service.GetSendLogs(c.Request.Context(), limit, offset)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"logs": logs, "total": total})
}
// GetDefaultContent returns default template content for a type
// GET /api/v1/admin/email-templates/default/:type
func (h *EmailTemplateHandler) GetDefaultContent(c *gin.Context) {
templateType := c.Param("type")
language := c.DefaultQuery("language", "de")
subject, bodyHTML, bodyText := h.service.GetDefaultTemplateContent(templateType, language)
c.JSON(http.StatusOK, gin.H{
"subject": subject,
"body_html": bodyHTML,
"body_text": bodyText,
})
}
// InitializeTemplates initializes default email templates
// POST /api/v1/admin/email-templates/initialize
func (h *EmailTemplateHandler) InitializeTemplates(c *gin.Context) {
role, exists := c.Get("user_role")
if !exists || (role != "admin" && role != "super_admin") {
c.JSON(http.StatusForbidden, gin.H{"error": "insufficient permissions"})
return
}
if err := h.service.InitDefaultTemplates(c.Request.Context()); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "default templates initialized"})
}