184 lines
5.3 KiB
Go
184 lines
5.3 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 - ONE OPERATION PER REQUEST
|
|
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 operation struct {
|
|
Op string `json:"op"`
|
|
Path string `json:"path"`
|
|
Value string `json:"value"`
|
|
From string `json:"from"`
|
|
}
|
|
if err := e.BindBody(&operation); err != nil {
|
|
return e.BadRequestError("Failed to parse operation data", err)
|
|
}
|
|
|
|
// Create event with single operation
|
|
incomingEvent := &Event{
|
|
ItemID: itemID,
|
|
Collection: collection,
|
|
Operation: operation.Op,
|
|
Path: operation.Path,
|
|
Value: operation.Value,
|
|
From: operation.From,
|
|
}
|
|
|
|
// 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 == "" || incomingEvent.Operation == "" {
|
|
return e.BadRequestError("Missing required fields: item_id, collection, operation", 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 == "" || incomingEvent.Operation == "" {
|
|
return e.BadRequestError("Missing required fields in event: item_id, collection, operation", 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()
|
|
})
|
|
}
|