Files
lndhub.go/main.go
2022-02-27 09:09:00 +01:00

184 lines
5.8 KiB
Go

package main
import (
"context"
"embed"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"time"
"github.com/getAlby/lndhub.go/controllers"
"github.com/getAlby/lndhub.go/db"
"github.com/getAlby/lndhub.go/db/migrations"
"github.com/getAlby/lndhub.go/lib"
"github.com/getAlby/lndhub.go/lib/responses"
"github.com/getAlby/lndhub.go/lib/service"
"github.com/getAlby/lndhub.go/lib/tokens"
"github.com/getAlby/lndhub.go/lnd"
"github.com/getsentry/sentry-go"
sentryecho "github.com/getsentry/sentry-go/echo"
"github.com/go-playground/validator/v10"
"github.com/joho/godotenv"
"github.com/kelseyhightower/envconfig"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/uptrace/bun/migrate"
"github.com/ziflex/lecho/v3"
)
//go:embed templates/index.html
var indexHtml string
//go:embed static/*
var staticContent embed.FS
func main() {
c := &service.Config{}
// Load configruation from environment variables
err := godotenv.Load(".env")
if err != nil {
fmt.Println("Failed to load .env file")
}
err = envconfig.Process("", c)
if err != nil {
log.Fatalf("Error loading environment variables: %v", err)
}
// Setup logging to STDOUT or a configrued log file
logger := lib.Logger(c.LogFilePath)
// Open a DB connection based on the configured DATABASE_URI
dbConn, err := db.Open(c.DatabaseUri)
if err != nil {
logger.Fatalf("Error initializing db connection: %v", err)
}
// Migrate the DB
ctx := context.Background()
migrator := migrate.NewMigrator(dbConn, migrations.Migrations)
err = migrator.Init(ctx)
if err != nil {
logger.Fatalf("Error initializing db migrator: %v", err)
}
_, err = migrator.Migrate(ctx)
if err != nil {
logger.Fatalf("Error migrating database: %v", err)
}
// New Echo app
e := echo.New()
e.HideBanner = true
e.HTTPErrorHandler = responses.HTTPErrorHandler
e.Validator = &lib.CustomValidator{Validator: validator.New()}
e.Use(middleware.Recover())
e.Use(middleware.BodyLimit("250K"))
e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(20)))
e.Logger = logger
e.Use(middleware.RequestID())
e.Use(lecho.Middleware(lecho.Config{
Logger: logger,
}))
// Setup exception tracking with Sentry if configured
if c.SentryDSN != "" {
if err = sentry.Init(sentry.ClientOptions{
Dsn: c.SentryDSN,
}); err != nil {
logger.Errorf("sentry init error: %v", err)
}
defer sentry.Flush(2 * time.Second)
e.Use(sentryecho.New(sentryecho.Options{}))
}
// Init new LND client
//lndClient, err := lnd.NewLNDclient(lnd.LNDoptions{
// Address: c.LNDAddress,
// MacaroonHex: c.LNDMacaroonHex,
// CertHex: c.LNDCertHex,
//})
//Init new CLN client
//re-use other config to not make things overcomplicated
lndClient, err := lnd.NewCLNClient(lnd.CLNClientOptions{
SparkUrl: c.LNDAddress,
SparkToken: c.LNDMacaroonHex,
})
if err != nil {
e.Logger.Fatalf("Error initializing the LND connection: %v", err)
}
getInfo, err := lndClient.GetInfo(ctx, &lnrpc.GetInfoRequest{})
if err != nil {
e.Logger.Fatalf("Error getting node info: %v", err)
}
logger.Infof("Connected to LND: %s - %s", getInfo.Alias, getInfo.IdentityPubkey)
svc := &service.LndhubService{
Config: c,
DB: dbConn,
LndClient: lndClient,
Logger: logger,
IdentityPubkey: getInfo.IdentityPubkey,
}
// Public endpoints for account creation and authentication
e.POST("/auth", controllers.NewAuthController(svc).Auth)
e.POST("/create", controllers.NewCreateUserController(svc).CreateUser)
// Secured endpoints which require a Authorization token (JWT)
secured := e.Group("", tokens.Middleware(c.JWTSecret))
secured.POST("/addinvoice", controllers.NewAddInvoiceController(svc).AddInvoice)
secured.POST("/payinvoice", controllers.NewPayInvoiceController(svc).PayInvoice)
secured.GET("/gettxs", controllers.NewGetTXSController(svc).GetTXS)
secured.GET("/getuserinvoices", controllers.NewGetTXSController(svc).GetUserInvoices)
secured.GET("/checkpayment/:payment_hash", controllers.NewCheckPaymentController(svc).CheckPayment)
secured.GET("/balance", controllers.NewBalanceController(svc).Balance)
secured.GET("/getinfo", controllers.NewGetInfoController(svc).GetInfo)
secured.POST("/bolt12/fetchinvoice", controllers.NewBolt12Controller(svc).FetchInvoice)
secured.POST("/bolt12/pay", controllers.NewBolt12Controller(svc).PayBolt12)
// These endpoints are currently not supported and we return a blank response for backwards compatibility
blankController := controllers.NewBlankController(svc)
secured.GET("/getbtc", blankController.GetBtc)
secured.GET("/getpending", blankController.GetPending)
//Index page endpoints, no Authorization required
homeController := controllers.NewHomeController(svc, indexHtml)
e.GET("/", homeController.Home)
e.GET("/qr", homeController.QR)
//workaround, just adding /static would make a request to these resources hit the authorized group
e.GET("/static/css/*", echo.WrapHandler(http.FileServer(http.FS(staticContent))))
e.GET("/static/img/*", echo.WrapHandler(http.FileServer(http.FS(staticContent))))
e.GET("/bolt12/decode/:offer", controllers.NewBolt12Controller(svc).Decode)
// Subscribe to LND invoice updates in the background
// CLN: todo: re-write logic
go svc.InvoiceUpdateSubscription(context.Background())
// Start server
go func() {
if err := e.Start(fmt.Sprintf(":%v", c.Port)); err != nil && err != http.ErrServerClosed {
e.Logger.Fatal("shutting down the server")
}
}()
// Wait for interrupt signal to gracefully shutdown the server with a timeout of 10 seconds.
// Use a buffered channel to avoid missing signals as recommended for signal.Notify
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := e.Shutdown(ctx); err != nil {
e.Logger.Fatal(err)
}
}