From 8fc78f16285d391f37f628fc9a35374f5d40138d Mon Sep 17 00:00:00 2001 From: im-adithya Date: Fri, 8 Nov 2024 01:13:04 +0530 Subject: [PATCH] feat: add endpoint for registering alby go 'notifications --- cmd/server/main.go | 1 + go.mod | 1 + go.sum | 2 + internal/nostr/expo.go | 126 ++++++++++++++++++ internal/nostr/models.go | 8 ++ internal/nostr/nostr.go | 16 ++- ...1071013_add_push_token_to_subscriptions.go | 23 ++++ migrations/migrate.go | 1 + 8 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 internal/nostr/expo.go create mode 100644 migrations/202411071013_add_push_token_to_subscriptions.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 408a7cb..9b6d7b8 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -50,6 +50,7 @@ func main() { e.POST("/nip47", svc.NIP47Handler) e.POST("/nip47/webhook", svc.NIP47WebhookHandler) e.POST("/nip47/notifications", svc.NIP47NotificationHandler) + e.POST("/nip47/notifications/go", svc.NIP47ExpoNotificationHandler) e.POST("/publish", svc.PublishHandler) e.POST("/subscriptions", svc.SubscriptionHandler) e.DELETE("/subscriptions/:id", svc.StopSubscriptionHandler) diff --git a/go.mod b/go.mod index fc02c56..41b1a49 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/oliveroneill/exponent-server-sdk-golang v0.0.0-20210823140141-d050598be512 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/puzpuzpuz/xsync/v3 v3.1.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect diff --git a/go.sum b/go.sum index 47a5267..42b726b 100644 --- a/go.sum +++ b/go.sum @@ -148,6 +148,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/nbd-wtf/go-nostr v0.31.4 h1:Qq+PHyKixRZR6Tn+omF7LykK/IR6qcdmThNY132pKsA= github.com/nbd-wtf/go-nostr v0.31.4/go.mod h1:vHKtHyLXDXzYBN0fi/9Y/Q5AD0p+hk8TQVKlldAi0gI= +github.com/oliveroneill/exponent-server-sdk-golang v0.0.0-20210823140141-d050598be512 h1:/ZSmjwl1inqsiHMhn+sPlEtSHdVTf+TH3LNGGdMQ/vA= +github.com/oliveroneill/exponent-server-sdk-golang v0.0.0-20210823140141-d050598be512/go.mod h1:Isv/48UnAjtxS8FD80Bito3ZJqZRyIMxKARIEITfW4k= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0= diff --git a/internal/nostr/expo.go b/internal/nostr/expo.go new file mode 100644 index 0000000..e2f5e1f --- /dev/null +++ b/internal/nostr/expo.go @@ -0,0 +1,126 @@ +package nostr + +import ( + "net/http" + "time" + + "github.com/labstack/echo/v4" + "github.com/nbd-wtf/go-nostr" + expo "github.com/oliveroneill/exponent-server-sdk-golang/sdk" + "github.com/sirupsen/logrus" +) + +func (svc *Service) NIP47ExpoNotificationHandler(c echo.Context) error { + var requestData NIP47ExpoNotificationRequest + // send in a pubkey and authenticate by signing + if err := c.Bind(&requestData); err != nil { + return c.JSON(http.StatusBadRequest, ErrorResponse{ + Message: "Error decoding notification request", + Error: err.Error(), + }) + } + + if (requestData.PushToken == "") { + return c.JSON(http.StatusBadRequest, ErrorResponse{ + Message: "push token is empty", + Error: "no push token in request data", + }) + } + + _, err := expo.NewExponentPushToken(requestData.PushToken) + if err != nil { + return c.JSON(http.StatusBadRequest, ErrorResponse{ + Message: "invalid push token", + Error: "invalid push token in request data", + }) + } + + if (requestData.WalletPubkey == "") { + return c.JSON(http.StatusBadRequest, ErrorResponse{ + Message: "wallet pubkey is empty", + Error: "no wallet pubkey in request data", + }) + } + + if (requestData.ConnPubkey == "") { + return c.JSON(http.StatusBadRequest, ErrorResponse{ + Message: "connection pubkey is empty", + Error: "no connection pubkey in request data", + }) + } + + svc.Logger.WithFields(logrus.Fields{ + "wallet_pubkey": requestData.WalletPubkey, + "relay_url": requestData.RelayUrl, + "push_token": requestData.PushToken, + }).Debug("Subscribing to send push notifications") + + subscription := Subscription{ + RelayUrl: requestData.RelayUrl, + PushToken: requestData.PushToken, + Open: true, + Since: time.Now(), + Authors: &[]string{requestData.WalletPubkey}, + Kinds: &[]int{NIP_47_NOTIFICATION_KIND}, + } + + tags := make(nostr.TagMap) + (tags)["p"] = []string{requestData.ConnPubkey} + + subscription.Tags = &tags + + err = svc.db.Create(&subscription).Error + + if err != nil { + svc.Logger.WithError(err).WithFields(logrus.Fields{ + "wallet_pubkey": requestData.WalletPubkey, + "relay_url": requestData.RelayUrl, + "push_token": requestData.PushToken, + }).Error("Failed to store subscription") + return c.JSON(http.StatusBadRequest, ErrorResponse{ + Message: "Failed to store subscription", + Error: err.Error(), + }) + } + + go svc.startSubscription(svc.Ctx, &subscription, nil, svc.handleSubscribedExpoNotification) + + return c.NoContent(http.StatusOK) +} + +func (svc *Service) handleSubscribedExpoNotification(event *nostr.Event, subscription *Subscription) { + svc.Logger.WithFields(logrus.Fields{ + "event_id": event.ID, + "event_kind": event.Kind, + "subscription_id": subscription.ID, + "relay_url": subscription.RelayUrl, + }).Debug("Received subscribed notification") + + pushToken, _ := expo.NewExponentPushToken(subscription.PushToken) + + response, err := svc.client.Publish( + &expo.PushMessage{ + To: []expo.ExponentPushToken{pushToken}, + Title: "New event", + Body: "", + Data: map[string]string{ + "content": event.Content, + "appPubkey": event.Tags.GetFirst([]string{"p", ""}).Value(), + }, + Priority: expo.DefaultPriority, + }, + ) + + someerr := response.ValidateResponse() + if err != nil || someerr != nil { + svc.Logger.WithFields(logrus.Fields{ + "push_token": subscription.PushToken, + }).Error("Failed to send expo notification") + return + } + + svc.Logger.WithFields(logrus.Fields{ + "event_id": event.ID, + "push_token": subscription.PushToken, + }).Debug("Push notification sent successfully") +} diff --git a/internal/nostr/models.go b/internal/nostr/models.go index 89e1f28..ae42993 100644 --- a/internal/nostr/models.go +++ b/internal/nostr/models.go @@ -28,6 +28,7 @@ type Subscription struct { ID uint RelayUrl string WebhookUrl string + PushToken string Open bool Ids *[]string `gorm:"-"` Kinds *[]int `gorm:"-"` @@ -185,6 +186,13 @@ type NIP47NotificationRequest struct { ConnPubkey string `json:"connectionPubkey"` } +type NIP47ExpoNotificationRequest struct { + RelayUrl string `json:"relayUrl"` + PushToken string `json:"pushToken"` + WalletPubkey string `json:"walletPubkey"` + ConnPubkey string `json:"connectionPubkey"` +} + type NIP47Response struct { Event *nostr.Event `json:"event,omitempty"` State string `json:"state"` diff --git a/internal/nostr/nostr.go b/internal/nostr/nostr.go index 301af7c..0f7b801 100644 --- a/internal/nostr/nostr.go +++ b/internal/nostr/nostr.go @@ -23,6 +23,7 @@ import ( "gorm.io/gorm" "github.com/jackc/pgx/v5/stdlib" + expo "github.com/oliveroneill/exponent-server-sdk-golang/sdk" sqltrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql" gormtrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/gorm.io/gorm.v1" ) @@ -49,6 +50,7 @@ type Service struct { subscriptions map[string]*nostr.Subscription subscriptionsMutex sync.Mutex relayMutex sync.Mutex + client *expo.PushClient } func NewService(ctx context.Context) (*Service, error) { @@ -116,6 +118,12 @@ func NewService(ctx context.Context) (*Service, error) { subscriptions := make(map[string]*nostr.Subscription) + // TODO: Check limits + client := expo.NewPushClient(&expo.ClientConfig{ + Host: "https://api.expo.dev", + APIURL: "/v2", + }) + var wg sync.WaitGroup svc := &Service{ Cfg: cfg, @@ -125,6 +133,7 @@ func NewService(ctx context.Context) (*Service, error) { Logger: logger, Relay: relay, subscriptions: subscriptions, + client: client, } logger.Info("Starting all open subscriptions...") @@ -139,7 +148,11 @@ func NewService(ctx context.Context) (*Service, error) { // Create a copy of the loop variable to // avoid passing address of the same variable subscription := sub - go svc.startSubscription(svc.Ctx, &subscription, nil, svc.handleSubscribedEvent) + handleEvent := svc.handleSubscribedEvent + if sub.PushToken != "" { + handleEvent = svc.handleSubscribedExpoNotification + } + go svc.startSubscription(svc.Ctx, &subscription, nil, handleEvent) } return svc, nil @@ -928,6 +941,7 @@ func (svc *Service) postEventToWebhook(event *nostr.Event, webhookURL string) { "event_kind": event.Kind, "webhook_url": webhookURL, }).Error("Failed to post event to webhook") + return } svc.Logger.WithFields(logrus.Fields{ diff --git a/migrations/202411071013_add_push_token_to_subscriptions.go b/migrations/202411071013_add_push_token_to_subscriptions.go new file mode 100644 index 0000000..2c54710 --- /dev/null +++ b/migrations/202411071013_add_push_token_to_subscriptions.go @@ -0,0 +1,23 @@ +package migrations + +import ( + "github.com/go-gormigrate/gormigrate/v2" + "gorm.io/gorm" +) + +// Add push_token column to subscriptions table +var _202411071013_add_push_token_to_subscriptions = &gormigrate.Migration{ + ID: "202411071013_add_push_token_to_subscriptions", + Migrate: func(tx *gorm.DB) error { + if err := tx.Exec("ALTER TABLE subscriptions ADD COLUMN push_token TEXT").Error; err != nil { + return err + } + return nil + }, + Rollback: func(tx *gorm.DB) error { + if err := tx.Exec("ALTER TABLE subscriptions DROP COLUMN push_token").Error; err != nil { + return err + } + return nil + }, +} \ No newline at end of file diff --git a/migrations/migrate.go b/migrations/migrate.go index 444adee..6fc528f 100644 --- a/migrations/migrate.go +++ b/migrations/migrate.go @@ -12,6 +12,7 @@ func Migrate(db *gorm.DB) error { _202404021628_add_uuid_to_subscriptions, _202404031539_add_indexes, _202407171220_add_response_received_at_to_request_events, + _202411071013_add_push_token_to_subscriptions, }) return m.Migrate()