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