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

176 lines
5.0 KiB
Go

package main
import (
"net/http"
"github.com/pocketbase/pocketbase/core"
)
// setupAPIRoutes sets up the event store API routes
func setupAPIRoutes(app core.App, eventStore *SimpleEventStore) {
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
// JSON Patch endpoint using PATCH method
se.Router.PATCH("/api/collections/{collection}/items/{itemId}", func(e *core.RequestEvent) error {
collection := e.Request.PathValue("collection")
itemID := e.Request.PathValue("itemId")
if collection == "" || itemID == "" {
return e.BadRequestError("Collection and itemId are required", nil)
}
var patches []PatchOperation
if err := e.BindBody(&patches); err != nil {
return e.BadRequestError("Failed to parse JSON Patch data", err)
}
// Create event with patches
incomingEvent := &Event{
ItemID: itemID,
Collection: collection,
Patches: patches,
}
// Process the event
processedEvent, err := eventStore.ProcessEvent(incomingEvent)
if err != nil {
return e.InternalServerError("Failed to process event", err)
}
return e.JSON(http.StatusOK, processedEvent)
})
// Legacy POST endpoint for compatibility
se.Router.POST("/api/events", func(e *core.RequestEvent) error {
var incomingEvent Event
if err := e.BindBody(&incomingEvent); err != nil {
return e.BadRequestError("Failed to parse event data", err)
}
// Validate required fields
if incomingEvent.ItemID == "" || incomingEvent.Collection == "" || len(incomingEvent.Patches) == 0 {
return e.BadRequestError("Missing required fields: item_id, collection, patches", nil)
}
// Process the event
processedEvent, err := eventStore.ProcessEvent(&incomingEvent)
if err != nil {
return e.InternalServerError("Failed to process event", err)
}
return e.JSON(http.StatusCreated, processedEvent)
})
// Sync endpoint for clients
se.Router.POST("/api/sync", func(e *core.RequestEvent) error {
var syncReq SyncRequest
if err := e.BindBody(&syncReq); err != nil {
return e.BadRequestError("Failed to parse sync request", err)
}
// Check if client is in sync
isValid, err := eventStore.ValidateSync(syncReq.LastSeq, syncReq.LastHash)
if err != nil {
return e.InternalServerError("Failed to validate sync", err)
}
var response SyncResponse
if !isValid {
// Full sync needed - send all events
events, err := eventStore.GetEventsSince(0)
if err != nil {
return e.InternalServerError("Failed to get events", err)
}
response.Events = events
response.FullSync = true
} else {
// Incremental sync - send events since last sequence
events, err := eventStore.GetEventsSince(syncReq.LastSeq)
if err != nil {
return e.InternalServerError("Failed to get events", err)
}
response.Events = events
response.FullSync = false
}
// Get current state
latestEvent, err := eventStore.GetLatestEvent()
if err != nil {
return e.InternalServerError("Failed to get latest event", err)
}
if latestEvent != nil {
response.CurrentSeq = latestEvent.Seq
response.CurrentHash = latestEvent.Hash
}
return e.JSON(http.StatusOK, response)
})
// Get all items endpoint
se.Router.GET("/api/items/{collection}", func(e *core.RequestEvent) error {
collection := e.Request.PathValue("collection")
if collection == "" {
return e.BadRequestError("Collection name required", nil)
}
items, err := eventStore.GetAllItems(collection)
if err != nil {
return e.InternalServerError("Failed to get items", err)
}
return e.JSON(http.StatusOK, map[string]interface{}{
"items": items,
})
})
// Batch events endpoint for client to send multiple events
se.Router.POST("/api/events/batch", func(e *core.RequestEvent) error {
var events []Event
if err := e.BindBody(&events); err != nil {
return e.BadRequestError("Failed to parse events data", err)
}
processedEvents := make([]Event, 0, len(events))
for _, incomingEvent := range events {
// Validate required fields
if incomingEvent.ItemID == "" || incomingEvent.Collection == "" || len(incomingEvent.Patches) == 0 {
return e.BadRequestError("Missing required fields in event: item_id, collection, patches", nil)
}
processedEvent, err := eventStore.ProcessEvent(&incomingEvent)
if err != nil {
return e.InternalServerError("Failed to process event", err)
}
processedEvents = append(processedEvents, *processedEvent)
}
return e.JSON(http.StatusCreated, map[string]interface{}{
"events": processedEvents,
})
})
// Get current state endpoint
se.Router.GET("/api/state", func(e *core.RequestEvent) error {
latestEvent, err := eventStore.GetLatestEvent()
if err != nil {
return e.InternalServerError("Failed to get latest event", err)
}
response := map[string]interface{}{
"seq": 0,
"hash": "",
}
if latestEvent != nil {
response["seq"] = latestEvent.Seq
response["hash"] = latestEvent.Hash
}
return e.JSON(http.StatusOK, response)
})
return se.Next()
})
}