package main import ( "embed" "fmt" "io/fs" "log" "net/http" "os" "path/filepath" "strings" "sync" "time" "github.com/gin-gonic/gin" "github.com/robfig/cron/v3" // "github.com/starfleetcptn/gomft/internal/api" "github.com/starfleetcptn/gomft/components" "github.com/starfleetcptn/gomft/internal/config" "github.com/starfleetcptn/gomft/internal/db" "github.com/starfleetcptn/gomft/internal/scheduler" "github.com/starfleetcptn/gomft/internal/web" "golang.org/x/crypto/bcrypt" ) //go:embed static var staticFiles embed.FS // setCacheHeaders sets appropriate cache headers based on file type func setCacheHeaders(c *gin.Context, path string) { // Set cache headers based on file type if strings.HasSuffix(path, ".js") || strings.HasSuffix(path, ".css") { c.Header("Cache-Control", "public, max-age=31536000") // Cache for 1 year } else if strings.HasSuffix(path, ".png") || strings.HasSuffix(path, ".jpg") || strings.HasSuffix(path, ".webp") { c.Header("Cache-Control", "public, max-age=86400") // Cache for 1 day } else { c.Header("Cache-Control", "no-cache") } } // setContentType sets the correct content type based on file extension func setContentType(c *gin.Context, path string) { switch { case strings.HasSuffix(path, ".js"): c.Header("Content-Type", "application/javascript") case strings.HasSuffix(path, ".css"): c.Header("Content-Type", "text/css") case strings.HasSuffix(path, ".png"): c.Header("Content-Type", "image/png") case strings.HasSuffix(path, ".jpg"), strings.HasSuffix(path, ".jpeg"): c.Header("Content-Type", "image/jpeg") case strings.HasSuffix(path, ".webp"): c.Header("Content-Type", "image/webp") case strings.HasSuffix(path, ".svg"): c.Header("Content-Type", "image/svg+xml") case strings.HasSuffix(path, ".woff2"): c.Header("Content-Type", "font/woff2") case strings.HasSuffix(path, ".woff"): c.Header("Content-Type", "font/woff") } } func main() { // Set Gin to release mode gin.SetMode(gin.ReleaseMode) log.SetFlags(log.LstdFlags | log.Lshortfile) log.Printf("Starting GoMFT server version %s...", components.AppVersion) // Initialize configuration cfg, err := config.Load() if err != nil { log.Fatalf("Failed to load configuration: %v", err) } log.Printf("Configuration loaded successfully") // Ensure required directories exist dirs := []string{ cfg.DataDir, cfg.BackupDir, } for _, dir := range dirs { if err := os.MkdirAll(dir, 0755); err != nil { log.Fatalf("Failed to create directory %s: %v", dir, err) } } log.Printf("Required directories created") // Initialize database dbPath := filepath.Join(cfg.DataDir, "gomft.db") database, err := db.Initialize(dbPath) if err != nil { log.Fatalf("Failed to initialize database: %v", err) } defer database.Close() log.Printf("Database initialized successfully") // Create default admin user if no users exist var count int64 database.Model(&db.User{}).Count(&count) if count == 0 { log.Printf("No users found, creating default admin user") // Generate password hash hashedPassword, err := bcrypt.GenerateFromPassword([]byte("admin"), bcrypt.DefaultCost) if err != nil { log.Fatalf("Failed to hash password: %v", err) } // Create admin user adminUser := &db.User{ Email: "admin@example.com", PasswordHash: string(hashedPassword), LastPasswordChange: time.Now(), } adminUser.SetIsAdmin(true) if err := database.CreateUser(adminUser); err != nil { log.Fatalf("Failed to create admin user: %v", err) } log.Printf("Default admin user created successfully") // Assign admin role to admin user if err := database.AssignRoleToUser(adminUser.ID, 1, 1); err != nil { log.Fatalf("Failed to assign admin role to admin user: %v", err) } log.Printf("Admin role assigned to admin user successfully") } // Initialize scheduler components log.Printf("Initializing scheduler components...") schedLogger := scheduler.NewLogger() // Create the scheduler's logger schedCron := cron.New(cron.WithChain(cron.Recover(cron.DefaultLogger))) // Create cron instance schedCron.Start() jobsMap := make(map[uint]cron.EntryID) var jobMutex sync.Mutex // Ensure *db.DB implements the required interfaces. // If not, this will cause a compile error later. // We assume for now that *db.DB has the necessary methods. var dbNotifier scheduler.NotificationDB = database var dbMetadata scheduler.MetadataDB = database var dbTransfer scheduler.TransferDB = database var dbJobExecutor scheduler.JobExecutorDB = database var dbScheduler scheduler.SchedulerDB = database notifier := scheduler.NewNotifier(dbNotifier, schedLogger, cfg.SkipSSLVerify) metadataHandler := scheduler.NewMetadataHandler(dbMetadata, schedLogger) transferExecutor := scheduler.NewTransferExecutor(dbTransfer, schedLogger, metadataHandler, notifier) jobExecutor := scheduler.NewJobExecutor(dbJobExecutor, schedLogger, schedCron, jobsMap, &jobMutex, transferExecutor, notifier) // Initialize scheduler with injected components scheduler := scheduler.New( dbScheduler, schedCron, schedLogger, jobExecutor, jobsMap, &jobMutex, ) // Defer Stop using the created scheduler instance defer scheduler.Stop() log.Printf("Scheduler initialized successfully") // Initialize Gin router with custom recovery middleware router := gin.New() router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { return fmt.Sprintf("[GIN] %v | %3d | %13v | %15s | %-7s %s\n%s", param.TimeStamp.Format("2006/01/02 - 15:04:05"), param.StatusCode, param.Latency, param.ClientIP, param.Method, param.Path, param.ErrorMessage, ) })) router.Use(gin.Recovery()) // Serve embedded static files with proper content types and caching staticFS, err := fs.Sub(staticFiles, "static") if err != nil { log.Fatalf("Failed to create sub-filesystem: %v", err) } // Custom static file handler router.GET("/static/*filepath", func(c *gin.Context) { path := c.Param("filepath") // Set content type and cache headers setContentType(c, path) setCacheHeaders(c, path) // Serve the file c.FileFromFS(path, http.FS(staticFS)) }) log.Printf("Embedded static files configured for serving") // Initialize web handlers webHandler, err := web.NewHandler(database, scheduler, cfg.JWTSecret, dbPath, cfg.BackupDir, cfg) if err != nil { log.Fatalf("Failed to initialize web handlers: %v", err) } webHandler.InitializeRoutes(router) log.Printf("Web handlers initialized successfully") // Initialize API routes // Commenting out the API routes initialization to avoid route conflicts // api.InitializeRoutes(router, database, scheduler, cfg.JWTSecret) // log.Printf("API routes initialized successfully") // Add middleware for security headers router.Use(func(c *gin.Context) { c.Writer.Header().Set("X-Frame-Options", "DENY") c.Writer.Header().Set("X-Content-Type-Options", "nosniff") c.Writer.Header().Set("X-XSS-Protection", "1; mode=block") c.Next() }) // Start the server log.Printf("Starting server on %s", cfg.ServerAddress) if err := router.Run(cfg.ServerAddress); err != nil { log.Fatalf("Failed to start server: %v", err) } }