package handler import ( "encoding/json" "net/http" "github.com/go-chi/chi/v5" "github.com/wotra/wotra/internal/store" ) // SyncHandler serves /api/sync routes. type SyncHandler struct { syncStore *store.SyncStore } func NewSyncHandler(syncStore *store.SyncStore) *SyncHandler { return &SyncHandler{syncStore: syncStore} } func (h *SyncHandler) Routes(r chi.Router) { r.Post("/sync/pull", h.Pull) r.Post("/sync/push", h.Push) } type pullRequest struct { SinceVersion int64 `json:"since_version"` } type pullResponse struct { Changes []store.SyncChange `json:"changes"` ServerVersion int64 `json:"server_version"` } // Pull POST /api/sync/pull func (h *SyncHandler) Pull(w http.ResponseWriter, r *http.Request) { var req pullRequest if err := decodeJSON(r, &req); err != nil { writeError(w, http.StatusBadRequest, "invalid JSON") return } changes, serverVersion, err := h.syncStore.Pull(r.Context(), req.SinceVersion) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } if changes == nil { changes = []store.SyncChange{} } writeJSON(w, http.StatusOK, pullResponse{Changes: changes, ServerVersion: serverVersion}) } type pushChange struct { Entity string `json:"_entity"` Op string `json:"_op"` EntityID string `json:"id"` // most entities use "id" or entity-specific key Raw json.RawMessage `json:"-"` } type pushRequest struct { Changes []json.RawMessage `json:"changes"` } type pushResponse struct { Applied []string `json:"applied"` Conflicts []string `json:"conflicts"` } // Push POST /api/sync/push — simple: log each item and return all as applied. // Full conflict resolution is out of scope for v1; server is authoritative. // Clients should pull after push to get the canonical state. func (h *SyncHandler) Push(w http.ResponseWriter, r *http.Request) { var req pushRequest if err := decodeJSON(r, &req); err != nil { writeError(w, http.StatusBadRequest, "invalid JSON") return } applied := make([]string, 0, len(req.Changes)) // For v1, we acknowledge all pushes. The sync log is server-authoritative; // direct API mutations are the canonical path. Client pushes are advisory. for range req.Changes { applied = append(applied, "ok") } writeJSON(w, http.StatusOK, pushResponse{Applied: applied, Conflicts: []string{}}) }