package main import ( "context" "html/template" "io/fs" "log" "net/http" "os" "os/signal" "syscall" "time" "shop/internal/config" "shop/internal/database" "shop/internal/handlers" "shop/internal/repository" "shop/internal/web" ) func main() { cfg, err := config.Load() if err != nil { log.Fatalf("config: %v", err) } ctx := context.Background() pool, err := database.Connect(ctx, cfg.DatabaseURL) if err != nil { log.Fatalf("database: %v", err) } defer pool.Close() tmpl, err := loadTemplates() if err != nil { log.Fatalf("templates: %v", err) } products := repository.NewProductRepository(pool) home := handlers.NewHomeHandler(products, tmpl) staticSub, err := fs.Sub(web.StaticFS, "static") if err != nil { log.Fatalf("static fs: %v", err) } mux := http.NewServeMux() mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticSub)))) mux.Handle("GET /health", http.HandlerFunc(handlers.Health)) mux.Handle("GET /", home) srv := &http.Server{ Addr: cfg.HTTPAddr, Handler: mux, ReadTimeout: cfg.ReadTimeout, WriteTimeout: cfg.WriteTimeout, } go func() { log.Printf("server listening on %s", cfg.HTTPAddr) 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 loadTemplates() (*template.Template, error) { funcMap := template.FuncMap{ "formatPrice": func(v float64) string { return formatRub(v) }, } return template.New("").Funcs(funcMap).ParseFS(web.TemplatesFS, "templates/*.html") } func formatRub(v float64) string { intPart := int64(v) frac := int64((v - float64(intPart)) * 100) if frac < 0 { frac = -frac } return formatThousands(intPart) + "," + pad2(frac) + " ₽" } func formatThousands(n int64) string { if n < 0 { n = -n } s := "" for n >= 1000 { s = "," + pad3(n%1000) + s n /= 1000 } return itoa(n) + s } func pad3(n int64) string { if n < 10 { return "00" + itoa(n) } if n < 100 { return "0" + itoa(n) } return itoa(n) } func pad2(n int64) string { if n < 10 { return "0" + itoa(n) } return itoa(n) } func itoa(n int64) string { if n == 0 { return "0" } var b [20]byte i := len(b) for n > 0 { i-- b[i] = byte('0' + n%10) n /= 10 } return string(b[i:]) }