package listeners import ( "context" "log/slog" "net/http" "sync" "sync/atomic" "time" "git.savin.nyc/alex/go-receipt-tracker/listeners/handlers" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "github.com/uptrace/bun" ) const TypeApi = "api" // HTTPHealthCheck is a listener for providing an HTTP healthcheck endpoint. type HttpApi struct { sync.RWMutex id string // the internal id of the listener address string // the network address to bind to config Config // configuration values for the listener listen *http.Server // the http server end uint32 // ensure the close methods are only called once db *bun.DB // } // NewHTTPHealthCheck initializes and returns a new HTTP listener, listening on an address. func NewHttpApi(config Config, db *bun.DB) *HttpApi { return &HttpApi{ id: config.ID, address: config.Address, config: config, db: db, } } // ID returns the id of the listener. func (l *HttpApi) ID() string { return l.id } // Address returns the address of the listener. func (l *HttpApi) Address() string { return l.address } // Protocol returns the address of the listener. func (l *HttpApi) Protocol() string { if l.listen != nil && l.listen.TLSConfig != nil { return "https" } return "http" } // Init initializes the listener. func (l *HttpApi) Init(_ *slog.Logger) error { handlers.DB = l.db router := gin.Default() router.Use(cors.Default()) v1 := router.Group("/api/v1") { v1.GET("/", handlers.HomePage) v1.GET("/receipts", handlers.GetReceipts) v1.GET("/receipts/:id", handlers.GetReceipt) v1.DELETE("/receipts/:id", handlers.RemoveReceipt) v1.POST("/receipts", handlers.AddReceipt) v1.PUT("/receipts/:id", handlers.UpdateReceipt) v1.GET("/receipts/:id/items", handlers.GetItems) } l.listen = &http.Server{ ReadTimeout: 5 * time.Second, WriteTimeout: 5 * time.Second, Addr: l.address, Handler: router.Handler(), } if l.config.TLSConfig != nil { l.listen.TLSConfig = l.config.TLSConfig } return nil } // Serve starts listening for new connections and serving responses. func (l *HttpApi) Serve() { if l.listen.TLSConfig != nil { _ = l.listen.ListenAndServeTLS("", "") } else { _ = l.listen.ListenAndServe() } } // Close closes the listener and any client connections. func (l *HttpApi) Close() { l.Lock() defer l.Unlock() if atomic.CompareAndSwapUint32(&l.end, 0, 1) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _ = l.listen.Shutdown(ctx) } }