diff --git a/api/server.go b/api/api.go similarity index 89% rename from api/server.go rename to api/api.go index cfda40d1..f6bd0677 100644 --- a/api/server.go +++ b/api/api.go @@ -49,11 +49,16 @@ func jsonHandler(log *zap.Logger, h func(r *http.Request) (any, error)) http.Han } } +type feedService interface { + Metas() []feed.Meta + GetFeedPosts(ctx context.Context, feedKey string, cursor string, limit int) (posts []feed.Post, err error) +} + func New( log *zap.Logger, hostname string, listenAddr string, - feedRegistry *feed.Service, + feedService feedService, pgxStore *store.PGXStore, bskyCredentials *bluesky.Credentials, authEngine *AuthEngine, @@ -75,13 +80,13 @@ func New( }) // Mount xrpc handlers - didEndpointPath, didHandler, err := didHandler(hostname) + didEndpointPath, didHandler, err := didHandler(log, hostname) if err != nil { return nil, fmt.Errorf("creating did handler: %w", err) } mux.Handle(didEndpointPath, didHandler) - mux.Handle(getFeedSkeletonHandler(log, feedRegistry)) - mux.Handle(describeFeedGeneratorHandler(log, hostname, feedRegistry)) + mux.Handle(getFeedSkeletonHandler(log, feedService)) + mux.Handle(describeFeedGeneratorHandler(log, hostname, feedService)) // Mount Buf Connect services modSvcHandler := &ModerationServiceHandler{ @@ -106,7 +111,7 @@ func New( mux.Handle( bffv1pbconnect.NewPublicServiceHandler( &PublicServiceHandler{ - feedMetaSourcer: feedRegistry, + feedService: feedService, }, interceptors, ), diff --git a/api/server_test.go b/api/api_test.go similarity index 76% rename from api/server_test.go rename to api/api_test.go index 7b0ac8a5..14656131 100644 --- a/api/server_test.go +++ b/api/api_test.go @@ -4,6 +4,7 @@ import ( "connectrpc.com/connect" "context" "errors" + "fmt" indigoTest "github.com/bluesky-social/indigo/testing" "github.com/stretchr/testify/require" "github.com/strideynet/bsky-furry-feed/bluesky" @@ -24,6 +25,18 @@ func actorAuthInterceptor(actor *indigoTest.TestUser) connect.UnaryInterceptorFu } } +type fakeFeedService struct { + metas []feed.Meta +} + +func (m *fakeFeedService) Metas() []feed.Meta { + return m.metas +} + +func (m *fakeFeedService) GetFeedPosts(ctx context.Context, feedKey string, cursor string, limit int) (posts []feed.Post, err error) { + return nil, fmt.Errorf("unimplemented") +} + type apiHarness struct { *testenv.Harness APIAddr string @@ -33,12 +46,23 @@ func startAPIHarness(ctx context.Context, t *testing.T) *apiHarness { harness := testenv.StartHarness(ctx, t) // Create PDS user for the API taking actions as the feed + lis, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + _ = harness.PDS.MustNewUser(t, "bff.tpds") srv, err := New( harness.Log, + "feed.test.furryli.st", "", - "", - &feed.Service{}, + &fakeFeedService{ + metas: []feed.Meta{ + { + ID: "fake-1", + DisplayName: "Fake", + Description: "My Description", + }, + }, + }, harness.Store, &bluesky.Credentials{ Identifier: "bff.tpds", @@ -53,8 +77,6 @@ func startAPIHarness(ctx context.Context, t *testing.T) *apiHarness { t.Cleanup(func() { srv.Close() }) - lis, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) go func() { err := srv.Serve(lis) diff --git a/api/describe_feed_generator.go b/api/describe_feed_generator.go index 7f517f7a..a45993ab 100644 --- a/api/describe_feed_generator.go +++ b/api/describe_feed_generator.go @@ -2,7 +2,6 @@ package api import ( "fmt" - "github.com/strideynet/bsky-furry-feed/feed" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.uber.org/zap" "net/http" @@ -20,10 +19,12 @@ type describeFeedGeneratorResponse struct { func describeFeedGeneratorHandler( log *zap.Logger, hostname string, - registry *feed.Service, + feedService feedService, ) (string, http.Handler) { feedURI := func(feedName string) string { return fmt.Sprintf( + // TODO(noah): This should be returning the profile that owns the + // content not the serverDID "at://%s/app.bsky.feed.generator/%s", serverDID(hostname), feedName, @@ -31,9 +32,9 @@ func describeFeedGeneratorHandler( } feeds := []describeFeedGeneratorResponseFeed{} - for _, id := range registry.IDs() { + for _, meta := range feedService.Metas() { feeds = append(feeds, describeFeedGeneratorResponseFeed{ - URI: feedURI(id), + URI: feedURI(meta.ID), }) } diff --git a/api/describe_feed_generator_test.go b/api/describe_feed_generator_test.go new file mode 100644 index 00000000..9bf878e4 --- /dev/null +++ b/api/describe_feed_generator_test.go @@ -0,0 +1,28 @@ +package api + +import ( + "context" + "github.com/stretchr/testify/require" + "io" + "net/http" + "testing" +) + +func TestAPI_DescribeFeedGenerator(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + t.Parallel() + + ctx := context.Background() + harness := startAPIHarness(ctx, t) + resp, err := http.Get(harness.APIAddr + "/xrpc/app.bsky.feed.describeFeedGenerator") + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode) + bytes, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + want := `{"did":"did:web:feed.test.furryli.st","feeds":[{"uri":"at://did:web:feed.test.furryli.st/app.bsky.feed.generator/fake-1"}]}` + "\n" + require.Equal(t, want, string(bytes)) +} diff --git a/api/did.go b/api/did.go index f705accb..93954dcf 100644 --- a/api/did.go +++ b/api/did.go @@ -1,8 +1,8 @@ package api import ( - "encoding/json" "fmt" + "go.uber.org/zap" "net/http" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" @@ -12,33 +12,32 @@ func serverDID(hostname string) string { return fmt.Sprintf("did:web:%s", hostname) } -func generateDIDJSON(hostname string) ([]byte, error) { - type Object map[string]any - - did := Object{ - "@context": []string{"https://www.w3.org/ns/did/v1"}, - "id": serverDID(hostname), - "service": []Object{{ - "id": "#bsky_fg", - "type": "BskyFeedGenerator", - "serviceEndpoint": fmt.Sprintf("https://%s", hostname), - }}, - } - - return json.Marshal(did) +type WebDIDService struct { + ID string `json:"id"` + Type string `json:"type"` + ServiceEndpoint string `json:"serviceEndpoint"` } -func didHandler(hostname string) (string, http.Handler, error) { - did, err := generateDIDJSON(hostname) - if err != nil { - return "", nil, fmt.Errorf("generating did json: %w", err) - } - - var h http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(200) +type WebDID struct { + Context []string `json:"@context"` + ID string `json:"id"` + Service []WebDIDService `json:"service"` +} - _, _ = w.Write(did) - } +func didHandler(log *zap.Logger, hostname string) (string, http.Handler, error) { + h := jsonHandler(log, func(r *http.Request) (any, error) { + return WebDID{ + Context: []string{"https://www.w3.org/ns/did/v1"}, + ID: serverDID(hostname), + Service: []WebDIDService{ + { + ID: "#bsky_fg", + Type: "BskyFeedGenerator", + ServiceEndpoint: fmt.Sprintf("https://%s", hostname), + }, + }, + }, nil + }) return "/.well-known/did.json", otelhttp.NewHandler(h, "get_well_known_did"), nil } diff --git a/api/did_test.go b/api/did_test.go new file mode 100644 index 00000000..db031af1 --- /dev/null +++ b/api/did_test.go @@ -0,0 +1,28 @@ +package api + +import ( + "context" + "github.com/stretchr/testify/require" + "io" + "net/http" + "testing" +) + +func TestAPI_WellKnownDID(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + t.Parallel() + + ctx := context.Background() + harness := startAPIHarness(ctx, t) + resp, err := http.Get(harness.APIAddr + "/.well-known/did.json") + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode) + bytes, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + want := `{"@context":["https://www.w3.org/ns/did/v1"],"id":"did:web:feed.test.furryli.st","service":[{"id":"#bsky_fg","type":"BskyFeedGenerator","serviceEndpoint":"https://feed.test.furryli.st"}]}` + "\n" + require.Equal(t, want, string(bytes)) +} diff --git a/api/get_feed_skeleton.go b/api/get_feed_skeleton.go index dfc70268..bb4913ce 100644 --- a/api/get_feed_skeleton.go +++ b/api/get_feed_skeleton.go @@ -2,7 +2,6 @@ package api import ( "fmt" - "github.com/strideynet/bsky-furry-feed/feed" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.uber.org/zap" "net/http" @@ -64,7 +63,7 @@ type getFeedSkeletonResponse struct { } func getFeedSkeletonHandler( - log *zap.Logger, registry *feed.Service, + log *zap.Logger, feedService feedService, ) (string, http.Handler) { h := jsonHandler(log, func(r *http.Request) (any, error) { ctx := r.Context() @@ -80,7 +79,7 @@ func getFeedSkeletonHandler( zap.Int("limit", params.limit), ) - posts, err := registry.GetFeedPosts(ctx, params.feed, params.cursor, params.limit) + posts, err := feedService.GetFeedPosts(ctx, params.feed, params.cursor, params.limit) if err != nil { return nil, fmt.Errorf("fetching feed %q: %w", params.feed, err) } diff --git a/api/moderation_test.go b/api/moderation_test.go index 1d46e10b..543d1867 100644 --- a/api/moderation_test.go +++ b/api/moderation_test.go @@ -11,7 +11,7 @@ import ( "testing" ) -func TestModerationServiceHandler_CreateActor(t *testing.T) { +func TestAPI_ModerationServiceHandler_CreateActor(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } diff --git a/api/public.go b/api/public.go index 76c21e7f..4fefb705 100644 --- a/api/public.go +++ b/api/public.go @@ -4,21 +4,16 @@ import ( "connectrpc.com/connect" "context" "fmt" - "github.com/strideynet/bsky-furry-feed/feed" v1 "github.com/strideynet/bsky-furry-feed/proto/bff/v1" ) -type feedMetaSourcer interface { - Metas() []feed.Meta -} - type PublicServiceHandler struct { - feedMetaSourcer feedMetaSourcer + feedService feedService } func (p *PublicServiceHandler) ListFeeds(_ context.Context, _ *connect.Request[v1.ListFeedsRequest]) (*connect.Response[v1.ListFeedsResponse], error) { feeds := []*v1.Feed{} - for _, f := range p.feedMetaSourcer.Metas() { + for _, f := range p.feedService.Metas() { feeds = append(feeds, &v1.Feed{ Id: f.ID, // TODO(noah): Take BLUESKY_USERNAME and inject that instead of this diff --git a/api/public_test.go b/api/public_test.go index 51a0616e..8acf64cb 100644 --- a/api/public_test.go +++ b/api/public_test.go @@ -9,16 +9,8 @@ import ( "testing" ) -type fakeMetaSourcer struct { - metas []feed.Meta -} - -func (m *fakeMetaSourcer) Metas() []feed.Meta { - return m.metas -} - func TestPublicServiceHandler_ListFeeds(t *testing.T) { - h := PublicServiceHandler{feedMetaSourcer: &fakeMetaSourcer{ + h := PublicServiceHandler{feedService: &fakeFeedService{ metas: []feed.Meta{ { ID: "foo", diff --git a/api/user_test.go b/api/user_test.go index 68a9e73f..8dd47230 100644 --- a/api/user_test.go +++ b/api/user_test.go @@ -11,7 +11,7 @@ import ( "testing" ) -func TestUserServiceHandler_GetMe(t *testing.T) { +func TestAPI_UserServiceHandler_GetMe(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } diff --git a/feed/feed.go b/feed/feed.go index 52b12f43..914617ec 100644 --- a/feed/feed.go +++ b/feed/feed.go @@ -63,15 +63,6 @@ func (s *Service) Register(m Meta, generateFunc GenerateFunc) { } } -// IDs returns a slice of the IDs of feeds which are eligible for generation. -func (s *Service) IDs() []string { - ids := make([]string, 0, len(s.feeds)) - for _, f := range s.feeds { - ids = append(ids, f.meta.ID) - } - return ids -} - func (s *Service) Metas() []Meta { metas := make([]Meta, 0, len(s.feeds)) for _, f := range s.feeds {