2023-07-19 11:47:46 +02:00
|
|
|
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"
|
|
|
|
)
|
|
|
|
|
|
|
|
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
|
|
|
|
mailSrv *mail.MailService
|
|
|
|
priceSrv *price.PriceConversor
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewRestServer(
|
|
|
|
config *cfg.Config,
|
|
|
|
orderSrv *services.Order,
|
|
|
|
btcService *btc.BitcoinService,
|
|
|
|
priceSrv *price.PriceConversor,
|
|
|
|
mailSrv *mail.MailService,
|
|
|
|
) *RestServer {
|
|
|
|
return &RestServer{
|
|
|
|
config: config,
|
|
|
|
orderSrv: orderSrv,
|
|
|
|
btcService: btcService,
|
|
|
|
priceSrv: priceSrv,
|
|
|
|
mailSrv: mailSrv,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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(limiter.New(limiter.Config{
|
|
|
|
Max: 5,
|
2023-07-20 09:00:07 +02:00
|
|
|
Expiration: 30 * time.Minute,
|
2023-07-19 11:47:46 +02:00
|
|
|
LimiterMiddleware: limiter.SlidingWindow{},
|
|
|
|
}))
|
|
|
|
|
|
|
|
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.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
|
2023-07-19 21:01:35 +02:00
|
|
|
order, err = s.orderSrv.OrderCompleted(ctx, order)
|
|
|
|
if err != nil {
|
2023-07-19 11:47:46 +02:00
|
|
|
log.Println("OrderCompleted:", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-07-19 21:20:59 +02:00
|
|
|
// notify
|
|
|
|
if s.config.WebhookUrl != "" {
|
|
|
|
go func() {
|
|
|
|
if err := s.orderSrv.Webhook(ctx, s.config.WebhookUrl, order); err != nil {
|
|
|
|
log.Println("orderSrv.Webhook:", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2023-07-19 11:47:46 +02:00
|
|
|
// Send email to client and provider
|
|
|
|
if err := s.mailSrv.SendProviderConfirm(mail.SendOK{
|
2023-07-19 21:01:35 +02:00
|
|
|
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,
|
2023-07-19 11:47:46 +02:00
|
|
|
}); 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 {
|
2023-07-20 08:50:24 +02:00
|
|
|
return c.Render("index", fiber.Map{
|
|
|
|
"host": s.config.Host,
|
|
|
|
})
|
2023-07-19 11:47:46 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|