Files
event-driven-shoppinglist/example_client.go
2025-09-29 08:14:53 +02:00

270 lines
7.5 KiB
Go

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()
// }()
// }