@@ -17,6 +17,8 @@ import (
1717 "golang.org/x/sync/singleflight"
1818 "k8s.io/apimachinery/pkg/util/sets"
1919
20+ "github.com/graphql-go/graphql"
21+ gql "github.com/operator-framework/operator-controller/internal/catalogd/graphql"
2022 "github.com/operator-framework/operator-registry/alpha/declcfg"
2123)
2224
@@ -193,9 +195,16 @@ func (s *LocalDirV1) StorageServerHandler() http.Handler {
193195 if s .EnableMetasHandler {
194196 mux .HandleFunc (s .RootURL .JoinPath ("{catalog}" , "api" , "v1" , "metas" ).Path , s .handleV1Metas )
195197 }
198+ mux .HandleFunc (s .RootURL .JoinPath ("{catalog}" , "api" , "v1" , "graphql" ).Path , s .handleV1GraphQL )
199+
196200 allowedMethodsHandler := func (next http.Handler , allowedMethods ... string ) http.Handler {
197201 allowedMethodSet := sets .New [string ](allowedMethods ... )
198202 return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
203+ // Allow POST requests for GraphQL endpoint
204+ if r .URL .Path != "" && r .URL .Path [len (r .URL .Path )- 7 :] == "graphql" && r .Method == http .MethodPost {
205+ next .ServeHTTP (w , r )
206+ return
207+ }
199208 if ! allowedMethodSet .Has (r .Method ) {
200209 http .Error (w , http .StatusText (http .StatusMethodNotAllowed ), http .StatusMethodNotAllowed )
201210 return
@@ -270,6 +279,59 @@ func (s *LocalDirV1) handleV1Metas(w http.ResponseWriter, r *http.Request) {
270279 serveJSONLines (w , r , indexReader )
271280}
272281
282+ func (s * LocalDirV1 ) handleV1GraphQL (w http.ResponseWriter , r * http.Request ) {
283+ s .m .RLock ()
284+ defer s .m .RUnlock ()
285+
286+ if r .Method != http .MethodPost {
287+ http .Error (w , "Only POST is allowed" , http .StatusMethodNotAllowed )
288+ return
289+ }
290+
291+ catalog := r .PathValue ("catalog" )
292+ catalogFile , _ , err := s .catalogData (catalog )
293+ if err != nil {
294+ httpError (w , err )
295+ return
296+ }
297+ defer catalogFile .Close ()
298+
299+ // Parse GraphQL query from request body
300+ var params struct {
301+ Query string `json:"query"`
302+ }
303+ if err := json .NewDecoder (r .Body ).Decode (& params ); err != nil {
304+ http .Error (w , "Invalid request body" , http .StatusBadRequest )
305+ return
306+ }
307+
308+ // Create catalog filesystem from the stored data
309+ catalogFS , err := s .createCatalogFS (catalog )
310+ if err != nil {
311+ httpError (w , err )
312+ return
313+ }
314+
315+ // Build dynamic GraphQL schema for this catalog
316+ dynamicSchema , err := s .buildCatalogGraphQLSchema (catalogFS )
317+ if err != nil {
318+ httpError (w , err )
319+ return
320+ }
321+
322+ // Execute GraphQL query
323+ result := graphql .Do (graphql.Params {
324+ Schema : dynamicSchema .Schema ,
325+ RequestString : params .Query ,
326+ })
327+
328+ w .Header ().Set ("Content-Type" , "application/json" )
329+ if err := json .NewEncoder (w ).Encode (result ); err != nil {
330+ httpError (w , err )
331+ return
332+ }
333+ }
334+
273335func (s * LocalDirV1 ) catalogData (catalog string ) (* os.File , os.FileInfo , error ) {
274336 catalogFile , err := os .Open (catalogFilePath (s .catalogDir (catalog )))
275337 if err != nil {
@@ -329,3 +391,50 @@ func (s *LocalDirV1) getIndex(catalog string) (*index, error) {
329391 }
330392 return idx .(* index ), nil
331393}
394+
395+ // createCatalogFS creates a filesystem interface for the catalog data
396+ func (s * LocalDirV1 ) createCatalogFS (catalog string ) (fs.FS , error ) {
397+ catalogDir := s .catalogDir (catalog )
398+ return os .DirFS (catalogDir ), nil
399+ }
400+
401+ // buildCatalogGraphQLSchema builds a dynamic GraphQL schema for the given catalog
402+ func (s * LocalDirV1 ) buildCatalogGraphQLSchema (catalogFS fs.FS ) (* gql.DynamicSchema , error ) {
403+ var metas []* declcfg.Meta
404+
405+ // Collect all metas from the catalog filesystem
406+ err := declcfg .WalkMetasFS (context .Background (), catalogFS , func (path string , meta * declcfg.Meta , err error ) error {
407+ if err != nil {
408+ return err
409+ }
410+ if meta != nil {
411+ metas = append (metas , meta )
412+ }
413+ return nil
414+ })
415+ if err != nil {
416+ return nil , fmt .Errorf ("error walking catalog metas: %w" , err )
417+ }
418+
419+ // Discover schema from collected metas
420+ catalogSchema , err := gql .DiscoverSchemaFromMetas (metas )
421+ if err != nil {
422+ return nil , fmt .Errorf ("error discovering schema: %w" , err )
423+ }
424+
425+ // Organize metas by schema for resolvers
426+ metasBySchema := make (map [string ][]* declcfg.Meta )
427+ for _ , meta := range metas {
428+ if meta .Schema != "" {
429+ metasBySchema [meta .Schema ] = append (metasBySchema [meta .Schema ], meta )
430+ }
431+ }
432+
433+ // Build dynamic GraphQL schema
434+ dynamicSchema , err := gql .BuildDynamicGraphQLSchema (catalogSchema , metasBySchema )
435+ if err != nil {
436+ return nil , fmt .Errorf ("error building GraphQL schema: %w" , err )
437+ }
438+
439+ return dynamicSchema , nil
440+ }
0 commit comments