diff --git a/api.go b/api.go index aef8f085..25bae7db 100644 --- a/api.go +++ b/api.go @@ -8,14 +8,22 @@ import ( "strings" "time" - "github.com/getAlby/nostr-wallet-connect/models/api" + models "github.com/getAlby/nostr-wallet-connect/models/api" "github.com/nbd-wtf/go-nostr" "gorm.io/gorm" ) -// TODO: these methods should be moved to a separate object, not in Service +type API struct { + svc *Service +} + +func NewAPI(svc *Service) *API { + return &API{ + svc: svc, + } +} -func (svc *Service) CreateApp(createAppRequest *api.CreateAppRequest) (*api.CreateAppResponse, error) { +func (api *API) CreateApp(createAppRequest *models.CreateAppRequest) (*models.CreateAppResponse, error) { name := createAppRequest.Name var pairingPublicKey string var pairingSecretKey string @@ -27,7 +35,7 @@ func (svc *Service) CreateApp(createAppRequest *api.CreateAppRequest) (*api.Crea //validate public key decoded, err := hex.DecodeString(pairingPublicKey) if err != nil || len(decoded) != 32 { - svc.Logger.Errorf("Invalid public key format: %s", pairingPublicKey) + api.svc.Logger.Errorf("Invalid public key format: %s", pairingPublicKey) return nil, errors.New(fmt.Sprintf("Invalid public key format: %s", pairingPublicKey)) } @@ -42,7 +50,7 @@ func (svc *Service) CreateApp(createAppRequest *api.CreateAppRequest) (*api.Crea var err error expiresAt, err = time.Parse(time.RFC3339, createAppRequest.ExpiresAt) if err != nil { - svc.Logger.Errorf("Invalid expiresAt: %s", pairingPublicKey) + api.svc.Logger.Errorf("Invalid expiresAt: %s", pairingPublicKey) return nil, errors.New(fmt.Sprintf("Invalid expiresAt: %v", err)) } } @@ -51,7 +59,7 @@ func (svc *Service) CreateApp(createAppRequest *api.CreateAppRequest) (*api.Crea expiresAt = time.Date(expiresAt.Year(), expiresAt.Month(), expiresAt.Day(), 23, 59, 59, 0, expiresAt.Location()) } - err := svc.db.Transaction(func(tx *gorm.DB) error { + err := api.svc.db.Transaction(func(tx *gorm.DB) error { err := tx.Save(&app).Error if err != nil { return err @@ -89,9 +97,9 @@ func (svc *Service) CreateApp(createAppRequest *api.CreateAppRequest) (*api.Crea return nil, err } - relayUrl, _ := svc.cfg.Get("Relay", "") + relayUrl, _ := api.svc.cfg.Get("Relay", "") - responseBody := &api.CreateAppResponse{} + responseBody := &models.CreateAppResponse{} responseBody.Name = name responseBody.Pubkey = pairingPublicKey responseBody.PairingSecret = pairingSecretKey @@ -101,7 +109,7 @@ func (svc *Service) CreateApp(createAppRequest *api.CreateAppRequest) (*api.Crea if err == nil { query := returnToUrl.Query() query.Add("relay", relayUrl) - query.Add("pubkey", svc.cfg.NostrPublicKey) + query.Add("pubkey", api.svc.cfg.NostrPublicKey) // if user.LightningAddress != "" { // query.Add("lud16", user.LightningAddress) // } @@ -114,23 +122,23 @@ func (svc *Service) CreateApp(createAppRequest *api.CreateAppRequest) (*api.Crea // if user.LightningAddress != "" { // lud16 = fmt.Sprintf("&lud16=%s", user.LightningAddress) // } - responseBody.PairingUri = fmt.Sprintf("nostr+walletconnect://%s?relay=%s&secret=%s%s", svc.cfg.NostrPublicKey, relayUrl, pairingSecretKey, lud16) + responseBody.PairingUri = fmt.Sprintf("nostr+walletconnect://%s?relay=%s&secret=%s%s", api.svc.cfg.NostrPublicKey, relayUrl, pairingSecretKey, lud16) return responseBody, nil } -func (svc *Service) DeleteApp(userApp *App) error { - return svc.db.Delete(userApp).Error +func (api *API) DeleteApp(userApp *App) error { + return api.svc.db.Delete(userApp).Error } -func (svc *Service) GetApp(userApp *App) *api.App { +func (api *API) GetApp(userApp *App) *models.App { var lastEvent NostrEvent - lastEventResult := svc.db.Where("app_id = ?", userApp.ID).Order("id desc").Limit(1).Find(&lastEvent) + lastEventResult := api.svc.db.Where("app_id = ?", userApp.ID).Order("id desc").Limit(1).Find(&lastEvent) paySpecificPermission := AppPermission{} appPermissions := []AppPermission{} var expiresAt *time.Time - svc.db.Where("app_id = ?", userApp.ID).Find(&appPermissions) + api.svc.db.Where("app_id = ?", userApp.ID).Find(&appPermissions) requestMethods := []string{} for _, appPerm := range appPermissions { @@ -148,10 +156,10 @@ func (svc *Service) GetApp(userApp *App) *api.App { budgetUsage := int64(0) maxAmount := paySpecificPermission.MaxAmount if maxAmount > 0 { - budgetUsage = svc.GetBudgetUsage(&paySpecificPermission) + budgetUsage = api.svc.GetBudgetUsage(&paySpecificPermission) } - response := api.App{ + response := models.App{ Name: userApp.Name, Description: userApp.Description, CreatedAt: userApp.CreatedAt, @@ -172,13 +180,13 @@ func (svc *Service) GetApp(userApp *App) *api.App { } -func (svc *Service) ListApps() ([]api.App, error) { +func (api *API) ListApps() ([]models.App, error) { apps := []App{} - svc.db.Find(&apps) + api.svc.db.Find(&apps) - apiApps := []api.App{} + apiApps := []models.App{} for _, userApp := range apps { - apiApp := api.App{ + apiApp := models.App{ // ID: app.ID, Name: userApp.Name, Description: userApp.Description, @@ -188,9 +196,9 @@ func (svc *Service) ListApps() ([]api.App, error) { } var lastEvent NostrEvent - result := svc.db.Where("app_id = ?", userApp.ID).Order("id desc").Limit(1).Find(&lastEvent) + result := api.svc.db.Where("app_id = ?", userApp.ID).Order("id desc").Limit(1).Find(&lastEvent) if result.Error != nil { - svc.Logger.Errorf("Failed to fetch last event %v", result.Error) + api.svc.Logger.Errorf("Failed to fetch last event %v", result.Error) return nil, errors.New("Failed to fetch last event") } if result.RowsAffected > 0 { @@ -201,40 +209,40 @@ func (svc *Service) ListApps() ([]api.App, error) { return apiApps, nil } -func (svc *Service) GetInfo() *api.InfoResponse { - info := api.InfoResponse{} - backend, _ := svc.cfg.Get("LNBackendType", "") +func (api *API) GetInfo() *models.InfoResponse { + info := models.InfoResponse{} + backend, _ := api.svc.cfg.Get("LNBackendType", "") info.SetupCompleted = backend != "" - info.Running = svc.lnClient != nil + info.Running = api.svc.lnClient != nil return &info } -func (svc *Service) Start(startRequest *api.StartRequest) error { - return svc.StartApp(startRequest.UnlockPassword) +func (api *API) Start(startRequest *models.StartRequest) error { + return api.svc.StartApp(startRequest.UnlockPassword) } -func (svc *Service) Setup(setupRequest *api.SetupRequest) error { +func (api *API) Setup(setupRequest *models.SetupRequest) error { // only update non-empty values if setupRequest.LNBackendType != "" { - svc.cfg.SetUpdate("LNBackendType", setupRequest.LNBackendType, "") + api.svc.cfg.SetUpdate("LNBackendType", setupRequest.LNBackendType, "") } if setupRequest.BreezAPIKey != "" { - svc.cfg.SetUpdate("BreezAPIKey", setupRequest.BreezAPIKey, setupRequest.UnlockPassword) + api.svc.cfg.SetUpdate("BreezAPIKey", setupRequest.BreezAPIKey, setupRequest.UnlockPassword) } if setupRequest.BreezMnemonic != "" { - svc.cfg.SetUpdate("BreezMnemonic", setupRequest.BreezMnemonic, setupRequest.UnlockPassword) + api.svc.cfg.SetUpdate("BreezMnemonic", setupRequest.BreezMnemonic, setupRequest.UnlockPassword) } if setupRequest.GreenlightInviteCode != "" { - svc.cfg.SetUpdate("GreenlightInviteCode", setupRequest.GreenlightInviteCode, setupRequest.UnlockPassword) + api.svc.cfg.SetUpdate("GreenlightInviteCode", setupRequest.GreenlightInviteCode, setupRequest.UnlockPassword) } if setupRequest.LNDAddress != "" { - svc.cfg.SetUpdate("LNDAddress", setupRequest.LNDAddress, setupRequest.UnlockPassword) + api.svc.cfg.SetUpdate("LNDAddress", setupRequest.LNDAddress, setupRequest.UnlockPassword) } if setupRequest.LNDCertHex != "" { - svc.cfg.SetUpdate("LNDCertHex", setupRequest.LNDCertHex, setupRequest.UnlockPassword) + api.svc.cfg.SetUpdate("LNDCertHex", setupRequest.LNDCertHex, setupRequest.UnlockPassword) } if setupRequest.LNDMacaroonHex != "" { - svc.cfg.SetUpdate("LNDMacaroonHex", setupRequest.LNDMacaroonHex, setupRequest.UnlockPassword) + api.svc.cfg.SetUpdate("LNDMacaroonHex", setupRequest.LNDMacaroonHex, setupRequest.UnlockPassword) } return nil diff --git a/echo_handlers.go b/http_service.go similarity index 64% rename from echo_handlers.go rename to http_service.go index a613aeb6..743e937b 100644 --- a/echo_handlers.go +++ b/http_service.go @@ -15,9 +15,19 @@ import ( "gorm.io/gorm" ) -// TODO: echo methods should not be on Service object +type HttpService struct { + svc *Service + api *API +} + +func NewHttpService(svc *Service) *HttpService { + return &HttpService{ + svc: svc, + api: NewAPI(svc), + } +} -func (svc *Service) ValidateUserMiddleware(next echo.HandlerFunc) echo.HandlerFunc { +func (httpSvc *HttpService) validateUserMiddleware(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { // TODO: check if login is required and check if user is logged in //sess, _ := session.Get(CookieName, c) @@ -28,7 +38,7 @@ func (svc *Service) ValidateUserMiddleware(next echo.HandlerFunc) echo.HandlerFu } } -func (svc *Service) RegisterSharedRoutes(e *echo.Echo) { +func (httpSvc *HttpService) RegisterSharedRoutes(e *echo.Echo) { e.HideBanner = true e.Use(echologrus.Middleware()) e.Use(middleware.Recover()) @@ -36,24 +46,24 @@ func (svc *Service) RegisterSharedRoutes(e *echo.Echo) { e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{ TokenLookup: "header:X-CSRF-Token", })) - e.Use(session.Middleware(sessions.NewCookieStore([]byte(svc.cfg.CookieSecret)))) + e.Use(session.Middleware(sessions.NewCookieStore([]byte(httpSvc.svc.cfg.CookieSecret)))) - authMiddleware := svc.ValidateUserMiddleware - e.GET("/api/apps", svc.AppsListHandler, authMiddleware) - e.GET("/api/apps/:pubkey", svc.AppsShowHandler, authMiddleware) - e.DELETE("/api/apps/:pubkey", svc.AppsDeleteHandler, authMiddleware) - e.POST("/api/apps", svc.AppsCreateHandler, authMiddleware) + authMiddleware := httpSvc.validateUserMiddleware + e.GET("/api/apps", httpSvc.appsListHandler, authMiddleware) + e.GET("/api/apps/:pubkey", httpSvc.appsShowHandler, authMiddleware) + e.DELETE("/api/apps/:pubkey", httpSvc.appsDeleteHandler, authMiddleware) + e.POST("/api/apps", httpSvc.appsCreateHandler, authMiddleware) - e.GET("/api/csrf", svc.CSRFHandler) - e.GET("/api/info", svc.InfoHandler) - e.POST("/api/logout", svc.LogoutHandler) - e.POST("/api/setup", svc.SetupHandler) - e.POST("/api/start", svc.StartHandler) + e.GET("/api/csrf", httpSvc.csrfHandler) + e.GET("/api/info", httpSvc.infoHandler) + e.POST("/api/logout", httpSvc.logoutHandler) + e.POST("/api/setup", httpSvc.setupHandler) + e.POST("/api/start", httpSvc.startHandler) frontend.RegisterHandlers(e) } -func (svc *Service) CSRFHandler(c echo.Context) error { +func (httpSvc *HttpService) csrfHandler(c echo.Context) error { csrf, _ := c.Get(middleware.DefaultCSRFConfig.ContextKey).(string) if csrf == "" { return c.JSON(http.StatusInternalServerError, ErrorResponse{ @@ -63,12 +73,12 @@ func (svc *Service) CSRFHandler(c echo.Context) error { return c.JSON(http.StatusOK, csrf) } -func (svc *Service) InfoHandler(c echo.Context) error { - responseBody := svc.GetInfo() +func (httpSvc *HttpService) infoHandler(c echo.Context) error { + responseBody := httpSvc.api.GetInfo() return c.JSON(http.StatusOK, responseBody) } -func (svc *Service) StartHandler(c echo.Context) error { +func (httpSvc *HttpService) startHandler(c echo.Context) error { var startRequest api.StartRequest if err := c.Bind(&startRequest); err != nil { return c.JSON(http.StatusBadRequest, ErrorResponse{ @@ -76,7 +86,7 @@ func (svc *Service) StartHandler(c echo.Context) error { }) } - err := svc.Start(&startRequest) + err := httpSvc.api.Start(&startRequest) if err != nil { return c.JSON(http.StatusInternalServerError, ErrorResponse{ Message: fmt.Sprintf("Failed to start node: %s", err.Error()), @@ -85,7 +95,7 @@ func (svc *Service) StartHandler(c echo.Context) error { return c.NoContent(http.StatusNoContent) } -func (svc *Service) LogoutHandler(c echo.Context) error { +func (httpSvc *HttpService) logoutHandler(c echo.Context) error { sess, err := session.Get(CookieName, c) if err != nil { return c.JSON(http.StatusInternalServerError, ErrorResponse{ @@ -101,9 +111,9 @@ func (svc *Service) LogoutHandler(c echo.Context) error { return c.NoContent(http.StatusNoContent) } -func (svc *Service) AppsListHandler(c echo.Context) error { +func (httpSvc *HttpService) appsListHandler(c echo.Context) error { - apps, err := svc.ListApps() + apps, err := httpSvc.api.ListApps() if err != nil { return c.JSON(http.StatusInternalServerError, ErrorResponse{ @@ -114,9 +124,9 @@ func (svc *Service) AppsListHandler(c echo.Context) error { return c.JSON(http.StatusOK, apps) } -func (svc *Service) AppsShowHandler(c echo.Context) error { +func (httpSvc *HttpService) appsShowHandler(c echo.Context) error { app := App{} - findResult := svc.db.Where("nostr_pubkey = ?", c.Param("pubkey")).First(&app) + findResult := httpSvc.svc.db.Where("nostr_pubkey = ?", c.Param("pubkey")).First(&app) if findResult.RowsAffected == 0 { return c.JSON(http.StatusNotFound, ErrorResponse{ @@ -124,12 +134,12 @@ func (svc *Service) AppsShowHandler(c echo.Context) error { }) } - response := svc.GetApp(&app) + response := httpSvc.api.GetApp(&app) return c.JSON(http.StatusOK, response) } -func (svc *Service) AppsDeleteHandler(c echo.Context) error { +func (httpSvc *HttpService) appsDeleteHandler(c echo.Context) error { pubkey := c.Param("pubkey") if pubkey == "" { return c.JSON(http.StatusBadRequest, ErrorResponse{ @@ -137,7 +147,7 @@ func (svc *Service) AppsDeleteHandler(c echo.Context) error { }) } app := App{} - result := svc.db.Where("nostr_pubkey = ?", pubkey).First(&app) + result := httpSvc.svc.db.Where("nostr_pubkey = ?", pubkey).First(&app) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return c.JSON(http.StatusNotFound, ErrorResponse{ @@ -149,7 +159,7 @@ func (svc *Service) AppsDeleteHandler(c echo.Context) error { }) } - if err := svc.DeleteApp(&app); err != nil { + if err := httpSvc.api.DeleteApp(&app); err != nil { return c.JSON(http.StatusInternalServerError, ErrorResponse{ Message: "Failed to delete app", }) @@ -157,7 +167,7 @@ func (svc *Service) AppsDeleteHandler(c echo.Context) error { return c.NoContent(http.StatusNoContent) } -func (svc *Service) AppsCreateHandler(c echo.Context) error { +func (httpSvc *HttpService) appsCreateHandler(c echo.Context) error { var requestData api.CreateAppRequest if err := c.Bind(&requestData); err != nil { return c.JSON(http.StatusBadRequest, ErrorResponse{ @@ -165,10 +175,10 @@ func (svc *Service) AppsCreateHandler(c echo.Context) error { }) } - responseBody, err := svc.CreateApp(&requestData) + responseBody, err := httpSvc.api.CreateApp(&requestData) if err != nil { - svc.Logger.Errorf("Failed to save app: %v", err) + httpSvc.svc.Logger.Errorf("Failed to save app: %v", err) return c.JSON(http.StatusInternalServerError, ErrorResponse{ Message: fmt.Sprintf("Failed to save app: %v", err), }) @@ -177,7 +187,7 @@ func (svc *Service) AppsCreateHandler(c echo.Context) error { return c.JSON(http.StatusOK, responseBody) } -func (svc *Service) SetupHandler(c echo.Context) error { +func (httpSvc *HttpService) setupHandler(c echo.Context) error { var setupRequest api.SetupRequest if err := c.Bind(&setupRequest); err != nil { return c.JSON(http.StatusBadRequest, ErrorResponse{ @@ -185,7 +195,7 @@ func (svc *Service) SetupHandler(c echo.Context) error { }) } - err := svc.Setup(&setupRequest) + err := httpSvc.api.Setup(&setupRequest) if err != nil { return c.JSON(http.StatusInternalServerError, ErrorResponse{ Message: fmt.Sprintf("Failed to setup node: %s", err.Error()), diff --git a/main.go b/main.go deleted file mode 100644 index 657db42b..00000000 --- a/main.go +++ /dev/null @@ -1,145 +0,0 @@ -package main - -import ( - "context" - "database/sql" - "errors" - "os" - "os/signal" - "path" - "sync" - - "github.com/getAlby/nostr-wallet-connect/migrations" - "github.com/glebarez/sqlite" - "github.com/joho/godotenv" - "github.com/kelseyhightower/envconfig" - "github.com/nbd-wtf/go-nostr" - "github.com/orandin/lumberjackrus" - log "github.com/sirupsen/logrus" - "gorm.io/gorm" -) - -// TODO: move to service.go -func NewService(ctx context.Context) (*Service, error) { - // Load config from environment variables / .env file - godotenv.Load(".env") - appConfig := &AppConfig{} - err := envconfig.Process("", appConfig) - if err != nil { - return nil, err - } - - logger := log.New() - logger.SetFormatter(&log.JSONFormatter{}) - logger.SetOutput(os.Stdout) - logger.SetLevel(log.InfoLevel) - - fileLoggerHook, err := lumberjackrus.NewHook( - &lumberjackrus.LogFile{ - Filename: path.Join(appConfig.Workdir, "log/nwc-general.log"), - }, - log.InfoLevel, - &log.JSONFormatter{}, - &lumberjackrus.LogFileOpts{ - log.ErrorLevel: &lumberjackrus.LogFile{ - Filename: path.Join(appConfig.Workdir, "log/nwc-error.log"), - MaxAge: 1, - MaxBackups: 2, - }, - }, - ) - if err != nil { - return nil, err - } - logger.AddHook(fileLoggerHook) - - var db *gorm.DB - var sqlDb *sql.DB - db, err = gorm.Open(sqlite.Open(appConfig.DatabaseUri), &gorm.Config{}) - if err != nil { - return nil, err - } - // Enable foreign keys for sqlite - db.Exec("PRAGMA foreign_keys=ON;") - sqlDb, err = db.DB() - if err != nil { - return nil, err - } - sqlDb.SetMaxOpenConns(1) - - err = migrations.Migrate(db) - if err != nil { - logger.Errorf("Failed to migrate: %v", err) - return nil, err - } - - ctx, _ = signal.NotifyContext(ctx, os.Interrupt) - - cfg := &Config{} - cfg.Init(db, appConfig) - - var wg sync.WaitGroup - svc := &Service{ - cfg: cfg, - db: db, - ctx: ctx, - wg: &wg, - Logger: logger, - } - - return svc, nil -} - -func (svc *Service) launchLNBackend(encryptionKey string) error { - if svc.lnClient != nil { - err := svc.lnClient.Shutdown() - if err != nil { - return err - } - svc.lnClient = nil - } - - lndBackend, _ := svc.cfg.Get("LNBackendType", "") - if lndBackend == "" { - return errors.New("No LNBackendType specified") - } - - svc.Logger.Infof("Launching LN Backend: %s", lndBackend) - var lnClient LNClient - var err error - switch lndBackend { - case LNDBackendType: - LNDAddress, _ := svc.cfg.Get("LNDAddress", encryptionKey) - LNDCertHex, _ := svc.cfg.Get("LNDCertHex", encryptionKey) - LNDMacaroonHex, _ := svc.cfg.Get("LNDMacaroonHex", encryptionKey) - - lnClient, err = NewLNDService(svc, LNDAddress, LNDCertHex, LNDMacaroonHex) - case lndBackend: - BreezMnemonic, _ := svc.cfg.Get("BreezMnemonic", encryptionKey) - BreezAPIKey, _ := svc.cfg.Get("BreezAPIKey", encryptionKey) - GreenlightInviteCode, _ := svc.cfg.Get("GreenlightInviteCode", encryptionKey) - BreezWorkdir := path.Join(svc.cfg.Env.Workdir, "breez") - - lnClient, err = NewBreezService(BreezMnemonic, BreezAPIKey, GreenlightInviteCode, BreezWorkdir) - default: - svc.Logger.Fatalf("Unsupported LNBackendType: %v", lndBackend) - } - if err != nil { - svc.Logger.Errorf("Failed to launch LN backend: %v", err) - return err - } - svc.lnClient = lnClient - return nil -} - -func (svc *Service) createFilters(identityPubkey string) nostr.Filters { - filter := nostr.Filter{ - Tags: nostr.TagMap{"p": []string{identityPubkey}}, - Kinds: []int{NIP_47_REQUEST_KIND}, - } - return []nostr.Filter{filter} -} - -func (svc *Service) noticeHandler(notice string) { - svc.Logger.Infof("Received a notice %s", notice) -} diff --git a/main_http.go b/main_http.go index ef86a803..0245e43b 100644 --- a/main_http.go +++ b/main_http.go @@ -27,7 +27,8 @@ func main() { e := echo.New() //register shared routes - svc.RegisterSharedRoutes(e) + httpSvc := NewHttpService(svc) + httpSvc.RegisterSharedRoutes(e) //start Echo server go func() { if err := e.Start(fmt.Sprintf(":%v", svc.cfg.Env.Port)); err != nil && err != http.ErrServerClosed { diff --git a/service.go b/service.go index 839aede0..f6b1a27c 100644 --- a/service.go +++ b/service.go @@ -8,9 +8,22 @@ import ( "sync" "time" + "database/sql" + "errors" + "os" + "os/signal" + "path" + "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip04" "github.com/sirupsen/logrus" + + "github.com/getAlby/nostr-wallet-connect/migrations" + "github.com/glebarez/sqlite" + "github.com/joho/godotenv" + "github.com/kelseyhightower/envconfig" + "github.com/orandin/lumberjackrus" + log "github.com/sirupsen/logrus" "gorm.io/gorm" ) @@ -25,6 +38,131 @@ type Service struct { wg *sync.WaitGroup } +// TODO: move to service.go +func NewService(ctx context.Context) (*Service, error) { + // Load config from environment variables / .env file + godotenv.Load(".env") + appConfig := &AppConfig{} + err := envconfig.Process("", appConfig) + if err != nil { + return nil, err + } + + logger := log.New() + logger.SetFormatter(&log.JSONFormatter{}) + logger.SetOutput(os.Stdout) + logger.SetLevel(log.InfoLevel) + + fileLoggerHook, err := lumberjackrus.NewHook( + &lumberjackrus.LogFile{ + Filename: path.Join(appConfig.Workdir, "log/nwc-general.log"), + }, + log.InfoLevel, + &log.JSONFormatter{}, + &lumberjackrus.LogFileOpts{ + log.ErrorLevel: &lumberjackrus.LogFile{ + Filename: path.Join(appConfig.Workdir, "log/nwc-error.log"), + MaxAge: 1, + MaxBackups: 2, + }, + }, + ) + if err != nil { + return nil, err + } + logger.AddHook(fileLoggerHook) + + var db *gorm.DB + var sqlDb *sql.DB + db, err = gorm.Open(sqlite.Open(appConfig.DatabaseUri), &gorm.Config{}) + if err != nil { + return nil, err + } + // Enable foreign keys for sqlite + db.Exec("PRAGMA foreign_keys=ON;") + sqlDb, err = db.DB() + if err != nil { + return nil, err + } + sqlDb.SetMaxOpenConns(1) + + err = migrations.Migrate(db) + if err != nil { + logger.Errorf("Failed to migrate: %v", err) + return nil, err + } + + ctx, _ = signal.NotifyContext(ctx, os.Interrupt) + + cfg := &Config{} + cfg.Init(db, appConfig) + + var wg sync.WaitGroup + svc := &Service{ + cfg: cfg, + db: db, + ctx: ctx, + wg: &wg, + Logger: logger, + } + + return svc, nil +} + +func (svc *Service) launchLNBackend(encryptionKey string) error { + if svc.lnClient != nil { + err := svc.lnClient.Shutdown() + if err != nil { + return err + } + svc.lnClient = nil + } + + lndBackend, _ := svc.cfg.Get("LNBackendType", "") + if lndBackend == "" { + return errors.New("No LNBackendType specified") + } + + svc.Logger.Infof("Launching LN Backend: %s", lndBackend) + var lnClient LNClient + var err error + switch lndBackend { + case LNDBackendType: + LNDAddress, _ := svc.cfg.Get("LNDAddress", encryptionKey) + LNDCertHex, _ := svc.cfg.Get("LNDCertHex", encryptionKey) + LNDMacaroonHex, _ := svc.cfg.Get("LNDMacaroonHex", encryptionKey) + + lnClient, err = NewLNDService(svc, LNDAddress, LNDCertHex, LNDMacaroonHex) + case lndBackend: + BreezMnemonic, _ := svc.cfg.Get("BreezMnemonic", encryptionKey) + BreezAPIKey, _ := svc.cfg.Get("BreezAPIKey", encryptionKey) + GreenlightInviteCode, _ := svc.cfg.Get("GreenlightInviteCode", encryptionKey) + BreezWorkdir := path.Join(svc.cfg.Env.Workdir, "breez") + + lnClient, err = NewBreezService(BreezMnemonic, BreezAPIKey, GreenlightInviteCode, BreezWorkdir) + default: + svc.Logger.Fatalf("Unsupported LNBackendType: %v", lndBackend) + } + if err != nil { + svc.Logger.Errorf("Failed to launch LN backend: %v", err) + return err + } + svc.lnClient = lnClient + return nil +} + +func (svc *Service) createFilters(identityPubkey string) nostr.Filters { + filter := nostr.Filter{ + Tags: nostr.TagMap{"p": []string{identityPubkey}}, + Kinds: []int{NIP_47_REQUEST_KIND}, + } + return []nostr.Filter{filter} +} + +func (svc *Service) noticeHandler(notice string) { + svc.Logger.Infof("Received a notice %s", notice) +} + func (svc *Service) StartSubscription(ctx context.Context, sub *nostr.Subscription) error { go func() { <-sub.EndOfStoredEvents diff --git a/service_test.go b/service_test.go index 259380e9..da2a4b69 100644 --- a/service_test.go +++ b/service_test.go @@ -8,7 +8,6 @@ import ( "time" "github.com/getAlby/nostr-wallet-connect/migrations" - "github.com/getAlby/nostr-wallet-connect/models/db" "github.com/glebarez/sqlite" "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip04" @@ -163,7 +162,7 @@ func TestHandleEvent(t *testing.T) { senderPubkey, err := nostr.GetPublicKey(senderPrivkey) assert.NoError(t, err) //test lnbc.. payload without having an app registered - ss, err := nip04.ComputeSharedSecret(svc.cfg.IdentityPubkey, senderPrivkey) + ss, err := nip04.ComputeSharedSecret(svc.cfg.NostrPublicKey, senderPrivkey) assert.NoError(t, err) payload, err := nip04.Encrypt(nip47PayJson, ss) assert.NoError(t, err) diff --git a/wails_app.go b/wails_app.go index 94e44406..9734add0 100644 --- a/wails_app.go +++ b/wails_app.go @@ -17,16 +17,20 @@ var assets embed.FS type WailsApp struct { ctx context.Context svc *Service + api *API } func NewApp(svc *Service) *WailsApp { - return &WailsApp{svc: svc} + return &WailsApp{ + svc: svc, + api: NewAPI(svc), + } } // startup is called when the app starts. The context is saved // so we can call the runtime methods -func (a *WailsApp) startup(ctx context.Context) { - a.ctx = ctx +func (app *WailsApp) startup(ctx context.Context) { + app.ctx = ctx } func LaunchWailsApp(app *WailsApp) { diff --git a/wails_handlers.go b/wails_handlers.go index 1354b05c..b9690d64 100644 --- a/wails_handlers.go +++ b/wails_handlers.go @@ -15,7 +15,7 @@ type WailsRequestRouterResponse struct { } // TODO: make this match echo -func (a *WailsApp) WailsRequestRouter(route string, method string, body string) WailsRequestRouterResponse { +func (app *WailsApp) WailsRequestRouter(route string, method string, body string) WailsRequestRouterResponse { appRegex := regexp.MustCompile( `/api/apps/([0-9a-f]+)`, ) @@ -25,8 +25,9 @@ func (a *WailsApp) WailsRequestRouter(route string, method string, body string) switch { case appMatch != nil && len(appMatch) > 1: pubkey := appMatch[1] + userApp := App{} - findResult := a.svc.db.Where("nostr_pubkey = ?", pubkey).First(&userApp) + findResult := app.svc.db.Where("nostr_pubkey = ?", pubkey).First(&userApp) if findResult.RowsAffected == 0 { return WailsRequestRouterResponse{Body: nil, Error: "App does not exist"} @@ -34,10 +35,10 @@ func (a *WailsApp) WailsRequestRouter(route string, method string, body string) switch method { case "GET": - app := a.svc.GetApp(&userApp) + app := app.api.GetApp(&userApp) return WailsRequestRouterResponse{Body: app, Error: ""} case "DELETE": - err := a.svc.DeleteApp(&userApp) + err := app.api.DeleteApp(&userApp) if err != nil { return WailsRequestRouterResponse{Body: nil, Error: err.Error()} } @@ -49,7 +50,7 @@ func (a *WailsApp) WailsRequestRouter(route string, method string, body string) case "/api/apps": switch method { case "GET": - apps, err := a.svc.ListApps() + apps, err := app.api.ListApps() if err != nil { return WailsRequestRouterResponse{Body: nil, Error: err.Error()} } @@ -58,37 +59,37 @@ func (a *WailsApp) WailsRequestRouter(route string, method string, body string) createAppRequest := &api.CreateAppRequest{} err := json.Unmarshal([]byte(body), createAppRequest) if err != nil { - a.svc.Logger.WithFields(logrus.Fields{ + app.svc.Logger.WithFields(logrus.Fields{ "route": route, "method": method, "body": body, }).Errorf("Failed to decode request to wails router: %v", err) return WailsRequestRouterResponse{Body: nil, Error: err.Error()} } - createAppResponse, err := a.svc.CreateApp(createAppRequest) + createAppResponse, err := app.api.CreateApp(createAppRequest) if err != nil { return WailsRequestRouterResponse{Body: nil, Error: err.Error()} } return WailsRequestRouterResponse{Body: createAppResponse, Error: ""} } case "/api/info": - infoResponse := a.svc.GetInfo() + infoResponse := app.api.GetInfo() res := WailsRequestRouterResponse{Body: *infoResponse, Error: ""} return res case "/api/start": startRequest := &api.StartRequest{} err := json.Unmarshal([]byte(body), startRequest) if err != nil { - a.svc.Logger.WithFields(logrus.Fields{ + app.svc.Logger.WithFields(logrus.Fields{ "route": route, "method": method, "body": body, }).Errorf("Failed to decode request to wails router: %v", err) return WailsRequestRouterResponse{Body: nil, Error: err.Error()} } - err = a.svc.Start(startRequest) + err = app.api.Start(startRequest) if err != nil { - a.svc.Logger.WithFields(logrus.Fields{ + app.svc.Logger.WithFields(logrus.Fields{ "route": route, "method": method, "body": body, @@ -102,16 +103,16 @@ func (a *WailsApp) WailsRequestRouter(route string, method string, body string) setupRequest := &api.SetupRequest{} err := json.Unmarshal([]byte(body), setupRequest) if err != nil { - a.svc.Logger.WithFields(logrus.Fields{ + app.svc.Logger.WithFields(logrus.Fields{ "route": route, "method": method, "body": body, }).Errorf("Failed to decode request to wails router: %v", err) return WailsRequestRouterResponse{Body: nil, Error: err.Error()} } - err = a.svc.Setup(setupRequest) + err = app.api.Setup(setupRequest) if err != nil { - a.svc.Logger.WithFields(logrus.Fields{ + app.svc.Logger.WithFields(logrus.Fields{ "route": route, "method": method, "body": body, @@ -120,6 +121,6 @@ func (a *WailsApp) WailsRequestRouter(route string, method string, body string) } return WailsRequestRouterResponse{Body: nil, Error: ""} } - a.svc.Logger.Errorf("Unhandled route: %s", route) + app.svc.Logger.Errorf("Unhandled route: %s", route) return WailsRequestRouterResponse{Body: nil, Error: fmt.Sprintf("Unhandled route: %s %s", method, route)} }