update customer
This commit is contained in:
@@ -2,9 +2,12 @@ package ucp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.k6n.net/mats/go-cart-actor/pkg/profile"
|
||||
messages "git.k6n.net/mats/go-cart-actor/proto/profile"
|
||||
@@ -24,7 +27,52 @@ func NewCustomerServer(applier ProfileApplier) *CustomerServer {
|
||||
// UCP Customer endpoints
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// handleGetCustomer handles GET /customers/{id}.
|
||||
// handleCreateCustomer handles POST /customers — generates a new profile ID
|
||||
// server-side (base62-encoded random 64-bit), applies the profile mutation,
|
||||
// and returns 201 with the generated ID. Unlike PUT /customers/{id}, the
|
||||
// caller does not choose the identifier.
|
||||
func (s *CustomerServer) handleCreateCustomer(w http.ResponseWriter, r *http.Request) {
|
||||
var req CustomerUpdateRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
rawId, err := profile.NewProfileId()
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "failed to generate id")
|
||||
return
|
||||
}
|
||||
id := uint64(rawId)
|
||||
|
||||
msg := &messages.SetProfile{
|
||||
Name: req.Name,
|
||||
Email: req.Email,
|
||||
Phone: req.Phone,
|
||||
Language: req.Language,
|
||||
Currency: req.Currency,
|
||||
AvatarUrl: req.AvatarUrl,
|
||||
}
|
||||
|
||||
if _, err := s.applier.Apply(r.Context(), id, msg); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "failed to create customer: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
g, err := s.applier.Get(r.Context(), id)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "failed to read customer: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, customerGrainToResponse(rawId.String(), g))
|
||||
}
|
||||
|
||||
// handleGetCustomer handles GET /customers/{id} with ETag-based conditional
|
||||
// GET support. When the client sends If-None-Match matching the current ETag,
|
||||
// a 304 Not Modified is returned with no body — the caller reuses their cached
|
||||
// response. The ETag is derived from the grain's lastChange timestamp and
|
||||
// customer data hash, so it changes on any mutation.
|
||||
func (s *CustomerServer) handleGetCustomer(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := parseCustomerID(r)
|
||||
if !ok {
|
||||
@@ -38,6 +86,19 @@ func (s *CustomerServer) handleGetCustomer(w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
etag := computeCustomerETag(r.PathValue("id"), g)
|
||||
|
||||
if match := r.Header.Get("If-None-Match"); match != "" {
|
||||
if match == etag || match == "*" {
|
||||
w.Header().Set("ETag", etag)
|
||||
w.Header().Set("Vary", "Accept-Encoding")
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("ETag", etag)
|
||||
w.Header().Set("Vary", "Accept-Encoding")
|
||||
writeJSON(w, http.StatusOK, customerGrainToResponse(r.PathValue("id"), g))
|
||||
}
|
||||
|
||||
@@ -317,6 +378,15 @@ func containsNotFound(errStr string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// computeCustomerETag returns a strong ETag for the customer's current state,
|
||||
// derived from the grain's lastChange timestamp and a hash of the ID + change
|
||||
// time. Any mutation (profile update, address change, linking) advances
|
||||
// lastChange, so the ETag changes when data changes.
|
||||
func computeCustomerETag(id string, g *profile.ProfileGrain) string {
|
||||
h := sha256.Sum256([]byte(id + "\x00" + g.GetLastChange().Format(time.RFC3339Nano)))
|
||||
return `"` + hex.EncodeToString(h[:16]) + `"`
|
||||
}
|
||||
|
||||
// customerGrainToResponse converts a ProfileGrain to a CustomerResponse.
|
||||
func customerGrainToResponse(id string, g *profile.ProfileGrain) CustomerResponse {
|
||||
resp := CustomerResponse{
|
||||
|
||||
Reference in New Issue
Block a user