99 "math"
1010 "net/http"
1111 "os"
12+ "strings"
1213 "subtrackr/internal/config"
1314 "subtrackr/internal/database"
1415 "subtrackr/internal/handlers"
@@ -57,6 +58,7 @@ func main() {
5758 settingsService := service .NewSettingsService (settingsRepo )
5859 emailService := service .NewEmailService (settingsService )
5960 pushoverService := service .NewPushoverService (settingsService )
61+ webhookService := service .NewWebhookService (settingsService )
6062 logoService := service .NewLogoService ()
6163
6264 // Handle CLI commands (run before starting HTTP server)
@@ -78,7 +80,7 @@ func main() {
7880 sessionService := service .NewSessionService (sessionSecret )
7981
8082 // Initialize handlers
81- subscriptionHandler := handlers .NewSubscriptionHandler (subscriptionService , settingsService , currencyService , emailService , pushoverService , logoService )
83+ subscriptionHandler := handlers .NewSubscriptionHandler (subscriptionService , settingsService , currencyService , emailService , pushoverService , webhookService , logoService )
8284 settingsHandler := handlers .NewSettingsHandler (settingsService )
8385 categoryHandler := handlers .NewCategoryHandler (categoryService )
8486 authHandler := handlers .NewAuthHandler (settingsService , sessionService , emailService )
@@ -115,6 +117,15 @@ func main() {
115117 return 0
116118 }
117119 },
120+ "fmtDate" : func (t * time.Time , format string ) string {
121+ if t == nil {
122+ return ""
123+ }
124+ return t .Format (format )
125+ },
126+ "fmtTime" : func (t time.Time , format string ) string {
127+ return t .Format (format )
128+ },
118129 })
119130
120131 // Load HTML templates with error handling
@@ -171,10 +182,10 @@ func main() {
171182 // }
172183
173184 // Start renewal reminder scheduler
174- go startRenewalReminderScheduler (subscriptionService , emailService , pushoverService , settingsService )
185+ go startRenewalReminderScheduler (subscriptionService , emailService , pushoverService , webhookService , settingsService )
175186
176187 // Start cancellation reminder scheduler
177- go startCancellationReminderScheduler (subscriptionService , emailService , pushoverService , settingsService )
188+ go startCancellationReminderScheduler (subscriptionService , emailService , pushoverService , webhookService , settingsService )
178189
179190 // Start server
180191 port := os .Getenv ("PORT" )
@@ -216,6 +227,15 @@ func loadTemplates() *template.Template {
216227 return 0
217228 }
218229 },
230+ "fmtDate" : func (t * time.Time , format string ) string {
231+ if t == nil {
232+ return ""
233+ }
234+ return t .Format (format )
235+ },
236+ "fmtTime" : func (t time.Time , format string ) string {
237+ return t .Format (format )
238+ },
219239 })
220240
221241 // Critical templates required for basic functionality
@@ -344,6 +364,8 @@ func setupRoutes(router *gin.Engine, handler *handlers.SubscriptionHandler, sett
344364 api .POST ("/settings/pushover" , settingsHandler .SavePushoverSettings )
345365 api .POST ("/settings/pushover/test" , settingsHandler .TestPushoverConnection )
346366 api .GET ("/settings/pushover" , settingsHandler .GetPushoverConfig )
367+ api .POST ("/settings/webhook" , settingsHandler .SaveWebhookSettings )
368+ api .POST ("/settings/webhook/test" , settingsHandler .TestWebhookConnection )
347369 api .POST ("/settings/notifications/:setting" , settingsHandler .UpdateNotificationSetting )
348370 api .GET ("/settings/notifications" , settingsHandler .GetNotificationSettings )
349371 api .GET ("/settings/smtp" , settingsHandler .GetSMTPConfig )
@@ -356,6 +378,9 @@ func setupRoutes(router *gin.Engine, handler *handlers.SubscriptionHandler, sett
356378 // Currency setting
357379 api .POST ("/settings/currency" , settingsHandler .UpdateCurrency )
358380
381+ // Date format setting
382+ api .POST ("/settings/date-format" , settingsHandler .UpdateDateFormat )
383+
359384 // Dark mode setting
360385 api .POST ("/settings/dark-mode" , settingsHandler .ToggleDarkMode )
361386
@@ -409,11 +434,11 @@ func setupRoutes(router *gin.Engine, handler *handlers.SubscriptionHandler, sett
409434
410435// startRenewalReminderScheduler starts a background goroutine that checks for
411436// upcoming renewals and sends reminder emails and Pushover notifications daily
412- func startRenewalReminderScheduler (subscriptionService * service.SubscriptionService , emailService * service.EmailService , pushoverService * service.PushoverService , settingsService * service.SettingsService ) {
437+ func startRenewalReminderScheduler (subscriptionService * service.SubscriptionService , emailService * service.EmailService , pushoverService * service.PushoverService , webhookService * service. WebhookService , settingsService * service.SettingsService ) {
413438 // Run immediately on startup (after a short delay to let server initialize)
414439 go func () {
415440 time .Sleep (30 * time .Second ) // Wait 30 seconds for server to fully start
416- checkAndSendRenewalReminders (subscriptionService , emailService , pushoverService , settingsService )
441+ checkAndSendRenewalReminders (subscriptionService , emailService , pushoverService , webhookService , settingsService )
417442 }()
418443
419444 // Then run daily at midnight
@@ -430,14 +455,14 @@ func startRenewalReminderScheduler(subscriptionService *service.SubscriptionServ
430455 log .Printf ("Panic in renewal reminder check: %v" , r )
431456 }
432457 }()
433- checkAndSendRenewalReminders (subscriptionService , emailService , pushoverService , settingsService )
458+ checkAndSendRenewalReminders (subscriptionService , emailService , pushoverService , webhookService , settingsService )
434459 }()
435460 }
436461 }()
437462}
438463
439464// checkAndSendRenewalReminders checks for subscriptions needing reminders and sends emails and Pushover notifications
440- func checkAndSendRenewalReminders (subscriptionService * service.SubscriptionService , emailService * service.EmailService , pushoverService * service.PushoverService , settingsService * service.SettingsService ) {
465+ func checkAndSendRenewalReminders (subscriptionService * service.SubscriptionService , emailService * service.EmailService , pushoverService * service.PushoverService , webhookService * service. WebhookService , settingsService * service.SettingsService ) {
441466 // Check if renewal reminders are enabled
442467 enabled , err := settingsService .GetBoolSetting ("renewal_reminders" , false )
443468 if err != nil || ! enabled {
@@ -470,10 +495,11 @@ func checkAndSendRenewalReminders(subscriptionService *service.SubscriptionServi
470495 for sub , daysUntil := range subscriptions {
471496 emailErr := emailService .SendRenewalReminder (sub , daysUntil )
472497 pushoverErr := pushoverService .SendRenewalReminder (sub , daysUntil )
498+ webhookErr := webhookService .SendRenewalReminder (sub , daysUntil )
473499
474- // If both fail, count as failed; otherwise consider it sent
475- if emailErr != nil && pushoverErr != nil {
476- log .Printf ("Error sending renewal reminder for subscription %s (ID: %d): email=%v, pushover=%v" , sub .Name , sub .ID , emailErr , pushoverErr )
500+ // If all fail, count as failed; otherwise consider it sent
501+ if emailErr != nil && pushoverErr != nil && webhookErr != nil {
502+ log .Printf ("Error sending renewal reminder for subscription %s (ID: %d): email=%v, pushover=%v, webhook=%v " , sub .Name , sub .ID , emailErr , pushoverErr , webhookErr )
477503 failedCount ++
478504 } else {
479505 // Mark reminder as sent for this renewal date
@@ -490,12 +516,20 @@ func checkAndSendRenewalReminders(subscriptionService *service.SubscriptionServi
490516 log .Printf ("Warning: Failed to update last reminder sent for subscription %s (ID: %d): %v" , sub .Name , sub .ID , updateErr )
491517 }
492518
519+ var failed []string
493520 if emailErr != nil {
494- log .Printf ("Sent Pushover renewal reminder for subscription %s (renews in %d days) - email failed: %v" , sub .Name , daysUntil , emailErr )
495- } else if pushoverErr != nil {
496- log .Printf ("Sent email renewal reminder for subscription %s (renews in %d days) - Pushover failed: %v" , sub .Name , daysUntil , pushoverErr )
521+ failed = append (failed , fmt .Sprintf ("email=%v" , emailErr ))
522+ }
523+ if pushoverErr != nil {
524+ failed = append (failed , fmt .Sprintf ("pushover=%v" , pushoverErr ))
525+ }
526+ if webhookErr != nil {
527+ failed = append (failed , fmt .Sprintf ("webhook=%v" , webhookErr ))
528+ }
529+ if len (failed ) > 0 {
530+ log .Printf ("Sent renewal reminder for subscription %s (renews in %d days) - some channels failed: %s" , sub .Name , daysUntil , strings .Join (failed , ", " ))
497531 } else {
498- log .Printf ("Sent renewal reminders (email and Pushover) for subscription %s (renews in %d days)" , sub .Name , daysUntil )
532+ log .Printf ("Sent renewal reminders for subscription %s (renews in %d days)" , sub .Name , daysUntil )
499533 }
500534 sentCount ++
501535 }
@@ -506,11 +540,11 @@ func checkAndSendRenewalReminders(subscriptionService *service.SubscriptionServi
506540
507541// startCancellationReminderScheduler starts a background goroutine that checks for
508542// upcoming cancellations and sends reminder emails and Pushover notifications daily
509- func startCancellationReminderScheduler (subscriptionService * service.SubscriptionService , emailService * service.EmailService , pushoverService * service.PushoverService , settingsService * service.SettingsService ) {
543+ func startCancellationReminderScheduler (subscriptionService * service.SubscriptionService , emailService * service.EmailService , pushoverService * service.PushoverService , webhookService * service. WebhookService , settingsService * service.SettingsService ) {
510544 // Run immediately on startup (after a short delay to let server initialize)
511545 go func () {
512546 time .Sleep (30 * time .Second ) // Wait 30 seconds for server to fully start
513- checkAndSendCancellationReminders (subscriptionService , emailService , pushoverService , settingsService )
547+ checkAndSendCancellationReminders (subscriptionService , emailService , pushoverService , webhookService , settingsService )
514548 }()
515549
516550 // Then run daily at midnight
@@ -527,14 +561,14 @@ func startCancellationReminderScheduler(subscriptionService *service.Subscriptio
527561 log .Printf ("Panic in cancellation reminder check: %v" , r )
528562 }
529563 }()
530- checkAndSendCancellationReminders (subscriptionService , emailService , pushoverService , settingsService )
564+ checkAndSendCancellationReminders (subscriptionService , emailService , pushoverService , webhookService , settingsService )
531565 }()
532566 }
533567 }()
534568}
535569
536570// checkAndSendCancellationReminders checks for subscriptions needing cancellation reminders and sends emails and Pushover notifications
537- func checkAndSendCancellationReminders (subscriptionService * service.SubscriptionService , emailService * service.EmailService , pushoverService * service.PushoverService , settingsService * service.SettingsService ) {
571+ func checkAndSendCancellationReminders (subscriptionService * service.SubscriptionService , emailService * service.EmailService , pushoverService * service.PushoverService , webhookService * service. WebhookService , settingsService * service.SettingsService ) {
538572 // Check if cancellation reminders are enabled
539573 enabled , err := settingsService .GetBoolSetting ("cancellation_reminders" , false )
540574 if err != nil || ! enabled {
@@ -567,10 +601,11 @@ func checkAndSendCancellationReminders(subscriptionService *service.Subscription
567601 for sub , daysUntil := range subscriptions {
568602 emailErr := emailService .SendCancellationReminder (sub , daysUntil )
569603 pushoverErr := pushoverService .SendCancellationReminder (sub , daysUntil )
604+ webhookErr := webhookService .SendCancellationReminder (sub , daysUntil )
570605
571- // If both fail, count as failed; otherwise consider it sent
572- if emailErr != nil && pushoverErr != nil {
573- log .Printf ("Error sending cancellation reminder for subscription %s (ID: %d): email=%v, pushover=%v" , sub .Name , sub .ID , emailErr , pushoverErr )
606+ // If all fail, count as failed; otherwise consider it sent
607+ if emailErr != nil && pushoverErr != nil && webhookErr != nil {
608+ log .Printf ("Error sending cancellation reminder for subscription %s (ID: %d): email=%v, pushover=%v, webhook=%v " , sub .Name , sub .ID , emailErr , pushoverErr , webhookErr )
574609 failedCount ++
575610 } else {
576611 // Mark reminder as sent for this cancellation date
@@ -587,12 +622,20 @@ func checkAndSendCancellationReminders(subscriptionService *service.Subscription
587622 log .Printf ("Warning: Failed to update last cancellation reminder sent for subscription %s (ID: %d): %v" , sub .Name , sub .ID , updateErr )
588623 }
589624
625+ var failed []string
590626 if emailErr != nil {
591- log .Printf ("Sent Pushover cancellation reminder for subscription %s (ends in %d days) - email failed: %v" , sub .Name , daysUntil , emailErr )
592- } else if pushoverErr != nil {
593- log .Printf ("Sent email cancellation reminder for subscription %s (ends in %d days) - Pushover failed: %v" , sub .Name , daysUntil , pushoverErr )
627+ failed = append (failed , fmt .Sprintf ("email=%v" , emailErr ))
628+ }
629+ if pushoverErr != nil {
630+ failed = append (failed , fmt .Sprintf ("pushover=%v" , pushoverErr ))
631+ }
632+ if webhookErr != nil {
633+ failed = append (failed , fmt .Sprintf ("webhook=%v" , webhookErr ))
634+ }
635+ if len (failed ) > 0 {
636+ log .Printf ("Sent cancellation reminder for subscription %s (ends in %d days) - some channels failed: %s" , sub .Name , daysUntil , strings .Join (failed , ", " ))
594637 } else {
595- log .Printf ("Sent cancellation reminders (email and Pushover) for subscription %s (ends in %d days)" , sub .Name , daysUntil )
638+ log .Printf ("Sent cancellation reminders for subscription %s (ends in %d days)" , sub .Name , daysUntil )
596639 }
597640 sentCount ++
598641 }
0 commit comments