~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
135 lines · 4.3 KB Raw History
package events

import (
	"context"
	"fmt"
	"log/slog"
	"net/http"
	"path/filepath"
	"sync"

	commonv1 "github.com/johnandersen777/socialweb-computer-kcp/api/common/v1alpha1"
	eventsv1 "github.com/johnandersen777/socialweb-computer-kcp/api/events/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/atproto/pdsclient"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/bus"
	contract "github.com/johnandersen777/socialweb-computer-kcp/internal/contract/events"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/atprotorecordwatch"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/eventsource"
	eventstreamctl "github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/eventstream"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/jetstreameventsource"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/pdseventsource"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/relayeventsource"
	ctlruntime "github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/runtime"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/events/delivery"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/eventstream"

	"k8s.io/apimachinery/pkg/runtime/schema"
)

const CursorDir = "eventstreams"

type Options struct {
	Bus *bus.Bus

	Dynamic eventstream.DynamicFactory

	Resolver *pdsclient.Resolver

	PLCURL string

	HTTPClient *http.Client

	StateDir string

	Cursors contract.CursorStore

	RecordWatchChildren atprotorecordwatch.Children

	RecordWatchRepos atprotorecordwatch.Repos

	RecordWatchValues atprotorecordwatch.ValueReader

	Scope func(workspace string) bool

	Log *slog.Logger
}

type sinks struct {
	ctx context.Context

	opts Options

	mu sync.Mutex

	markets map[string]*eventstream.MarketSink
}

func Register(ctx context.Context, m *ctlruntime.Manager, opts Options) (func(context.Context) error, error) {
	log := opts.Log
	if log == nil {
		log = slog.New(slog.DiscardHandler)
	}
	opts.Log = log
	backend := eventsource.BackendOptions{Bus: opts.Bus, Log: log}
	if _, err := eventsource.Register(m, eventsource.Options{Bus: opts.Bus, Log: log}); err != nil {
		return nil, err
	}
	if _, err := pdseventsource.Register(ctx, m, backend); err != nil {
		return nil, err
	}
	if _, err := relayeventsource.Register(ctx, m, backend); err != nil {
		return nil, err
	}
	if _, err := jetstreameventsource.Register(ctx, m, backend); err != nil {
		return nil, err
	}
	cursors := opts.Cursors
	if cursors == nil {
		cursors = delivery.NewCursorStore(filepath.Join(opts.StateDir, CursorDir))
	}
	streams, err := eventstreamctl.Register(ctx, m, eventstreamctl.Options{Bus: opts.Bus, Log: log, Cursors: cursors})
	if err != nil {
		return nil, err
	}
	watches := eventstream.NewWatches(eventstream.WatchesConfig{PLCDirectoryURL: opts.PLCURL, HTTPClient: resolverHTTP(opts.Resolver),
		External: opts.HTTPClient, Log: log})
	if _, err := atprotorecordwatch.Register(m, atprotorecordwatch.Options{Bus: opts.Bus, Backend: watches, Children: opts.RecordWatchChildren,
		Repos: opts.RecordWatchRepos, Values: opts.RecordWatchValues, Scope: opts.Scope, Log: log}); err != nil {
		return nil, err
	}
	s := &sinks{ctx: ctx, opts: opts, markets: map[string]*eventstream.MarketSink{}}
	bus.Provide[contract.Sink](opts.Bus, schema.GroupKind{Group: eventsv1.GroupName, Kind: contract.HandlerKind}, contract.CapabilityDeliver, s.openMarket)
	return streams.Run, nil
}

func resolverHTTP(r *pdsclient.Resolver) *http.Client {
	switch {
	case r == nil:
		return nil
	case r.HTTPClient != nil:
		return r.HTTPClient
	case r.Handlers != nil:
		return r.Handlers.HTTPClient()
	}
	return nil
}

func (s *sinks) openMarket(_ context.Context, to commonv1.ObjectRef, _ []string) (contract.Sink, error) {
	role, ok := eventstream.MarketRole(to.Name)
	if !ok {
		return nil, fmt.Errorf("events: %s names no market role", to.String())
	}
	key := to.Workspace + "/" + to.Name
	s.mu.Lock()
	defer s.mu.Unlock()
	if sink := s.markets[key]; sink != nil {
		return sink, nil
	}
	sink, err := eventstream.NewMarketSink(eventstream.MarketOptions{Workspace: to.Workspace, Role: role, Dynamic: s.opts.Dynamic,
		Resolver: s.opts.Resolver, PLCDirectoryURL: s.opts.PLCURL, Context: s.ctx, Log: s.opts.Log})
	if err != nil {
		return nil, err
	}
	s.markets[key] = sink
	return sink, nil
}