Files
Snip/backend/cmd/snip/main.go
T
2026-06-15 21:25:57 +07:00

101 lines
2.2 KiB
Go

// Command snip is a single binary exposing three subcommands — api, frontend
// and redirect — so one Docker image can back three docker-compose services.
package main
import (
"context"
"errors"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/snip/backend/internal/adapter/postgres"
"github.com/snip/backend/internal/config"
"github.com/snip/backend/internal/di"
)
func main() {
log.SetFlags(log.LstdFlags | log.Lmsgprefix)
if len(os.Args) < 2 {
usage()
os.Exit(2)
}
cmd := os.Args[1]
cfg := config.Load()
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
container := di.New(cfg)
var (
app *di.App
addr string
err error
)
switch cmd {
case "migrate":
if cfg.Store != "postgres" {
log.Printf("snip migrate: STORE=%s, nothing to do", cfg.Store)
os.Exit(0)
}
if err := postgres.Migrate(ctx, cfg.DatabaseURL); err != nil {
log.Fatalf("snip migrate: %v", err)
}
log.Println("snip migrate: schema is up to date")
os.Exit(0)
case "frontend":
app, addr = container.BuildFrontend(), listenAddr("8081")
case "api":
app, err = container.BuildAPI(ctx)
addr = listenAddr("8080")
case "redirect":
app, err = container.BuildRedirect(ctx)
addr = listenAddr("8082")
default:
usage()
os.Exit(2)
}
if err != nil {
log.Fatalf("snip %s: startup failed: %v", cmd, err)
}
defer app.Close()
serve(ctx, cmd, addr, app.Handler)
}
func listenAddr(def string) string {
if p := os.Getenv("PORT"); p != "" {
return ":" + p
}
return ":" + def
}
func serve(ctx context.Context, name, addr string, h http.Handler) {
srv := &http.Server{
Addr: addr,
Handler: h,
ReadHeaderTimeout: 5 * time.Second,
}
go func() {
log.Printf("snip %s listening on %s", name, addr)
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("snip %s: %v", name, err)
}
}()
<-ctx.Done()
log.Printf("snip %s: shutting down", name)
shutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_ = srv.Shutdown(shutCtx)
}
func usage() {
fmt.Fprintln(os.Stderr, "usage: snip <migrate|api|frontend|redirect>")
}