This commit is contained in:
Tomas Dvorak
2026-01-26 08:13:18 +01:00
parent aa036b6550
commit dfc079288f
505 changed files with 95755 additions and 5712 deletions
+498 -117
View File
@@ -29,7 +29,11 @@ func GetNewsletterAutomation() *services.NewsletterAutomation {
func SetupRoutes(api *gin.RouterGroup, db *gorm.DB) {
emailService := email.NewEmailService(config.AppConfig, db)
// Initialize services
weatherService := services.NewWeatherService(db)
// Initialize controllers
healthController := &controllers.HealthController{DB: db}
baseController := &controllers.BaseController{DB: db}
authController := controllers.NewAuthController(db)
contactController := controllers.NewContactController(db, emailService)
@@ -47,24 +51,47 @@ func SetupRoutes(api *gin.RouterGroup, db *gorm.DB) {
navigationController := controllers.NewNavigationController(db)
pollController := controllers.NewPollController(db)
sweepstakesController := controllers.NewSweepstakesController(db, emailService)
i18nController := controllers.NewI18nController()
clothingController := controllers.NewClothingController(db)
pageElementConfigController := controllers.NewPageElementConfigController(db)
articleController := controllers.NewArticleController(db)
var eshopAdminController *controllers.EshopAdminController
if config.AppConfig.EshopEnabled {
eshopAdminController = controllers.NewEshopAdminController(db)
}
myuibrixController := &controllers.MyUIbrixController{DB: db}
editorPreviewController := controllers.NewEditorPreviewController(db)
shortLinkController := controllers.NewShortLinkController(db)
commentController := controllers.NewCommentController(db)
engagementController := controllers.NewEngagementController(db, emailService)
facrController := controllers.NewFACRController(db)
manualFACRAdminController := controllers.NewManualFACRAdminController(db)
youtubeController := controllers.NewYouTubeController(db)
umamiController := controllers.NewUmamiController()
imageProcessingController := &controllers.ImageProcessingController{}
errorController := controllers.NewErrorController(db)
directoryController := controllers.NewDirectoryController(db)
financialController := controllers.NewFinancialController(db)
invoiceController := controllers.NewInvoiceController(db)
ticketController := controllers.NewTicketController(db)
ticketCheckoutController := controllers.NewTicketCheckoutController(db)
qrCodeController := controllers.NewQRCodeController(db)
// Facility management controllers
facilityController := controllers.NewFacilityController()
equipmentController := controllers.NewEquipmentController()
maintenanceController := controllers.NewMaintenanceController()
facilityWeatherController := controllers.NewWeatherController("") // For facilities
adminWeatherController := controllers.NewAdminWeatherController(weatherService) // For admin dashboard
bookingCalendarController := controllers.NewBookingCalendarController()
// API v1 group
{
// Health check
api.GET("/health", baseController.HealthCheck)
api.GET("/health/live", healthController.Liveness)
api.GET("/health/ready", healthController.Readiness)
api.GET("/health/full", healthController.Health)
// CSRF token for cookie-based clients
api.GET("/csrf-token", middleware.GetCSRFToken)
@@ -85,6 +112,10 @@ func SetupRoutes(api *gin.RouterGroup, db *gorm.DB) {
// Public shortlink creation for visitors (same-site only)
api.POST("/shortlinks/public", middleware.RateLimit(30, time.Minute), shortLinkController.PublicCreateShortLink)
// Public i18n endpoints
api.GET("/i18n/languages", i18nController.GetLanguages)
api.GET("/i18n/translations/:language", i18nController.GetTranslations)
// Email tracking (public)
api.GET("/email/open.gif", emailController.OpenPixel)
api.GET("/email/click", emailController.ClickRedirect)
@@ -103,6 +134,11 @@ func SetupRoutes(api *gin.RouterGroup, db *gorm.DB) {
api.POST("/errors", middleware.RateLimit(120, time.Minute), errorController.Ingest)
// Weather endpoints (public)
api.GET("/weather", adminWeatherController.GetWeather)
api.GET("/weather/club", adminWeatherController.GetWeatherForClub)
api.GET("/weather/match", adminWeatherController.GetWeatherForMatch)
// Auth routes
auth := api.Group("/auth")
{
@@ -149,6 +185,9 @@ func SetupRoutes(api *gin.RouterGroup, db *gorm.DB) {
// CSRF protect state-changing requests when relying on cookies (Bearer tokens are auto-exempt)
protected.Use(middleware.CSRFProtection())
{
// User language preference
protected.POST("/i18n/user-language", i18nController.SetUserLanguage)
// Sweepstakes (protected)
protected.POST("/sweepstakes/:id/enter", middleware.RateLimit(30, time.Minute), sweepstakesController.Enter)
protected.POST("/sweepstakes/:id/played", sweepstakesController.MarkVisualPlayed)
@@ -205,6 +244,13 @@ func SetupRoutes(api *gin.RouterGroup, db *gorm.DB) {
ai.POST("/about/generate", aiController.GenerateAboutPage)
ai.POST("/css/generate", aiController.GenerateCSS)
ai.POST("/instagram/generate", aiController.GenerateInstagram)
ai.POST("/instagram/images", aiController.GenerateInstagramImages)
ai.POST("/main-image/generate", aiController.GenerateMainImage)
ai.POST("/ocr/process", aiController.ProcessOCR)
ai.POST("/voice/transcribe", aiController.TranscribeAudio)
ai.POST("/test-parse", aiController.TestAIParse)
ai.POST("/translate", aiController.TranslateText)
ai.GET("/usage/status", aiController.GetAIUsageStatus)
}
// User profile
@@ -245,7 +291,7 @@ func SetupRoutes(api *gin.RouterGroup, db *gorm.DB) {
// Teams (protected)
teams := protected.Group("/teams")
teams.Use(middleware.RoleAuth("admin"))
teams.Use(middleware.RoleAuth("editor"))
{
teams.POST("", baseController.CreateTeam)
teams.PUT("/:id", baseController.UpdateTeam)
@@ -254,7 +300,7 @@ func SetupRoutes(api *gin.RouterGroup, db *gorm.DB) {
// Players (protected)
players := protected.Group("/players")
players.Use(middleware.RoleAuth("admin"))
players.Use(middleware.RoleAuth("editor"))
{
players.POST("", baseController.CreatePlayer)
players.PUT("/:id", baseController.UpdatePlayer)
@@ -283,6 +329,19 @@ func SetupRoutes(api *gin.RouterGroup, db *gorm.DB) {
admin := protected.Group("/admin")
admin.Use(middleware.RoleAuth("admin"))
{
// i18n management
i18nAdmin := admin.Group("/i18n")
{
i18nAdmin.GET("/languages", i18nController.AdminGetAllLanguages)
i18nAdmin.POST("/languages", i18nController.AdminCreateLanguage)
i18nAdmin.PUT("/languages/:id", i18nController.AdminUpdateLanguage)
i18nAdmin.DELETE("/languages/:id", i18nController.AdminDeleteLanguage)
i18nAdmin.GET("/translations", i18nController.AdminGetTranslations)
i18nAdmin.POST("/translations", i18nController.AdminCreateTranslation)
i18nAdmin.PUT("/translations/:id", i18nController.AdminUpdateTranslation)
i18nAdmin.DELETE("/translations/:id", i18nController.AdminDeleteTranslation)
}
// Errors
admin.GET("/errors", errorController.AdminList)
admin.GET("/errors/:id", errorController.AdminGet)
@@ -290,6 +349,11 @@ func SetupRoutes(api *gin.RouterGroup, db *gorm.DB) {
admin.GET("/errors/external", errorController.AdminListExternal)
admin.GET("/errors/external/:id", errorController.AdminGetExternal)
// Directory Service
admin.POST("/directory/register", directoryController.RegisterInstance)
admin.POST("/directory/heartbeat", directoryController.Heartbeat)
admin.GET("/directory/info", directoryController.GetInstanceInfo)
// Comments
commentsAdmin := admin.Group("/comments")
{
@@ -301,8 +365,41 @@ func SetupRoutes(api *gin.RouterGroup, db *gorm.DB) {
commentsAdmin.GET("/unban-requests", commentController.AdminListUnban)
commentsAdmin.POST("/unban-requests/:id/resolve", commentController.AdminResolveUnban)
}
// E-shop product management (admin)
if config.AppConfig.EshopEnabled {
eshop := admin.Group("/eshop")
{
// Products
eshop.GET("/products", eshopAdminController.AdminListProducts)
eshop.GET("/products/:id", eshopAdminController.AdminGetProduct)
eshop.POST("/products", eshopAdminController.AdminCreateProduct)
eshop.PUT("/products/:id", eshopAdminController.AdminUpdateProduct)
eshop.DELETE("/products/:id", eshopAdminController.AdminDeleteProduct)
// Variants
eshop.GET("/products/:id/variants", eshopAdminController.AdminListVariants)
eshop.POST("/products/:id/variants", eshopAdminController.AdminCreateVariant)
eshop.PUT("/variants/:id", eshopAdminController.AdminUpdateVariant)
eshop.DELETE("/variants/:id", eshopAdminController.AdminDeleteVariant)
}
}
// Admin-only endpoints for managing sponsors, etc. (user CRUD removed; no handlers defined)
// Manual FACR (manual club data mode) CSV templates & imports
manual := admin.Group("/manual")
{
manual.GET("/competitions", manualFACRAdminController.ListManualCompetitions)
manual.POST("/competitions", manualFACRAdminController.CreateManualCompetition)
manual.PUT("/competitions/:id", manualFACRAdminController.UpdateManualCompetition)
manual.DELETE("/competitions/:id", manualFACRAdminController.DeleteManualCompetition)
manual.GET("/matches/template", manualFACRAdminController.GetMatchesTemplateCSV)
manual.GET("/tables/template", manualFACRAdminController.GetTablesTemplateCSV)
manual.POST("/matches/import", manualFACRAdminController.ImportMatchesCSV)
manual.POST("/tables/import", manualFACRAdminController.ImportTablesCSV)
manual.POST("/matches/import-json", manualFACRAdminController.ImportMatchesJSON)
manual.POST("/tables/import-json", manualFACRAdminController.ImportTablesJSON)
}
// Competition aliases (admin)
admin.GET("/competition-aliases", baseController.GetCompetitionAliases)
admin.PUT("/competition-aliases/:code", baseController.PutCompetitionAlias)
@@ -385,6 +482,7 @@ func SetupRoutes(api *gin.RouterGroup, db *gorm.DB) {
// Newsletter management
admin.GET("/newsletter/subscribers", contactController.GetNewsletterSubscribers)
admin.POST("/newsletter/subscribers", contactController.CreateNewsletterSubscriber)
admin.POST("/newsletter/send", contactController.SendNewsletter)
admin.POST("/newsletter/preview", contactController.PreviewNewsletter)
admin.POST("/newsletter/test", contactController.SendNewsletterTest)
@@ -484,6 +582,44 @@ func SetupRoutes(api *gin.RouterGroup, db *gorm.DB) {
clothing.POST("/reorder", clothingController.UpdateClothingOrder)
}
// Admin QR codes management
qr := admin.Group("/qr-codes")
{
qr.GET("", qrCodeController.GetQRCodes)
qr.POST("", qrCodeController.CreateQRCode)
qr.GET("/:id", qrCodeController.GetQRCode)
qr.PUT("/:id", qrCodeController.UpdateQRCode)
qr.DELETE("/:id", qrCodeController.DeleteQRCode)
}
// Facility management (admin)
facilities := admin.Group("/facilities")
{
facilities.GET("", facilityController.GetFacilities)
facilities.GET("/:id", facilityController.GetFacility)
facilities.POST("", facilityController.CreateFacility)
facilities.PUT("/:id", facilityController.UpdateFacility)
facilities.DELETE("/:id", facilityController.DeleteFacility)
facilities.GET("/:id/bookings", facilityController.GetFacilityBookings)
}
// Equipment management (admin)
equipment := admin.Group("/equipment")
{
equipment.GET("", equipmentController.GetEquipment)
equipment.POST("", equipmentController.CreateEquipment)
equipment.PUT("/:id", equipmentController.UpdateEquipment)
equipment.DELETE("/:id", equipmentController.DeleteEquipment)
}
// Maintenance management (admin)
maintenance := admin.Group("/maintenance")
{
maintenance.GET("", maintenanceController.GetMaintenance)
maintenance.POST("", maintenanceController.CreateMaintenance)
maintenance.PUT("/:id", maintenanceController.UpdateMaintenance)
}
// Polls management (admin)
polls := admin.Group("/polls")
{
@@ -560,125 +696,370 @@ func SetupRoutes(api *gin.RouterGroup, db *gorm.DB) {
sw.POST("/:id/finalize", sweepstakesController.AdminFinalize)
}
// Financial management (admin)
financial := admin.Group("/financial")
{
// Dashboard and overview
financial.GET("/dashboard", financialController.GetFinancialDashboard)
// Budget management
budgets := financial.Group("/budgets")
{
budgets.GET("", financialController.GetBudgets)
budgets.GET("/:id", financialController.GetBudget)
budgets.POST("", financialController.CreateBudget)
budgets.PUT("/:id", financialController.UpdateBudget)
budgets.DELETE("/:id", financialController.DeleteBudget)
budgets.GET("/categories", financialController.GetBudgetCategories)
budgets.GET("/overview", financialController.GetBudgetOverview)
}
// Sponsorship management
sponsorships := financial.Group("/sponsorships")
{
sponsorships.GET("", financialController.GetSponsorships)
sponsorships.GET("/:id", financialController.GetSponsorship)
sponsorships.POST("", financialController.CreateSponsorship)
sponsorships.PUT("/:id", financialController.UpdateSponsorship)
sponsorships.DELETE("/:id", financialController.DeleteSponsorship)
sponsorships.GET("/overview", financialController.GetSponsorshipOverview)
}
// Expense management
expenses := financial.Group("/expenses")
{
expenses.GET("", financialController.GetExpenses)
expenses.GET("/:id", financialController.GetExpense)
expenses.POST("", financialController.CreateExpense)
expenses.PUT("/:id", financialController.UpdateExpense)
expenses.DELETE("/:id", financialController.DeleteExpense)
expenses.PATCH("/:id/approve", financialController.ApproveExpense)
expenses.PATCH("/:id/reject", financialController.RejectExpense)
expenses.POST("/upload-receipt", financialController.UploadReceipt)
expenses.GET("/categories", financialController.GetExpenseCategories)
expenses.GET("/overview", financialController.GetExpenseOverview)
}
// Reports and analytics
reports := financial.Group("/reports")
{
reports.GET("", financialController.GetFinancialReports)
reports.POST("/generate", financialController.GenerateFinancialReport)
reports.GET("/:id", financialController.GetFinancialReport)
reports.DELETE("/:id", financialController.DeleteFinancialReport)
}
// Settings
financial.GET("/settings", financialController.GetFinancialSettings)
financial.PUT("/settings", financialController.UpdateFinancialSettings)
}
// Invoice management (admin)
invoices := admin.Group("/invoices")
{
// Invoice CRUD
invoices.GET("", invoiceController.GetInvoices)
invoices.GET("/:id", invoiceController.GetInvoice)
invoices.POST("", invoiceController.CreateInvoice)
invoices.PUT("/:id", invoiceController.UpdateInvoice)
invoices.DELETE("/:id", invoiceController.DeleteInvoice)
// Invoice operations
invoices.POST("/:id/generate-pdf", invoiceController.GenerateInvoicePDF)
invoices.POST("/:id/send", invoiceController.SendInvoice)
// Customer management
customers := invoices.Group("/customers")
{
customers.GET("", invoiceController.GetCustomers)
customers.GET("/:id", invoiceController.GetCustomer)
customers.POST("", invoiceController.CreateCustomer)
customers.PUT("/:id", invoiceController.UpdateCustomer)
customers.DELETE("/:id", invoiceController.DeleteCustomer)
customers.GET("/autofill", invoiceController.AutofillCustomerByICO)
}
// Settings
invoices.GET("/settings", invoiceController.GetInvoiceSettings)
invoices.PUT("/settings", invoiceController.UpdateInvoiceSettings)
invoices.GET("/ares-search/:ico", invoiceController.SearchSupplierByICO)
}
}
// Protected routes end
RegisterAnalyticsRoutes(api, db)
RegisterContactInfoRoutes(api, db)
api.POST("/upload", middleware.RateLimit(30, time.Minute), baseController.UploadImage)
imageProcessing := api.Group("/image-processing")
imageProcessing.Use(middleware.JWTAuth(db))
imageProcessing.Use(middleware.CSRFProtection())
imageProcessing.Use(middleware.RoleAuth("editor"))
{
imageProcessing.POST("/process", imageProcessingController.ProcessImage)
imageProcessing.POST("/crop-upload", imageProcessingController.CropAndUpload)
imageProcessing.POST("/quick-edit", imageProcessingController.QuickEdit)
}
api.GET("/scoreboard", scoreboardController.GetPublic)
api.GET("/scoreboard/colors/derive", scoreboardController.DeriveColors)
api.GET("/scoreboard/sponsors", scoreboardController.ListSponsors)
api.GET("/scoreboard/qr", scoreboardController.GetQR)
api.GET("/settings", baseController.GetPublicSettings)
api.GET("/competition-aliases", baseController.GetPublicCompetitionAliases)
api.GET("/public/team-logo-overrides", baseController.GetPublicTeamLogoOverrides)
// Articles (public; use optional auth so admin/editor can see drafts in list when requesting published=false)
articlesPub := api.Group("/articles")
articlesPub.Use(middleware.JWTOptional(db))
{
articlesPub.GET("/featured", baseController.GetFeaturedArticles)
articlesPub.GET("", baseController.GetArticles)
articlesPub.GET("/slug/:slug", baseController.GetArticleBySlug)
articlesPub.GET("/:id", baseController.GetArticle)
articlesPub.POST("/:id/read", baseController.IncrementArticleRead)
articlesPub.POST("/:id/track-view", baseController.TrackArticleView)
articlesPub.GET("/:id/match-link", baseController.GetArticleMatchLink)
}
api.GET("/categories", baseController.GetCategories)
api.GET("/youtube/videos", youtubeController.GetYouTubeVideos)
api.GET("/about", aboutController.GetPublicAboutPage)
api.GET("/teams", baseController.GetTeams)
api.GET("/teams/:id", baseController.GetTeam)
api.GET("/players", baseController.GetPlayers)
api.GET("/players/:id", baseController.GetPlayer)
api.GET("/sponsors", baseController.GetSponsors)
api.GET("/banners", baseController.GetBanners)
api.GET("/matches", baseController.GetMatches)
api.GET("/matches/history", baseController.GetMatchesHistory)
api.GET("/standings", baseController.GetStandings)
api.GET("/gallery/albums", galleryController.GetGalleryAlbums)
api.GET("/gallery/albums/:id", galleryController.GetGalleryAlbum)
api.GET("/gallery/proxy-image", galleryController.ProxyImage)
api.GET("/zonerama/album", baseController.GetZoneramaAlbum)
api.GET("/zonerama-album", baseController.GetZoneramaAlbum)
api.GET("/zonerama/picks", baseController.GetZoneramaPicks)
api.GET("/clothing", clothingController.GetClothing)
api.GET("/sweepstakes/current", sweepstakesController.GetCurrent)
api.GET("/sweepstakes/:id/visual", sweepstakesController.PublicVisualData)
pollsPub := api.Group("/polls")
pollsPub.Use(middleware.JWTOptional(db))
{
pollsPub.GET("", pollController.GetPolls)
pollsPub.GET("/:id", pollController.GetPoll)
pollsPub.POST("/:id/vote", middleware.RateLimit(10, time.Minute), pollController.Vote)
pollsPub.GET("/:id/results", pollController.GetPollResults)
}
api.POST("/contact", middleware.RateLimit(10, time.Minute), contactController.SubmitContactForm)
api.POST("/newsletter/subscribe", middleware.RateLimit(30, time.Minute), contactController.SubscribeToNewsletter)
api.POST("/newsletter/unsubscribe/:email", middleware.RateLimit(30, time.Minute), contactController.UnsubscribeFromNewsletter)
api.POST("/newsletter/setup", middleware.RateLimit(30, time.Minute), contactController.SetupNewsletterPreferences)
api.GET("/newsletter/preferences", contactController.GetNewsletterPreferencesByToken)
api.POST("/newsletter/preferences", contactController.SaveNewsletterPreferencesByToken)
api.POST("/newsletter/unsubscribe-token", contactController.UnsubscribeByToken)
facr := api.Group("/facr")
{
facr.GET("/club/search", facrController.SearchClubs)
facr.GET("/club/:type/:id", facrController.GetClubInfo)
facr.GET("/club/:type/:id/table", facrController.GetClubTables)
}
// Public facility management routes
api.GET("/facilities", facilityController.GetPublicFacilities)
api.GET("/facilities/calendar", bookingCalendarController.GetCalendarEvents)
api.POST("/facilities/bookings", middleware.JWTAuth(db), facilityController.CreateBooking)
api.GET("/facilities/:id", facilityController.GetFacility)
api.GET("/facilities/:id/availability", facilityController.GetFacilityAvailability)
api.GET("/facilities/:id/weather", facilityWeatherController.GetWeatherForecast)
// Public ticket endpoints
tickets := api.Group("/tickets")
{
tickets.GET("/available", ticketController.GetAvailableTickets)
tickets.GET("/campaigns", ticketController.GetCampaigns)
tickets.GET("/campaigns/:id", ticketController.GetCampaign)
// Ticket reservation and validation require auth
tickets.Use(middleware.JWTAuth(db))
{
tickets.POST("/reserve", ticketController.ReserveTickets)
tickets.POST("/:id/confirm", ticketController.ConfirmTicket)
tickets.POST("/:id/validate", ticketController.ValidateTicket)
}
}
// Ticket checkout (requires auth)
ticketCheckout := api.Group("/ticket-checkout")
ticketCheckout.Use(middleware.JWTAuth(db))
{
ticketCheckout.POST("/order", ticketCheckoutController.CreateTicketOrder)
ticketCheckout.POST("/orders/:order_id/complete", ticketCheckoutController.CompleteTicketOrder)
ticketCheckout.GET("/orders", ticketCheckoutController.GetTicketOrders)
}
// User tickets and QR codes (require auth)
userTickets := api.Group("/tickets")
userTickets.Use(middleware.JWTAuth(db))
{
userTickets.GET("/my-tickets", ticketController.GetMyTickets)
userTickets.GET("/:id/qr", qrCodeController.GenerateTicketQR)
userTickets.GET("/:id/qr-download", qrCodeController.DownloadTicketQR)
userTickets.POST("/validate-qr", qrCodeController.ValidateTicketFromQR)
}
api.GET("/umami/config", umamiController.GetUmamiConfig)
api.POST("/umami/initialize-setup", umamiController.InitializeUmamiSetup)
// Adblock-safe public alias for config (avoids 'umami' keyword)
api.GET("/insights/config", umamiController.GetUmamiConfig)
// ... (rest of the code remains the same)
umami := api.Group("/admin/umami")
umami.Use(middleware.JWTAuth(db))
umami.Use(middleware.RoleAuth("admin"))
{
umami.POST("/initialize", umamiController.InitializeUmami)
umami.GET("/stats", umamiController.GetStats)
umami.GET("/metrics/:type", umamiController.GetMetrics)
umami.GET("/pageviews", umamiController.GetPageviews)
}
// Adblock-safe admin aliases (avoid 'umami'/'metrics' in path)
insights := api.Group("/admin/insights")
insights.Use(middleware.JWTAuth(db))
insights.Use(middleware.RoleAuth("admin"))
{
insights.POST("/initialize", umamiController.InitializeUmami)
insights.GET("/summary", umamiController.GetStats)
insights.GET("/breakdown/:type", umamiController.GetMetrics)
insights.GET("/pageviews", umamiController.GetPageviews)
}
// Accountant routes (role: accountant)
// Accountants have access to financial management, budgets, expenses, sponsorships, and invoices
accountant := api.Group("/accountant")
accountant.Use(middleware.JWTAuth(db))
accountant.Use(middleware.RoleAuth("accountant"))
{
// Financial dashboard
accountant.GET("/dashboard", financialController.GetFinancialDashboard)
// Budget management
budgets := accountant.Group("/budgets")
{
budgets.GET("", financialController.GetBudgets)
budgets.GET("/:id", financialController.GetBudget)
budgets.POST("", financialController.CreateBudget)
budgets.PUT("/:id", financialController.UpdateBudget)
budgets.DELETE("/:id", financialController.DeleteBudget)
budgets.GET("/categories", financialController.GetBudgetCategories)
budgets.GET("/overview", financialController.GetBudgetOverview)
}
// Sponsorship management
sponsorships := accountant.Group("/sponsorships")
{
sponsorships.GET("", financialController.GetSponsorships)
sponsorships.GET("/:id", financialController.GetSponsorship)
sponsorships.POST("", financialController.CreateSponsorship)
sponsorships.PUT("/:id", financialController.UpdateSponsorship)
sponsorships.DELETE("/:id", financialController.DeleteSponsorship)
sponsorships.GET("/overview", financialController.GetSponsorshipOverview)
}
// Expense management
expenses := accountant.Group("/expenses")
{
expenses.GET("", financialController.GetExpenses)
expenses.GET("/:id", financialController.GetExpense)
expenses.POST("", financialController.CreateExpense)
expenses.PUT("/:id", financialController.UpdateExpense)
expenses.DELETE("/:id", financialController.DeleteExpense)
expenses.PATCH("/:id/approve", financialController.ApproveExpense)
expenses.PATCH("/:id/reject", financialController.RejectExpense)
expenses.POST("/upload-receipt", financialController.UploadReceipt)
expenses.GET("/categories", financialController.GetExpenseCategories)
expenses.GET("/overview", financialController.GetExpenseOverview)
}
// Financial reports
reports := accountant.Group("/reports")
{
reports.GET("", financialController.GetFinancialReports)
reports.POST("/generate", financialController.GenerateFinancialReport)
reports.GET("/:id", financialController.GetFinancialReport)
reports.DELETE("/:id", financialController.DeleteFinancialReport)
}
// Financial settings
accountant.GET("/settings", financialController.GetFinancialSettings)
accountant.PUT("/settings", financialController.UpdateFinancialSettings)
// Invoice management
invoices := accountant.Group("/invoices")
{
// Invoice CRUD
invoices.GET("", invoiceController.GetInvoices)
invoices.GET("/:id", invoiceController.GetInvoice)
invoices.POST("", invoiceController.CreateInvoice)
invoices.PUT("/:id", invoiceController.UpdateInvoice)
invoices.DELETE("/:id", invoiceController.DeleteInvoice)
// Invoice operations
invoices.POST("/:id/generate-pdf", invoiceController.GenerateInvoicePDF)
invoices.POST("/:id/send", invoiceController.SendInvoice)
// Customer management
customers := invoices.Group("/customers")
{
customers.GET("", invoiceController.GetCustomers)
customers.GET("/:id", invoiceController.GetCustomer)
customers.POST("", invoiceController.CreateCustomer)
customers.PUT("/:id", invoiceController.UpdateCustomer)
customers.DELETE("/:id", invoiceController.DeleteCustomer)
customers.GET("/autofill", invoiceController.AutofillCustomerByICO)
}
// Invoice settings
invoices.GET("/settings", invoiceController.GetInvoiceSettings)
invoices.PUT("/settings", invoiceController.UpdateInvoiceSettings)
invoices.GET("/ares-search/:ico", invoiceController.SearchSupplierByICO)
}
// Ticket management (admin)
ticketsAdmin := admin.Group("/tickets")
{
// Campaigns
ticketsAdmin.GET("/campaigns", ticketController.AdminGetCampaigns)
ticketsAdmin.POST("/campaigns", ticketController.AdminCreateCampaign)
ticketsAdmin.GET("/campaigns/:id", ticketController.GetCampaign)
ticketsAdmin.PUT("/campaigns/:id", ticketController.AdminUpdateCampaign)
ticketsAdmin.DELETE("/campaigns/:id", ticketController.AdminDeleteCampaign)
// Ticket types
ticketsAdmin.GET("/types", ticketController.AdminGetTicketTypes)
ticketsAdmin.POST("/types", ticketController.AdminCreateTicketType)
ticketsAdmin.GET("/types/:id", ticketController.AdminGetTicketType)
ticketsAdmin.PUT("/types/:id", ticketController.AdminUpdateTicketType)
ticketsAdmin.DELETE("/types/:id", ticketController.AdminDeleteTicketType)
// Ticket management
ticketsAdmin.GET("", ticketController.AdminGetTickets)
ticketsAdmin.GET("/:id", ticketController.AdminGetTicket)
ticketsAdmin.PATCH("/:id/status", ticketController.AdminUpdateTicketStatus)
ticketsAdmin.POST("/:id/validate", ticketController.AdminValidateTicket)
ticketsAdmin.GET("/sales/overview", ticketController.AdminGetSalesOverview)
ticketsAdmin.GET("/sales/export", ticketController.AdminExportSales)
}
}
}
RegisterAnalyticsRoutes(api, db)
api.GET("/umami/config", umamiController.GetUmamiConfig)
api.POST("/umami/initialize-setup", umamiController.InitializeUmamiSetup)
// Adblock-safe public alias for config (avoids 'umami' keyword)
api.GET("/insights/config", umamiController.GetUmamiConfig)
umami := api.Group("/admin/umami")
umami.Use(middleware.JWTAuth(db))
umami.Use(middleware.RoleAuth("admin"))
{
umami.POST("/initialize", umamiController.InitializeUmami)
umami.GET("/stats", umamiController.GetStats)
umami.GET("/metrics/:type", umamiController.GetMetrics)
umami.GET("/pageviews", umamiController.GetPageviews)
}
// Adblock-safe admin aliases (avoid 'umami'/'metrics' in path)
insights := api.Group("/admin/insights")
insights.Use(middleware.JWTAuth(db))
insights.Use(middleware.RoleAuth("admin"))
{
insights.POST("/initialize", umamiController.InitializeUmami)
insights.GET("/summary", umamiController.GetStats)
insights.GET("/breakdown/:type", umamiController.GetMetrics)
insights.GET("/pageviews", umamiController.GetPageviews)
}
RegisterContactInfoRoutes(api, db)
api.POST("/upload", middleware.RateLimit(30, time.Minute), baseController.UploadImage)
imageProcessing := api.Group("/image-processing")
imageProcessing.Use(middleware.JWTAuth(db))
imageProcessing.Use(middleware.CSRFProtection())
imageProcessing.Use(middleware.RoleAuth("editor"))
{
imageProcessing.POST("/process", imageProcessingController.ProcessImage)
imageProcessing.POST("/crop-upload", imageProcessingController.CropAndUpload)
imageProcessing.POST("/quick-edit", imageProcessingController.QuickEdit)
}
api.GET("/scoreboard", scoreboardController.GetPublic)
api.GET("/scoreboard/colors/derive", scoreboardController.DeriveColors)
api.GET("/scoreboard/sponsors", scoreboardController.ListSponsors)
api.GET("/scoreboard/qr", scoreboardController.GetQR)
api.GET("/settings", baseController.GetPublicSettings)
api.GET("/competition-aliases", baseController.GetPublicCompetitionAliases)
api.GET("/public/team-logo-overrides", baseController.GetPublicTeamLogoOverrides)
// Articles (public; use optional auth so admin/editor can see drafts in list when requesting published=false)
articlesPub := api.Group("/articles")
articlesPub.Use(middleware.JWTOptional(db))
{
articlesPub.GET("/featured", baseController.GetFeaturedArticles)
articlesPub.GET("", baseController.GetArticles)
articlesPub.GET("/slug/:slug", baseController.GetArticleBySlug)
articlesPub.GET("/:id", baseController.GetArticle)
articlesPub.POST("/:id/read", baseController.IncrementArticleRead)
articlesPub.POST("/:id/track-view", baseController.TrackArticleView)
articlesPub.GET("/:id/match-link", baseController.GetArticleMatchLink)
}
api.GET("/categories", baseController.GetCategories)
api.GET("/youtube/videos", youtubeController.GetYouTubeVideos)
api.GET("/about", aboutController.GetPublicAboutPage)
api.GET("/teams", baseController.GetTeams)
api.GET("/teams/:id", baseController.GetTeam)
api.GET("/players", baseController.GetPlayers)
api.GET("/players/:id", baseController.GetPlayer)
api.GET("/sponsors", baseController.GetSponsors)
api.GET("/banners", baseController.GetBanners)
api.GET("/matches", baseController.GetMatches)
api.GET("/matches/history", baseController.GetMatchesHistory)
api.GET("/standings", baseController.GetStandings)
api.GET("/gallery/albums", galleryController.GetGalleryAlbums)
api.GET("/gallery/albums/:id", galleryController.GetGalleryAlbum)
api.GET("/gallery/proxy-image", galleryController.ProxyImage)
api.GET("/zonerama/album", baseController.GetZoneramaAlbum)
api.GET("/zonerama-album", baseController.GetZoneramaAlbum)
api.GET("/zonerama/picks", baseController.GetZoneramaPicks)
api.GET("/clothing", clothingController.GetClothing)
api.GET("/sweepstakes/current", sweepstakesController.GetCurrent)
api.GET("/sweepstakes/:id/visual", sweepstakesController.PublicVisualData)
pollsPub := api.Group("/polls")
pollsPub.Use(middleware.JWTOptional(db))
{
pollsPub.GET("", pollController.GetPolls)
pollsPub.GET("/:id", pollController.GetPoll)
pollsPub.POST("/:id/vote", middleware.RateLimit(10, time.Minute), pollController.Vote)
pollsPub.GET("/:id/results", pollController.GetPollResults)
}
api.POST("/contact", middleware.RateLimit(10, time.Minute), contactController.SubmitContactForm)
api.POST("/newsletter/subscribe", middleware.RateLimit(30, time.Minute), contactController.SubscribeToNewsletter)
api.POST("/newsletter/unsubscribe/:email", middleware.RateLimit(30, time.Minute), contactController.UnsubscribeFromNewsletter)
api.POST("/newsletter/setup", middleware.RateLimit(30, time.Minute), contactController.SetupNewsletterPreferences)
api.GET("/newsletter/preferences", contactController.GetNewsletterPreferencesByToken)
api.POST("/newsletter/preferences", contactController.SaveNewsletterPreferencesByToken)
api.POST("/newsletter/unsubscribe-token", contactController.UnsubscribeByToken)
facr := api.Group("/facr")
{
facr.GET("/club/search", facrController.SearchClubs)
facr.GET("/club/:type/:id", facrController.GetClubInfo)
facr.GET("/club/:type/:id/table", facrController.GetClubTables)
}
}
}
func SetupRootRoutes(r *gin.Engine, db *gorm.DB) {