Hello from MCP server
package main
import (
"fmt"
"log"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
"golang.org/x/exp/slices"
)
// TODO: make sure these errors surface as HTTP responses
// update this logic to look for primary org and then parent orgs (i.e. inherited books)
type Change struct {
Collection string `json:"collection"`
Operation string `json:"operation"`
Data map[string]any `json:"data"`
ID string `json:"id,omitempty"` // for update/delete operations
}
func ProcessChangeset(app core.App, changes []Change, orgId string, bookId string) error {
// Start a transaction for all changes
return app.RunInTransaction(func(txApp core.App) error {
for _, change := range changes {
if err := processChange(txApp, change, orgId, bookId); err != nil {
return fmt.Errorf("failed to process change for collection %s: %w", change.Collection, err)
}
}
return nil
})
}
func processChange(app core.App, change Change, orgId string, bookId string) error {
collection, err := app.FindCollectionByNameOrId(change.Collection)
if err != nil {
return fmt.Errorf("collection not found: %s", change.Collection)
}
allowedCollections := []string{"menus", "offers", "menuTiers", "problems", "problemTags", "checklists", "costsMaterial", "costsTime", "tierSets", "tiers", "formulas", "contentItems", "menuCopy", "warrantyCopy"}
allowedCollection := slices.Contains(allowedCollections, collection.Name)
if !allowedCollection {
return fmt.Errorf("collection not found: %s", change.Collection)
}
switch change.Operation {
case "create":
return createRecord(app, collection, change.Data, orgId, bookId)
case "update":
return updateRecord(app, collection, change.Data, orgId, bookId)
case "delete":
return deleteRecord(app, collection, change.Data, orgId)
default:
return fmt.Errorf("unsupported operation: %s", change.Operation)
}
}
func createRecord(app core.App, collection *core.Collection, data map[string]any, orgId string, bookId string) error {
record := core.NewRecord(collection)
globalCollections := []string{"currencies"}
warrantyCopy, err := app.FindCollectionByNameOrId("warrantyCopy")
if err != nil {
return err
}
warranties, err := app.FindAllRecords(warrantyCopy)
if err != nil {
return err
}
log.Println("Query for all warranties")
for _, warranty := range warranties {
log.Println("..", warranty)
}
if refsRaw, ok := data["refs"]; ok {
if refsSlice, ok := refsRaw.([]any); ok {
for _, refItem := range refsSlice {
if refMap, ok := refItem.(map[string]any); ok {
collectionName, _ := refMap["collection"].(string)
refIds, _ := refMap["refIds"].([]any)
var recordIds []string
for _, refId := range refIds {
if _, ok := refId.(string); ok {
var refRecord *core.Record
var err error
if slices.Contains(globalCollections, collectionName) {
refRecord, err = app.FindFirstRecordByFilter(collectionName, "refId = {:refId}", dbx.Params{"refId": refId})
} else {
refRecord, err = app.FindFirstRecordByFilter(collectionName, "refId = {:refId} && org = {:orgId}", dbx.Params{"refId": refId, "orgId": orgId})
}
if err != nil {
log.Println("Not found", refId)
return err
}
recordIds = append(recordIds, refRecord.Id)
}
}
record.Set(collectionName, recordIds)
} else {
log.Println("Invalid ref item format")
}
}
} else {
log.Println("Invalid ref format: expected array")
}
}
// Set the data fields
for key, value := range data {
if key != "refs" { // skip ref since we handled it above
record.Set(key, value)
}
}
record.Set("org", orgId)
record.Set("book", bookId)
log.Println("collection", collection.Name)
// TODO: we will probably have other types of tags to add here,
// e.g. tags for content items
// if collection.Name == "problemTags" {
// cleanTagName(record)
// }
// Validate and save
if err := app.Validate(record); err != nil {
return fmt.Errorf("validation failed: %w", err)
}
if err := app.Save(record); err != nil {
return fmt.Errorf("save failed: %w", err)
}
log.Printf("Created record in %s: %s", collection.Name, record.Id)
return nil
}
func updateRecord(app core.App, collection *core.Collection, data map[string]any, orgId string, bookId string) error {
// record, err := app.FindFirstRecordByData(collection, "refId", data["refId"])
record, err := app.FindFirstRecordByFilter(collection, "refId = {:refId} && org = {:org}", dbx.Params{
"refId": data["refId"],
"org": orgId,
})
if err != nil {
return createRecord(app, collection, data, orgId, bookId)
// return fmt.Errorf("record not found: %s", data["refId"])
}
for key, value := range data {
record.Set(key, value)
}
record.Set("org", orgId)
record.Set("book", bookId)
// TODO: we will probably have other types of tags to add here,
// e.g. tags for content items
// if collection.Name == "problemTags" {
// cleanTagName(record)
// }
if err := app.Validate(record); err != nil {
return fmt.Errorf("validation failed: %w", err)
}
if err := app.Save(record); err != nil {
return fmt.Errorf("save failed: %w", err)
}
log.Printf("Updated record in %s: %s", collection.Name, record.Id)
return nil
}
func deleteRecord(app core.App, collection *core.Collection, data map[string]any, orgId string) error {
// Find record by refId and org, similar to updateRecord
record, err := app.FindFirstRecordByFilter(collection, "refId = {:refId} && org = {:org}", dbx.Params{
"refId": data["refId"],
"org": orgId,
})
if err != nil {
return fmt.Errorf("record not found: refId=%s, org=%s", data["refId"], orgId)
}
if err := app.Delete(record); err != nil {
return fmt.Errorf("delete failed: %w", err)
}
log.Printf("Deleted record from %s: refId=%s", collection.Name, data["refId"])
return nil
}
func onPricebookChanges(app core.App) {
app.OnRecordAfterCreateSuccess("pricebookChanges").BindFunc(func(e *core.RecordEvent) error {
// Skip changeset processing - just store the record without applying changes
// This allows uploading changes without modifying the backend database
log.Printf("Stored pricebookChanges record (processing skipped)")
return e.Next()
})
}