package tools import ( "context" "encoding/json" "fmt" "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" "gogs.dmsc.dev/arp/graph" "gogs.dmsc.dev/arp/graph/model" ) // Mutate executes a GraphQL mutation func Mutate(ctx context.Context, resolver *graph.Resolver, schema *ast.Schema, args map[string]interface{}) (CallToolResult, error) { mutationStr, ok := args["mutation"].(string) if !ok { return CallToolResult{ Content: []ContentBlock{ {Type: "text", Text: "Missing required 'mutation' parameter"}, }, IsError: true, }, nil } // Parse variables variables := make(map[string]interface{}) if v, ok := args["variables"].(map[string]interface{}); ok { variables = v } // Parse the mutation mutationDoc, err := gqlparser.LoadQuery(schema, mutationStr) if err != nil { return CallToolResult{ Content: []ContentBlock{ {Type: "text", Text: fmt.Sprintf("Mutation parse error: %v", err)}, }, IsError: true, }, nil } // Execute each operation var results []interface{} for _, op := range mutationDoc.Operations { if op.Operation != ast.Mutation { continue } result, errMsg := executeMutation(ctx, resolver, schema, mutationDoc, op, variables) if errMsg != "" { return CallToolResult{ Content: []ContentBlock{ {Type: "text", Text: errMsg}, }, IsError: true, }, nil } results = append(results, result) } // Format response var responseText string if len(results) == 1 { bytes, _ := json.MarshalIndent(results[0], "", " ") responseText = string(bytes) } else { bytes, _ := json.MarshalIndent(results, "", " ") responseText = string(bytes) } return CallToolResult{ Content: []ContentBlock{ {Type: "text", Text: responseText}, }, }, nil } func executeMutation(ctx context.Context, resolver *graph.Resolver, schema *ast.Schema, doc *ast.QueryDocument, op *ast.OperationDefinition, variables map[string]interface{}) (map[string]interface{}, string) { result := make(map[string]interface{}) for _, sel := range op.SelectionSet { field, ok := sel.(*ast.Field) if !ok { continue } value, errMsg := resolveMutationField(ctx, resolver, schema, field, variables) if errMsg != "" { return nil, errMsg } result[field.Alias] = value } return result, "" } func resolveMutationField(ctx context.Context, resolver *graph.Resolver, schema *ast.Schema, field *ast.Field, variables map[string]interface{}) (interface{}, string) { // Get field arguments args := make(map[string]interface{}) for _, arg := range field.Arguments { value, err := arg.Value.Value(variables) if err != nil { return nil, fmt.Sprintf("failed to evaluate argument %s: %v", arg.Name, err) } args[arg.Name] = value } // Resolve based on field name switch field.Name { // User mutations case "createUser": input := parseNewUser(args["input"]) if input == nil { return nil, "missing or invalid input for createUser" } user, err := resolver.Mutation().CreateUser(ctx, *input) if err != nil { return nil, err.Error() } return user, "" case "updateUser": id, _ := args["id"].(string) input := parseUpdateUserInput(args["input"]) if input == nil { return nil, "missing or invalid input for updateUser" } user, err := resolver.Mutation().UpdateUser(ctx, id, *input) if err != nil { return nil, err.Error() } return user, "" case "deleteUser": id, _ := args["id"].(string) user, err := resolver.Mutation().DeleteUser(ctx, id) if err != nil { return nil, err.Error() } return user, "" // Note mutations case "createNote": input := parseNewNote(args["input"]) if input == nil { return nil, "missing or invalid input for createNote" } note, err := resolver.Mutation().CreateNote(ctx, *input) if err != nil { return nil, err.Error() } return note, "" case "updateNote": id, _ := args["id"].(string) input := parseUpdateNoteInput(args["input"]) if input == nil { return nil, "missing or invalid input for updateNote" } note, err := resolver.Mutation().UpdateNote(ctx, id, *input) if err != nil { return nil, err.Error() } return note, "" case "deleteNote": id, _ := args["id"].(string) note, err := resolver.Mutation().DeleteNote(ctx, id) if err != nil { return nil, err.Error() } return note, "" // Role mutations case "createRole": input := parseNewRole(args["input"]) if input == nil { return nil, "missing or invalid input for createRole" } role, err := resolver.Mutation().CreateRole(ctx, *input) if err != nil { return nil, err.Error() } return role, "" case "updateRole": id, _ := args["id"].(string) input := parseUpdateRoleInput(args["input"]) if input == nil { return nil, "missing or invalid input for updateRole" } role, err := resolver.Mutation().UpdateRole(ctx, id, *input) if err != nil { return nil, err.Error() } return role, "" case "deleteRole": id, _ := args["id"].(string) role, err := resolver.Mutation().DeleteRole(ctx, id) if err != nil { return nil, err.Error() } return role, "" // Permission mutations case "createPermission": input := parseNewPermission(args["input"]) if input == nil { return nil, "missing or invalid input for createPermission" } perm, err := resolver.Mutation().CreatePermission(ctx, *input) if err != nil { return nil, err.Error() } return perm, "" case "updatePermission": id, _ := args["id"].(string) input := parseUpdatePermissionInput(args["input"]) if input == nil { return nil, "missing or invalid input for updatePermission" } perm, err := resolver.Mutation().UpdatePermission(ctx, id, *input) if err != nil { return nil, err.Error() } return perm, "" case "deletePermission": id, _ := args["id"].(string) perm, err := resolver.Mutation().DeletePermission(ctx, id) if err != nil { return nil, err.Error() } return perm, "" // Service mutations case "createService": input := parseNewService(args["input"]) if input == nil { return nil, "missing or invalid input for createService" } service, err := resolver.Mutation().CreateService(ctx, *input) if err != nil { return nil, err.Error() } return service, "" case "updateService": id, _ := args["id"].(string) input := parseUpdateServiceInput(args["input"]) if input == nil { return nil, "missing or invalid input for updateService" } service, err := resolver.Mutation().UpdateService(ctx, id, *input) if err != nil { return nil, err.Error() } return service, "" case "deleteService": id, _ := args["id"].(string) service, err := resolver.Mutation().DeleteService(ctx, id) if err != nil { return nil, err.Error() } return service, "" // Task mutations case "createTask": input := parseNewTask(args["input"]) if input == nil { return nil, "missing or invalid input for createTask" } task, err := resolver.Mutation().CreateTask(ctx, *input) if err != nil { return nil, err.Error() } return task, "" case "updateTask": id, _ := args["id"].(string) input := parseUpdateTaskInput(args["input"]) if input == nil { return nil, "missing or invalid input for updateTask" } task, err := resolver.Mutation().UpdateTask(ctx, id, *input) if err != nil { return nil, err.Error() } return task, "" case "deleteTask": id, _ := args["id"].(string) task, err := resolver.Mutation().DeleteTask(ctx, id) if err != nil { return nil, err.Error() } return task, "" // TaskStatus mutations case "createTaskStatus": input := parseNewTaskStatus(args["input"]) if input == nil { return nil, "missing or invalid input for createTaskStatus" } status, err := resolver.Mutation().CreateTaskStatus(ctx, *input) if err != nil { return nil, err.Error() } return status, "" case "updateTaskStatus": id, _ := args["id"].(string) input := parseUpdateTaskStatusInput(args["input"]) if input == nil { return nil, "missing or invalid input for updateTaskStatus" } status, err := resolver.Mutation().UpdateTaskStatus(ctx, id, *input) if err != nil { return nil, err.Error() } return status, "" case "deleteTaskStatus": id, _ := args["id"].(string) status, err := resolver.Mutation().DeleteTaskStatus(ctx, id) if err != nil { return nil, err.Error() } return status, "" // Message mutations case "createMessage": input := parseNewMessage(args["input"]) if input == nil { return nil, "missing or invalid input for createMessage" } msg, err := resolver.Mutation().CreateMessage(ctx, *input) if err != nil { return nil, err.Error() } return msg, "" case "updateMessage": id, _ := args["id"].(string) input := parseUpdateMessageInput(args["input"]) if input == nil { return nil, "missing or invalid input for updateMessage" } msg, err := resolver.Mutation().UpdateMessage(ctx, id, *input) if err != nil { return nil, err.Error() } return msg, "" case "deleteMessage": id, _ := args["id"].(string) msg, err := resolver.Mutation().DeleteMessage(ctx, id) if err != nil { return nil, err.Error() } return msg, "" default: return nil, fmt.Sprintf("unknown mutation: %s", field.Name) } } // Helper functions func getString(m map[string]interface{}, key string) string { if v, ok := m[key]; ok { if s, ok := v.(string); ok { return s } } return "" } func parseStringSlice(v interface{}) []string { if v == nil { return nil } if arr, ok := v.([]interface{}); ok { result := make([]string, 0, len(arr)) for _, item := range arr { if s, ok := item.(string); ok { result = append(result, s) } } return result } if arr, ok := v.([]string); ok { return arr } return nil } func parseStringPtr(v interface{}) *string { if v == nil { return nil } if s, ok := v.(string); ok { return &s } return nil } // Input parsing functions func parseNewUser(v interface{}) *model.NewUser { if v == nil { return nil } m, ok := v.(map[string]interface{}) if !ok { return nil } return &model.NewUser{ Email: getString(m, "email"), Password: getString(m, "password"), Roles: parseStringSlice(m["roles"]), } } func parseUpdateUserInput(v interface{}) *model.UpdateUserInput { if v == nil { return nil } m, ok := v.(map[string]interface{}) if !ok { return nil } return &model.UpdateUserInput{ Email: parseStringPtr(m["email"]), Password: parseStringPtr(m["password"]), Roles: parseStringSlice(m["roles"]), } } func parseNewNote(v interface{}) *model.NewNote { if v == nil { return nil } m, ok := v.(map[string]interface{}) if !ok { return nil } return &model.NewNote{ Title: getString(m, "title"), Content: getString(m, "content"), UserID: getString(m, "userId"), ServiceID: getString(m, "serviceId"), } } func parseUpdateNoteInput(v interface{}) *model.UpdateNoteInput { if v == nil { return nil } m, ok := v.(map[string]interface{}) if !ok { return nil } return &model.UpdateNoteInput{ Title: parseStringPtr(m["title"]), Content: parseStringPtr(m["content"]), UserID: parseStringPtr(m["userId"]), ServiceID: parseStringPtr(m["serviceId"]), } } func parseNewRole(v interface{}) *model.NewRole { if v == nil { return nil } m, ok := v.(map[string]interface{}) if !ok { return nil } return &model.NewRole{ Name: getString(m, "name"), Description: getString(m, "description"), Permissions: parseStringSlice(m["permissions"]), } } func parseUpdateRoleInput(v interface{}) *model.UpdateRoleInput { if v == nil { return nil } m, ok := v.(map[string]interface{}) if !ok { return nil } return &model.UpdateRoleInput{ Name: parseStringPtr(m["name"]), Description: parseStringPtr(m["description"]), Permissions: parseStringSlice(m["permissions"]), } } func parseNewPermission(v interface{}) *model.NewPermission { if v == nil { return nil } m, ok := v.(map[string]interface{}) if !ok { return nil } return &model.NewPermission{ Code: getString(m, "code"), Description: getString(m, "description"), } } func parseUpdatePermissionInput(v interface{}) *model.UpdatePermissionInput { if v == nil { return nil } m, ok := v.(map[string]interface{}) if !ok { return nil } return &model.UpdatePermissionInput{ Code: parseStringPtr(m["code"]), Description: parseStringPtr(m["description"]), } } func parseNewService(v interface{}) *model.NewService { if v == nil { return nil } m, ok := v.(map[string]interface{}) if !ok { return nil } return &model.NewService{ Name: getString(m, "name"), Description: parseStringPtr(m["description"]), CreatedByID: getString(m, "createdById"), Participants: parseStringSlice(m["participants"]), } } func parseUpdateServiceInput(v interface{}) *model.UpdateServiceInput { if v == nil { return nil } m, ok := v.(map[string]interface{}) if !ok { return nil } return &model.UpdateServiceInput{ Name: parseStringPtr(m["name"]), Description: parseStringPtr(m["description"]), CreatedByID: parseStringPtr(m["createdById"]), Participants: parseStringSlice(m["participants"]), } } func parseNewTask(v interface{}) *model.NewTask { if v == nil { return nil } m, ok := v.(map[string]interface{}) if !ok { return nil } return &model.NewTask{ Title: getString(m, "title"), Content: getString(m, "content"), CreatedByID: getString(m, "createdById"), AssigneeID: parseStringPtr(m["assigneeId"]), StatusID: parseStringPtr(m["statusId"]), DueDate: parseStringPtr(m["dueDate"]), Priority: getString(m, "priority"), } } func parseUpdateTaskInput(v interface{}) *model.UpdateTaskInput { if v == nil { return nil } m, ok := v.(map[string]interface{}) if !ok { return nil } return &model.UpdateTaskInput{ Title: parseStringPtr(m["title"]), Content: parseStringPtr(m["content"]), CreatedByID: parseStringPtr(m["createdById"]), AssigneeID: parseStringPtr(m["assigneeId"]), StatusID: parseStringPtr(m["statusId"]), DueDate: parseStringPtr(m["dueDate"]), Priority: parseStringPtr(m["priority"]), } } func parseNewTaskStatus(v interface{}) *model.NewTaskStatus { if v == nil { return nil } m, ok := v.(map[string]interface{}) if !ok { return nil } return &model.NewTaskStatus{ Code: getString(m, "code"), Label: getString(m, "label"), } } func parseUpdateTaskStatusInput(v interface{}) *model.UpdateTaskStatusInput { if v == nil { return nil } m, ok := v.(map[string]interface{}) if !ok { return nil } return &model.UpdateTaskStatusInput{ Code: parseStringPtr(m["code"]), Label: parseStringPtr(m["label"]), } } func parseNewMessage(v interface{}) *model.NewMessage { if v == nil { return nil } m, ok := v.(map[string]interface{}) if !ok { return nil } return &model.NewMessage{ Content: getString(m, "content"), Receivers: parseStringSlice(m["receivers"]), } } func parseUpdateMessageInput(v interface{}) *model.UpdateMessageInput { if v == nil { return nil } m, ok := v.(map[string]interface{}) if !ok { return nil } return &model.UpdateMessageInput{ Content: parseStringPtr(m["content"]), Receivers: parseStringSlice(m["receivers"]), } }