Initial commit: breakpilot-core - Shared Infrastructure
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>
This commit is contained in:
471
consent-service/cmd/server/main.go
Normal file
471
consent-service/cmd/server/main.go
Normal file
@@ -0,0 +1,471 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/breakpilot/consent-service/internal/config"
|
||||
"github.com/breakpilot/consent-service/internal/database"
|
||||
"github.com/breakpilot/consent-service/internal/handlers"
|
||||
"github.com/breakpilot/consent-service/internal/middleware"
|
||||
"github.com/breakpilot/consent-service/internal/services"
|
||||
"github.com/breakpilot/consent-service/internal/services/jitsi"
|
||||
"github.com/breakpilot/consent-service/internal/services/matrix"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Load configuration
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load config: %v", err)
|
||||
}
|
||||
|
||||
// Initialize database
|
||||
db, err := database.Connect(cfg.DatabaseURL)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect to database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Run migrations
|
||||
if err := database.Migrate(db); err != nil {
|
||||
log.Fatalf("Failed to run migrations: %v", err)
|
||||
}
|
||||
|
||||
// Setup Gin router
|
||||
if cfg.Environment == "production" {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
router := gin.Default()
|
||||
|
||||
// Global middleware
|
||||
router.Use(middleware.CORS())
|
||||
router.Use(middleware.RequestLogger())
|
||||
router.Use(middleware.RateLimiter())
|
||||
|
||||
// Health check
|
||||
router.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "healthy",
|
||||
"service": "consent-service",
|
||||
"version": "1.0.0",
|
||||
})
|
||||
})
|
||||
|
||||
// Initialize services
|
||||
authService := services.NewAuthService(db.Pool, cfg.JWTSecret, cfg.JWTRefreshSecret)
|
||||
oauthService := services.NewOAuthService(db.Pool, cfg.JWTSecret)
|
||||
totpService := services.NewTOTPService(db.Pool, "BreakPilot")
|
||||
emailService := services.NewEmailService(services.EmailConfig{
|
||||
Host: cfg.SMTPHost,
|
||||
Port: cfg.SMTPPort,
|
||||
Username: cfg.SMTPUsername,
|
||||
Password: cfg.SMTPPassword,
|
||||
FromName: cfg.SMTPFromName,
|
||||
FromAddr: cfg.SMTPFromAddr,
|
||||
BaseURL: cfg.FrontendURL,
|
||||
})
|
||||
notificationService := services.NewNotificationService(db.Pool, emailService)
|
||||
deadlineService := services.NewDeadlineService(db.Pool, notificationService)
|
||||
emailTemplateService := services.NewEmailTemplateService(db.Pool)
|
||||
dsrService := services.NewDSRService(db.Pool, notificationService, emailService)
|
||||
|
||||
// Initialize handlers
|
||||
h := handlers.New(db)
|
||||
authHandler := handlers.NewAuthHandler(authService, emailService)
|
||||
oauthHandler := handlers.NewOAuthHandler(oauthService, totpService, authService)
|
||||
notificationHandler := handlers.NewNotificationHandler(notificationService)
|
||||
deadlineHandler := handlers.NewDeadlineHandler(deadlineService)
|
||||
emailTemplateHandler := handlers.NewEmailTemplateHandler(emailTemplateService)
|
||||
dsrHandler := handlers.NewDSRHandler(dsrService)
|
||||
|
||||
// Initialize Matrix service (if enabled)
|
||||
var matrixService *matrix.MatrixService
|
||||
if cfg.MatrixEnabled && cfg.MatrixAccessToken != "" {
|
||||
matrixService = matrix.NewMatrixService(matrix.Config{
|
||||
HomeserverURL: cfg.MatrixHomeserverURL,
|
||||
AccessToken: cfg.MatrixAccessToken,
|
||||
ServerName: cfg.MatrixServerName,
|
||||
})
|
||||
log.Println("Matrix service initialized")
|
||||
} else {
|
||||
log.Println("Matrix service disabled or not configured")
|
||||
}
|
||||
|
||||
// Initialize Jitsi service (if enabled)
|
||||
var jitsiService *jitsi.JitsiService
|
||||
if cfg.JitsiEnabled {
|
||||
jitsiService = jitsi.NewJitsiService(jitsi.Config{
|
||||
BaseURL: cfg.JitsiBaseURL,
|
||||
AppID: cfg.JitsiAppID,
|
||||
AppSecret: cfg.JitsiAppSecret,
|
||||
})
|
||||
log.Println("Jitsi service initialized")
|
||||
} else {
|
||||
log.Println("Jitsi service disabled")
|
||||
}
|
||||
|
||||
// Initialize communication handlers
|
||||
communicationHandler := handlers.NewCommunicationHandlers(matrixService, jitsiService)
|
||||
|
||||
// Initialize default email templates (runs only once)
|
||||
if err := emailTemplateService.InitDefaultTemplates(context.Background()); err != nil {
|
||||
log.Printf("Warning: Failed to initialize default email templates: %v", err)
|
||||
}
|
||||
|
||||
// API v1 routes
|
||||
v1 := router.Group("/api/v1")
|
||||
{
|
||||
// =============================================
|
||||
// OAuth 2.0 Endpoints (RFC 6749)
|
||||
// =============================================
|
||||
oauth := v1.Group("/oauth")
|
||||
{
|
||||
// Authorization endpoint (requires user auth for consent)
|
||||
oauth.GET("/authorize", middleware.AuthMiddleware(cfg.JWTSecret), oauthHandler.Authorize)
|
||||
// Token endpoint (public)
|
||||
oauth.POST("/token", oauthHandler.Token)
|
||||
// Revocation endpoint (RFC 7009)
|
||||
oauth.POST("/revoke", oauthHandler.Revoke)
|
||||
// Introspection endpoint (RFC 7662)
|
||||
oauth.POST("/introspect", oauthHandler.Introspect)
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// Authentication Routes (with 2FA support)
|
||||
// =============================================
|
||||
auth := v1.Group("/auth")
|
||||
{
|
||||
// Registration with mandatory 2FA setup
|
||||
auth.POST("/register", oauthHandler.RegisterWith2FA)
|
||||
// Login with 2FA support
|
||||
auth.POST("/login", oauthHandler.LoginWith2FA)
|
||||
// 2FA challenge verification (during login)
|
||||
auth.POST("/2fa/verify", oauthHandler.Verify2FAChallenge)
|
||||
// Legacy endpoints (kept for compatibility)
|
||||
auth.POST("/logout", authHandler.Logout)
|
||||
auth.POST("/refresh", authHandler.RefreshToken)
|
||||
auth.POST("/verify-email", authHandler.VerifyEmail)
|
||||
auth.POST("/resend-verification", authHandler.ResendVerification)
|
||||
auth.POST("/forgot-password", authHandler.ForgotPassword)
|
||||
auth.POST("/reset-password", authHandler.ResetPassword)
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// 2FA Management Routes (require auth)
|
||||
// =============================================
|
||||
twoFA := v1.Group("/auth/2fa")
|
||||
twoFA.Use(middleware.AuthMiddleware(cfg.JWTSecret))
|
||||
{
|
||||
twoFA.GET("/status", oauthHandler.Get2FAStatus)
|
||||
twoFA.POST("/setup", oauthHandler.Setup2FA)
|
||||
twoFA.POST("/verify-setup", oauthHandler.Verify2FASetup)
|
||||
twoFA.POST("/disable", oauthHandler.Disable2FA)
|
||||
twoFA.POST("/recovery-codes", oauthHandler.RegenerateRecoveryCodes)
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// Profile Routes (require auth)
|
||||
// =============================================
|
||||
profile := v1.Group("/profile")
|
||||
profile.Use(middleware.AuthMiddleware(cfg.JWTSecret))
|
||||
{
|
||||
profile.GET("", authHandler.GetProfile)
|
||||
profile.PUT("", authHandler.UpdateProfile)
|
||||
profile.PUT("/password", authHandler.ChangePassword)
|
||||
profile.GET("/sessions", authHandler.GetActiveSessions)
|
||||
profile.DELETE("/sessions/:id", authHandler.RevokeSession)
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// Public consent routes (require user auth)
|
||||
// =============================================
|
||||
public := v1.Group("")
|
||||
public.Use(middleware.AuthMiddleware(cfg.JWTSecret))
|
||||
{
|
||||
// Documents
|
||||
public.GET("/documents", h.GetDocuments)
|
||||
public.GET("/documents/:type", h.GetDocumentByType)
|
||||
public.GET("/documents/:type/latest", h.GetLatestDocumentVersion)
|
||||
|
||||
// User Consent
|
||||
public.POST("/consent", h.CreateConsent)
|
||||
public.GET("/consent/my", h.GetMyConsents)
|
||||
public.GET("/consent/check/:documentType", h.CheckConsent)
|
||||
public.DELETE("/consent/:id", h.WithdrawConsent)
|
||||
|
||||
// Cookie Consent
|
||||
public.GET("/cookies/categories", h.GetCookieCategories)
|
||||
public.POST("/cookies/consent", h.SetCookieConsent)
|
||||
public.GET("/cookies/consent/my", h.GetMyCookieConsent)
|
||||
|
||||
// GDPR / Data Subject Rights
|
||||
public.GET("/privacy/my-data", h.GetMyData)
|
||||
public.POST("/privacy/export", h.RequestDataExport)
|
||||
public.POST("/privacy/delete", h.RequestDataDeletion)
|
||||
|
||||
// Data Subject Requests (User-facing)
|
||||
public.POST("/dsr", dsrHandler.CreateDSR)
|
||||
public.GET("/dsr", dsrHandler.GetMyDSRs)
|
||||
public.GET("/dsr/:id", dsrHandler.GetMyDSR)
|
||||
public.POST("/dsr/:id/cancel", dsrHandler.CancelMyDSR)
|
||||
|
||||
// Notifications
|
||||
public.GET("/notifications", notificationHandler.GetNotifications)
|
||||
public.GET("/notifications/unread-count", notificationHandler.GetUnreadCount)
|
||||
public.PUT("/notifications/:id/read", notificationHandler.MarkAsRead)
|
||||
public.PUT("/notifications/read-all", notificationHandler.MarkAllAsRead)
|
||||
public.DELETE("/notifications/:id", notificationHandler.DeleteNotification)
|
||||
public.GET("/notifications/preferences", notificationHandler.GetPreferences)
|
||||
public.PUT("/notifications/preferences", notificationHandler.UpdatePreferences)
|
||||
|
||||
// Consent Deadlines & Suspension Status
|
||||
public.GET("/consent/deadlines", deadlineHandler.GetPendingDeadlines)
|
||||
public.GET("/account/suspension-status", deadlineHandler.GetSuspensionStatus)
|
||||
}
|
||||
|
||||
// Admin routes (require admin auth)
|
||||
admin := v1.Group("/admin")
|
||||
admin.Use(middleware.AuthMiddleware(cfg.JWTSecret))
|
||||
admin.Use(middleware.AdminOnly())
|
||||
{
|
||||
// Document Management
|
||||
admin.GET("/documents", h.AdminGetDocuments)
|
||||
admin.POST("/documents", h.AdminCreateDocument)
|
||||
admin.PUT("/documents/:id", h.AdminUpdateDocument)
|
||||
admin.DELETE("/documents/:id", h.AdminDeleteDocument)
|
||||
admin.GET("/documents/:docId/versions", h.AdminGetVersions)
|
||||
|
||||
// Version Management
|
||||
admin.POST("/versions", h.AdminCreateVersion)
|
||||
admin.PUT("/versions/:id", h.AdminUpdateVersion)
|
||||
admin.DELETE("/versions/:id", h.AdminDeleteVersion)
|
||||
admin.POST("/versions/:id/archive", h.AdminArchiveVersion)
|
||||
admin.POST("/versions/:id/submit-review", h.AdminSubmitForReview)
|
||||
admin.POST("/versions/:id/approve", h.AdminApproveVersion)
|
||||
admin.POST("/versions/:id/reject", h.AdminRejectVersion)
|
||||
admin.GET("/versions/:id/compare", h.AdminCompareVersions)
|
||||
admin.GET("/versions/:id/approval-history", h.AdminGetApprovalHistory)
|
||||
|
||||
// Publishing (DSB role recommended but Admin can also do it in dev)
|
||||
admin.POST("/versions/:id/publish", h.AdminPublishVersion)
|
||||
|
||||
// Cookie Categories
|
||||
admin.GET("/cookies/categories", h.AdminGetCookieCategories)
|
||||
admin.POST("/cookies/categories", h.AdminCreateCookieCategory)
|
||||
admin.PUT("/cookies/categories/:id", h.AdminUpdateCookieCategory)
|
||||
admin.DELETE("/cookies/categories/:id", h.AdminDeleteCookieCategory)
|
||||
|
||||
// Statistics & Audit
|
||||
admin.GET("/stats/consents", h.GetConsentStats)
|
||||
admin.GET("/stats/cookies", h.GetCookieStats)
|
||||
admin.GET("/audit-log", h.GetAuditLog)
|
||||
|
||||
// Deadline Management (for testing/manual trigger)
|
||||
admin.POST("/deadlines/process", deadlineHandler.TriggerDeadlineProcessing)
|
||||
|
||||
// Scheduled Publishing
|
||||
admin.GET("/scheduled-versions", h.GetScheduledVersions)
|
||||
admin.POST("/scheduled-publishing/process", h.ProcessScheduledPublishing)
|
||||
|
||||
// OAuth Client Management
|
||||
admin.GET("/oauth/clients", oauthHandler.AdminGetClients)
|
||||
admin.POST("/oauth/clients", oauthHandler.AdminCreateClient)
|
||||
|
||||
// =============================================
|
||||
// E-Mail Template Management
|
||||
// =============================================
|
||||
admin.GET("/email-templates/types", emailTemplateHandler.GetAllTemplateTypes)
|
||||
admin.GET("/email-templates", emailTemplateHandler.GetAllTemplates)
|
||||
admin.GET("/email-templates/settings", emailTemplateHandler.GetSettings)
|
||||
admin.PUT("/email-templates/settings", emailTemplateHandler.UpdateSettings)
|
||||
admin.GET("/email-templates/stats", emailTemplateHandler.GetEmailStats)
|
||||
admin.GET("/email-templates/logs", emailTemplateHandler.GetSendLogs)
|
||||
admin.GET("/email-templates/default/:type", emailTemplateHandler.GetDefaultContent)
|
||||
admin.POST("/email-templates/initialize", emailTemplateHandler.InitializeTemplates)
|
||||
admin.GET("/email-templates/:id", emailTemplateHandler.GetTemplate)
|
||||
admin.POST("/email-templates", emailTemplateHandler.CreateTemplate)
|
||||
admin.GET("/email-templates/:id/versions", emailTemplateHandler.GetTemplateVersions)
|
||||
|
||||
// E-Mail Template Versions
|
||||
admin.GET("/email-template-versions/:id", emailTemplateHandler.GetVersion)
|
||||
admin.POST("/email-template-versions", emailTemplateHandler.CreateVersion)
|
||||
admin.PUT("/email-template-versions/:id", emailTemplateHandler.UpdateVersion)
|
||||
admin.POST("/email-template-versions/:id/submit", emailTemplateHandler.SubmitForReview)
|
||||
admin.POST("/email-template-versions/:id/approve", emailTemplateHandler.ApproveVersion)
|
||||
admin.POST("/email-template-versions/:id/reject", emailTemplateHandler.RejectVersion)
|
||||
admin.POST("/email-template-versions/:id/publish", emailTemplateHandler.PublishVersion)
|
||||
admin.GET("/email-template-versions/:id/approvals", emailTemplateHandler.GetApprovals)
|
||||
admin.POST("/email-template-versions/:id/preview", emailTemplateHandler.PreviewVersion)
|
||||
admin.POST("/email-template-versions/:id/send-test", emailTemplateHandler.SendTestEmail)
|
||||
|
||||
// =============================================
|
||||
// Data Subject Requests (DSR) Management
|
||||
// =============================================
|
||||
admin.GET("/dsr", dsrHandler.AdminListDSR)
|
||||
admin.GET("/dsr/stats", dsrHandler.AdminGetDSRStats)
|
||||
admin.POST("/dsr", dsrHandler.AdminCreateDSR)
|
||||
admin.GET("/dsr/:id", dsrHandler.AdminGetDSR)
|
||||
admin.PUT("/dsr/:id", dsrHandler.AdminUpdateDSR)
|
||||
admin.POST("/dsr/:id/status", dsrHandler.AdminUpdateDSRStatus)
|
||||
admin.POST("/dsr/:id/verify-identity", dsrHandler.AdminVerifyIdentity)
|
||||
admin.POST("/dsr/:id/assign", dsrHandler.AdminAssignDSR)
|
||||
admin.POST("/dsr/:id/extend", dsrHandler.AdminExtendDSRDeadline)
|
||||
admin.POST("/dsr/:id/complete", dsrHandler.AdminCompleteDSR)
|
||||
admin.POST("/dsr/:id/reject", dsrHandler.AdminRejectDSR)
|
||||
admin.GET("/dsr/:id/history", dsrHandler.AdminGetDSRHistory)
|
||||
admin.GET("/dsr/:id/communications", dsrHandler.AdminGetDSRCommunications)
|
||||
admin.POST("/dsr/:id/communicate", dsrHandler.AdminSendDSRCommunication)
|
||||
admin.GET("/dsr/:id/exception-checks", dsrHandler.AdminGetExceptionChecks)
|
||||
admin.POST("/dsr/:id/exception-checks/init", dsrHandler.AdminInitExceptionChecks)
|
||||
admin.PUT("/dsr/:id/exception-checks/:checkId", dsrHandler.AdminUpdateExceptionCheck)
|
||||
admin.POST("/dsr/deadlines/process", dsrHandler.ProcessDeadlines)
|
||||
|
||||
// DSR Templates
|
||||
admin.GET("/dsr-templates", dsrHandler.AdminGetDSRTemplates)
|
||||
admin.GET("/dsr-templates/published", dsrHandler.AdminGetPublishedDSRTemplates)
|
||||
admin.GET("/dsr-templates/:id/versions", dsrHandler.AdminGetDSRTemplateVersions)
|
||||
admin.POST("/dsr-templates/:id/versions", dsrHandler.AdminCreateDSRTemplateVersion)
|
||||
admin.POST("/dsr-template-versions/:versionId/publish", dsrHandler.AdminPublishDSRTemplateVersion)
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// Communication Routes (Matrix + Jitsi)
|
||||
// =============================================
|
||||
communicationHandler.RegisterRoutes(v1, cfg.JWTSecret, middleware.AuthMiddleware(cfg.JWTSecret))
|
||||
|
||||
// =============================================
|
||||
// Cookie Banner SDK Routes (Public - Anonymous)
|
||||
// =============================================
|
||||
// Diese Endpoints werden vom @breakpilot/consent-sdk verwendet
|
||||
// für anonyme (device-basierte) Cookie-Einwilligungen.
|
||||
banner := v1.Group("/banner")
|
||||
{
|
||||
// Public Endpoints (keine Auth erforderlich)
|
||||
banner.POST("/consent", h.CreateBannerConsent)
|
||||
banner.GET("/consent", h.GetBannerConsent)
|
||||
banner.DELETE("/consent/:consentId", h.RevokeBannerConsent)
|
||||
banner.GET("/config/:siteId", h.GetSiteConfig)
|
||||
banner.GET("/consent/export", h.ExportBannerConsent)
|
||||
}
|
||||
|
||||
// Banner Admin Routes (require admin auth)
|
||||
bannerAdmin := v1.Group("/banner/admin")
|
||||
bannerAdmin.Use(middleware.AuthMiddleware(cfg.JWTSecret))
|
||||
bannerAdmin.Use(middleware.AdminOnly())
|
||||
{
|
||||
bannerAdmin.GET("/stats/:siteId", h.GetBannerStats)
|
||||
}
|
||||
}
|
||||
|
||||
// Start background scheduler for scheduled publishing
|
||||
go startScheduledPublishingWorker(db)
|
||||
|
||||
// Start DSR deadline monitoring worker
|
||||
go startDSRDeadlineWorker(dsrService)
|
||||
|
||||
// Start server
|
||||
port := cfg.Port
|
||||
if port == "" {
|
||||
port = "8080"
|
||||
}
|
||||
|
||||
log.Printf("Starting Consent Service on port %s", port)
|
||||
if err := router.Run(":" + port); err != nil {
|
||||
log.Fatalf("Failed to start server: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// startScheduledPublishingWorker runs every minute to check for scheduled versions
|
||||
func startScheduledPublishingWorker(db *database.DB) {
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
log.Println("Scheduled publishing worker started (checking every minute)")
|
||||
|
||||
for range ticker.C {
|
||||
processScheduledVersions(db)
|
||||
}
|
||||
}
|
||||
|
||||
func processScheduledVersions(db *database.DB) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Find all scheduled versions that are due
|
||||
rows, err := db.Pool.Query(ctx, `
|
||||
SELECT id, document_id, version
|
||||
FROM document_versions
|
||||
WHERE status = 'scheduled'
|
||||
AND scheduled_publish_at IS NOT NULL
|
||||
AND scheduled_publish_at <= NOW()
|
||||
`)
|
||||
if err != nil {
|
||||
log.Printf("Scheduler: Error fetching scheduled versions: %v", err)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var publishedCount int
|
||||
for rows.Next() {
|
||||
var versionID, docID string
|
||||
var version string
|
||||
if err := rows.Scan(&versionID, &docID, &version); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Publish this version
|
||||
_, err := db.Pool.Exec(ctx, `
|
||||
UPDATE document_versions
|
||||
SET status = 'published', published_at = NOW(), updated_at = NOW()
|
||||
WHERE id = $1
|
||||
`, versionID)
|
||||
|
||||
if err == nil {
|
||||
// Archive previous published versions for this document
|
||||
db.Pool.Exec(ctx, `
|
||||
UPDATE document_versions
|
||||
SET status = 'archived', updated_at = NOW()
|
||||
WHERE document_id = $1 AND id != $2 AND status = 'published'
|
||||
`, docID, versionID)
|
||||
|
||||
// Log the publishing
|
||||
details := fmt.Sprintf("Version %s automatically published by scheduler", version)
|
||||
db.Pool.Exec(ctx, `
|
||||
INSERT INTO consent_audit_log (action, entity_type, entity_id, details, user_agent)
|
||||
VALUES ('version_scheduled_published', 'document_version', $1, $2, 'scheduler')
|
||||
`, versionID, details)
|
||||
|
||||
publishedCount++
|
||||
log.Printf("Scheduler: Published version %s (ID: %s)", version, versionID)
|
||||
}
|
||||
}
|
||||
|
||||
if publishedCount > 0 {
|
||||
log.Printf("Scheduler: Published %d version(s)", publishedCount)
|
||||
}
|
||||
}
|
||||
|
||||
// startDSRDeadlineWorker monitors DSR deadlines and sends notifications
|
||||
func startDSRDeadlineWorker(dsrService *services.DSRService) {
|
||||
ticker := time.NewTicker(1 * time.Hour)
|
||||
defer ticker.Stop()
|
||||
|
||||
log.Println("DSR deadline monitoring worker started (checking every hour)")
|
||||
|
||||
// Run immediately on startup
|
||||
ctx := context.Background()
|
||||
if err := dsrService.ProcessDeadlines(ctx); err != nil {
|
||||
log.Printf("DSR Worker: Error processing deadlines: %v", err)
|
||||
}
|
||||
|
||||
for range ticker.C {
|
||||
ctx := context.Background()
|
||||
if err := dsrService.ProcessDeadlines(ctx); err != nil {
|
||||
log.Printf("DSR Worker: Error processing deadlines: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user