mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 10:42:57 +00:00
hot fix #1
This commit is contained in:
@@ -4,14 +4,14 @@ import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
"net/url"
|
||||
|
||||
"fotbal-club/internal/config"
|
||||
"fotbal-club/internal/controllers"
|
||||
@@ -113,6 +113,52 @@ func main() {
|
||||
&models.UploadedFile{},
|
||||
&models.FileUsage{},
|
||||
&models.ErrorEvent{},
|
||||
&models.ManualCompetition{},
|
||||
&models.ManualMatch{},
|
||||
&models.ManualTableRow{},
|
||||
&models.Language{},
|
||||
&models.Translation{},
|
||||
&models.ContentTranslation{},
|
||||
&models.UserLanguagePreference{},
|
||||
// E-shop models (best-effort auto-migrate)
|
||||
&models.EshopProductCategory{},
|
||||
&models.EshopProduct{},
|
||||
&models.EshopProductVariant{},
|
||||
&models.EshopCart{},
|
||||
&models.EshopCartItem{},
|
||||
&models.EshopOrder{},
|
||||
&models.EshopOrderItem{},
|
||||
&models.EshopPayment{},
|
||||
&models.EshopShippingLabel{},
|
||||
&models.EshopSettings{},
|
||||
// Facility management models
|
||||
&models.Facility{},
|
||||
&models.FacilityAvailabilityRule{},
|
||||
&models.FacilityBooking{},
|
||||
&models.FacilityEquipment{},
|
||||
&models.FacilityMaintenance{},
|
||||
&models.WeatherCondition{},
|
||||
&models.FacilityBookingTemplate{},
|
||||
&models.Event{},
|
||||
&models.EventAttachment{},
|
||||
&models.QRCode{},
|
||||
// Financial models
|
||||
&models.Budget{},
|
||||
&models.Sponsorship{},
|
||||
&models.SponsorshipPayment{},
|
||||
&models.SponsorshipDocument{},
|
||||
&models.Expense{},
|
||||
&models.ExpenseDocument{},
|
||||
&models.FinancialReport{},
|
||||
&models.FinancialSettings{},
|
||||
// Invoice models
|
||||
&models.Invoice{},
|
||||
&models.InvoiceItem{},
|
||||
&models.InvoicePayment{},
|
||||
&models.InvoiceCustomer{},
|
||||
&models.InvoiceSettings{},
|
||||
&models.InvoiceSequence{},
|
||||
&models.InvoiceTemplate{},
|
||||
); err != nil {
|
||||
log.Printf("Warning: AutoMigrate failed: %v", err)
|
||||
}
|
||||
@@ -173,7 +219,7 @@ func main() {
|
||||
r.Use(middleware.RequestSizeLimit(2 * 1024 * 1024))
|
||||
// Enforce JSON for mutating API calls (uploads exempt)
|
||||
r.Use(middleware.ValidateContentType())
|
||||
|
||||
|
||||
// Set max multipart memory to match upload size limit (default is 32MB)
|
||||
r.MaxMultipartMemory = config.AppConfig.MaxUploadSize
|
||||
|
||||
@@ -183,26 +229,34 @@ func main() {
|
||||
// Apply strict security headers
|
||||
r.Use(middleware.SecurityHeaders())
|
||||
|
||||
// CORS only (security headers handled by middleware.SecurityHeaders)
|
||||
r.Use(func(c *gin.Context) {
|
||||
// CORS: reflect the Origin only if it is allowed. In development, also allow localhost/127.0.0.1 any port.
|
||||
origin := c.Request.Header.Get("Origin")
|
||||
allowed := false
|
||||
path := c.Request.URL.Path
|
||||
if strings.HasPrefix(path, "/api/v1/admin/scoreboard") || strings.HasPrefix(path, "/api/v1/scoreboard") {
|
||||
if origin != "" {
|
||||
allowed = true
|
||||
}
|
||||
}
|
||||
// 1) Explicit exact-origin allow list
|
||||
for _, ao := range config.AppConfig.AllowedOrigins {
|
||||
if ao == origin {
|
||||
allowed = true
|
||||
break
|
||||
// Add i18n middleware for language detection and context
|
||||
i18nMiddleware, err := middleware.NewI18nMiddleware()
|
||||
if err != nil {
|
||||
log.Printf("Warning: Failed to initialize i18n middleware: %v", err)
|
||||
} else {
|
||||
r.Use(i18nMiddleware.Middleware())
|
||||
}
|
||||
|
||||
// CORS only (security headers handled by middleware.SecurityHeaders)
|
||||
r.Use(func(c *gin.Context) {
|
||||
// CORS: reflect the Origin only if it is allowed. In development, also allow localhost/127.0.0.1 any port.
|
||||
origin := c.Request.Header.Get("Origin")
|
||||
allowed := false
|
||||
path := c.Request.URL.Path
|
||||
if strings.HasPrefix(path, "/api/v1/admin/scoreboard") || strings.HasPrefix(path, "/api/v1/scoreboard") {
|
||||
if origin != "" {
|
||||
allowed = true
|
||||
}
|
||||
}
|
||||
// 1) Explicit exact-origin allow list
|
||||
for _, ao := range config.AppConfig.AllowedOrigins {
|
||||
if ao == origin {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// 2) Wildcard support: ALLOWED_ORIGINS="*" means reflect any non-empty Origin
|
||||
if !allowed {
|
||||
if !allowed {
|
||||
for _, ao := range config.AppConfig.AllowedOrigins {
|
||||
if ao == "*" && origin != "" {
|
||||
allowed = true
|
||||
@@ -223,29 +277,29 @@ func main() {
|
||||
allowed = true
|
||||
}
|
||||
}
|
||||
if allowed && origin != "" {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
c.Writer.Header().Set("Vary", "Origin")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
// Expose request id header for client-side diagnostics
|
||||
c.Writer.Header().Set("Access-Control-Expose-Headers", "X-Request-ID")
|
||||
}
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization, Cache-Control, X-Requested-With, X-Session-Token, X-Admin-Token, X-Dev-Admin, X-CSRF-Token")
|
||||
if c.Request.Header.Get("Access-Control-Request-Private-Network") != "" {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Private-Network", "true")
|
||||
}
|
||||
if allowed && origin != "" {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
c.Writer.Header().Set("Vary", "Origin")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
// Expose request id header for client-side diagnostics
|
||||
c.Writer.Header().Set("Access-Control-Expose-Headers", "X-Request-ID")
|
||||
}
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization, Cache-Control, X-Requested-With, X-Session-Token, X-Admin-Token, X-Dev-Admin, X-CSRF-Token")
|
||||
if c.Request.Header.Get("Access-Control-Request-Private-Network") != "" {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Private-Network", "true")
|
||||
}
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
})
|
||||
})
|
||||
|
||||
// Set optimal caching for static assets and generated caches
|
||||
r.Use(middleware.AssetCacheControl())
|
||||
// Set optimal caching for static assets and generated caches
|
||||
r.Use(middleware.AssetCacheControl())
|
||||
|
||||
// Setup API routes
|
||||
api := r.Group("/api/v1")
|
||||
@@ -263,12 +317,12 @@ func main() {
|
||||
// Expose cached JSON files for the frontend at /cache/*
|
||||
r.Static("/cache", "./cache")
|
||||
|
||||
// Serve static assets and uploads
|
||||
// Map /dist to ./static to expose files like /dist/img/logo-club-empty.svg
|
||||
r.Static("/dist", "./static")
|
||||
r.Static("/uploads", config.AppConfig.UploadDir)
|
||||
// Serve premium asset pack (CSS/JS) for cloned pro pages
|
||||
r.Static("/premium-assets", "./pro")
|
||||
// Serve static assets and uploads
|
||||
// Map /dist to ./static to expose files like /dist/img/logo-club-empty.svg
|
||||
r.Static("/dist", "./static")
|
||||
r.Static("/uploads", config.AppConfig.UploadDir)
|
||||
// Serve premium asset pack (CSS/JS) for cloned pro pages
|
||||
r.Static("/premium-assets", "./pro")
|
||||
|
||||
// Ensure gallery flat files exist at startup (best effort)
|
||||
_ = services.RegenerateFlatGalleryFiles()
|
||||
@@ -281,7 +335,7 @@ func main() {
|
||||
}
|
||||
services.StartPrefetcher(prefetchTarget)
|
||||
|
||||
services.StartErrorReviewAutoRegister(dbInstance)
|
||||
services.StartErrorReviewAutoRegister(dbInstance)
|
||||
|
||||
// Start newsletter scheduler (automated emails - legacy weekly)
|
||||
services.StartNewsletterScheduler(dbInstance, emailSvc)
|
||||
@@ -296,6 +350,10 @@ func main() {
|
||||
// Store newsletter automation instance for use in controllers
|
||||
controllers.SetNewsletterAutomation(newsletterAutomation)
|
||||
|
||||
// Initialize and start directory heartbeat service
|
||||
directoryController := controllers.NewDirectoryController(dbInstance)
|
||||
directoryController.StartDirectoryHeartbeat()
|
||||
|
||||
// Start server with graceful shutdown
|
||||
port := config.AppConfig.Port
|
||||
// Fail fast in production if JWT secret is left as default
|
||||
@@ -304,8 +362,8 @@ func main() {
|
||||
}
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: ":" + port,
|
||||
Handler: r,
|
||||
Addr: ":" + port,
|
||||
Handler: r,
|
||||
ReadTimeout: config.AppConfig.ReadTimeout,
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
WriteTimeout: config.AppConfig.WriteTimeout,
|
||||
|
||||
Reference in New Issue
Block a user