btc-pay-checker/internal/api/server.go

168 lines
4.2 KiB
Go

package api
import (
"context"
"log"
"time"
"github.com/docker/go-units"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/cors"
"github.com/gofiber/fiber/v3/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
}