~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
119 lines · 3.1 KB Raw History
package gatewaymain

import (
	"cmp"
	"context"
	"errors"
	"log/slog"
	"net/http"
	"time"

	"github.com/bluesky-social/indigo/atproto/identity"

	"github.com/johnandersen777/socialweb-computer-kcp/internal/gateway"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/plcsweep"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/revocation"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/xrpcserver"
)

type Deps struct {
	Store gateway.Store
	Deny  revocation.Deny

	Feed gateway.Feed

	Directory identity.Directory

	Revoker gateway.Revoker

	Certs gateway.CertRevoker

	Tenant gateway.TenantAPI

	Sweep *plcsweep.Sweeper

	SweepInterval time.Duration
}

type Gateway struct {
	handler       http.Handler
	surface       *xrpcserver.Server
	sweep         *plcsweep.Sweeper
	sweepInterval time.Duration
	log           *slog.Logger
}

func Build(ctx context.Context, cfg Config, d Deps, log *slog.Logger) (*Gateway, error) {
	if log == nil {
		log = slog.Default()
	}

	verbs := &gateway.Server{
		Deny: d.Deny, Store: d.Store, Revoker: d.Revoker, Certs: d.Certs, Feed: d.Feed,
		Tenant: d.Tenant,
	}

	surface, err := xrpcserver.New(ctx, xrpcserver.Options{
		Gateway:   verbs,
		Audiences: cfg.Audiences,
		Directory: d.Directory,
	})
	if err != nil {
		return nil, err
	}

	interval := d.SweepInterval
	interval = cmp.Or(interval, plcsweep.DefaultInterval)
	g := &Gateway{
		handler:       surface.Handler(),
		surface:       surface,
		sweep:         d.Sweep,
		sweepInterval: interval,
		log:           log,
	}
	g.report(d)
	return g, nil
}

func (g *Gateway) Handler() http.Handler { return g.handler }

func (g *Gateway) Close() { g.surface.Close() }

func (g *Gateway) Run(ctx context.Context) error {
	if g.sweep == nil {
		<-ctx.Done()
		return nil
	}
	err := g.sweep.Run(ctx, g.sweepInterval, g.onPass)
	if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
		return nil
	}
	return err
}

func (g *Gateway) onPass(outcomes []plcsweep.Outcome) {
	for _, o := range outcomes {
		switch {
		case o.Skipped:
			g.log.Warn("plc sweep: did not resolve", "did", o.DID, "why", o.Why)
		case o.Revoke:
			g.log.Info("plc sweep: deny written", "did", o.DID, "why", o.Why)
		default:
			g.log.Debug("plc sweep: live", "did", o.DID, "why", o.Why)
		}
	}
}

func (g *Gateway) report(d Deps) {
	if g.sweep == nil {
		g.log.Warn("the PLC sweep is UNWIRED",
			"missing", "Deps.Sweep: plcsweep.Worklist, plcsweep.Resolver and plcsweep.Revoker are all implemented, and main.buildDeps builds and wires all three. This warning means the composition root skipped that construction, so g.Run -- the only caller of sweep.Run -- has nothing to run",
			"reached by", "a caller that supplies a nil Deps.Sweep, which today is the wiring smoke tests ONLY",
			"consequence", "an account deleted at PLC between logins is not noticed by this process")
	}
	if d.Revoker == nil {
		g.log.Warn("revokeIdentity is routed but UNWRITABLE",
			"missing", "gateway.Revoker -- the deny's write half has no implementation",
			"consequence", "revokeIdentity answers 503 ClusterUnavailable")
	}
}