package main import ( "context" "crypto/subtle" "database/sql" "encoding/json" "fmt" "github.com/gin-gonic/gin" "log" "net/http" "os" "os/signal" "strings" "syscall" "time" ) // Serve the advanced ledger locally. The bearer token selects the tenant; a body cannot override it. func serve(db *sql.DB) error { var keys map[string]string if e := json.Unmarshal([]byte(os.Getenv("ADMISSION_KEYS_JSON")), &keys); e != nil || len(keys) == 0 { return fmt.Errorf("provide ADMISSION_KEYS_JSON tenant -> token") } gin.SetMode(gin.ReleaseMode) r := gin.New() r.Use(gin.Recovery()) r.Use(func(c *gin.Context) { token, ok := strings.CutPrefix(c.GetHeader("Authorization"), "Bearer ") for t, v := range keys { if ok && len(v) >= 16 && subtle.ConstantTimeCompare([]byte(v), []byte(token)) == 1 { c.Set("tenant", t) c.Next() return } } c.AbortWithStatus(401) }) r.POST("/requests", func(c *gin.Context) { var b struct { ID string Cost int Priority int } c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 8192) if c.ShouldBindJSON(&b) != nil { c.Status(400) return } if e := submit(db, b.ID, c.GetString("tenant"), b.Cost, b.Priority); e != nil { c.JSON(409, gin.H{"error": "invalid or conflicting request"}) return } c.JSON(202, gin.H{"id": b.ID}) }) r.GET("/requests/:id", func(c *gin.Context) { var phase string var node sql.NullString e := db.QueryRowContext(c.Request.Context(), "SELECT state,node FROM reservation WHERE tenant=? AND id=?", c.GetString("tenant"), c.Param("id")).Scan(&phase, &node) if e != nil { c.Status(404) return } c.JSON(200, gin.H{"phase": phase, "logical_pool": node.String}) }) ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt) defer stop() workerDone := make(chan struct{}) go func() { defer close(workerDone) t := time.NewTicker(time.Second) defer t.Stop() for { select { case <-ctx.Done(): return case <-t.C: if e := admit(db); e != nil { log.Printf("admit: %v", e) } if e := deliver(db); e != nil { log.Printf("outbox: %v", e) } } } }() srv := http.Server{Addr: "127.0.0.1:18191", Handler: r, ReadHeaderTimeout: 3 * time.Second} drained := make(chan struct{}) go func() { defer close(drained) <-ctx.Done() d, cancel := context.WithTimeout(context.Background(), 8*time.Second) defer cancel() srv.Shutdown(d) }() e := srv.ListenAndServe() stop() <-workerDone <-drained if e == http.ErrServerClosed { return nil } return e }