package graphql import ( "encoding/json" "fmt" "net/http" "github.com/graphql-go/graphql" "github.com/restxlsx/restxlsx/internal/excel" "github.com/restxlsx/restxlsx/internal/logger" ) type Handler struct { schema graphql.Schema engine *excel.Engine Logger *logger.Logger } func New(engine *excel.Engine, log *logger.Logger) (*Handler, error) { h := &Handler{engine: engine, Logger: log} schema, err := h.buildSchema() if err != nil { return nil, err } h.schema = schema return h, nil } func (h *Handler) buildSchema() (graphql.Schema, error) { fields := graphql.Fields{ "tables": &graphql.Field{ Type: graphql.NewList(graphql.String), Resolve: func(p graphql.ResolveParams) (any, error) { return h.engine.ListTables(), nil }, }, } for _, name := range h.engine.ListTables() { t := h.engine.GetTable(name) tableType := buildObjectType(name, t) nameInner := name fields[fmt.Sprintf("get_%s", name)] = &graphql.Field{ Type: tableType, Args: graphql.FieldConfigArgument{ "id": &graphql.ArgumentConfig{ Type: graphql.String, }, }, Resolve: func(p graphql.ResolveParams) (any, error) { table := h.engine.GetTable(nameInner) if table == nil { return nil, nil } if id, ok := p.Args["id"]; ok && id != "" { idStr := fmt.Sprintf("%v", id) idColInner := h.engine.IDColumn(nameInner) for _, row := range table.Rows { if fmt.Sprintf("%v", row[idColInner]) == idStr { return row, nil } } return nil, nil } return table.Rows, nil }, } fields[fmt.Sprintf("list_%s", name)] = &graphql.Field{ Type: graphql.NewList(tableType), Resolve: func(p graphql.ResolveParams) (any, error) { table := h.engine.GetTable(nameInner) if table == nil { return []map[string]any{}, nil } return table.Rows, nil }, } fields[fmt.Sprintf("create_%s", name)] = &graphql.Field{ Type: tableType, Args: buildInputArgs(t), Resolve: func(p graphql.ResolveParams) (any, error) { table := h.engine.GetTable(nameInner) if table == nil { return nil, fmt.Errorf("table not found: %s", nameInner) } rec := make(map[string]any) for _, col := range table.Columns { if v, ok := p.Args[col]; ok { rec[col] = v } } idColInner := h.engine.IDColumn(nameInner) if idColInner != "" { if _, ok := rec[idColInner]; !ok { rec[idColInner] = len(table.Rows) + 1 } } table.Rows = append(table.Rows, rec) if err := h.engine.Flush(); err != nil { return nil, err } return rec, nil }, } } queryType := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: fields, }) return graphql.NewSchema(graphql.SchemaConfig{ Query: queryType, }) } func buildObjectType(name string, t *excel.Table) *graphql.Object { fields := graphql.Fields{} for _, col := range t.Columns { colName := col fields[colName] = &graphql.Field{ Type: graphql.String, Resolve: func(p graphql.ResolveParams) (any, error) { if row, ok := p.Source.(map[string]any); ok { return fmt.Sprintf("%v", row[colName]), nil } return nil, nil }, } } return graphql.NewObject(graphql.ObjectConfig{ Name: name, Fields: fields, }) } func buildInputArgs(t *excel.Table) graphql.FieldConfigArgument { args := graphql.FieldConfigArgument{} for _, col := range t.Columns { args[col] = &graphql.ArgumentConfig{ Type: graphql.String, } } return args } func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var params struct { Query string `json:"query"` OperationName string `json:"operationName"` Variables map[string]any `json:"variables"` } if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { http.Error(w, `{"error":"invalid JSON"}`, http.StatusBadRequest) return } h.engine.Lock() result := graphql.Do(graphql.Params{ Schema: h.schema, RequestString: params.Query, OperationName: params.OperationName, VariableValues: params.Variables, }) h.engine.Unlock() w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(result) }