package admin_api import ( "fmt" "net/http" "git.ohea.xyz/cursorius/server/database" "github.com/google/uuid" "github.com/graphql-go/graphql" "github.com/graphql-go/handler" "github.com/op/go-logging" ) var log = logging.MustGetLogger("cursorius-server") func createSchema(db database.Database) (graphql.Schema, error) { webhookType := graphql.NewObject(graphql.ObjectConfig{ Name: "Webhook", Description: "A webhook for triggering pipelines", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The id of the webhook.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if webhook, ok := p.Source.(database.Webhook); ok { return webhook.Id, nil } return nil, nil }, }, "serverType": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The format of the webhook.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if webhook, ok := p.Source.(database.Webhook); ok { return webhook.ServerType, nil } return nil, nil }, }, "secret": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The secret used to validate the webhook.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if webhook, ok := p.Source.(database.Webhook); ok { return webhook.Secret, nil } return nil, nil }, }, }, }) runType := graphql.NewObject(graphql.ObjectConfig{ Name: "Run", Description: "A pipeline run", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The id of the run.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if run, ok := p.Source.(database.Run); ok { return run.Id, nil } return nil, nil }, }, "progress": &graphql.Field{ Type: graphql.Boolean, Description: "The progress status of the run.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if run, ok := p.Source.(database.Run); ok { return run.InProgress, nil } return nil, nil }, }, "result": &graphql.Field{ // TODO: handle bigint properly here Type: graphql.Float, Description: "The result of the run.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if run, ok := p.Source.(database.Run); ok { return run.Result, nil } return nil, nil }, }, "stdout": &graphql.Field{ Type: graphql.String, Description: "The stdout used to validate the run.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if run, ok := p.Source.(database.Run); ok { return run.Stdout, nil } return nil, nil }, }, "stderr": &graphql.Field{ Type: graphql.String, Description: "The stderr used to validate the run.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if run, ok := p.Source.(database.Run); ok { return run.Stderr, nil } return nil, nil }, }, }, }) pipelineType := graphql.NewObject(graphql.ObjectConfig{ Name: "Pipeline", Description: "A pipeline for running ci jobs", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The id of the pipeline.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if pipeline, ok := p.Source.(database.Pipeline); ok { return pipeline.Id, nil } return nil, nil }, }, "name": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The name of the pipeline.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if pipeline, ok := p.Source.(database.Pipeline); ok { return pipeline.Name, nil } return nil, nil }, }, "url": &graphql.Field{ Type: graphql.NewNonNull(graphql.String), Description: "The url of the pipeline.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if pipeline, ok := p.Source.(database.Pipeline); ok { return pipeline.Url, nil } return nil, nil }, }, "pollInterval": &graphql.Field{ Type: graphql.NewNonNull(graphql.Int), Description: "The polling interval for the pipeline.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if pipeline, ok := p.Source.(database.Pipeline); ok { return pipeline.PollInterval, nil } return nil, nil }, }, "webhooks": &graphql.Field{ Type: graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(webhookType))), Description: "The list of webhooks for the pipeline.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if pipeline, ok := p.Source.(database.Pipeline); ok { return db.GetWebhooksForPipeline(pipeline.Id) } return []database.Webhook{}, nil }, }, "runs": &graphql.Field{ Type: graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(runType))), Description: "The list of runs for the pipeline.", Resolve: func(p graphql.ResolveParams) (interface{}, error) { if pipeline, ok := p.Source.(database.Pipeline); ok { return db.GetRunsForPipeline(pipeline.Id) } return []database.Webhook{}, nil }, }, }, }) queryType := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{ "Pipeline": &graphql.Field{ Type: pipelineType, Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), Description: "The id of the requested pipeline.", }, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { id, err := uuid.Parse(p.Args["id"].(string)) if err != nil { return nil, err } return db.GetPipelineById(id) }, }, "Pipelines": &graphql.Field{ Type: graphql.NewNonNull(graphql.NewList(pipelineType)), Args: graphql.FieldConfigArgument{}, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return db.GetPipelines() }, }, }, }) mutationType := graphql.NewObject(graphql.ObjectConfig{ Name: "Mutation", Fields: graphql.Fields{ "createPipeline": &graphql.Field{ Type: pipelineType, Description: "Create a new pipeline", Args: graphql.FieldConfigArgument{ "name": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, "url": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, "pollInterval": &graphql.ArgumentConfig{ Type: graphql.Int, }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { var interval int if intervalVal, ok := params.Args["pollInterval"]; ok { interval = intervalVal.(int) } else { interval = 0 } pipeline, err := db.CreatePipeline( params.Args["name"].(string), params.Args["url"].(string), interval, ) if err != nil { return nil, err } log.Infof("Created pipeline with id: %v, name: %v, url: %v, and poll interval: %v", pipeline.Id, pipeline.Name, pipeline.Url, pipeline.PollInterval) return pipeline, nil }, }, "createWebhook": &graphql.Field{ Type: webhookType, Description: "Create a new webhook", Args: graphql.FieldConfigArgument{ "type": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, "pipeline_id": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), }, }, Resolve: func(params graphql.ResolveParams) (interface{}, error) { id, err := uuid.Parse(params.Args["id"].(string)) if err != nil { return nil, err } webhook, err := db.CreateWebhook( database.WebhookSender(params.Args["type"].(string)), id, ) if err != nil { return nil, err } return webhook, nil }, }, }, }) schema, err := graphql.NewSchema(graphql.SchemaConfig{ Query: queryType, Mutation: mutationType, }) if err != nil { return schema, fmt.Errorf("Could not create schema: %w", err) } return schema, nil } func CreateHandler(db database.Database, mux *http.ServeMux) error { schema, err := createSchema(db) if err != nil { return err } h := handler.New(&handler.Config{ Schema: &schema, Pretty: true, GraphiQL: true, }) mux.Handle("/graphql", h) return nil }