package api import ( "context" "log" "time" "github.com/docker/go-units" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/limiter" "github.com/gofiber/template/handlebars/v2" "gitea.urkob.com/urko/btc-pay-checker/internal/api/handler" "gitea.urkob.com/urko/btc-pay-checker/internal/domain" "gitea.urkob.com/urko/btc-pay-checker/internal/services" "gitea.urkob.com/urko/btc-pay-checker/internal/services/btc" "gitea.urkob.com/urko/btc-pay-checker/internal/services/mail" "gitea.urkob.com/urko/btc-pay-checker/internal/services/price" "gitea.urkob.com/urko/btc-pay-checker/kit/cfg" "gitea.urkob.com/urko/emailsender/pkg/email" ) const ( MAX_FILE_SIZE = 25 MAX_FILE_SIZE_MiB = MAX_FILE_SIZE * units.MiB ) type RestServer struct { app *fiber.App config *cfg.Config btcService *btc.BitcoinService orderSrv *services.Order emailSrv *mail.MailService priceSrv *price.PriceConversor } func NewRestServer( config *cfg.Config, orderSrv *services.Order, btcService *btc.BitcoinService, priceSrv *price.PriceConversor, emailSrv *email.EmailService, ) *RestServer { return &RestServer{ config: config, orderSrv: orderSrv, btcService: btcService, priceSrv: priceSrv, emailSrv: mail.NewMailService(emailSrv), } } func (s *RestServer) Start(ctx context.Context, apiPort, views string) error { engine := handlebars.New(views, ".hbs") s.app = fiber.New(fiber.Config{ Views: engine, BodyLimit: MAX_FILE_SIZE_MiB, }) s.app.Use(cors.New(cors.Config{ AllowMethods: "GET,POST,OPTIONS", AllowOrigins: "*", AllowHeaders: "Origin, Accept, Content-Type, X-CSRF-Token, Authorization", ExposeHeaders: "Origin", })) s.loadViews(views + "/images") orderHandler := handler.NewOrderHandler(s.config.WalletAddress, s.orderSrv, s.priceSrv) notifChan := make(chan domain.Notification) go s.btcService.Notify(ctx, notifChan) go s.onNotification(ctx, notifChan) s.app.Use(limiter.New(limiter.Config{ Max: 5, Expiration: 30 * time.Minute, LimiterMiddleware: limiter.SlidingWindow{}, })) s.app.Post("/order", orderHandler.Post) if err := s.app.Listen(":" + apiPort); err != nil { log.Fatalln("app.Listen:", err) return err } return nil } // onNotification Step // 1- update the block where tx has been forged // 2- send email to client alerting his block has been forged // 3- to upload file into arweave func (s *RestServer) onNotification(ctx context.Context, notifChan chan domain.Notification) { for notif := range notifChan { order, err := s.orderSrv.FromAmount(ctx, notif.Amount, notif.DoneAt) if err != nil { log.Println("error while retrieve transaction from forged block: ", err) continue } order.Block = notif.BlockHash order.Tx = notif.Tx order, err = s.orderSrv.OrderCompleted(ctx, order) if err != nil { log.Println("OrderCompleted:", err) continue } // notify if s.config.WebhookUrl != "" { go func() { if err := s.orderSrv.Webhook(ctx, s.config.WebhookUrl, order); err != nil { log.Println("orderSrv.Webhook:", err) } }() } // Send email to client and provider if err := s.emailSrv.SendProviderConfirm(mail.SendOK{ Tx: order.Tx, Block: order.Block, Amount: order.Amount, ExplorerUrl: s.btcService.Explorer(order.Tx), CustomerID: order.ClientID, OrderID: order.OrderID, Timestamp: order.PaidAt, To: order.Email, }); err != nil { log.Println("error while send confirm email:", err) continue } } } func (s *RestServer) loadViews(imagesDir string) { s.app.Static("/images", imagesDir) s.app.Get("/", func(c *fiber.Ctx) error { return c.Render("index", fiber.Map{ "host": s.config.Host, }) }) s.app.Get("/error", func(c *fiber.Ctx) error { message := c.Query("message") return renderError(c, nil, message) }) } func renderError(c *fiber.Ctx, err error, message string) error { if err != nil { log.Printf("renderError: %s\n", err) } return c.Render("error", fiber.Map{ "message": message, }) } func (s *RestServer) Shutdown() error { if err := s.app.Server().Shutdown(); err != nil { log.Printf("app.Server().Shutdown(): %s\n", err) return err } return nil }