@@ -18,6 +18,8 @@ import (
1818 "golang.org/x/sync/singleflight"
1919 "k8s.io/apimachinery/pkg/util/sets"
2020
21+ "github.com/graphql-go/graphql"
22+ gql "github.com/operator-framework/operator-controller/internal/catalogd/graphql"
2123 "github.com/operator-framework/operator-registry/alpha/declcfg"
2224)
2325
@@ -225,9 +227,16 @@ func (s *LocalDirV1) StorageServerHandler() http.Handler {
225227 if s .EnableMetasHandler {
226228 mux .HandleFunc (s .RootURL .JoinPath ("{catalog}" , "api" , "v1" , "metas" ).Path , s .handleV1Metas )
227229 }
230+ mux .HandleFunc (s .RootURL .JoinPath ("{catalog}" , "api" , "v1" , "graphql" ).Path , s .handleV1GraphQL )
231+
228232 allowedMethodsHandler := func (next http.Handler , allowedMethods ... string ) http.Handler {
229233 allowedMethodSet := sets .New [string ](allowedMethods ... )
230234 return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
235+ // Allow POST requests for GraphQL endpoint
236+ if r .URL .Path != "" && r .URL .Path [len (r .URL .Path )- 7 :] == "graphql" && r .Method == http .MethodPost {
237+ next .ServeHTTP (w , r )
238+ return
239+ }
231240 if ! allowedMethodSet .Has (r .Method ) {
232241 http .Error (w , http .StatusText (http .StatusMethodNotAllowed ), http .StatusMethodNotAllowed )
233242 return
@@ -302,6 +311,59 @@ func (s *LocalDirV1) handleV1Metas(w http.ResponseWriter, r *http.Request) {
302311 serveJSONLines (w , r , indexReader )
303312}
304313
314+ func (s * LocalDirV1 ) handleV1GraphQL (w http.ResponseWriter , r * http.Request ) {
315+ s .m .RLock ()
316+ defer s .m .RUnlock ()
317+
318+ if r .Method != http .MethodPost {
319+ http .Error (w , "Only POST is allowed" , http .StatusMethodNotAllowed )
320+ return
321+ }
322+
323+ catalog := r .PathValue ("catalog" )
324+ catalogFile , _ , err := s .catalogData (catalog )
325+ if err != nil {
326+ httpError (w , err )
327+ return
328+ }
329+ defer catalogFile .Close ()
330+
331+ // Parse GraphQL query from request body
332+ var params struct {
333+ Query string `json:"query"`
334+ }
335+ if err := json .NewDecoder (r .Body ).Decode (& params ); err != nil {
336+ http .Error (w , "Invalid request body" , http .StatusBadRequest )
337+ return
338+ }
339+
340+ // Create catalog filesystem from the stored data
341+ catalogFS , err := s .createCatalogFS (catalog )
342+ if err != nil {
343+ httpError (w , err )
344+ return
345+ }
346+
347+ // Build dynamic GraphQL schema for this catalog
348+ dynamicSchema , err := s .buildCatalogGraphQLSchema (catalogFS )
349+ if err != nil {
350+ httpError (w , err )
351+ return
352+ }
353+
354+ // Execute GraphQL query
355+ result := graphql .Do (graphql.Params {
356+ Schema : dynamicSchema .Schema ,
357+ RequestString : params .Query ,
358+ })
359+
360+ w .Header ().Set ("Content-Type" , "application/json" )
361+ if err := json .NewEncoder (w ).Encode (result ); err != nil {
362+ httpError (w , err )
363+ return
364+ }
365+ }
366+
305367func (s * LocalDirV1 ) catalogData (catalog string ) (* os.File , os.FileInfo , error ) {
306368 catalogFile , err := os .Open (catalogFilePath (s .catalogDir (catalog )))
307369 if err != nil {
@@ -361,3 +423,50 @@ func (s *LocalDirV1) getIndex(catalog string) (*index, error) {
361423 }
362424 return idx .(* index ), nil
363425}
426+
427+ // createCatalogFS creates a filesystem interface for the catalog data
428+ func (s * LocalDirV1 ) createCatalogFS (catalog string ) (fs.FS , error ) {
429+ catalogDir := s .catalogDir (catalog )
430+ return os .DirFS (catalogDir ), nil
431+ }
432+
433+ // buildCatalogGraphQLSchema builds a dynamic GraphQL schema for the given catalog
434+ func (s * LocalDirV1 ) buildCatalogGraphQLSchema (catalogFS fs.FS ) (* gql.DynamicSchema , error ) {
435+ var metas []* declcfg.Meta
436+
437+ // Collect all metas from the catalog filesystem
438+ err := declcfg .WalkMetasFS (context .Background (), catalogFS , func (path string , meta * declcfg.Meta , err error ) error {
439+ if err != nil {
440+ return err
441+ }
442+ if meta != nil {
443+ metas = append (metas , meta )
444+ }
445+ return nil
446+ })
447+ if err != nil {
448+ return nil , fmt .Errorf ("error walking catalog metas: %w" , err )
449+ }
450+
451+ // Discover schema from collected metas
452+ catalogSchema , err := gql .DiscoverSchemaFromMetas (metas )
453+ if err != nil {
454+ return nil , fmt .Errorf ("error discovering schema: %w" , err )
455+ }
456+
457+ // Organize metas by schema for resolvers
458+ metasBySchema := make (map [string ][]* declcfg.Meta )
459+ for _ , meta := range metas {
460+ if meta .Schema != "" {
461+ metasBySchema [meta .Schema ] = append (metasBySchema [meta .Schema ], meta )
462+ }
463+ }
464+
465+ // Build dynamic GraphQL schema
466+ dynamicSchema , err := gql .BuildDynamicGraphQLSchema (catalogSchema , metasBySchema )
467+ if err != nil {
468+ return nil , fmt .Errorf ("error building GraphQL schema: %w" , err )
469+ }
470+
471+ return dynamicSchema , nil
472+ }
0 commit comments