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" "shop/internal/version" ) 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) userRepo := repository.NewUserRepository(pool) cartRepo := repository.NewCartRepository(pool) orderRepo := repository.NewOrderRepository(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) } tmpl, err := handlers.ParseTemplates() if err != nil { log.Fatalf("templates: %v", err) } adminSession := auth.NewSession(cfg.SessionSecret, "shop_admin_session", "/admin") userSession := auth.NewSession(cfg.SessionSecret, "shop_user_session", "/") cartSession := auth.NewSession(cfg.SessionSecret, "shop_cart_key", "/") customer := handlers.NewCustomerHandler( productRepo, userRepo, cartRepo, orderRepo, statsRepo, userSession, cartSession, tmpl, ) admin := handlers.NewAdminHandler(adminRepo, productRepo, statsRepo, storage, adminSession, tmpl) mux := http.NewServeMux() mux.Handle("GET /static/", http.StripPrefix("/static/", handlers.StaticHandler())) mux.Handle("GET /uploads/", storage.FileServer()) mux.HandleFunc("GET /health", customer.Health) mux.HandleFunc("GET /{$}", customer.Home) mux.HandleFunc("GET /register", customer.RegisterPage) mux.HandleFunc("POST /register", customer.Register) mux.HandleFunc("GET /login", customer.LoginPage) mux.HandleFunc("POST /login", customer.Login) mux.HandleFunc("POST /logout", customer.Logout) mux.HandleFunc("GET /cart", customer.CartPage) mux.HandleFunc("POST /cart/add", customer.CartAdd) mux.HandleFunc("POST /cart/update", customer.CartUpdate) mux.HandleFunc("POST /cart/remove", customer.CartRemove) mux.HandleFunc("GET /checkout", customer.CheckoutPage) mux.HandleFunc("POST /checkout", customer.Checkout) mux.HandleFunc("GET /account", customer.Account) mux.HandleFunc("POST /account", customer.Account) 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("Shop %s listening on %s", version.Version, 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() _ = srv.Shutdown(shutdownCtx) } func getEnv(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback }