Skip to content

Commit

Permalink
Add tools to debug events/notifications (#42)
Browse files Browse the repository at this point in the history
Also adds a field which clearly notes when a notification was created.
  • Loading branch information
boreq authored Aug 25, 2023
1 parent e3997b2 commit cf9221e
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 24 deletions.
166 changes: 166 additions & 0 deletions cmd/notification-service-list-events/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package main

import (
"context"
"fmt"
"os"
"sort"
"sync"

"github.com/boreq/errors"
"github.com/nbd-wtf/go-nostr"
"github.com/planetary-social/go-notification-service/cmd/notification-service/di"
"github.com/planetary-social/go-notification-service/internal"
configadapters "github.com/planetary-social/go-notification-service/service/adapters/config"
"github.com/planetary-social/go-notification-service/service/domain"
"github.com/planetary-social/go-notification-service/service/domain/notifications"
)

func main() {
if err := run(); err != nil {
fmt.Printf("error: %s\n", err)
os.Exit(1)
}
}

func run() error {
ctx := context.Background()

if len(os.Args) != 2 {
return errors.New("usage: program <npub>")
}

publicKey, err := domain.NewPublicKeyFromNpub(os.Args[1])
if err != nil {
return errors.Wrap(err, "error decoding the npub")
}

cfg, err := configadapters.NewEnvironmentConfigLoader().Load()
if err != nil {
return errors.Wrap(err, "error creating a config")
}

service, cleanup, err := di.BuildService(ctx, cfg)
if err != nil {
return errors.Wrap(err, "error building a service")
}
defer cleanup()

tokensSet := internal.NewEmptySet[domain.APNSToken]()

if err := listTokens(ctx, service, publicKey, tokensSet); err != nil {
return errors.Wrap(err, "error listing APNs tokens")
}

if err := listEvents(ctx, service, publicKey, tokensSet); err != nil {
return errors.Wrap(err, "error listing APNs tokens")
}

return nil
}

func listTokens(ctx context.Context, service di.Service, publicKey domain.PublicKey, tokensSet *internal.Set[domain.APNSToken]) error {
fmt.Println()
fmt.Println("listing stored APNs tokens related to public key", publicKey.Hex())

tokens, err := service.App().Queries.GetTokens.Handle(ctx, publicKey)
if err != nil {
return errors.Wrap(err, "error getting APNs tokens")
}

if len(tokens) == 0 {
fmt.Println("no stored tokens")
}

for _, token := range tokens {
tokensSet.Put(token)
fmt.Println("token", token.Hex())
}

return nil
}

type eventWithNotifications struct {
Event domain.Event
Notifications []notifications.Notification
}

func listEvents(ctx context.Context, service di.Service, publicKey domain.PublicKey, tokensSet *internal.Set[domain.APNSToken]) error {
fmt.Println()
fmt.Println("listing stored events related to public key", publicKey.Hex())

filters, err := domain.NewFilters(nostr.Filters{
{
Tags: map[string][]string{
"p": {
publicKey.Hex(),
},
},
Limit: 100,
},
})
if err != nil {
return errors.Wrap(err, "error creating filters")
}

var events []eventWithNotifications
var eventsLock sync.Mutex
var eventsWaitGroup sync.WaitGroup

for v := range service.App().Queries.GetEvents.Handle(ctx, filters) {
if err := v.Err(); err != nil {
return errors.Wrap(err, "handler returned an error")
}

if v.EOSE() {
break
}

eventsWaitGroup.Add(1)

event := v.Event()
go func() {
defer eventsWaitGroup.Done()

notifications, err := service.App().Queries.GetNotifications.Handle(ctx, event.Id())
if err != nil {
fmt.Println(errors.Wrapf(err, "error getting notifications for event '%s'", event.Id().Hex()))
return
}

eventsLock.Lock()
defer eventsLock.Unlock()

events = append(events, eventWithNotifications{
Event: event,
Notifications: notifications,
})
}()
}

eventsWaitGroup.Wait()

sort.Slice(events, func(i, j int) bool {
return events[i].Event.CreatedAt().Before(events[j].Event.CreatedAt())
})

for _, eventWithNotifications := range events {
evt := eventWithNotifications.Event

fmt.Println("event", evt.Id().Hex(), "type", evt.Kind().Int(), "number of tags", len(evt.Tags()), "created at", evt.CreatedAt())

if evt.PubKey() == publicKey {
fmt.Println("-> own event")
}

for _, notification := range eventWithNotifications.Notifications {
fmt.Printf("-> notification %s created at %s", notification.UUID(), notification.CreatedAt())
if !tokensSet.Contains(notification.APNSToken()) {
fmt.Print(" (for someone else)")
}
fmt.Println()
}
}

return nil
}
35 changes: 25 additions & 10 deletions service/adapters/firestore/repository_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package firestore

import (
"context"
"fmt"
"time"

"cloud.google.com/go/firestore"
Expand All @@ -25,9 +24,10 @@ const (
eventFieldKind = "kind"
eventFieldRaw = "raw"

eventNotificationUUID = "uuid"
eventNotificationToken = "token"
eventNotificationPayload = "payload"
eventNotificationUUID = "uuid"
eventNotificationToken = "token"
eventNotificationPayload = "payload"
eventNotificationCreatedAt = "createdAt"
)

type EventRepository struct {
Expand Down Expand Up @@ -91,10 +91,16 @@ func (e *EventRepository) SaveNotificationForEvent(notification notifications.No
Collection(collectionEventsNotifications).
Doc(notification.UUID().String())

createdAt := notification.CreatedAt()
if createdAt == nil {
return errors.New("new notifications should always have the createdAt value populated")
}

notificationDocData := map[string]any{
eventNotificationUUID: ensureType[string](notification.UUID().String()),
eventNotificationToken: ensureType[string](notification.APNSToken().Hex()),
eventNotificationPayload: ensureType[[]byte](notification.Payload()),
eventNotificationUUID: ensureType[string](notification.UUID().String()),
eventNotificationToken: ensureType[string](notification.APNSToken().Hex()),
eventNotificationPayload: ensureType[[]byte](notification.Payload()),
eventNotificationCreatedAt: ensureType[time.Time](*createdAt),
}

if err := e.tx.Set(notificationDocPath, notificationDocData, firestore.MergeAll); err != nil {
Expand Down Expand Up @@ -160,8 +166,6 @@ func (e *EventRepository) GetNotifications(ctx context.Context, id domain.EventI
}
}

fmt.Println("create time", doc.CreateTime)

data := make(map[string]any)
if err := doc.DataTo(&data); err != nil {
return nil, errors.Wrap(err, "error getting doc data")
Expand All @@ -177,7 +181,18 @@ func (e *EventRepository) GetNotifications(ctx context.Context, id domain.EventI
return nil, errors.Wrap(err, "error creating a token")
}

notification, err := notifications.NewNotification(event, uuid, token, data[eventNotificationPayload].([]byte))
var createdAt *time.Time
if loadedCreatedAt, ok := data[eventNotificationCreatedAt].(time.Time); ok {
createdAt = &loadedCreatedAt
}

notification, err := notifications.NewNotificationFromHistory(
event,
uuid,
token,
data[eventNotificationPayload].([]byte),
createdAt,
)
if err != nil {
return nil, errors.Wrap(err, "error creating a notification")
}
Expand Down
43 changes: 29 additions & 14 deletions service/domain/notifications/notifications.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package notifications

import (
"time"

"github.com/boreq/errors"
"github.com/google/uuid"
"github.com/planetary-social/go-notification-service/internal/logging"
Expand Down Expand Up @@ -33,7 +35,7 @@ func (g *Generator) Generate(mention domain.PublicKey, token domain.APNSToken, e
return nil, errors.Wrap(err, "error generating a notification id")
}

notification, err := NewNotification(event, id, token, payloadJSON)
notification, err := NewNotification(event, id, token, payloadJSON, time.Now())
if err != nil {
return nil, errors.Wrap(err, "error creating a notification")
}
Expand Down Expand Up @@ -63,39 +65,48 @@ func (g *Generator) mentionedThemself(mention domain.PublicKey, event domain.Eve
type Notification struct {
event domain.Event

uuid NotificationUUID
token domain.APNSToken
payload []byte
uuid NotificationUUID
token domain.APNSToken
payload []byte
createdAt *time.Time // old notifications don't have this value
}

func NewNotification(
event domain.Event,
uuid NotificationUUID,
token domain.APNSToken,
payload []byte,
createdAt time.Time,
) (Notification, error) {
if len(payload) == 0 {
return Notification{}, errors.New("empty payload")
}
return Notification{
event: event,
uuid: uuid,
token: token,
payload: payload,
event: event,
uuid: uuid,
token: token,
payload: payload,
createdAt: &createdAt,
}, nil
}

func MustNewNotification(
func NewNotificationFromHistory(
event domain.Event,
uuid NotificationUUID,
token domain.APNSToken,
payload []byte,
) Notification {
v, err := NewNotification(event, uuid, token, payload)
if err != nil {
panic(err)
createdAt *time.Time,
) (Notification, error) {
if len(payload) == 0 {
return Notification{}, errors.New("empty payload")
}
return v
return Notification{
event: event,
uuid: uuid,
token: token,
payload: payload,
createdAt: createdAt,
}, nil
}

func (n Notification) Event() domain.Event {
Expand All @@ -114,6 +125,10 @@ func (n Notification) Payload() []byte {
return n.payload
}

func (n Notification) CreatedAt() *time.Time {
return n.createdAt
}

type NotificationUUID struct {
s string
}
Expand Down
14 changes: 14 additions & 0 deletions service/domain/public_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/boreq/errors"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/nbd-wtf/go-nostr/nip19"
)

type PublicKey struct {
Expand All @@ -25,6 +26,19 @@ func NewPublicKeyFromHex(s string) (PublicKey, error) {
return PublicKey{s}, nil
}

func NewPublicKeyFromNpub(s string) (PublicKey, error) {
prefix, hexString, err := nip19.Decode(s)
if err != nil {
return PublicKey{}, errors.Wrap(err, "error decoding a nip19 entity")
}

if prefix != "npub" {
return PublicKey{}, errors.New("passed something which isn't an npub")
}

return NewPublicKeyFromHex(hexString.(string))
}

func (k PublicKey) Hex() string {
return k.s
}
Expand Down

0 comments on commit cf9221e

Please sign in to comment.