package main import ( "log" "net/http" "os" "strings" "github.com/imc-vibe/backend/internal/api" "github.com/imc-vibe/backend/internal/auth" "github.com/imc-vibe/backend/internal/config" "github.com/imc-vibe/backend/internal/db" ) func main() { cfg := config.Load() database, err := db.Connect(cfg) if err != nil { log.Fatalf("Failed to connect to database: %v", err) } if err := database.InitSchema(); err != nil { log.Printf("Warning: Could not initialize schema: %v", err) } if cfg.AdminUser != "" && cfg.AdminPassword != "" { hash, err := auth.HashPassword(cfg.AdminPassword) if err != nil { log.Printf("Warning: Failed to hash admin password: %v", err) } else { if err := database.EnsureAdminUser(cfg.AdminUser, hash); err != nil { log.Printf("Warning: Failed to create admin user: %v", err) } else { log.Printf("Admin user '%s' created or already exists", cfg.AdminUser) } } } useEmbedded := os.Getenv("USE_EMBEDDED") != "false" port := cfg.Port frontendFS := FrontendFileSystem() router := http.NewServeMux() router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"status":"ok"}`)) }) apiRouter := api.New(database, cfg) router.Handle("/api/", apiRouter.Handler()) if useEmbedded { router.HandleFunc("/favicon.png", serveStaticFile(frontendFS, "embed/favicon.png")) router.Handle("/_app/", http.StripPrefix("/_app/", serveStaticPrefixed(frontendFS, "embed/_app/"))) router.HandleFunc("/", serveSPA(frontendFS)) log.Println("Using embedded frontend") } else { router.Handle("/", http.FileServer(http.Dir("../frontend/build"))) log.Println("Using filesystem frontend") } log.Printf("Server starting on http://localhost:%s", port) log.Fatal(http.ListenAndServe(":"+port, router)) } func serveStaticPrefixed(fsys http.FileSystem, prefix string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { path := prefix + r.URL.Path log.Printf("Static file requested: /_app/%s -> %s", r.URL.Path, path) file, err := fsys.Open(path) if err != nil { log.Printf("Error opening file %s: %v", path, err) http.NotFound(w, r) return } defer file.Close() fi, err := file.Stat() if err != nil { http.NotFound(w, r) return } if fi.IsDir() { http.NotFound(w, r) return } contentType := mimeType(path) w.Header().Set("Content-Type", contentType) http.ServeContent(w, r, fi.Name(), fi.ModTime(), file) } } func serveStaticFile(fsys http.FileSystem, filename string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { log.Printf("Static file requested: /%s", filename) file, err := fsys.Open(filename) if err != nil { log.Printf("Error opening file %s: %v", filename, err) http.NotFound(w, r) return } defer file.Close() fi, err := file.Stat() if err != nil { http.NotFound(w, r) return } contentType := mimeType(filename) w.Header().Set("Content-Type", contentType) http.ServeContent(w, r, fi.Name(), fi.ModTime(), file) } } func serveSPA(fsys http.FileSystem) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { path := r.URL.Path if strings.HasPrefix(path, "/api/") { http.NotFound(w, r) return } if path == "/" || path == "" { path = "embed/index.html" } else { path = "embed/index.html" } file, err := fsys.Open(path) if err != nil { log.Printf("Error opening %s: %v", path, err) http.Error(w, "Not found", http.StatusNotFound) return } defer file.Close() fi, _ := file.Stat() w.Header().Set("Content-Type", "text/html; charset=utf-8") http.ServeContent(w, r, fi.Name(), fi.ModTime(), file) } } func mimeType(path string) string { switch { case strings.HasSuffix(path, ".html"): return "text/html; charset=utf-8" case strings.HasSuffix(path, ".css"): return "text/css" case strings.HasSuffix(path, ".js"): return "application/javascript" case strings.HasSuffix(path, ".json"): return "application/json" case strings.HasSuffix(path, ".png"): return "image/png" case strings.HasSuffix(path, ".jpg") || strings.HasSuffix(path, ".jpeg"): return "image/jpeg" case strings.HasSuffix(path, ".svg"): return "image/svg+xml" case strings.HasSuffix(path, ".ico"): return "image/x-icon" case strings.HasSuffix(path, ".woff"): return "font/woff" case strings.HasSuffix(path, ".woff2"): return "font/woff2" case strings.HasSuffix(path, ".ttf"): return "font/ttf" case strings.HasSuffix(path, ".eot"): return "application/vnd.ms-fontobject" default: return "text/plain" } }