package gql_ev import ( "context" "crypto/sha256" "errors" "fmt" "strings" "time" "github.com/keys-pub/keys" "github.com/sour-is/ev/internal/logz" "github.com/sour-is/ev/pkg/domain" "github.com/sour-is/ev/pkg/es" "github.com/sour-is/ev/pkg/msgbus" "go.opentelemetry.io/otel/metric/instrument/syncint64" "go.uber.org/multierr" ) type Resolver struct { es *es.EventStore Mresolver_posts syncint64.Counter Mresolver_post_added syncint64.Counter Mresolver_post_added_event syncint64.Counter Mresolver_create_salty_user syncint64.Counter Mresolver_salty_user syncint64.Counter } func New(ctx context.Context, es *es.EventStore) (*Resolver, error) { ctx, span := logz.Span(ctx) defer span.End() m := logz.Meter(ctx) var err, errs error r := &Resolver{es: es} r.Mresolver_posts, err = m.SyncInt64().Counter("resolver_posts") errs = multierr.Append(errs, err) r.Mresolver_post_added, err = m.SyncInt64().Counter("resolver_post_added") errs = multierr.Append(errs, err) r.Mresolver_post_added_event, err = m.SyncInt64().Counter("resolver_post_added") errs = multierr.Append(errs, err) r.Mresolver_create_salty_user, err = m.SyncInt64().Counter("resolver_create_salty_user") errs = multierr.Append(errs, err) r.Mresolver_salty_user, err = m.SyncInt64().Counter("resolver_salty_user") errs = multierr.Append(errs, err) span.RecordError(err) return r, errs } // Posts is the resolver for the events field. func (r *Resolver) Posts(ctx context.Context, streamID string, paging *PageInput) (*Connection, error) { ctx, span := logz.Span(ctx) defer span.End() r.Mresolver_posts.Add(ctx, 1) lis, err := r.es.Read(ctx, streamID, paging.GetIdx(0), paging.GetCount(30)) if err != nil { span.RecordError(err) return nil, err } edges := make([]Edge, 0, len(lis)) for i := range lis { span.AddEvent(fmt.Sprint("post ", i, " of ", len(lis))) e := lis[i] m := e.EventMeta() post, ok := e.(*msgbus.PostEvent) if !ok { continue } edges = append(edges, PostEvent{ ID: lis[i].EventMeta().EventID.String(), Payload: string(post.Payload), Tags: post.Tags, Meta: &m, }) } var first, last uint64 if first, err = r.es.FirstIndex(ctx, streamID); err != nil { span.RecordError(err) return nil, err } if last, err = r.es.LastIndex(ctx, streamID); err != nil { span.RecordError(err) return nil, err } return &Connection{ Paging: &PageInfo{ Next: lis.Last().EventMeta().Position < last, Prev: lis.First().EventMeta().Position > first, Begin: lis.First().EventMeta().Position, End: lis.Last().EventMeta().Position, }, Edges: edges, }, nil } func (r *Resolver) PostAdded(ctx context.Context, streamID string, after int64) (<-chan *PostEvent, error) { ctx, span := logz.Span(ctx) defer span.End() r.Mresolver_post_added.Add(ctx, 1) es := r.es.EventStream() if es == nil { return nil, fmt.Errorf("EventStore does not implement streaming") } sub, err := es.Subscribe(ctx, streamID, after) if err != nil { span.RecordError(err) return nil, err } ch := make(chan *PostEvent) go func() { ctx, span := logz.Span(ctx) defer span.End() defer func() { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() err := sub.Close(ctx) span.RecordError(err) }() for sub.Recv(ctx) { events, err := sub.Events(ctx) if err != nil { span.RecordError(err) break } span.AddEvent(fmt.Sprintf("received %d events", len(events))) r.Mresolver_post_added_event.Add(ctx, int64(len(events))) for _, e := range events { m := e.EventMeta() if p, ok := e.(*msgbus.PostEvent); ok { select { case ch <- &PostEvent{ ID: m.EventID.String(), Payload: string(p.Payload), Tags: p.Tags, Meta: &m, }: continue case <-ctx.Done(): return } } } } }() return ch, nil } func (r *Resolver) CreateSaltyUser(ctx context.Context, nick string, pub string) (*SaltyUser, error) { ctx, span := logz.Span(ctx) defer span.End() r.Mresolver_create_salty_user.Add(ctx, 1) streamID := fmt.Sprintf("saltyuser-%x", sha256.Sum256([]byte(strings.ToLower(nick)))) span.AddEvent(streamID) key, err := keys.NewEdX25519PublicKeyFromID(keys.ID(pub)) if err != nil { span.RecordError(err) return nil, err } a, err := es.Create(ctx, r.es, streamID, func(ctx context.Context, agg *domain.SaltyUser) error { return agg.OnUserRegister(nick, key) }) switch { case errors.Is(err, es.ErrShouldNotExist): span.RecordError(err) return nil, fmt.Errorf("user exists") case err != nil: span.RecordError(err) return nil, fmt.Errorf("internal error") } return &SaltyUser{ Nick: nick, Pubkey: pub, Inbox: a.Inbox.String(), }, nil } func (r *Resolver) SaltyUser(ctx context.Context, nick string) (*SaltyUser, error) { ctx, span := logz.Span(ctx) defer span.End() r.Mresolver_salty_user.Add(ctx, 1) streamID := fmt.Sprintf("saltyuser-%x", sha256.Sum256([]byte(strings.ToLower(nick)))) span.AddEvent(streamID) a, err := es.Update(ctx, r.es, streamID, func(ctx context.Context, agg *domain.SaltyUser) error { return nil }) switch { case errors.Is(err, es.ErrShouldExist): span.RecordError(err) return nil, fmt.Errorf("user not found") case err != nil: span.RecordError(err) return nil, fmt.Errorf("%w internal error", err) } return &SaltyUser{ Nick: nick, Pubkey: a.Pubkey.String(), Inbox: a.Inbox.String(), }, err }