package main import ( "bufio" "context" "errors" "io/fs" "log/slog" "net/http" "os" "os/signal" "strings" "syscall" "time" assets "shared-album" "shared-album/internal/app" "shared-album/internal/config" "shared-album/internal/db" "shared-album/internal/sse" "shared-album/internal/storage" ) func main() { loadDotEnv(".env") loadDotEnv("../.env") logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) slog.SetDefault(logger) cfg, err := config.Load() if err != nil { slog.Error("config error", "error", err) os.Exit(1) } ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() migFS, err := fs.Sub(assets.Migrations, "migrations") if err != nil { slog.Error("migrations fs", "error", err) os.Exit(1) } if err := db.Migrate(cfg.Postgres.DSN(), migFS); err != nil { slog.Error("migration failed", "error", err) os.Exit(1) } slog.Info("migrations applied") pool, err := db.Connect(ctx, cfg.Postgres.DSN()) if err != nil { slog.Error("database connect failed", "error", err) os.Exit(1) } defer pool.Close() store, err := storage.Open(ctx, cfg) if err != nil { slog.Error("storage init failed", "error", err) os.Exit(1) } if err := store.EnsureBucket(ctx); err != nil { slog.Warn("could not ensure bucket", "error", err) } else { slog.Info("storage bucket ready", "bucket", store.Bucket()) } hub := sse.NewHub(cfg.SSE.ClientBuffer) server := app.New(cfg, pool, store, hub) go server.StartWorker(ctx) httpServer := &http.Server{ Addr: cfg.Server.Addr, Handler: server.Router(), ReadHeaderTimeout: 10 * time.Second, } go func() { slog.Info("listening", "addr", cfg.Server.Addr) if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { slog.Error("server error", "error", err) stop() } }() <-ctx.Done() slog.Info("shutting down") shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() _ = httpServer.Shutdown(shutdownCtx) } func loadDotEnv(path string) { f, err := os.Open(path) if err != nil { return } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line == "" || strings.HasPrefix(line, "#") { continue } key, value, ok := strings.Cut(line, "=") if !ok { continue } key = strings.TrimSpace(key) value = strings.TrimSpace(value) if _, exists := os.LookupEnv(key); !exists { _ = os.Setenv(key, value) } } }