Read INVOICE_MEMO_YEARLY and INVOICE_MEMO_LIFETIME from the environment and pass the user pubkey in LNbits payment extra for invoice creation.
178 lines
4.8 KiB
Go
178 lines
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"os"
|
|
"os/signal"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/noderunners/nip05api/internal/audit"
|
|
"github.com/noderunners/nip05api/internal/config"
|
|
"github.com/noderunners/nip05api/internal/db"
|
|
"github.com/noderunners/nip05api/internal/dm"
|
|
"github.com/noderunners/nip05api/internal/expiry"
|
|
httpapi "github.com/noderunners/nip05api/internal/http"
|
|
"github.com/noderunners/nip05api/internal/invoice"
|
|
applog "github.com/noderunners/nip05api/internal/log"
|
|
"github.com/noderunners/nip05api/internal/messages"
|
|
"github.com/noderunners/nip05api/internal/nostr"
|
|
"github.com/noderunners/nip05api/internal/payments"
|
|
syncw "github.com/noderunners/nip05api/internal/sync"
|
|
"github.com/noderunners/nip05api/internal/user"
|
|
"github.com/noderunners/nip05api/internal/webhook"
|
|
)
|
|
|
|
// version is overridden at build time via -ldflags "-X main.version=..."
|
|
var version = "dev"
|
|
|
|
func main() {
|
|
if err := run(); err != nil {
|
|
slog.Error("fatal", "err", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func run() error {
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
applog.Setup("info")
|
|
return err
|
|
}
|
|
applog.Setup(cfg.LogLevel)
|
|
slog.Info("starting",
|
|
"version", version,
|
|
"domain", cfg.Domain,
|
|
"port", cfg.Port,
|
|
"lightning", cfg.Lightning.Enabled,
|
|
"dm", cfg.DM.Enabled,
|
|
"sync", cfg.Nostr.UsernameSyncEnabled,
|
|
"webhook", cfg.Webhook.URL != "",
|
|
)
|
|
|
|
database, err := db.Open(cfg.DatabasePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer database.Close()
|
|
|
|
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
|
defer cancel()
|
|
|
|
if err := database.Migrate(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
tmpls, err := messages.Load(cfg.DM.MessagesFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
userRepo := user.NewRepo(database)
|
|
userSvc := user.NewService(userRepo, cfg.ReservedUsernames)
|
|
|
|
auditLogger := audit.New(database)
|
|
|
|
pool := nostr.NewPool(cfg.Nostr.Relays)
|
|
defer pool.Close()
|
|
|
|
hookRepo := webhook.NewRepo(database)
|
|
hookSvc := webhook.NewService(hookRepo, cfg.Domain, cfg.Webhook.URL != "")
|
|
hookWorker := webhook.NewWorker(hookRepo, cfg.Webhook.URL, cfg.Webhook.Secret,
|
|
cfg.Webhook.TimeoutSecs, cfg.Webhook.MaxRetries)
|
|
|
|
dmRepo := dm.NewRepo(database)
|
|
dmSvc := dm.NewService(dmRepo, tmpls, cfg.DM.Enabled)
|
|
var dmWorker *dm.Worker
|
|
if cfg.DM.Enabled {
|
|
dmWorker, err = dm.NewWorker(dmRepo, pool, cfg.DM.Nsec, cfg.DM.Kind)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
var invSvc *invoice.Service
|
|
var lnClient *invoice.LNbitsClient
|
|
if cfg.Lightning.Enabled {
|
|
lnClient = invoice.NewLNbits(cfg.Lightning.LNbitsURL, cfg.Lightning.LNbitsInvoiceKey)
|
|
invRepo := invoice.NewRepo(database)
|
|
invSvc = invoice.NewService(invRepo, userSvc, lnClient,
|
|
invoice.Pricing{
|
|
YearlySats: cfg.Lightning.PriceYearlySats,
|
|
LifetimeSats: cfg.Lightning.PriceLifetimeSats,
|
|
ExpiryMins: cfg.Lightning.InvoiceExpiryMins,
|
|
MemoYearly: cfg.Lightning.InvoiceMemoYearly,
|
|
MemoLifetime: cfg.Lightning.InvoiceMemoLifetime,
|
|
}, cfg.Domain)
|
|
}
|
|
|
|
payWorker := payments.NewWorker(invSvc, userSvc, lnClient, dmSvc, hookSvc, auditLogger,
|
|
cfg.Domain, cfg.FrontendURL, cfg.Lightning.Enabled && invSvc != nil)
|
|
syncWorker := syncw.NewWorker(userSvc, pool, cfg.Nostr.SyncIntervalMins,
|
|
cfg.Nostr.UsernameSyncEnabled, cfg.Domain, cfg.ReservedUsernames)
|
|
expiryWorker := expiry.NewWorker(userSvc, dmSvc, hookSvc, auditLogger,
|
|
cfg.Domain, cfg.FrontendURL, cfg.Expiry.GraceDays,
|
|
cfg.Expiry.ReminderDays, cfg.Expiry.CronHourUTC)
|
|
|
|
cleanupWorker := newCleanupWorker(database)
|
|
|
|
srv := httpapi.NewServer(httpapi.Deps{
|
|
Cfg: cfg,
|
|
DB: database,
|
|
Users: userSvc,
|
|
Invoices: invSvc,
|
|
DMs: dmSvc,
|
|
Hooks: hookSvc,
|
|
Audit: auditLogger,
|
|
Version: version,
|
|
})
|
|
|
|
var wg sync.WaitGroup
|
|
startWorker(&wg, ctx, "webhook", hookWorker.Run)
|
|
if dmWorker != nil {
|
|
startWorker(&wg, ctx, "dm", dmWorker.Run)
|
|
}
|
|
startWorker(&wg, ctx, "payments", payWorker.Run)
|
|
startWorker(&wg, ctx, "sync", syncWorker.Run)
|
|
startWorker(&wg, ctx, "expiry", expiryWorker.Run)
|
|
startWorker(&wg, ctx, "cleanup", cleanupWorker.Run)
|
|
|
|
serverErr := make(chan error, 1)
|
|
go func() {
|
|
slog.Info("http listening", "addr", cfg.Addr())
|
|
if err := srv.ListenAndServe(); err != nil && err.Error() != "http: Server closed" {
|
|
serverErr <- err
|
|
}
|
|
close(serverErr)
|
|
}()
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
slog.Info("shutdown signal received")
|
|
case err := <-serverErr:
|
|
if err != nil {
|
|
cancel()
|
|
wg.Wait()
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := httpapi.Shutdown(context.Background(), srv); err != nil {
|
|
slog.Error("server shutdown", "err", err)
|
|
}
|
|
wg.Wait()
|
|
slog.Info("shutdown complete")
|
|
return nil
|
|
}
|
|
|
|
func startWorker(wg *sync.WaitGroup, ctx context.Context, name string, run func(context.Context)) {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
defer slog.Info("worker stopped", "name", name)
|
|
slog.Info("worker started", "name", name)
|
|
run(ctx)
|
|
}()
|
|
}
|