package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" "time" ) // ExampleClient demonstrates how to interact with the event store API type ExampleClient struct { baseURL string client *http.Client } func NewExampleClient(baseURL string) *ExampleClient { return &ExampleClient{ baseURL: baseURL, client: &http.Client{Timeout: 10 * time.Second}, } } func (c *ExampleClient) CreateItem(itemID, content string) error { // Create item using JSON Patch "add" operations patches := []PatchOperation{ {Op: "add", Path: "/content", Value: content}, } return c.sendPatch("shopping_items", itemID, patches) } func (c *ExampleClient) UpdateItem(itemID, content string) error { // Update item using JSON Patch "replace" operation patches := []PatchOperation{ {Op: "replace", Path: "/content", Value: content}, } return c.sendPatch("shopping_items", itemID, patches) } func (c *ExampleClient) DeleteItem(itemID string) error { // Delete item using JSON Patch "add" operation for deleted_at patches := []PatchOperation{ {Op: "add", Path: "/deleted_at", Value: time.Now()}, } return c.sendPatch("shopping_items", itemID, patches) } // sendPatch sends JSON Patch operations using the PATCH method func (c *ExampleClient) sendPatch(collection, itemID string, patches []PatchOperation) error { jsonData, err := json.Marshal(patches) if err != nil { return fmt.Errorf("failed to marshal patches: %w", err) } url := fmt.Sprintf("%s/api/collections/%s/items/%s", c.baseURL, collection, itemID) req, err := http.NewRequest("PATCH", url, bytes.NewBuffer(jsonData)) if err != nil { return fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/json") resp, err := c.client.Do(req) if err != nil { return fmt.Errorf("failed to send patch: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("patch failed: %s", string(body)) } return nil } // sendEvent sends event using the legacy POST method func (c *ExampleClient) sendEvent(event Event) error { jsonData, err := json.Marshal(event) if err != nil { return fmt.Errorf("failed to marshal event: %w", err) } resp, err := c.client.Post(c.baseURL+"/api/events", "application/json", bytes.NewBuffer(jsonData)) if err != nil { return fmt.Errorf("failed to send event: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("event creation failed: %s", string(body)) } return nil } func (c *ExampleClient) SyncEvents(lastSeq int, lastHash string) (*SyncResponse, error) { syncReq := SyncRequest{ LastSeq: lastSeq, LastHash: lastHash, } jsonData, err := json.Marshal(syncReq) if err != nil { return nil, fmt.Errorf("failed to marshal sync request: %w", err) } resp, err := c.client.Post(c.baseURL+"/api/sync", "application/json", bytes.NewBuffer(jsonData)) if err != nil { return nil, fmt.Errorf("failed to sync: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("sync failed: %s", string(body)) } var syncResp SyncResponse if err := json.NewDecoder(resp.Body).Decode(&syncResp); err != nil { return nil, fmt.Errorf("failed to decode sync response: %w", err) } return &syncResp, nil } func (c *ExampleClient) GetItems(collection string) ([]map[string]interface{}, error) { resp, err := c.client.Get(c.baseURL + "/api/items/" + collection) if err != nil { return nil, fmt.Errorf("failed to get items: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("get items failed: %s", string(body)) } var result map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, fmt.Errorf("failed to decode response: %w", err) } items, ok := result["items"].([]interface{}) if !ok { return nil, fmt.Errorf("invalid response format") } var typedItems []map[string]interface{} for _, item := range items { if typedItem, ok := item.(map[string]interface{}); ok { typedItems = append(typedItems, typedItem) } } return typedItems, nil } // Advanced JSON Patch operations examples func (c *ExampleClient) AdvancedPatchExample(itemID string) error { // Example: Create item with multiple fields patches := []PatchOperation{ {Op: "add", Path: "/content", Value: "Advanced Shopping Item"}, {Op: "add", Path: "/quantity", Value: 5}, {Op: "add", Path: "/tags", Value: []string{"grocery", "urgent"}}, {Op: "add", Path: "/metadata", Value: map[string]interface{}{ "store": "SuperMart", "category": "food", }}, } if err := c.sendPatch("shopping_items", itemID, patches); err != nil { return fmt.Errorf("failed to create advanced item: %w", err) } // Example: Complex updates using different operations updatePatches := []PatchOperation{ {Op: "replace", Path: "/quantity", Value: 3}, // Update quantity {Op: "add", Path: "/tags/-", Value: "sale"}, // Append to array {Op: "replace", Path: "/metadata/store", Value: "MegaMart"}, // Update nested field {Op: "add", Path: "/metadata/priority", Value: "high"}, // Add new nested field {Op: "remove", Path: "/tags/0"}, // Remove first tag } return c.sendPatch("shopping_items", itemID, updatePatches) } // Example usage function func runClientExample() { client := NewExampleClient("http://localhost:8090") fmt.Println("=== RFC6902 JSON Patch Event Store Example ===") // Create some shopping items using JSON Patch fmt.Println("Creating shopping items with JSON Patch...") if err := client.CreateItem("item1", "Milk"); err != nil { fmt.Printf("Error creating item1: %v\n", err) return } if err := client.CreateItem("item2", "Bread"); err != nil { fmt.Printf("Error creating item2: %v\n", err) return } // Update an item using JSON Patch replace fmt.Println("Updating item1 with JSON Patch replace...") if err := client.UpdateItem("item1", "Organic Milk"); err != nil { fmt.Printf("Error updating item1: %v\n", err) return } // Advanced JSON Patch operations fmt.Println("Demonstrating advanced JSON Patch operations...") if err := client.AdvancedPatchExample("item3"); err != nil { fmt.Printf("Error with advanced patch example: %v\n", err) return } // Get current items fmt.Println("Fetching current items...") items, err := client.GetItems("shopping_items") if err != nil { fmt.Printf("Error getting items: %v\n", err) return } fmt.Printf("Current items: %+v\n", items) // Sync events (simulate client sync) fmt.Println("Syncing events...") syncResp, err := client.SyncEvents(0, "") if err != nil { fmt.Printf("Error syncing: %v\n", err) return } fmt.Printf("Sync response - Found %d events\n", len(syncResp.Events)) // Soft delete an item using JSON Patch fmt.Println("Soft deleting item2 with JSON Patch...") if err := client.DeleteItem("item2"); err != nil { fmt.Printf("Error deleting item2: %v\n", err) return } // Get items again to see the change fmt.Println("Fetching items after deletion...") items, err = client.GetItems("shopping_items") if err != nil { fmt.Printf("Error getting items: %v\n", err) return } fmt.Printf("Items after deletion: %+v\n", items) fmt.Println("=== JSON Patch Example completed ===") } // Uncomment the following to run the example: // func init() { // go func() { // time.Sleep(2 * time.Second) // Wait for server to start // runClientExample() // }() // }