Files
lndhub.go/cmd/server/main.go
Michael Bumann e33693398e Soft delete users (#476)
* Update Makefile

* Optionally load test DB from env variable

* Add option to soft-delete a user

This allows users to be marked as deleted.
An additional middleware checks if a user is deleted or deactivated and rejects
requests for those as StatusUnauthorized.

note: the middelware adds an additional DB query to load the user.
2024-01-09 19:38:01 +02:00

250 lines
7.4 KiB
Go

package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"sync"
"time"
"github.com/getAlby/lndhub.go/lnd"
"github.com/getAlby/lndhub.go/rabbitmq"
ddEcho "gopkg.in/DataDog/dd-trace-go.v1/contrib/labstack/echo.v4"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
"github.com/getAlby/lndhub.go/db"
"github.com/getAlby/lndhub.go/db/migrations"
"github.com/getAlby/lndhub.go/docs"
"github.com/getAlby/lndhub.go/lib"
"github.com/getAlby/lndhub.go/lib/service"
"github.com/getAlby/lndhub.go/lib/tokens"
"github.com/getAlby/lndhub.go/lib/transport"
"github.com/getsentry/sentry-go"
"github.com/joho/godotenv"
"github.com/kelseyhightower/envconfig"
"github.com/labstack/echo/v4"
echoSwagger "github.com/swaggo/echo-swagger"
"github.com/uptrace/bun/migrate"
)
// @title LndHub.go
// @version 0.9.0
// @description Accounting wrapper for the Lightning Network providing separate accounts for end-users.
// @contact.name Alby
// @contact.url https://getalby.com
// @contact.email hello@getalby.com
// @license.name GNU GPLv3
// @license.url https://www.gnu.org/licenses/gpl-3.0.en.html
// @BasePath /
// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl /auth
// @schemes https http
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)
if err != nil {
logger.Fatalf("Error initializing db connection: %v", err)
}
// Migrate the DB
//Todo: use timeout for startupcontext
startupCtx := context.Background()
migrator := migrate.NewMigrator(dbConn, migrations.Migrations)
err = migrator.Init(startupCtx)
if err != nil {
logger.Fatalf("Error initializing db migrator: %v", err)
}
_, err = migrator.Migrate(startupCtx)
if err != nil {
logger.Fatalf("Error migrating database: %v", err)
}
// Setup exception tracking with Sentry if configured
// sentry init needs to happen before the echo middlewares are added
if c.SentryDSN != "" {
if err = sentry.Init(sentry.ClientOptions{
Dsn: c.SentryDSN,
IgnoreErrors: []string{"401"},
EnableTracing: c.SentryTracesSampleRate > 0,
TracesSampleRate: c.SentryTracesSampleRate,
}); err != nil {
logger.Errorf("sentry init error: %v", err)
}
}
// Init new LND client
lnCfg, err := lnd.LoadConfig()
if err != nil {
logger.Fatalf("Error loading LN config: %v", err)
}
lndClient, err := lnd.InitLNClient(lnCfg, logger, startupCtx)
if err != nil {
logger.Fatalf("Error initializing the %s connection: %v", lnCfg.LNClientType, err)
}
logger.Infof("Connected to %s: %s", lnCfg.LNClientType, lndClient.GetMainPubkey())
// If no RABBITMQ_URI was provided we will not attempt to create a client
// No rabbitmq features will be available in this case.
var rabbitmqClient rabbitmq.Client
if c.RabbitMQUri != "" {
amqpClient, err := rabbitmq.DialAMQP(c.RabbitMQUri, rabbitmq.WithAmqpLogger(logger))
if err != nil {
logger.Fatal(err)
}
defer amqpClient.Close()
rabbitmqClient, err = rabbitmq.NewClient(amqpClient,
rabbitmq.WithLogger(logger),
rabbitmq.WithLndInvoiceExchange(c.RabbitMQLndInvoiceExchange),
rabbitmq.WithLndHubInvoiceExchange(c.RabbitMQLndhubInvoiceExchange),
rabbitmq.WithLndInvoiceConsumerQueueName(c.RabbitMQInvoiceConsumerQueueName),
rabbitmq.WithLndPaymentExchange(c.RabbitMQLndPaymentExchange),
rabbitmq.WithLndPaymentConsumerQueueName(c.RabbitMQPaymentConsumerQueueName),
)
if err != nil {
logger.Fatal(err)
}
// close the connection gently at the end of the runtime
defer rabbitmqClient.Close()
}
svc := &service.LndhubService{
Config: c,
DB: dbConn,
LndClient: lndClient,
Logger: logger,
InvoicePubSub: service.NewPubsub(),
RabbitMQClient: rabbitmqClient,
}
//init echo server
e := transport.InitEcho(c, logger)
//if Datadog is configured, add datadog middleware
if c.DatadogAgentUrl != "" {
tracer.Start(tracer.WithAgentAddr(c.DatadogAgentUrl))
defer tracer.Stop()
e.Use(ddEcho.Middleware(ddEcho.WithServiceName("lndhub.go")))
}
logMw := transport.CreateLoggingMiddleware(logger)
// strict rate limit for requests for sending payments
strictRateLimitMiddleware := transport.CreateRateLimitMiddleware(c.StrictRateLimit, c.BurstRateLimit)
secured := e.Group("", tokens.Middleware(c.JWTSecret), svc.ValidateUserMiddleware(), logMw)
securedWithStrictRateLimit := e.Group("", tokens.Middleware(c.JWTSecret), svc.ValidateUserMiddleware(), strictRateLimitMiddleware, logMw)
transport.RegisterLegacyEndpoints(svc, e, secured, securedWithStrictRateLimit, strictRateLimitMiddleware, tokens.AdminTokenMiddleware(c.AdminToken), logMw)
transport.RegisterV2Endpoints(svc, e, secured, securedWithStrictRateLimit, strictRateLimitMiddleware, tokens.AdminTokenMiddleware(c.AdminToken), logMw)
//Swagger API spec
docs.SwaggerInfo.Host = c.Host
e.GET("/swagger/*", echoSwagger.WrapHandler)
var backgroundWg sync.WaitGroup
backGroundCtx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
// Subscribe to LND invoice updates in the background
backgroundWg.Add(1)
go func() {
err = svc.StartInvoiceRoutine(backGroundCtx)
if err != nil {
sentry.CaptureException(err)
//we want to restart in case of an error here
svc.Logger.Fatal(err)
}
svc.Logger.Info("Invoice routine done")
backgroundWg.Done()
}()
// Check the status of all pending outgoing payments
backgroundWg.Add(1)
go func() {
err = svc.StartPendingPaymentRoutine(backGroundCtx)
if err != nil {
sentry.CaptureException(err)
//in case of an error here no restart is necessary
svc.Logger.Error(err)
}
svc.Logger.Info("Pending payment check routines done")
backgroundWg.Done()
}()
//Start webhook subscription
if svc.Config.WebhookUrl != "" {
backgroundWg.Add(1)
go func() {
svc.StartWebhookSubscription(backGroundCtx, svc.Config.WebhookUrl)
svc.Logger.Info("Webhook routine done")
backgroundWg.Done()
}()
}
//Start rabbit publisher
if svc.RabbitMQClient != nil {
backgroundWg.Add(1)
go func() {
err = svc.RabbitMQClient.StartPublishInvoices(backGroundCtx,
svc.SubscribeIncomingOutgoingInvoices,
svc.EncodeInvoiceWithUserLogin,
)
if err != nil {
svc.Logger.Error(err)
sentry.CaptureException(err)
}
svc.Logger.Info("Rabbit invoice publisher done")
backgroundWg.Done()
}()
}
//Start Prometheus server if necessary
var echoPrometheus *echo.Echo
if svc.Config.EnablePrometheus {
go transport.StartPrometheusEcho(logger, svc, e)
}
// 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")
}
}()
<-backGroundCtx.Done()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := e.Shutdown(ctx); err != nil {
e.Logger.Fatal(err)
}
if echoPrometheus != nil {
if err := echoPrometheus.Shutdown(ctx); err != nil {
e.Logger.Fatal(err)
}
}
//Wait for graceful shutdown of background routines
backgroundWg.Wait()
svc.Logger.Info("LNDhub exiting gracefully. Goodbye.")
}