package main import ( "embed" "fmt" "io/fs" "net" "net/http" "os" "os/exec" "runtime" "time" "github.com/gin-gonic/gin" "qr-scanner/config" "qr-scanner/handlers" "qr-scanner/services" ) //go:embed static/* var staticFS embed.FS func main() { cfg := config.Default().WithEnv() if err := os.MkdirAll(cfg.TempDir, 0o755); err != nil { panic(err) } store := services.NewTaskStore() scanner := services.NewScanner(store, time.Duration(cfg.DebugDelayMS)*time.Millisecond) exporter := services.NewExporter() uploadHandler := handlers.NewUploadHandler(cfg, store) scanHandler := handlers.NewScanHandler(cfg, store, scanner) progressHandler := handlers.NewProgressHandler(store) resultsHandler := handlers.NewResultsHandler(store) exportHandler := handlers.NewExportHandler(store, exporter) cancelHandler := handlers.NewCancelHandler(store) r := gin.Default() if cfg.MaxUploadMB > 0 { r.MaxMultipartMemory = cfg.MaxUploadMB * 1024 * 1024 } sub, err := fs.Sub(staticFS, "static") if err != nil { panic(err) } r.GET("/", func(c *gin.Context) { c.Redirect(http.StatusFound, "/static/index.html") }) r.StaticFS("/static", http.FS(sub)) api := r.Group("/api") { api.POST("/upload", uploadHandler.HandleUpload) api.POST("/scan", scanHandler.StartScan) api.GET("/progress/:taskID", progressHandler.GetProgress) api.GET("/progress/:taskID/stream", progressHandler.StreamProgress) api.GET("/results/:taskID", resultsHandler.GetResults) api.GET("/export/:taskID", exportHandler.Download) api.POST("/cancel/:taskID", cancelHandler.Cancel) } go func() { retention := time.Duration(cfg.RetentionMinutes) * time.Minute ticker := time.NewTicker(time.Minute) defer ticker.Stop() for range ticker.C { store.Cleanup(retention) } }() addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) ln, err := net.Listen("tcp", addr) if err != nil { panic(err) } openURL := fmt.Sprintf("http://%s:%d/", browserHost(cfg.Host), cfg.Port) _ = openBrowser(openURL) srv := &http.Server{Handler: r} if err := srv.Serve(ln); err != nil && err != http.ErrServerClosed { panic(err) } } func browserHost(listenHost string) string { if listenHost == "" || listenHost == "0.0.0.0" || listenHost == "::" { return "localhost" } return listenHost } func openBrowser(url string) error { switch runtime.GOOS { case "windows": return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() case "darwin": return exec.Command("open", url).Start() default: return exec.Command("xdg-open", url).Start() } }