package main import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" "shop/internal/auth" "shop/internal/config" "shop/internal/database" "shop/internal/handlers" "shop/internal/middleware" "shop/internal/repository" "shop/internal/upload" ) func main() { ctx := context.Background() cfg := config.Load() pool, err := database.Connect(ctx) if err != nil { log.Fatalf("database: %v", err) } defer pool.Close() if err := database.Migrate(ctx, pool); err != nil { log.Fatalf("migrate: %v", err) } productRepo := repository.NewProductRepository(pool) adminRepo := repository.NewAdminRepository(pool) statsRepo := repository.NewStatsRepository(pool) storage, err := upload.NewStorage(cfg.UploadDir) if err != nil { log.Fatalf("upload storage: %v", err) } if cfg.AdminEmail != "" && cfg.AdminPassword != "" { if err := adminRepo.Upsert(ctx, cfg.AdminEmail, cfg.AdminPassword); err != nil { log.Fatalf("admin seed: %v", err) } log.Printf("admin ready: %s", cfg.AdminEmail) } else { log.Println("warning: set ADMIN_EMAIL and ADMIN_PASSWORD in .env to create admin") } home, err := handlers.NewHomeHandler(productRepo, statsRepo) if err != nil { log.Fatalf("handler: %v", err) } session := auth.NewSession(cfg.SessionSecret) admin := handlers.NewAdminHandler(adminRepo, productRepo, statsRepo, storage, session, home.Templates()) mux := http.NewServeMux() mux.Handle("GET /static/", http.StripPrefix("/static/", home.Static())) mux.Handle("GET /uploads/", storage.FileServer()) mux.HandleFunc("GET /health", home.Health) mux.HandleFunc("GET /{$}", home.Home) mux.HandleFunc("GET /admin/login", admin.LoginPage) mux.HandleFunc("POST /admin/login", admin.Login) mux.HandleFunc("POST /admin/logout", admin.Logout) mux.HandleFunc("GET /admin/{$}", admin.Dashboard) mux.HandleFunc("GET /admin/products", admin.ProductList) mux.HandleFunc("GET /admin/products/new", admin.ProductNew) mux.HandleFunc("POST /admin/products/save", admin.ProductSave) mux.HandleFunc("GET /admin/products/{id}/edit", admin.ProductEdit) mux.HandleFunc("POST /admin/products/{id}/delete", admin.ProductDelete) mux.HandleFunc("POST /admin/products/{id}/images/{imageId}/delete", admin.ImageDelete) addr := ":" + getEnv("APP_PORT", "8080") srv := &http.Server{ Addr: addr, Handler: middleware.TrustProxy(mux), ReadTimeout: 15 * time.Second, WriteTimeout: 60 * time.Second, IdleTimeout: 60 * time.Second, } go func() { log.Printf("server listening on %s", addr) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %v", err) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := srv.Shutdown(shutdownCtx); err != nil { log.Printf("shutdown: %v", err) } } func getEnv(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback }