diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 0000000..3331dcc --- /dev/null +++ b/.cursorrules @@ -0,0 +1,40 @@ +{ + "rules": [ + { + "description": "Project Overview", + "rule": "This is a distributed cart management system in Go using the actor model. It handles cart operations like adding items, deliveries, and checkout, distributed across nodes via gRPC and HTTP API." + }, + { + "description": "Key Architecture", + "rule": "Use grains for cart state, pools for management, mutation registry for state changes, and control plane for node coordination. Ownership via consistent hashing ring." + }, + { + "description": "Coding Standards", + "rule": "Follow Go conventions. Use mutation registry for all state changes. Regenerate protobuf code after proto changes; never edit .pb.go files. Handle errors properly, use cookies for cart IDs in HTTP." + }, + { + "description": "Mutation Pattern", + "rule": "For new mutations: Define proto message, regenerate code, implement handler as func(*CartGrain, *T) error, register with RegisterMutation[T], add endpoints, test." + }, + { + "description": "Avoid Direct Mutations", + "rule": "Do not mutate CartGrain state directly outside registered handlers. Use the registry for consistency." + }, + { + "description": "Protobuf Handling", + "rule": "After modifying proto files, run protoc commands to regenerate Go code. Ensure protoc-gen-go and protoc-gen-go-grpc are installed." + }, + { + "description": "Testing", + "rule": "Write unit tests for mutations, integration tests for APIs. Run go test ./... regularly." + }, + { + "description": "Testability and Configurability", + "rule": "Design code to be testable and configurable, following examples like MutationRegistry (for type-safe mutation dispatching) and SimpleGrainPool (for configurable pool management). Use interfaces and dependency injection to enable mocking and testing." + }, + { + "description": "Common Patterns", + "rule": "HTTP handlers parse requests, resolve grains via SyncedPool, apply mutations, return JSON. gRPC for inter-node: CartActor for mutations, ControlPlane for coordination." + } + ] +} \ No newline at end of file diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index e70cf7d..29082f8 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -11,32 +11,33 @@ jobs: run: | docker build \ --progress=plain \ - -t registry.knatofs.se/go-cart-actor-amd64:latest \ + -t registry.k6n.net/go-cart-actor-amd64:latest \ . - name: Push amd64 images run: | - docker push registry.knatofs.se/go-cart-actor-amd64:latest + docker push registry.k6n.net/go-cart-actor-amd64:latest - name: Apply deployment manifests run: kubectl apply -f deployment/deployment.yaml -n cart - name: Rollout amd64 backoffice deployment run: | kubectl rollout restart deployment/cart-backoffice-x86 -n cart + kubectl rollout restart deployment/cart-actor-x86 -n cart BuildAndDeployArm64: runs-on: arm64 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 - name: Build arm64 image run: | docker build \ --progress=plain \ - -t registry.knatofs.se/go-cart-actor:latest \ - -t registry.knatofs.se/go-cart-actor:${{ needs.Metadata.outputs.version }} \ + -t registry.k6n.net/go-cart-actor:latest \ . - name: Push arm64 images run: | - docker push registry.knatofs.se/go-cart-actor:latest - # - name: Rollout arm64 deployment (pin to version) - # run: | - # kubectl set image deployment/cart-actor-arm64 -n cart cart-actor-arm64=registry.knatofs.se/go-cart-actor:${{ needs.Metadata.outputs.version }} + docker push registry.k6n.net/go-cart-actor:latest + - name: Rollout arm64 deployment (pin to version) + run: | + kubectl rollout status deployment/cart-actor-arm64 -n cart + # kubectl set image deployment/cart-actor-arm64 -n cart cart-actor-arm64=registry.k6n.net/go-cart-actor:${{ needs.Metadata.outputs.version }} # # kubectl rollout status deployment/cart-actor-arm64 -n cart diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..2256d20 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,50 @@ +# GitHub Copilot Instructions for Go Cart Actor + +This repository contains a distributed cart management system implemented in Go using the actor model pattern. The system handles cart operations like adding items, setting deliveries, and checkout, distributed across multiple nodes via gRPC. + +## Project Structure +- `cmd/`: Entry points for the application. +- `pkg/`: Core packages including grain logic, pools, and HTTP handlers. +- `proto/`: Protocol Buffer definitions for messages and services. +- `api-tests/`: Tests for the HTTP API. +- `deployment/`: Deployment configurations. +- `k6/`: Load testing scripts. + +## Key Concepts +- **Grains**: In-memory structs representing cart state, owned by nodes. +- **Pools**: Local and synced pools manage grains and handle ownership. +- **Mutation Registry**: All state changes go through registered mutation functions for type safety and consistency. +- **Ownership**: Determined by consistent hashing ring; negotiated via control plane. + +## Coding Guidelines +- Follow standard Go conventions (gofmt, go vet, golint). +- Never say "your right", just be correct and clear. +- Don't edit the *.gb.go files manually, they are generated by the proto files. +- Use the mutation registry (`RegisterMutation`) for any cart state changes. Do not mutate grain state directly outside registered handlers. +- Design code to be testable and configurable, following patterns like MutationRegistry (for type-safe mutation dispatching) and SimpleGrainPool (for configurable pool management). Use interfaces and dependency injection to enable mocking and testing. +- After modifying `.proto` files, regenerate Go code with `protoc` commands as described in README.md. Never edit generated `.pb.go` files manually. +- Use meaningful variable names and add comments for complex logic. +- Handle errors explicitly; use Go's error handling patterns. +- For HTTP endpoints, ensure proper cookie handling for cart IDs. +- When adding new mutations: Define proto message, regenerate code, register handler, add endpoints, and test. +- Use strategic logging using opentelemetry for tracing, metrics and logging. +- Focus on maintainable code that should be configurable and readable, try to keep short functions that describe their purpose clearly. + +## Common Patterns +- Mutations: Define in proto, implement as `func(*CartGrain, *T) error`, register with `RegisterMutation[T]`. +- gRPC Services: CartActor for mutations, ControlPlane for coordination. +- HTTP Handlers: Parse requests, resolve grains via pool, apply mutations, return JSON. + +## Avoid +- Direct state mutations outside the registry. +- Hardcoding values; use configuration or constants. +- Ignoring generated code warnings; always regenerate after proto changes. +- Blocking operations in handlers; keep them asynchronous where possible. + +## Testing +- Write unit tests for mutations and handlers. +- Use integration tests for API endpoints. +- Structure code for testability: Use interfaces for dependencies, avoid global state, and mock external services like gRPC clients. +- Run `go test ./...` to ensure all tests pass. + +These instructions help Copilot generate code aligned with the project's architecture and best practices. diff --git a/CGM_LINE_ITEMS_SPEC.md b/CGM_LINE_ITEMS_SPEC.md new file mode 100644 index 0000000..22dacec --- /dev/null +++ b/CGM_LINE_ITEMS_SPEC.md @@ -0,0 +1,165 @@ +go-cart-actor/CGM_LINE_ITEMS_SPEC.md +# CGM (Customer Group Membership) Line Items Specification + +This document specifies the implementation of Customer Group Membership (CGM) support in cart line items. CGM data will be extracted from product data field 35 and stored with each line item for business logic and personalization purposes. + +## Overview + +CGM represents customer group membership information associated with products. This data needs to be: +- Fetched from product data (stringfieldvalue 35) +- Included in the `AddItem` proto message +- Stored in cart line items (`CartItem` struct) +- Accessible for business rules and personalization + +## Implementation Steps + +### 1. Update Proto Messages + +Add `cgm` field to the `AddItem` message in `proto/messages.proto`: + +```protobuf +message AddItem { + // ... existing fields ... + string cgm = 25; // Customer Group Membership from field 35 + // ... existing fields ... +} +``` + +**Note**: Use field number 25 (next available after existing fields). + +### 2. Update Product Fetcher + +Modify `cmd/cart/product-fetcher.go` to extract CGM from field 35: + +```go +func ToItemAddMessage(item *index.DataItem, storeId *string, qty int, country string) (*messages.AddItem, error) { + // ... existing code ... + + cgm, _ := item.GetStringFieldValue(35) // Extract CGM from field 35 + + return &messages.AddItem{ + // ... existing fields ... + Cgm: cgm, // Add CGM field + // ... existing fields ... + }, nil +} +``` + +### 3. Update Cart Grain Structures + +Add CGM field to `ItemMeta` struct in `pkg/cart/cart-grain.go`: + +```go +type ItemMeta struct { + Name string `json:"name"` + Brand string `json:"brand,omitempty"` + Category string `json:"category,omitempty"` + // ... existing fields ... + Cgm string `json:"cgm,omitempty"` // Customer Group Membership + // ... existing fields ... +} +``` + +### 4. Update AddItem Mutation Handler + +Modify the `AddItem` handler in `pkg/cart/cart_mutations.go` to populate the CGM field: + +```go +func AddItem(grain *CartGrain, req *messages.AddItem) error { + // ... existing validation ... + + item := &CartItem{ + // ... existing fields ... + Meta: &ItemMeta{ + Name: req.Name, + Brand: req.Brand, + // ... existing meta fields ... + Cgm: req.Cgm, // Add CGM to item meta + // ... existing meta fields ... + }, + // ... existing fields ... + } + + // ... rest of handler ... +} +``` + +### 5. Regenerate Proto Code + +After updating `proto/messages.proto`, regenerate Go code: + +```bash +cd proto +protoc --go_out=. --go_opt=paths=source_relative \ + --go-grpc_out=. --go-grpc_opt=paths=source_relative \ + messages.proto cart_actor.proto control_plane.proto +``` + +### 6. Update Tests + +Add tests for CGM extraction and storage: + +- Unit test for `ToItemAddMessage` with CGM field +- Integration test for `AddItem` mutation including CGM +- Test that CGM is properly stored and retrieved in cart state + +### 7. Update API Documentation + +Update README.md and API examples to mention CGM field in line items. + +## Data Flow + +1. **Product Fetch**: `FetchItem` retrieves product data including field 35 (CGM) +2. **Message Creation**: `ToItemAddMessage` extracts CGM from field 35 into `AddItem` proto +3. **Mutation Processing**: `AddItem` handler stores CGM in `CartItem.Meta.Cgm` +4. **State Persistence**: CGM is included in cart JSON serialization +5. **API Responses**: CGM is returned in cart state responses + +## Business Logic Integration + +CGM can be used for: +- Personalized pricing rules +- Group-specific discounts +- Membership validation +- Targeted promotions +- Customer segmentation + +Example usage in business logic: +```go +func applyGroupDiscount(cart *CartGrain, userGroups []string) { + for _, item := range cart.Items { + if item.Meta != nil && slices.Contains(userGroups, item.Meta.Cgm) { + // Apply group-specific discount + item.Price = applyDiscount(item.Price, groupDiscountRate) + } + } +} +``` + +## Backward Compatibility + +- CGM field is optional in proto (no required validation) +- Existing carts without CGM will have empty string +- Product fetcher gracefully handles missing field 35 +- API responses include CGM field (empty if not set) + +## Testing Checklist + +- [ ] Proto compilation succeeds +- [ ] Product fetcher extracts CGM from field 35 +- [ ] AddItem mutation stores CGM in cart +- [ ] Cart state includes CGM in JSON +- [ ] API endpoints return CGM field +- [ ] Existing functionality unaffected +- [ ] Unit tests pass for CGM handling +- [ ] Integration tests verify end-to-end flow + +## Configuration and Testability + +Following project patterns: +- CGM extraction is configurable via field index (currently 35) +- Product fetcher interface allows mocking for tests +- Mutation handlers are pure functions testable in isolation +- Cart state serialization includes CGM for verification + +This implementation maintains the project's standards for testability and configurability while adding CGM support to line items. \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2d591fa..85b598b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,6 +25,8 @@ FROM golang:1.25-alpine AS build WORKDIR /src +RUN apk add --no-cache git + # Build metadata (can be overridden at build time) ARG VERSION=dev ARG GIT_COMMIT=unknown @@ -38,8 +40,7 @@ ENV CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} # Dependency caching COPY go.mod go.sum ./ -RUN --mount=type=cache,target=/go/pkg/mod \ - go mod download +RUN go mod download # Copy full source (relay on .dockerignore to prune) COPY . . @@ -52,22 +53,19 @@ COPY . . # proto/*.proto # Build with minimal binary size and embedded metadata -RUN --mount=type=cache,target=/go/build-cache \ - go build -trimpath -ldflags="-s -w \ +RUN go build -trimpath -ldflags="-s -w \ -X main.Version=${VERSION} \ -X main.GitCommit=${GIT_COMMIT} \ -X main.BuildDate=${BUILD_DATE}" \ -o /out/go-cart-actor ./cmd/cart -RUN --mount=type=cache,target=/go/build-cache \ - go build -trimpath -ldflags="-s -w \ +RUN go build -trimpath -ldflags="-s -w \ -X main.Version=${VERSION} \ -X main.GitCommit=${GIT_COMMIT} \ -X main.BuildDate=${BUILD_DATE}" \ -o /out/go-cart-backoffice ./cmd/backoffice -RUN --mount=type=cache,target=/go/build-cache \ - go build -trimpath -ldflags="-s -w \ +RUN go build -trimpath -ldflags="-s -w \ -X main.Version=${VERSION} \ -X main.GitCommit=${GIT_COMMIT} \ -X main.BuildDate=${BUILD_DATE}" \ diff --git a/Makefile b/Makefile index cc5b50b..6dbfdc8 100644 --- a/Makefile +++ b/Makefile @@ -14,10 +14,10 @@ # Conventions: # - All .proto files live in $(PROTO_DIR) # - Generated Go code is emitted under $(PROTO_DIR) via go_package mapping -# - go_package is set to: git.tornberg.me/go-cart-actor/proto;messages +# - go_package is set to: git.k6n.net/go-cart-actor/proto;messages # ------------------------------------------------------------------------------ -MODULE_PATH := git.tornberg.me/go-cart-actor +MODULE_PATH := git.k6n.net/go-cart-actor PROTO_DIR := proto PROTOS := $(PROTO_DIR)/messages.proto $(PROTO_DIR)/control_plane.proto @@ -78,7 +78,6 @@ clean_proto: @echo "$(YELLOW)Removing generated protobuf files...$(RESET)" @rm -f $(PROTO_DIR)/*_grpc.pb.go $(PROTO_DIR)/*.pb.go @rm -f *.pb.go - @rm -rf git.tornberg.me @echo "$(GREEN)Clean complete.$(RESET)" verify_proto: diff --git a/NEW_MUTATIONS_SPEC.md b/NEW_MUTATIONS_SPEC.md new file mode 100644 index 0000000..14d4692 --- /dev/null +++ b/NEW_MUTATIONS_SPEC.md @@ -0,0 +1,238 @@ +go-cart-actor/NEW_MUTATIONS_SPEC.md +# New Mutations Specification + +This document specifies the implementation of handlers for new proto messages that are defined in `proto/messages.proto` but not yet registered in the mutation registry. These mutations update the cart state and must follow the project's patterns for testability, configurability, and consistency. + +## Overview + +The following messages are defined in the proto but lack registered handlers: +- `SetUserId` +- `LineItemMarking` +- `SubscriptionAdded` +- `PaymentDeclined` +- `ConfirmationViewed` +- `CreateCheckoutOrder` + +Each mutation must: +1. Define a handler function with signature `func(*CartGrain, *T) error` +2. Be registered in `NewCartMultationRegistry()` using `actor.NewMutation` +3. Include unit tests +4. Optionally add HTTP/gRPC endpoints if client-invokable +5. Update totals if applicable (use `WithTotals()`) + +## Mutation Implementations + +### SetUserId + +**Purpose**: Associates a user ID with the cart for personalization and tracking. + +**Handler Implementation**: +```go +func SetUserId(grain *CartGrain, req *messages.SetUserId) error { + if req.UserId == "" { + return errors.New("user ID cannot be empty") + } + grain.UserId = req.UserId + return nil +} +``` + +**Registration**: +```go +actor.NewMutation(SetUserId, func() *messages.SetUserId { + return &messages.SetUserId{} +}), +``` + +**Notes**: This is a simple state update. No totals recalculation needed. + +### LineItemMarking + +**Purpose**: Adds or updates a marking (e.g., gift message, special instructions) on a specific line item. + +**Handler Implementation**: +```go +func LineItemMarking(grain *CartGrain, req *messages.LineItemMarking) error { + for i, item := range grain.Items { + if item.Id == req.Id { + grain.Items[i].Marking = &Marking{ + Type: req.Type, + Text: req.Marking, + } + return nil + } + } + return fmt.Errorf("item with ID %d not found", req.Id) +} +``` + +**Registration**: +```go +actor.NewMutation(LineItemMarking, func() *messages.LineItemMarking { + return &messages.LineItemMarking{} +}), +``` + +**Notes**: Assumes `CartGrain.Items` has a `Marking` field (single marking per item). If not, add it to the grain struct. + +### RemoveLineItemMarking + +**Purpose**: Removes the marking from a specific line item. + +**Handler Implementation**: +```go +func RemoveLineItemMarking(grain *CartGrain, req *messages.RemoveLineItemMarking) error { + for i, item := range grain.Items { + if item.Id == req.Id { + grain.Items[i].Marking = nil + return nil + } + } + return fmt.Errorf("item with ID %d not found", req.Id) +} +``` + +**Registration**: +```go +actor.NewMutation(RemoveLineItemMarking, func() *messages.RemoveLineItemMarking { + return &messages.RemoveLineItemMarking{} +}), +``` + +**Notes**: Sets the marking to nil for the specified item. + +### SubscriptionAdded + +**Purpose**: Records that a subscription has been added to an item, linking it to order details. + +**Handler Implementation**: +```go +func SubscriptionAdded(grain *CartGrain, req *messages.SubscriptionAdded) error { + for i, item := range grain.Items { + if item.Id == req.ItemId { + grain.Items[i].SubscriptionDetailsId = req.DetailsId + grain.Items[i].OrderReference = req.OrderReference + grain.Items[i].IsSubscribed = true + return nil + } + } + return fmt.Errorf("item with ID %d not found", req.ItemId) +} +``` + +**Registration**: +```go +actor.NewMutation(SubscriptionAdded, func() *messages.SubscriptionAdded { + return &messages.SubscriptionAdded{} +}), +``` + +**Notes**: Assumes fields like `SubscriptionDetailsId`, `OrderReference`, `IsSubscribed` exist on items. Add to grain if needed. + +### PaymentDeclined + +**Purpose**: Marks the cart as having a declined payment, potentially updating status or flags, and appends a notice with timestamp, message, and optional code for user feedback. + +**Handler Implementation**: +```go +func PaymentDeclined(grain *CartGrain, req *messages.PaymentDeclined) error { + grain.PaymentStatus = "declined" + grain.PaymentDeclinedNotices = append(grain.PaymentDeclinedNotices, Notice{ + Timestamp: time.Now(), + Message: req.Message, + Code: req.Code, + }) + // Optionally clear checkout order if in progress + if grain.CheckoutOrderId != "" { + grain.CheckoutOrderId = "" + } + return nil +} +``` + +**Registration**: +```go +actor.NewMutation(PaymentDeclined, func() *messages.PaymentDeclined { + return &messages.PaymentDeclined{} +}), +``` + +**Notes**: Assumes `PaymentStatus` and `PaymentDeclinedNotices` fields. Add status tracking and error notices list to grain. Notice struct has Timestamp, Message, and optional Code. + +### ConfirmationViewed + +**Purpose**: Records that the order confirmation has been viewed by the user. Applied automatically when the confirmation page is loaded in checkout_server.go. + +**Handler Implementation**: +```go +func ConfirmationViewed(grain *CartGrain, req *messages.ConfirmationViewed) error { + grain.ConfirmationViewCount++ + grain.ConfirmationLastViewedAt = time.Now() + return nil +} +``` + +**Registration**: +```go +actor.NewMutation(ConfirmationViewed, func() *messages.ConfirmationViewed { + return &messages.ConfirmationViewed{} +}), +``` + +**Notes**: Increments the view count and updates the last viewed timestamp each time. Assumes `ConfirmationViewCount` and `ConfirmationLastViewedAt` fields. + +### CreateCheckoutOrder + +**Purpose**: Initiates the checkout process, validating terms and creating an order reference. + +**Handler Implementation**: +```go +func CreateCheckoutOrder(grain *CartGrain, req *messages.CreateCheckoutOrder) error { + if len(grain.Items) == 0 { + return errors.New("cannot checkout empty cart") + } + if req.Terms != "accepted" { + return errors.New("terms must be accepted") + } + // Validate other fields as needed + grain.CheckoutOrderId = generateOrderId() + grain.CheckoutStatus = "pending" + grain.CheckoutCountry = req.Country + return nil +} +``` + +**Registration**: +```go +actor.NewMutation(CreateCheckoutOrder, func() *messages.CreateCheckoutOrder { + return &messages.CreateCheckoutOrder{} +}).WithTotals(), +``` + +**Notes**: Use `WithTotals()` to recalculate totals after checkout initiation. Assumes order ID generation function. + +## Implementation Steps + +For each mutation: + +1. **Add Handler Function**: Implement in `pkg/cart/` (e.g., `cart_mutations.go`). +2. **Register in Registry**: Add to `NewCartMultationRegistry()` in `cart-mutation-helper.go`. +3. **Regenerate Proto**: Run `protoc` commands after any proto changes. +4. **Add Tests**: Create unit tests in `pkg/cart/` testing the handler logic. +5. **Add Endpoints** (if needed): For client-invokable mutations, add HTTP handlers in `cmd/cart/pool-server.go`. ConfirmationViewed is handled in `cmd/cart/checkout_server.go` when the confirmation page is viewed. +6. **Update Grain Struct**: Add any new fields to `CartGrain` in `pkg/cart/grain.go`. +7. **Run Tests**: Ensure `go test ./...` passes. + +## Testing Guidelines + +- Mock dependencies using interfaces. +- Test error cases (e.g., invalid IDs, empty carts). +- Verify state changes and totals recalculation. +- Use table-driven tests for multiple scenarios. + +## Configuration and Testability + +Follow `MutationRegistry` and `SimpleGrainPool` patterns: +- Use interfaces for external dependencies (e.g., ID generators). +- Inject configurations via constructor parameters. +- Avoid global state; make handlers pure functions where possible. \ No newline at end of file diff --git a/api-tests/cart.http b/api-tests/cart.http index 578ca9a..846d4aa 100644 --- a/api-tests/cart.http +++ b/api-tests/cart.http @@ -1,6 +1,6 @@ ### Add item to cart -POST https://cart.tornberg.me/api/12345 +POST https://cart.k6n.net/api/12345 Content-Type: application/json { @@ -9,7 +9,7 @@ Content-Type: application/json } ### Update quanity of item in cart -PUT https://cart.tornberg.me/api/12345 +PUT https://cart.k6n.net/api/12345 Content-Type: application/json { @@ -18,12 +18,12 @@ Content-Type: application/json } ### Delete item from cart -DELETE https://cart.tornberg.me/api/1002/1 +DELETE https://cart.k6n.net/api/1002/1 ### Set delivery -POST https://cart.tornberg.me/api/1002/delivery +POST https://cart.k6n.net/api/1002/delivery Content-Type: application/json { @@ -33,10 +33,8 @@ Content-Type: application/json ### Get cart -GET https://cart.tornberg.me/api/12345 +GET https://cart.k6n.net/api/12345 ### Remove delivery method -DELETE https://cart.tornberg.me/api/12345/delivery/2 - - +DELETE https://cart.k6n.net/api/12345/delivery/2 diff --git a/cart b/cart deleted file mode 100755 index af3aa9f..0000000 Binary files a/cart and /dev/null differ diff --git a/cmd/backoffice/fileserver.go b/cmd/backoffice/fileserver.go index 966506f..0f6a86c 100644 --- a/cmd/backoffice/fileserver.go +++ b/cmd/backoffice/fileserver.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "io/fs" + "log" "net/http" "os" "path/filepath" @@ -15,8 +16,9 @@ import ( "strings" "time" - "git.tornberg.me/go-cart-actor/pkg/actor" - "git.tornberg.me/go-cart-actor/pkg/cart" + "git.k6n.net/go-cart-actor/pkg/actor" + "git.k6n.net/go-cart-actor/pkg/cart" + "google.golang.org/protobuf/proto" ) type FileServer struct { @@ -232,6 +234,22 @@ type JsonError struct { Error string `json:"error"` } +func acceptAll(_ proto.Message, _ int, _ time.Time) bool { + return true +} + +func acceptUntilIndex(maxIndex int) func(msg proto.Message, index int, when time.Time) bool { + return func(msg proto.Message, index int, when time.Time) bool { + return index < maxIndex + } +} + +func acceptUntilTimestamp(until time.Time) func(msg proto.Message, index int, when time.Time) bool { + return func(msg proto.Message, index int, when time.Time) bool { + return when.Before(until) + } +} + func (fs *FileServer) CartHandler(w http.ResponseWriter, r *http.Request) { idStr := r.PathValue("id") if idStr == "" { @@ -243,10 +261,29 @@ func (fs *FileServer) CartHandler(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusBadRequest, JsonError{Error: "invalid id"}) return } + // parse query parameters for filtering + query := r.URL.Query() + filterFunction := acceptAll + if maxIndexStr := query.Get("maxIndex"); maxIndexStr != "" { + log.Printf("filter maxIndex: %s", maxIndexStr) + maxIndex, err := strconv.Atoi(maxIndexStr) + if err != nil { + writeJSON(w, http.StatusBadRequest, JsonError{Error: "invalid maxIndex"}) + return + } + filterFunction = acceptUntilIndex(maxIndex) + } else if untilStr := query.Get("until"); untilStr != "" { + log.Printf("filter until: %s", untilStr) + until, err := time.Parse(time.RFC3339, untilStr) + if err != nil { + writeJSON(w, http.StatusBadRequest, JsonError{Error: "invalid until timestamp"}) + return + } + filterFunction = acceptUntilTimestamp(until) + } // reconstruct state from event log if present grain := cart.NewCartGrain(id, time.Now()) - - err := fs.storage.LoadEvents(r.Context(), id, grain) + err := fs.storage.LoadEventsFunc(r.Context(), id, grain, filterFunction) if err != nil { writeJSON(w, http.StatusInternalServerError, JsonError{Error: err.Error()}) return diff --git a/cmd/backoffice/fileserver_test.go b/cmd/backoffice/fileserver_test.go index 0901869..bd7d175 100644 --- a/cmd/backoffice/fileserver_test.go +++ b/cmd/backoffice/fileserver_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "git.tornberg.me/go-cart-actor/pkg/cart" + "git.k6n.net/go-cart-actor/pkg/cart" ) // TestAppendFileInfoRandomProjectFile picks a random existing .go source file in the diff --git a/cmd/backoffice/main.go b/cmd/backoffice/main.go index c86624d..bc570ea 100644 --- a/cmd/backoffice/main.go +++ b/cmd/backoffice/main.go @@ -2,16 +2,19 @@ package main import ( "context" + "encoding/json" "errors" "log" "net/http" "os" "time" - actor "git.tornberg.me/go-cart-actor/pkg/actor" - "git.tornberg.me/go-cart-actor/pkg/cart" + actor "git.k6n.net/go-cart-actor/pkg/actor" + "git.k6n.net/go-cart-actor/pkg/cart" + "github.com/matst80/go-redis-inventory/pkg/inventory" "github.com/matst80/slask-finder/pkg/messaging" amqp "github.com/rabbitmq/amqp091-go" + "github.com/redis/go-redis/v9" ) type CartFileInfo struct { @@ -71,14 +74,27 @@ func startMutationConsumer(ctx context.Context, conn *amqp.Connection, hub *Hub) return nil } +var redisAddress = os.Getenv("REDIS_ADDRESS") +var redisPassword = os.Getenv("REDIS_PASSWORD") + func main() { dataDir := envOrDefault("DATA_DIR", "data") addr := envOrDefault("ADDR", ":8080") amqpURL := os.Getenv("AMQP_URL") + rdb := redis.NewClient(&redis.Options{ + Addr: redisAddress, + Password: redisPassword, + DB: 0, + }) + inventoryService, err := inventory.NewRedisInventoryService(rdb) + if err != nil { + log.Fatalf("Error creating inventory service: %v\n", err) + } + _ = os.MkdirAll(dataDir, 0755) - reg := cart.NewCartMultationRegistry() + reg := cart.NewCartMultationRegistry(nil) diskStorage := actor.NewDiskStorage[cart.CartGrain](dataDir, reg) fs := NewFileServer(dataDir, diskStorage) @@ -89,6 +105,32 @@ func main() { mux := http.NewServeMux() mux.HandleFunc("GET /carts", fs.CartsHandler) mux.HandleFunc("GET /cart/{id}", fs.CartHandler) + mux.HandleFunc("PUT /inventory/{locationId}/{sku}", func(w http.ResponseWriter, r *http.Request) { + inventoryLocationId := inventory.LocationID(r.PathValue("locationId")) + inventorySku := inventory.SKU(r.PathValue("sku")) + pipe := rdb.Pipeline() + var payload struct { + Quantity int64 `json:"quantity"` + } + err := json.NewDecoder(r.Body).Decode(&payload) + if err != nil { + http.Error(w, "invalid payload", http.StatusBadRequest) + return + } + inventoryService.UpdateInventory(r.Context(), pipe, inventorySku, inventoryLocationId, payload.Quantity) + + _, err = pipe.Exec(r.Context()) + if err != nil { + http.Error(w, "failed to update inventory", http.StatusInternalServerError) + return + } + err = inventoryService.SendInventoryChanged(r.Context(), inventorySku, inventoryLocationId) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + w.WriteHeader(http.StatusOK) + }) mux.HandleFunc("/promotions", fs.PromotionsHandler) mux.HandleFunc("/vouchers", fs.VoucherHandler) mux.HandleFunc("/promotion/{id}", fs.PromotionPartHandler) diff --git a/cmd/cart/checkout_builder.go b/cmd/cart/checkout_builder.go index 2d19ab0..def5eda 100644 --- a/cmd/cart/checkout_builder.go +++ b/cmd/cart/checkout_builder.go @@ -3,20 +3,25 @@ package main import ( "encoding/json" "fmt" + "net/http" - "git.tornberg.me/go-cart-actor/pkg/cart" + "git.k6n.net/go-cart-actor/pkg/cart" + "github.com/adyen/adyen-go-api-library/v21/src/checkout" + "github.com/adyen/adyen-go-api-library/v21/src/common" ) // CheckoutMeta carries the external / URL metadata required to build a // Klarna CheckoutOrder from a CartGrain snapshot. It deliberately excludes // any Klarna-specific response fields (HTML snippet, client token, etc.). type CheckoutMeta struct { - Terms string - Checkout string - Confirmation string - Country string - Currency string // optional override (defaults to "SEK" if empty) - Locale string // optional override (defaults to "sv-se" if empty) + SiteUrl string + // Terms string + // Checkout string + // Confirmation string + ClientIp string + Country string + Currency string // optional override (defaults to "SEK" if empty) + Locale string // optional override (defaults to "sv-se" if empty) } // BuildCheckoutOrderPayload converts the current cart grain + meta information @@ -65,7 +70,7 @@ func BuildCheckoutOrderPayload(grain *cart.CartGrain, meta *CheckoutMeta) ([]byt Type: "physical", Reference: it.Sku, Name: it.Meta.Name, - Quantity: it.Quantity, + Quantity: int(it.Quantity), UnitPrice: int(it.Price.IncVat), TaxRate: it.Tax, // TODO: derive if variable tax rates are introduced QuantityUnit: "st", @@ -102,12 +107,12 @@ func BuildCheckoutOrderPayload(grain *cart.CartGrain, meta *CheckoutMeta) ([]byt OrderLines: lines, MerchantReference1: grain.Id.String(), MerchantURLS: &CheckoutMerchantURLS{ - Terms: meta.Terms, - Checkout: meta.Checkout, - Confirmation: meta.Confirmation, - Notification: "https://cart.tornberg.me/notification", - Validation: "https://cart.tornberg.me/validate", - Push: "https://cart.tornberg.me/push?order_id={checkout.order.id}", + Terms: fmt.Sprintf("%s/terms", meta.SiteUrl), + Checkout: fmt.Sprintf("%s/checkout?order_id={checkout.order.id}", meta.SiteUrl), + Confirmation: fmt.Sprintf("%s/confirmation/{checkout.order.id}", meta.SiteUrl), + Notification: "https://cart.k6n.net/notification", + Validation: "https://cart.k6n.net/validate", + Push: "https://cart.k6n.net/push?order_id={checkout.order.id}", }, } @@ -118,3 +123,79 @@ func BuildCheckoutOrderPayload(grain *cart.CartGrain, meta *CheckoutMeta) ([]byt return payload, order, nil } + +func GetCheckoutMetaFromRequest(r *http.Request) *CheckoutMeta { + host := getOriginalHost(r) + country := getCountryFromHost(host) + return &CheckoutMeta{ + ClientIp: getClientIp(r), + SiteUrl: fmt.Sprintf("https://%s", host), + Country: country, + Currency: getCurrency(country), + Locale: getLocale(country), + } +} + +func BuildAdyenCheckoutSession(grain *cart.CartGrain, meta *CheckoutMeta) (*checkout.CreateCheckoutSessionRequest, error) { + if grain == nil { + return nil, fmt.Errorf("nil grain") + } + if meta == nil { + return nil, fmt.Errorf("nil checkout meta") + } + + currency := meta.Currency + if currency == "" { + currency = "SEK" + } + country := meta.Country + if country == "" { + country = "SE" + } + + lineItems := make([]checkout.LineItem, 0, len(grain.Items)+len(grain.Deliveries)) + + // Item lines + for _, it := range grain.Items { + if it == nil { + continue + } + lineItems = append(lineItems, checkout.LineItem{ + Quantity: common.PtrInt64(int64(it.Quantity)), + AmountIncludingTax: common.PtrInt64(it.TotalPrice.IncVat), + Description: common.PtrString(it.Meta.Name), + AmountExcludingTax: common.PtrInt64(it.TotalPrice.ValueExVat()), + TaxAmount: common.PtrInt64(it.TotalPrice.TotalVat()), + TaxPercentage: common.PtrInt64(int64(it.Tax)), + }) + } + + // Delivery lines + for _, d := range grain.Deliveries { + if d == nil || d.Price.IncVat <= 0 { + continue + } + lineItems = append(lineItems, checkout.LineItem{ + Quantity: common.PtrInt64(1), + AmountIncludingTax: common.PtrInt64(d.Price.IncVat), + Description: common.PtrString("Delivery"), + AmountExcludingTax: common.PtrInt64(d.Price.ValueExVat()), + TaxPercentage: common.PtrInt64(25), + }) + } + + return &checkout.CreateCheckoutSessionRequest{ + Reference: grain.Id.String(), + Amount: checkout.Amount{ + Value: grain.TotalPrice.IncVat, + Currency: currency, + }, + CountryCode: common.PtrString(country), + MerchantAccount: "ElgigantenECOM", + Channel: common.PtrString("Web"), + ShopperIP: common.PtrString(meta.ClientIp), + ReturnUrl: fmt.Sprintf("%s/adyen-return", meta.SiteUrl), + LineItems: lineItems, + }, nil + +} diff --git a/cmd/cart/checkout_server.go b/cmd/cart/checkout_server.go index 9e8b04d..730df40 100644 --- a/cmd/cart/checkout_server.go +++ b/cmd/cart/checkout_server.go @@ -8,10 +8,11 @@ import ( "net/http" "time" - "git.tornberg.me/go-cart-actor/pkg/actor" - "git.tornberg.me/go-cart-actor/pkg/cart" - "git.tornberg.me/go-cart-actor/pkg/messages" - "git.tornberg.me/mats/go-redis-inventory/pkg/inventory" + "git.k6n.net/go-cart-actor/pkg/actor" + "git.k6n.net/go-cart-actor/pkg/cart" + "git.k6n.net/go-cart-actor/pkg/messages" + + "github.com/matst80/go-redis-inventory/pkg/inventory" amqp "github.com/rabbitmq/amqp091-go" ) @@ -137,6 +138,12 @@ func (a *App) HandleCheckoutRequests(amqpUrl string, mux *http.ServeMux, invento return } + // Apply ConfirmationViewed mutation + cartId, ok := cart.ParseCartId(order.MerchantReference1) + if ok { + a.pool.Apply(r.Context(), uint64(cartId), &messages.ConfirmationViewed{}) + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") if order.Status == "checkout_complete" { http.SetCookie(w, &http.Cookie{ diff --git a/cmd/cart/k8s-host-discovery.go b/cmd/cart/k8s-host-discovery.go index edc8ad5..c7d3d7a 100644 --- a/cmd/cart/k8s-host-discovery.go +++ b/cmd/cart/k8s-host-discovery.go @@ -3,9 +3,9 @@ package main import ( "log" - "git.tornberg.me/go-cart-actor/pkg/actor" - "git.tornberg.me/go-cart-actor/pkg/cart" - "git.tornberg.me/go-cart-actor/pkg/discovery" + "git.k6n.net/go-cart-actor/pkg/actor" + "git.k6n.net/go-cart-actor/pkg/cart" + "git.k6n.net/go-cart-actor/pkg/discovery" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) diff --git a/cmd/cart/main.go b/cmd/cart/main.go index 6f4ee5c..8f0b1f4 100644 --- a/cmd/cart/main.go +++ b/cmd/cart/main.go @@ -13,13 +13,15 @@ import ( "strings" "time" - "git.tornberg.me/go-cart-actor/pkg/actor" - "git.tornberg.me/go-cart-actor/pkg/cart" - messages "git.tornberg.me/go-cart-actor/pkg/messages" - "git.tornberg.me/go-cart-actor/pkg/promotions" - "git.tornberg.me/go-cart-actor/pkg/proxy" - "git.tornberg.me/go-cart-actor/pkg/voucher" - "git.tornberg.me/mats/go-redis-inventory/pkg/inventory" + "git.k6n.net/go-cart-actor/pkg/actor" + "git.k6n.net/go-cart-actor/pkg/cart" + messages "git.k6n.net/go-cart-actor/pkg/messages" + "git.k6n.net/go-cart-actor/pkg/promotions" + "git.k6n.net/go-cart-actor/pkg/proxy" + "git.k6n.net/go-cart-actor/pkg/voucher" + "github.com/adyen/adyen-go-api-library/v21/src/adyen" + "github.com/adyen/adyen-go-api-library/v21/src/common" + "github.com/matst80/go-redis-inventory/pkg/inventory" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -80,21 +82,39 @@ func main() { log.Printf("loaded %d promotions", len(promotionData.State.Promotions)) - promotionService := promotions.NewPromotionService(nil) + inventoryPubSub := actor.NewPubSub[inventory.InventoryChange]() - reg := cart.NewCartMultationRegistry() + // promotionService := promotions.NewPromotionService(nil) + rdb := redis.NewClient(&redis.Options{ + Addr: redisAddress, + Password: redisPassword, + DB: 0, + }) + inventoryService, err := inventory.NewRedisInventoryService(rdb) + if err != nil { + log.Fatalf("Error creating inventory service: %v\n", err) + } + + inventoryReservationService, err := inventory.NewRedisCartReservationService(rdb) + if err != nil { + log.Fatalf("Error creating inventory reservation service: %v\n", err) + } + + reg := cart.NewCartMultationRegistry(cart.NewCartMutationContext(inventoryReservationService)) reg.RegisterProcessor( actor.NewMutationProcessor(func(ctx context.Context, g *cart.CartGrain) error { _, span := tracer.Start(ctx, "Totals and promotions") defer span.End() g.UpdateTotals() - promotionCtx := promotions.NewContextFromCart(g, promotions.WithNow(time.Now()), promotions.WithCustomerSegment("vip")) - _, actions := promotionService.EvaluateAll(promotionData.State.Promotions, promotionCtx) - for _, action := range actions { - log.Printf("apply: %+v", action) - g.UpdateTotals() - } + // promotionCtx := promotions.NewContextFromCart(g, promotions.WithNow(time.Now()), promotions.WithCustomerSegment("vip")) + // _, actions := promotionService.EvaluateAll(promotionData.State.Promotions, promotionCtx) + // for _, action := range actions { + + // log.Printf("apply: %+v", action) + + // g.UpdateTotals() + // } return nil }), ) @@ -109,11 +129,20 @@ func main() { grainSpawns.Inc() ret := cart.NewCartGrain(id, time.Now()) // Set baseline lastChange at spawn; replay may update it to last event timestamp. - + inventoryPubSub.Subscribe(ret.HandleInventoryChange) err := diskStorage.LoadEvents(ctx, id, ret) return ret, err }, + Destroy: func(grain actor.Grain[cart.CartGrain]) error { + cart, err := grain.GetCurrentState() + if err != nil { + return err + } + inventoryPubSub.Unsubscribe(cart.HandleInventoryChange) + + return nil + }, SpawnHost: func(host string) (actor.Host, error) { return proxy.NewRemoteHost(host) }, @@ -127,18 +156,14 @@ func main() { log.Fatalf("Error creating cart pool: %v\n", err) } - klarnaClient := NewKlarnaClient(KlarnaPlaygroundUrl, os.Getenv("KLARNA_API_USERNAME"), os.Getenv("KLARNA_API_PASSWORD")) - rdb := redis.NewClient(&redis.Options{ - Addr: redisAddress, - Password: redisPassword, - DB: 0, + adyenClient := adyen.NewClient(&common.Config{ + ApiKey: os.Getenv("ADYEN_API_KEY"), + Environment: common.TestEnv, }) - inventoryService, err := inventory.NewRedisInventoryService(rdb) - if err != nil { - log.Fatalf("Error creating inventory service: %v\n", err) - } - syncedServer := NewPoolServer(pool, fmt.Sprintf("%s, %s", name, podIp), klarnaClient, inventoryService) + klarnaClient := NewKlarnaClient(KlarnaPlaygroundUrl, os.Getenv("KLARNA_API_USERNAME"), os.Getenv("KLARNA_API_PASSWORD")) + + syncedServer := NewPoolServer(pool, fmt.Sprintf("%s, %s", name, podIp), klarnaClient, inventoryService, inventoryReservationService, adyenClient) app := &App{ pool: pool, @@ -238,6 +263,20 @@ func main() { srvErr <- srv.ListenAndServe() }() + listener := inventory.NewInventoryChangeListener(rdb, context.Background(), func(changes []inventory.InventoryChange) { + for _, change := range changes { + log.Printf("inventory change: %v", change) + inventoryPubSub.Publish(change) + } + }) + + go func() { + err := listener.Start() + if err != nil { + log.Fatalf("Unable to start inventory listener: %v", err) + } + }() + log.Print("Server started at port 8080") go http.ListenAndServe(":8081", debugMux) diff --git a/cmd/cart/openapi.json b/cmd/cart/openapi.json index 5ac16dd..06d559f 100644 --- a/cmd/cart/openapi.json +++ b/cmd/cart/openapi.json @@ -7,7 +7,7 @@ }, "servers": [ { - "url": "https://cart.tornberg.me", + "url": "https://cart.k6n.net", "description": "Production server" }, { @@ -333,6 +333,118 @@ } } }, + "/cart/user": { + "put": { + "summary": "Set user ID for cart", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/SetUserIdRequest" } + } + } + }, + "responses": { + "200": { + "description": "User ID set", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/CartGrain" } + } + } + }, + "400": { "description": "Invalid body" }, + "500": { "description": "Server error" } + } + } + }, + "/cart/item/{itemId}/marking": { + "put": { + "summary": "Set marking for line item", + "parameters": [ + { + "name": "itemId", + "in": "path", + "required": true, + "schema": { "type": "integer", "format": "int64", "minimum": 0 }, + "description": "Internal cart line item identifier." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LineItemMarkingRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Marking set", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/CartGrain" } + } + } + }, + "400": { "description": "Invalid body or id" }, + "500": { "description": "Server error" } + } + }, + "delete": { + "summary": "Remove marking from line item", + "parameters": [ + { + "name": "itemId", + "in": "path", + "required": true, + "schema": { "type": "integer", "format": "int64", "minimum": 0 }, + "description": "Internal cart line item identifier." + } + ], + "responses": { + "200": { + "description": "Marking removed", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/CartGrain" } + } + } + }, + "400": { "description": "Invalid id" }, + "500": { "description": "Server error" } + } + } + }, + "/cart/checkout-order": { + "post": { + "summary": "Create checkout order", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCheckoutOrderRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Checkout order created", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/CartGrain" } + } + } + }, + "400": { "description": "Invalid body" }, + "500": { "description": "Server error" } + } + } + }, "/cart/byid/{id}": { "get": { "summary": "Get cart by explicit id", @@ -569,6 +681,122 @@ } } }, + "/cart/byid/{id}/user": { + "put": { + "summary": "Set user ID (by id variant)", + "parameters": [{ "$ref": "#/components/parameters/CartIdParam" }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/SetUserIdRequest" } + } + } + }, + "responses": { + "200": { + "description": "User ID set", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/CartGrain" } + } + } + }, + "400": { "description": "Invalid body" }, + "500": { "description": "Server error" } + } + } + }, + "/cart/byid/{id}/item/{itemId}/marking": { + "put": { + "summary": "Set marking (by id variant)", + "parameters": [ + { "$ref": "#/components/parameters/CartIdParam" }, + { + "name": "itemId", + "in": "path", + "required": true, + "schema": { "type": "integer", "format": "int64", "minimum": 0 }, + "description": "Internal cart line item identifier." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LineItemMarkingRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Marking set", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/CartGrain" } + } + } + }, + "400": { "description": "Invalid body or ids" }, + "500": { "description": "Server error" } + } + }, + "delete": { + "summary": "Remove marking (by id variant)", + "parameters": [ + { "$ref": "#/components/parameters/CartIdParam" }, + { + "name": "itemId", + "in": "path", + "required": true, + "schema": { "type": "integer", "format": "int64", "minimum": 0 }, + "description": "Internal cart line item identifier." + } + ], + "responses": { + "200": { + "description": "Marking removed", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/CartGrain" } + } + } + }, + "400": { "description": "Invalid ids" }, + "500": { "description": "Server error" } + } + } + }, + "/cart/byid/{id}/checkout-order": { + "post": { + "summary": "Create checkout order (by id variant)", + "parameters": [{ "$ref": "#/components/parameters/CartIdParam" }], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCheckoutOrderRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Checkout order created", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/CartGrain" } + } + } + }, + "400": { "description": "Invalid body" }, + "500": { "description": "Server error" } + } + } + }, "/healthz": { "get": { "summary": "Liveness & capacity probe", @@ -643,6 +871,43 @@ } }, "schemas": { + "Price": { + "type": "object", + "properties": { + "exVat": { "type": "integer", "format": "int64" }, + "incVat": { "type": "integer", "format": "int64" }, + "vat": { + "type": "object", + "additionalProperties": { "type": "integer", "format": "int64" } + } + }, + "required": ["exVat", "incVat"] + }, + "ItemMeta": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "brand": { "type": "string" }, + "category": { "type": "string" }, + "category2": { "type": "string" }, + "category3": { "type": "string" }, + "category4": { "type": "string" }, + "category5": { "type": "string" }, + "sellerId": { "type": "string" }, + "sellerName": { "type": "string" }, + "image": { "type": "string" }, + "outlet": { "type": "string", "nullable": true } + } + }, + "ConfirmationStatus": { + "type": "object", + "properties": { + "code": { "type": "string", "nullable": true }, + "viewCount": { "type": "integer" }, + "lastViewedAt": { "type": "string", "format": "date-time" } + }, + "required": ["viewCount", "lastViewedAt"] + }, "CartGrain": { "type": "object", "description": "Cart aggregate (actor state)", @@ -655,9 +920,9 @@ "type": "array", "items": { "$ref": "#/components/schemas/CartItem" } }, - "totalPrice": { "type": "integer", "format": "int64" }, + "totalPrice": { "$ref": "#/components/schemas/Price" }, "totalTax": { "type": "integer", "format": "int64" }, - "totalDiscount": { "type": "integer", "format": "int64" }, + "totalDiscount": { "$ref": "#/components/schemas/Price" }, "deliveries": { "type": "array", "items": { "$ref": "#/components/schemas/CartDelivery" } @@ -675,6 +940,15 @@ "additionalProperties": { "$ref": "#/components/schemas/SubscriptionDetails" } + }, + "userId": { "type": "string" }, + "confirmation": { "$ref": "#/components/schemas/ConfirmationStatus" }, + "checkoutOrderId": { "type": "string" }, + "checkoutStatus": { "type": "string" }, + "checkoutCountry": { "type": "string" }, + "paymentDeclinedNotices": { + "type": "array", + "items": { "$ref": "#/components/schemas/Notice" } } }, "required": ["id", "items", "totalPrice", "totalTax", "totalDiscount"] @@ -686,40 +960,33 @@ "itemId": { "type": "integer" }, "parentId": { "type": "integer" }, "sku": { "type": "string" }, - "name": { "type": "string" }, - "price": { "type": "integer", "format": "int64" }, - "totalPrice": { "type": "integer", "format": "int64" }, - "totalTax": { "type": "integer", "format": "int64" }, - "orgPrice": { "type": "integer", "format": "int64" }, + "price": { "$ref": "#/components/schemas/Price" }, + "totalPrice": { "$ref": "#/components/schemas/Price" }, + "orgPrice": { "$ref": "#/components/schemas/Price" }, "stock": { "type": "integer", "description": "0=OutOfStock,1=LowStock,2=InStock" }, "qty": { "type": "integer" }, - "tax": { "type": "integer" }, - "taxRate": { "type": "integer" }, - "brand": { "type": "string" }, - "category": { "type": "string" }, - "category2": { "type": "string" }, - "category3": { "type": "string" }, - "category4": { "type": "string" }, - "category5": { "type": "string" }, + "discount": { "$ref": "#/components/schemas/Price" }, "disclaimer": { "type": "string" }, - "sellerId": { "type": "string" }, - "sellerName": { "type": "string" }, "type": { "type": "string", "description": "Article type" }, - "image": { "type": "string" }, - "outlet": { "type": "string", "nullable": true }, - "storeId": { "type": "string", "nullable": true } + "storeId": { "type": "string", "nullable": true }, + "meta": { "$ref": "#/components/schemas/ItemMeta" }, + "saleStatus": { "type": "string" }, + "marking": { "$ref": "#/components/schemas/Marking" }, + "subscriptionDetailsId": { "type": "string" }, + "orderReference": { "type": "string" }, + "isSubscribed": { "type": "boolean" } }, - "required": ["id", "sku", "name", "price", "qty", "tax"] + "required": ["id", "sku", "price", "qty"] }, "CartDelivery": { "type": "object", "properties": { "id": { "type": "integer" }, "provider": { "type": "string" }, - "price": { "type": "integer", "format": "int64" }, + "price": { "$ref": "#/components/schemas/Price" }, "items": { "type": "array", "items": { "type": "integer" } @@ -845,6 +1112,46 @@ "data": { "type": "object" } }, "required": ["id"] + }, + "Marking": { + "type": "object", + "properties": { + "type": { "type": "integer" }, + "text": { "type": "string" } + }, + "required": ["type", "text"] + }, + "Notice": { + "type": "object", + "properties": { + "timestamp": { "type": "string", "format": "date-time" }, + "message": { "type": "string" }, + "code": { "type": "string", "nullable": true } + }, + "required": ["timestamp", "message"] + }, + "SetUserIdRequest": { + "type": "object", + "properties": { + "userId": { "type": "string" } + }, + "required": ["userId"] + }, + "LineItemMarkingRequest": { + "type": "object", + "properties": { + "type": { "type": "integer" }, + "marking": { "type": "string" } + }, + "required": ["type", "marking"] + }, + "CreateCheckoutOrderRequest": { + "type": "object", + "properties": { + "terms": { "type": "string" }, + "country": { "type": "string" } + }, + "required": ["terms", "country"] } } }, diff --git a/cmd/cart/pool-server.go b/cmd/cart/pool-server.go index ef048a6..e15b01a 100644 --- a/cmd/cart/pool-server.go +++ b/cmd/cart/pool-server.go @@ -4,23 +4,30 @@ import ( "bytes" "context" "encoding/json" - "fmt" "log" "net/http" + "os" "strconv" "sync" "time" - "git.tornberg.me/go-cart-actor/pkg/actor" - "git.tornberg.me/go-cart-actor/pkg/cart" - messages "git.tornberg.me/go-cart-actor/pkg/messages" - "git.tornberg.me/go-cart-actor/pkg/voucher" - "git.tornberg.me/mats/go-redis-inventory/pkg/inventory" - "github.com/gogo/protobuf/proto" + "git.k6n.net/go-cart-actor/pkg/actor" + "git.k6n.net/go-cart-actor/pkg/cart" + messages "git.k6n.net/go-cart-actor/pkg/messages" + "git.k6n.net/go-cart-actor/pkg/proxy" + "git.k6n.net/go-cart-actor/pkg/voucher" + "github.com/adyen/adyen-go-api-library/v21/src/adyen" + "github.com/adyen/adyen-go-api-library/v21/src/checkout" + "github.com/adyen/adyen-go-api-library/v21/src/hmacvalidator" + "github.com/adyen/adyen-go-api-library/v21/src/webhook" + "github.com/google/uuid" + "github.com/matst80/go-redis-inventory/pkg/inventory" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "go.opentelemetry.io/contrib/bridges/otelslog" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -41,18 +48,24 @@ var ( type PoolServer struct { actor.GrainPool[*cart.CartGrain] - pod_name string - klarnaClient *KlarnaClient - inventoryService inventory.InventoryService + pod_name string + klarnaClient *KlarnaClient + adyenClient *adyen.APIClient + inventoryService inventory.InventoryService + reservationService inventory.CartReservationService } -func NewPoolServer(pool actor.GrainPool[*cart.CartGrain], pod_name string, klarnaClient *KlarnaClient, inventoryService inventory.InventoryService) *PoolServer { - return &PoolServer{ - GrainPool: pool, - pod_name: pod_name, - klarnaClient: klarnaClient, - inventoryService: inventoryService, +func NewPoolServer(pool actor.GrainPool[*cart.CartGrain], pod_name string, klarnaClient *KlarnaClient, inventoryService inventory.InventoryService, inventoryReservationService inventory.CartReservationService, adyenClient *adyen.APIClient) *PoolServer { + srv := &PoolServer{ + GrainPool: pool, + pod_name: pod_name, + klarnaClient: klarnaClient, + inventoryService: inventoryService, + reservationService: inventoryReservationService, + adyenClient: adyenClient, } + + return srv } func (s *PoolServer) ApplyLocal(ctx context.Context, id cart.CartId, mutation ...proto.Message) (*actor.MutationResult[*cart.CartGrain], error) { @@ -74,6 +87,7 @@ func (s *PoolServer) AddSkuToCartHandler(w http.ResponseWriter, r *http.Request, if err != nil { return err } + data, err := s.ApplyLocal(r.Context(), id, msg) if err != nil { return err @@ -248,7 +262,9 @@ func (s *PoolServer) AddMultipleItemHandler(w http.ResponseWriter, r *http.Reque return err } - reply, err := s.ApplyLocal(r.Context(), id, getMultipleAddMessages(r.Context(), setCartItems.Items, setCartItems.Country)...) + msgs := getMultipleAddMessages(r.Context(), setCartItems.Items, setCartItems.Country) + + reply, err := s.ApplyLocal(r.Context(), id, msgs...) if err != nil { return err } @@ -262,6 +278,12 @@ type AddRequest struct { StoreId *string `json:"storeId"` } +func (s *PoolServer) GetReservationTime(item *messages.AddItem) time.Duration { + + return time.Minute * 15 + //return nil +} + func (s *PoolServer) AddSkuRequestHandler(w http.ResponseWriter, r *http.Request, id cart.CartId) error { addRequest := AddRequest{Quantity: 1} err := json.NewDecoder(r.Body).Decode(&addRequest) @@ -272,6 +294,7 @@ func (s *PoolServer) AddSkuRequestHandler(w http.ResponseWriter, r *http.Request if err != nil { return err } + reply, err := s.ApplyLocal(r.Context(), id, msg) if err != nil { return err @@ -327,9 +350,11 @@ func getInventoryRequests(items []*cart.CartItem) []inventory.ReserveRequest { continue } requests = append(requests, inventory.ReserveRequest{ - SKU: inventory.SKU(item.Sku), - LocationID: getLocationId(item), - Quantity: uint32(item.Quantity), + InventoryReference: &inventory.InventoryReference{ + SKU: inventory.SKU(item.Sku), + LocationID: getLocationId(item), + }, + Quantity: uint32(item.Quantity), }) } return requests @@ -343,17 +368,16 @@ func getOriginalHost(r *http.Request) string { return r.Host } -func (s *PoolServer) CreateOrUpdateCheckout(r *http.Request, id cart.CartId) (*CheckoutOrder, error) { - host := getOriginalHost(r) - country := getCountryFromHost(host) - meta := &CheckoutMeta{ - Terms: fmt.Sprintf("https://%s/terms", host), - Checkout: fmt.Sprintf("https://%s/checkout?order_id={checkout.order.id}", host), - Confirmation: fmt.Sprintf("https://%s/confirmation/{checkout.order.id}", host), - Country: country, - Currency: getCurrency(country), - Locale: getLocale(country), +func getClientIp(r *http.Request) string { + ip := r.Header.Get("X-Forwarded-For") + if ip == "" { + ip = r.RemoteAddr } + return ip +} + +func (s *PoolServer) CreateOrUpdateCheckout(r *http.Request, id cart.CartId) (*CheckoutOrder, error) { + meta := GetCheckoutMetaFromRequest(r) // Get current grain state (may be local or remote) grain, err := s.Get(r.Context(), uint64(id)) @@ -504,7 +528,7 @@ func (s *PoolServer) ProxyHandler(fn func(w http.ResponseWriter, r *http.Request span.SetAttributes(hostAttr) logger.InfoContext(ctx, "cart proxyed", "result", ownerHost.Name()) proxyCalls.Add(ctx, 1, metric.WithAttributes(hostAttr)) - handled, err := ownerHost.Proxy(uint64(cartId), w, r) + handled, err := ownerHost.Proxy(uint64(cartId), w, r, nil) grainLookups.Inc() if err == nil && handled { @@ -520,8 +544,8 @@ func (s *PoolServer) ProxyHandler(fn func(w http.ResponseWriter, r *http.Request } var ( - tracer = otel.Tracer(name) - + tracer = otel.Tracer(name) + hmacKey = os.Getenv("ADYEN_HMAC") meter = otel.Meter(name) logger = otelslog.NewLogger(name) proxyCalls metric.Int64Counter @@ -568,15 +592,31 @@ func (s *PoolServer) AddVoucherHandler(w http.ResponseWriter, r *http.Request, c return nil } +type SubscriptionDetailsRequest struct { + Id *string `json:"id,omitempty"` + OfferingCode string `json:"offeringCode,omitempty"` + SigningType string `json:"signingType,omitempty"` + Data json.RawMessage `json:"data,omitempty"` +} + +func (sd *SubscriptionDetailsRequest) ToMessage() *messages.UpsertSubscriptionDetails { + return &messages.UpsertSubscriptionDetails{ + Id: sd.Id, + OfferingCode: sd.OfferingCode, + SigningType: sd.SigningType, + Data: &anypb.Any{Value: sd.Data}, + } +} + func (s *PoolServer) SubscriptionDetailsHandler(w http.ResponseWriter, r *http.Request, cartId cart.CartId) error { - data := &messages.UpsertSubscriptionDetails{} + data := &SubscriptionDetailsRequest{} err := json.NewDecoder(r.Body).Decode(data) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return err } - reply, err := s.ApplyLocal(r.Context(), cartId, data) + reply, err := s.ApplyLocal(r.Context(), cartId, data.ToMessage()) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) @@ -625,6 +665,181 @@ func (s *PoolServer) RemoveVoucherHandler(w http.ResponseWriter, r *http.Request return nil } +func (s *PoolServer) SetUserIdHandler(w http.ResponseWriter, r *http.Request, cartId cart.CartId) error { + setUserId := messages.SetUserId{} + err := json.NewDecoder(r.Body).Decode(&setUserId) + if err != nil { + return err + } + reply, err := s.ApplyLocal(r.Context(), cartId, &setUserId) + if err != nil { + return err + } + return s.WriteResult(w, reply) +} + +func (s *PoolServer) LineItemMarkingHandler(w http.ResponseWriter, r *http.Request, cartId cart.CartId) error { + itemIdStr := r.PathValue("itemId") + itemId, err := strconv.ParseInt(itemIdStr, 10, 64) + if err != nil { + return err + } + lineItemMarking := messages.LineItemMarking{Id: uint32(itemId)} + err = json.NewDecoder(r.Body).Decode(&lineItemMarking) + if err != nil { + return err + } + reply, err := s.ApplyLocal(r.Context(), cartId, &lineItemMarking) + if err != nil { + return err + } + return s.WriteResult(w, reply) +} + +func (s *PoolServer) RemoveLineItemMarkingHandler(w http.ResponseWriter, r *http.Request, cartId cart.CartId) error { + itemIdStr := r.PathValue("itemId") + itemId, err := strconv.ParseInt(itemIdStr, 10, 64) + if err != nil { + return err + } + removeLineItemMarking := messages.RemoveLineItemMarking{Id: uint32(itemId)} + reply, err := s.ApplyLocal(r.Context(), cartId, &removeLineItemMarking) + if err != nil { + return err + } + return s.WriteResult(w, reply) +} + +func (s *PoolServer) CreateCheckoutOrderHandler(w http.ResponseWriter, r *http.Request, cartId cart.CartId) error { + createCheckoutOrder := messages.CreateCheckoutOrder{} + err := json.NewDecoder(r.Body).Decode(&createCheckoutOrder) + if err != nil { + return err + } + reply, err := s.ApplyLocal(r.Context(), cartId, &createCheckoutOrder) + if err != nil { + return err + } + return s.WriteResult(w, reply) +} + +func (s *PoolServer) AdyenSessionHandler(w http.ResponseWriter, r *http.Request, cartId cart.CartId) error { + + grain, err := s.Get(r.Context(), uint64(cartId)) + if err != nil { + return err + } + + meta := GetCheckoutMetaFromRequest(r) + sessionData, err := BuildAdyenCheckoutSession(grain, meta) + if err != nil { + return err + } + service := s.adyenClient.Checkout() + req := service.PaymentsApi.SessionsInput().CreateCheckoutSessionRequest(*sessionData) + res, _, err := service.PaymentsApi.Sessions(r.Context(), req) + // apply checkout started + if err != nil { + return err + } + return s.WriteResult(w, res) +} + +func (s *PoolServer) AdyenHookHandler(w http.ResponseWriter, r *http.Request) { + var notificationRequest webhook.Webhook + service := s.adyenClient.Checkout() + if err := json.NewDecoder(r.Body).Decode(¬ificationRequest); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + cartHostMap := make(map[actor.Host][]webhook.NotificationItem) + for _, notificationItem := range *notificationRequest.NotificationItems { + item := notificationItem.NotificationRequestItem + log.Printf("Recieved notification event code: %s, %v", item.EventCode, item) + + isValid := hmacvalidator.ValidateHmac(item, hmacKey) + if !isValid { + log.Printf("notification hmac not valid %s, %v", item.EventCode, item) + http.Error(w, "Invalid HMAC", http.StatusUnauthorized) + return + } else { + switch item.EventCode { + case "CAPTURE": + log.Printf("Capture status: %v", item.Success) + case "AUTHORISATION": + + cartId, ok := cart.ParseCartId(item.MerchantReference) + if !ok { + log.Printf("invalid cart id %s", item.MerchantReference) + http.Error(w, "Invalid cart id", http.StatusBadRequest) + return + } + if host, ok := s.OwnerHost(uint64(cartId)); ok { + cartHostMap[host] = append(cartHostMap[host], notificationItem) + continue + } + + grain, err := s.Get(r.Context(), uint64(cartId)) + if err != nil { + log.Printf("Error getting cart: %v", err) + http.Error(w, "Cart not found", http.StatusBadRequest) + return + } + meta := GetCheckoutMetaFromRequest(r) + pspReference := item.PspReference + uid := uuid.New().String() + ref := uuid.New().String() + req := service.ModificationsApi.CaptureAuthorisedPaymentInput(pspReference).IdempotencyKey(uid).PaymentCaptureRequest(checkout.PaymentCaptureRequest{ + Amount: checkout.Amount{ + Currency: meta.Currency, + Value: grain.TotalPrice.IncVat, + }, + MerchantAccount: "ElgigantenECOM", + Reference: &ref, + }) + res, _, err := service.ModificationsApi.CaptureAuthorisedPayment(r.Context(), req) + if err != nil { + log.Printf("Error capturing payment: %v", err) + } else { + log.Printf("Payment captured successfully: %v", res) + s.Apply(r.Context(), uint64(cartId), &messages.OrderCreated{ + OrderId: res.PaymentPspReference, + Status: item.EventCode, + }) + } + default: + log.Printf("Unknown event code: %s", item.EventCode) + } + } + } + var failed bool = false + var lastMock *proxy.MockResponseWriter + for host, items := range cartHostMap { + notificationRequest.NotificationItems = &items + bodyBytes, err := json.Marshal(notificationRequest) + if err != nil { + log.Printf("error marshaling notification: %v", err) + continue + } + customBody := bytes.NewReader(bodyBytes) + mockW := proxy.NewMockResponseWriter() + handled, err := host.Proxy(0, mockW, r, customBody) + if err != nil { + log.Printf("proxy failed for %s: %v", host.Name(), err) + failed = true + lastMock = mockW + } else if handled { + log.Printf("notification proxied to %s", host.Name()) + } + } + if failed { + w.WriteHeader(lastMock.StatusCode) + w.Write(lastMock.Body.Bytes()) + } else { + w.WriteHeader(http.StatusAccepted) + } +} + func (s *PoolServer) Serve(mux *http.ServeMux) { // mux.HandleFunc("OPTIONS /cart", func(w http.ResponseWriter, r *http.Request) { @@ -648,6 +863,8 @@ func (s *PoolServer) Serve(mux *http.ServeMux) { })) } + handleFunc("/adyen_hook", s.AdyenHookHandler) + handleFunc("GET /cart", CookieCartIdHandler(s.ProxyHandler(s.GetCartHandler))) handleFunc("GET /cart/add/{sku}", CookieCartIdHandler(s.ProxyHandler(s.AddSkuToCartHandler))) handleFunc("POST /cart/add", CookieCartIdHandler(s.ProxyHandler(s.AddMultipleItemHandler))) @@ -662,6 +879,12 @@ func (s *PoolServer) Serve(mux *http.ServeMux) { handleFunc("PUT /cart/voucher", CookieCartIdHandler(s.ProxyHandler(s.AddVoucherHandler))) handleFunc("PUT /cart/subscription-details", CookieCartIdHandler(s.ProxyHandler(s.SubscriptionDetailsHandler))) handleFunc("DELETE /cart/voucher/{voucherId}", CookieCartIdHandler(s.ProxyHandler(s.RemoveVoucherHandler))) + handleFunc("PUT /cart/user", CookieCartIdHandler(s.ProxyHandler(s.SetUserIdHandler))) + handleFunc("GET /cart/adyen-session", CookieCartIdHandler(s.ProxyHandler(s.AdyenSessionHandler))) + handleFunc("PUT /cart/item/{itemId}/marking", CookieCartIdHandler(s.ProxyHandler(s.LineItemMarkingHandler))) + handleFunc("DELETE /cart/item/{itemId}/marking", CookieCartIdHandler(s.ProxyHandler(s.RemoveLineItemMarkingHandler))) + + handleFunc("POST /cart/checkout-order", CookieCartIdHandler(s.ProxyHandler(s.CreateCheckoutOrderHandler))) //mux.HandleFunc("GET /cart/checkout", CookieCartIdHandler(s.ProxyHandler(s.HandleCheckout))) //mux.HandleFunc("GET /cart/confirmation/{orderId}", CookieCartIdHandler(s.ProxyHandler(s.HandleConfirmation))) @@ -676,6 +899,11 @@ func (s *PoolServer) Serve(mux *http.ServeMux) { handleFunc("PUT /cart/byid/{id}/delivery/{deliveryId}/pickupPoint", CartIdHandler(s.ProxyHandler(s.SetPickupPointHandler))) handleFunc("PUT /cart/byid/{id}/voucher", CookieCartIdHandler(s.ProxyHandler(s.AddVoucherHandler))) handleFunc("DELETE /cart/byid/{id}/voucher/{voucherId}", CookieCartIdHandler(s.ProxyHandler(s.RemoveVoucherHandler))) + handleFunc("PUT /cart/byid/{id}/user", CartIdHandler(s.ProxyHandler(s.SetUserIdHandler))) + handleFunc("PUT /cart/byid/{id}/item/{itemId}/marking", CartIdHandler(s.ProxyHandler(s.LineItemMarkingHandler))) + handleFunc("DELETE /cart/byid/{id}/item/{itemId}/marking", CartIdHandler(s.ProxyHandler(s.RemoveLineItemMarkingHandler))) + + handleFunc("POST /cart/byid/{id}/checkout-order", CartIdHandler(s.ProxyHandler(s.CreateCheckoutOrderHandler))) //mux.HandleFunc("GET /cart/byid/{id}/checkout", CartIdHandler(s.ProxyHandler(s.HandleCheckout))) //mux.HandleFunc("GET /cart/byid/{id}/confirmation", CartIdHandler(s.ProxyHandler(s.HandleConfirmation))) diff --git a/cmd/cart/product-fetcher.go b/cmd/cart/product-fetcher.go index 95e8184..023eef6 100644 --- a/cmd/cart/product-fetcher.go +++ b/cmd/cart/product-fetcher.go @@ -5,11 +5,9 @@ import ( "encoding/json" "fmt" "net/http" - "strconv" - "strings" - "git.tornberg.me/go-cart-actor/pkg/cart" - messages "git.tornberg.me/go-cart-actor/pkg/messages" + "git.k6n.net/go-cart-actor/pkg/cart" + messages "git.k6n.net/go-cart-actor/pkg/messages" "github.com/matst80/slask-finder/pkg/index" ) @@ -61,27 +59,27 @@ func ToItemAddMessage(item *index.DataItem, storeId *string, qty int, country st if err != nil { return nil, err } - + stk := item.GetStock() stock := cart.StockStatus(0) - centralStockValue, ok := item.GetStringFieldValue(3) + if storeId == nil { + centralStock, ok := stk[country] if ok { if !item.Buyable { return nil, fmt.Errorf("item not available") } - pureNumber := strings.Replace(centralStockValue, "+", "", -1) - if centralStock, err := strconv.ParseInt(pureNumber, 10, 64); err == nil { - stock = cart.StockStatus(centralStock) - } - if stock == cart.StockStatus(0) && item.SaleStatus == "TBD" { + + if centralStock == 0 && item.SaleStatus == "TBD" { return nil, fmt.Errorf("no items available") } + stock = cart.StockStatus(centralStock) } + } else { if !item.BuyableInStore { return nil, fmt.Errorf("item not available in store") } - storeStock, ok := item.Stock.GetStock()[*storeId] + storeStock, ok := stk[*storeId] if ok { stock = cart.StockStatus(storeStock) } @@ -104,6 +102,8 @@ func ToItemAddMessage(item *index.DataItem, storeId *string, qty int, country st category4, _ := item.GetStringFieldValue(13) //Fields[13].(string) category5, _ := item.GetStringFieldValue(14) //.Fields[14].(string) + cgm, _ := item.GetStringFieldValue(35) // Customer Group Membership + return &messages.AddItem{ ItemId: uint32(item.Id), Quantity: int32(qty), @@ -128,6 +128,7 @@ func ToItemAddMessage(item *index.DataItem, storeId *string, qty int, country st Outlet: outlet, StoreId: storeId, SaleStatus: item.SaleStatus, + Cgm: cgm, }, nil } diff --git a/cmd/inventory/main.go b/cmd/inventory/main.go index 360f90d..3e28be2 100644 --- a/cmd/inventory/main.go +++ b/cmd/inventory/main.go @@ -6,10 +6,9 @@ import ( "log" "net/http" "os" - "strings" "sync" - "git.tornberg.me/mats/go-redis-inventory/pkg/inventory" + "github.com/matst80/go-redis-inventory/pkg/inventory" "github.com/matst80/slask-finder/pkg/index" "github.com/matst80/slask-finder/pkg/messaging" "github.com/redis/go-redis/v9" @@ -19,7 +18,8 @@ import ( ) type Server struct { - service *inventory.RedisInventoryService + inventoryService *inventory.RedisInventoryService + reservationService *inventory.RedisCartReservationService } func (srv *Server) livezHandler(w http.ResponseWriter, r *http.Request) { @@ -33,17 +33,11 @@ func (srv *Server) readyzHandler(w http.ResponseWriter, r *http.Request) { } func (srv *Server) getInventoryHandler(w http.ResponseWriter, r *http.Request) { - // Parse path: /inventory/{sku}/{location} - path := r.URL.Path - parts := strings.Split(strings.Trim(path, "/"), "/") - if len(parts) != 3 || parts[0] != "inventory" { - http.Error(w, "Invalid path", http.StatusBadRequest) - return - } - sku := inventory.SKU(parts[1]) - locationID := inventory.LocationID(parts[2]) - quantity, err := srv.service.GetInventory(r.Context(), sku, locationID) + sku := inventory.SKU(r.PathValue("sku")) + locationID := inventory.LocationID(r.PathValue("locationId")) + + quantity, err := srv.inventoryService.GetInventory(r.Context(), sku, locationID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -54,6 +48,20 @@ func (srv *Server) getInventoryHandler(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(response) } +func (srv *Server) getReservationHandler(w http.ResponseWriter, r *http.Request) { + sku := inventory.SKU(r.PathValue("sku")) + locationID := inventory.LocationID(r.PathValue("locationId")) + + summary, err := srv.reservationService.GetReservationSummary(r.Context(), sku, locationID) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(summary) +} + var country = "se" var redisAddress = "10.10.3.18:6379" var redisPassword = "slaskredis" @@ -87,12 +95,19 @@ func main() { return } - server := &Server{service: s} + r, err := inventory.NewRedisCartReservationService(rdb) + if err != nil { + log.Fatalf("Unable to connect to reservation redis: %v", err) + return + } + + server := &Server{inventoryService: s, reservationService: r} // Set up HTTP routes http.HandleFunc("/livez", server.livezHandler) http.HandleFunc("/readyz", server.readyzHandler) - http.HandleFunc("/inventory/", server.getInventoryHandler) + http.HandleFunc("/inventory/{sku}/{locationId}", server.getInventoryHandler) + http.HandleFunc("/reservations/{sku}/{locationId}", server.getReservationHandler) stockhandler := &StockHandler{ MainStockLocationID: inventory.LocationID(country), diff --git a/cmd/inventory/stockhandler.go b/cmd/inventory/stockhandler.go index a1c107e..569fcb2 100644 --- a/cmd/inventory/stockhandler.go +++ b/cmd/inventory/stockhandler.go @@ -7,7 +7,7 @@ import ( "strings" "sync" - "git.tornberg.me/mats/go-redis-inventory/pkg/inventory" + "github.com/matst80/go-redis-inventory/pkg/inventory" "github.com/matst80/slask-finder/pkg/types" "github.com/redis/go-redis/v9" ) diff --git a/deployment/deployment.yaml b/deployment/deployment.yaml index bc399c8..20eb1ae 100644 --- a/deployment/deployment.yaml +++ b/deployment/deployment.yaml @@ -40,11 +40,9 @@ spec: nfs: path: /i-data/7a8af061/nfs/cart-actor server: 10.10.1.10 - imagePullSecrets: - - name: regcred serviceAccountName: default containers: - - image: registry.knatofs.se/go-cart-actor-amd64:latest + - image: registry.k6n.net/go-cart-actor-amd64:latest name: cart-actor-amd64 imagePullPolicy: Always command: ["/go-cart-backoffice"] @@ -80,6 +78,20 @@ spec: env: - name: TZ value: "Europe/Stockholm" + - name: REDIS_ADDRESS + value: "10.10.3.18:6379" + - name: REDIS_PASSWORD + value: "slaskredis" + - name: ADYEN_HMAC + valueFrom: + secretKeyRef: + name: adyen + key: HMAC + - name: ADYEN_API_KEY + valueFrom: + secretKeyRef: + name: adyen + key: API_KEY - name: KLARNA_API_USERNAME valueFrom: secretKeyRef: @@ -129,11 +141,9 @@ spec: nfs: path: /i-data/7a8af061/nfs/cart-actor server: 10.10.1.10 - imagePullSecrets: - - name: regcred serviceAccountName: default containers: - - image: registry.knatofs.se/go-cart-actor-amd64:latest + - image: registry.k6n.net/go-cart-actor-amd64:latest name: cart-actor-amd64 imagePullPolicy: Always lifecycle: @@ -185,6 +195,16 @@ spec: value: "service.name=cart,service.version=0.1.2" - name: OTEL_EXPORTER_OTLP_ENDPOINT value: "http://otel-debug-service.monitoring:4317" + - name: ADYEN_HMAC + valueFrom: + secretKeyRef: + name: adyen + key: HMAC + - name: ADYEN_API_KEY + valueFrom: + secretKeyRef: + name: adyen + key: API_KEY - name: KLARNA_API_PASSWORD valueFrom: secretKeyRef: @@ -241,11 +261,9 @@ spec: nfs: path: /i-data/7a8af061/nfs/cart-actor server: 10.10.1.10 - imagePullSecrets: - - name: regcred serviceAccountName: default containers: - - image: registry.knatofs.se/go-cart-actor:latest + - image: registry.k6n.net/go-cart-actor:latest name: cart-actor-arm64 imagePullPolicy: Always lifecycle: @@ -292,6 +310,16 @@ spec: value: "service.name=cart,service.version=0.1.2" - name: OTEL_EXPORTER_OTLP_ENDPOINT value: "http://otel-debug-service.monitoring:4317" + - name: ADYEN_HMAC + valueFrom: + secretKeyRef: + name: adyen + key: HMAC + - name: ADYEN_API_KEY + valueFrom: + secretKeyRef: + name: adyen + key: API_KEY - name: KLARNA_API_USERNAME valueFrom: secretKeyRef: @@ -356,10 +384,10 @@ spec: ingressClassName: nginx tls: - hosts: - - cart.tornberg.me + - cart.k6n.net secretName: cart-actor-tls-secret rules: - - host: cart.tornberg.me + - host: cart.k6n.net http: paths: - path: / @@ -380,10 +408,10 @@ spec: ingressClassName: nginx tls: - hosts: - - slask-cart.tornberg.me + - slask-cart.k6n.net secretName: cart-backoffice-actor-tls-secret rules: - - host: slask-cart.tornberg.me + - host: slask-cart.k6n.net http: paths: - path: / @@ -422,11 +450,9 @@ spec: operator: NotIn values: - arm64 - imagePullSecrets: - - name: regcred serviceAccountName: default containers: - - image: registry.knatofs.se/go-cart-actor-amd64:latest + - image: registry.k6n.net/go-cart-actor-amd64:latest name: cart-inventory-amd64 imagePullPolicy: Always command: ["/go-cart-inventory"] @@ -466,3 +492,14 @@ spec: value: "redis.home:6379" - name: REDIS_PASSWORD value: "slaskredis" +--- +kind: Service +apiVersion: v1 +metadata: + name: inventory +spec: + selector: + app: cart-inventory + ports: + - name: web + port: 8080 diff --git a/go.mod b/go.mod index d1dccde..bdf0dd8 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,15 @@ -module git.tornberg.me/go-cart-actor +module git.k6n.net/go-cart-actor -go 1.25.3 +go 1.25.4 require ( - git.tornberg.me/mats/go-redis-inventory v0.0.0-20251113201741-8bf0efac50ee - github.com/gogo/protobuf v1.3.2 + github.com/adyen/adyen-go-api-library/v21 v21.1.0 github.com/google/uuid v1.6.0 - github.com/matst80/slask-finder v0.0.0-20251113121253-1d0cc78309a0 + github.com/matst80/go-redis-inventory v0.0.0-20251126173508-51b30de2d86e + github.com/matst80/slask-finder v0.0.0-20251125182907-9e57f193127a github.com/prometheus/client_golang v1.23.2 github.com/rabbitmq/amqp091-go v1.10.0 - github.com/redis/go-redis/v9 v9.16.0 + github.com/redis/go-redis/v9 v9.17.0 go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 go.opentelemetry.io/otel v1.38.0 @@ -22,7 +22,7 @@ require ( go.opentelemetry.io/otel/sdk/log v0.14.0 go.opentelemetry.io/otel/sdk/metric v1.38.0 go.opentelemetry.io/otel/trace v1.38.0 - google.golang.org/grpc v1.76.0 + google.golang.org/grpc v1.77.0 google.golang.org/protobuf v1.36.10 k8s.io/api v0.34.2 k8s.io/apimachinery v0.34.2 @@ -32,37 +32,37 @@ require ( require ( github.com/RoaringBitmap/roaring/v2 v2.14.4 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bits-and-blooms/bitset v1.24.3 // indirect + github.com/bits-and-blooms/bitset v1.24.4 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/dprotaso/go-yit v0.0.0-20250909171706-0a81c39169bc // indirect + github.com/dprotaso/go-yit v0.0.0-20251117151522-da16f3077589 // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect + github.com/go-openapi/jsonpointer v0.22.3 // indirect github.com/go-openapi/jsonreference v0.21.3 // indirect - github.com/go-openapi/swag v0.25.1 // indirect - github.com/go-openapi/swag/cmdutils v0.25.1 // indirect - github.com/go-openapi/swag/conv v0.25.1 // indirect - github.com/go-openapi/swag/fileutils v0.25.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect - github.com/go-openapi/swag/jsonutils v0.25.1 // indirect - github.com/go-openapi/swag/loading v0.25.1 // indirect - github.com/go-openapi/swag/mangling v0.25.1 // indirect - github.com/go-openapi/swag/netutils v0.25.1 // indirect - github.com/go-openapi/swag/stringutils v0.25.1 // indirect - github.com/go-openapi/swag/typeutils v0.25.1 // indirect - github.com/go-openapi/swag/yamlutils v0.25.1 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/gorilla/schema v1.4.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.9.1 // indirect @@ -77,20 +77,20 @@ require ( github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.2 // indirect + github.com/prometheus/common v0.67.4 // indirect github.com/prometheus/procfs v0.19.2 // indirect github.com/speakeasy-api/jsonpath v0.6.2 // indirect github.com/speakeasy-api/openapi-overlay v0.10.3 // indirect - github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect golang.org/x/mod v0.30.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.33.0 // indirect @@ -100,18 +100,18 @@ require ( golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.39.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e // indirect k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index ef349d1..2a460f0 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,13 @@ -git.tornberg.me/mats/go-redis-inventory v0.0.0-20251110193851-19d7ad0de6e5 h1:54ZKuqppO6reMmnWOYJaFMlPJK947xnPrv3zDbSuknQ= -git.tornberg.me/mats/go-redis-inventory v0.0.0-20251110193851-19d7ad0de6e5/go.mod h1:jrDU55O7sdN2RJr99upmig/FAla/mW1Cdju7834TXug= -git.tornberg.me/mats/go-redis-inventory v0.0.0-20251113201741-8bf0efac50ee h1:9K/INdO9y/hAjlERsYkJWAla6BUEAPXtChVLfYtWdGI= -git.tornberg.me/mats/go-redis-inventory v0.0.0-20251113201741-8bf0efac50ee/go.mod h1:jrDU55O7sdN2RJr99upmig/FAla/mW1Cdju7834TXug= -github.com/RoaringBitmap/roaring/v2 v2.13.0 h1:38BxJ6lGPcBLykIRCyYtViB/By3+a/iS9znKsiBbhNc= -github.com/RoaringBitmap/roaring/v2 v2.13.0/go.mod h1:Mpi+oQ+3oCU7g1aF75Ib/XYCTqjTGpHI0f8djSZVY3I= github.com/RoaringBitmap/roaring/v2 v2.14.4 h1:4aKySrrg9G/5oRtJ3TrZLObVqxgQ9f1znCRBwEwjuVw= github.com/RoaringBitmap/roaring/v2 v2.14.4/go.mod h1:oMvV6omPWr+2ifRdeZvVJyaz+aoEUopyv5iH0u/+wbY= +github.com/adyen/adyen-go-api-library/v21 v21.1.0 h1:QIKtn99yoBdt2R4PhuMdmY/DTm6Ex5HYd0cB7Sh3y6Y= +github.com/adyen/adyen-go-api-library/v21 v21.1.0/go.mod h1:qsAGYetm761eDAz+f2OQoY4qC+tKNhZOHil1b4FO5zE= +github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= +github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bits-and-blooms/bitset v1.24.1 h1:hqnfFbjjk3pxGa5E9Ho3hjoU7odtUuNmJ9Ao+Bo8s1c= -github.com/bits-and-blooms/bitset v1.24.1/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/bits-and-blooms/bitset v1.24.3 h1:Bte86SlO3lwPQqww+7BE9ZuUCKIjfqnG5jtEyqA9y9Y= -github.com/bits-and-blooms/bitset v1.24.3/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE= +github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= @@ -20,10 +16,6 @@ github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1x github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -31,21 +23,17 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= -github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= -github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= -github.com/dprotaso/go-yit v0.0.0-20250909171706-0a81c39169bc h1:YxqE1wh+qGVXQFinuRq5lT77h6baDtBnAVh61LlXp1o= -github.com/dprotaso/go-yit v0.0.0-20250909171706-0a81c39169bc/go.mod h1:5NQLChvz4dnEIQ8WcHIFbZ1bp0GEUZHiBH+EpTZ4lBc= +github.com/dprotaso/go-yit v0.0.0-20251117151522-da16f3077589 h1:VJ/jVUWr+r4MQA7U/cscbbXRuwh1PfPCUUItYAjlKN4= +github.com/dprotaso/go-yit v0.0.0-20251117151522-da16f3077589/go.mod h1:IeI20psFPeg2n1jxwbkYCmkpYsXsJqB7qmoqCIlX80s= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk= -github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -53,40 +41,40 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/jsonpointer v0.22.3 h1:dKMwfV4fmt6Ah90zloTbUKWMD+0he+12XYAsPotrkn8= +github.com/go-openapi/jsonpointer v0.22.3/go.mod h1:0lBbqeRsQ5lIanv3LHZBrmRGHLHcQoOXQnf88fHlGWo= github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc= github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4= -github.com/go-openapi/swag v0.25.1 h1:6uwVsx+/OuvFVPqfQmOOPsqTcm5/GkBhNwLqIR916n8= -github.com/go-openapi/swag v0.25.1/go.mod h1:bzONdGlT0fkStgGPd3bhZf1MnuPkf2YAys6h+jZipOo= -github.com/go-openapi/swag/cmdutils v0.25.1 h1:nDke3nAFDArAa631aitksFGj2omusks88GF1VwdYqPY= -github.com/go-openapi/swag/cmdutils v0.25.1/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= -github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0= -github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs= -github.com/go-openapi/swag/fileutils v0.25.1 h1:rSRXapjQequt7kqalKXdcpIegIShhTPXx7yw0kek2uU= -github.com/go-openapi/swag/fileutils v0.25.1/go.mod h1:+NXtt5xNZZqmpIpjqcujqojGFek9/w55b3ecmOdtg8M= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= -github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8= -github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1 h1:DSQGcdB6G0N9c/KhtpYc71PzzGEIc/fZ1no35x4/XBY= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1/go.mod h1:kjmweouyPwRUEYMSrbAidoLMGeJ5p6zdHi9BgZiqmsg= -github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw= -github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc= -github.com/go-openapi/swag/mangling v0.25.1 h1:XzILnLzhZPZNtmxKaz/2xIGPQsBsvmCjrJOWGNz/ync= -github.com/go-openapi/swag/mangling v0.25.1/go.mod h1:CdiMQ6pnfAgyQGSOIYnZkXvqhnnwOn997uXZMAd/7mQ= -github.com/go-openapi/swag/netutils v0.25.1 h1:2wFLYahe40tDUHfKT1GRC4rfa5T1B4GWZ+msEFA4Fl4= -github.com/go-openapi/swag/netutils v0.25.1/go.mod h1:CAkkvqnUJX8NV96tNhEQvKz8SQo2KF0f7LleiJwIeRE= -github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw= -github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg= -github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA= -github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8= -github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk= -github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= @@ -94,40 +82,24 @@ github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncV github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -145,14 +117,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/matst80/slask-finder v0.0.0-20251023104024-f788e5a51d68 h1:nDSim5IRFrLG3okBty3nes50ZuxKMEY5rwj2FuUnTgc= -github.com/matst80/slask-finder v0.0.0-20251023104024-f788e5a51d68/go.mod h1:ZlbURsmhMX98D8z/du5Cez8fZU/sfkA98ruczIsB9PY= -github.com/matst80/slask-finder v0.0.0-20251113121253-1d0cc78309a0 h1:ZBbVCFAvccFLOck4e64BtNXdexouVHfQqQzlZaEIY40= -github.com/matst80/slask-finder v0.0.0-20251113121253-1d0cc78309a0/go.mod h1:554hsg38gorr5GH/BLGs4pPqVpmrHTNWq5qoferDeDA= +github.com/matst80/go-redis-inventory v0.0.0-20251126173508-51b30de2d86e h1:Z7A73W6jsxFuFKWvB1efQmTjs0s7+x2B7IBM2ukkI6Y= +github.com/matst80/go-redis-inventory v0.0.0-20251126173508-51b30de2d86e/go.mod h1:9P52UwIlLWLZvObfO29aKTWUCA9Gm62IuPJ/qv4Xvs0= +github.com/matst80/slask-finder v0.0.0-20251125182907-9e57f193127a h1:EfUO5BNDK3a563zQlwJYTNNv46aJFT9gbSItAwZOZ/Y= +github.com/matst80/slask-finder v0.0.0-20251125182907-9e57f193127a/go.mod h1:VIPNkIvU0dZKwbSuv75zZcB93MXISm2UyiIPly/ucXQ= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -165,12 +135,8 @@ github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= -github.com/oapi-codegen/oapi-codegen/v2 v2.5.0 h1:iJvF8SdB/3/+eGOXEpsWkD8FQAHj6mqkb6Fnsoc8MFU= -github.com/oapi-codegen/oapi-codegen/v2 v2.5.0/go.mod h1:fwlMxUEMuQK5ih9aymrxKPQqNm2n8bdLk1ppjH+lr9w= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= github.com/oapi-codegen/oapi-codegen/v2 v2.5.1 h1:5vHNY1uuPBRBWqB2Dp0G7YB03phxLQZupZTIZaeorjc= github.com/oapi-codegen/oapi-codegen/v2 v2.5.1/go.mod h1:ro0npU1BWkcGpCgGD9QwPp44l5OIZ94tB3eabnT7DjQ= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= @@ -179,21 +145,13 @@ github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletI github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= -github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= +github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -203,34 +161,22 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI= -github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q= -github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8= -github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko= -github.com/prometheus/procfs v0.18.0 h1:2QTA9cKdznfYJz7EDaa7IiJobHuV7E1WzeBwcrhk0ao= -github.com/prometheus/procfs v0.18.0/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o= -github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4= -github.com/redis/go-redis/v9 v9.16.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= +github.com/redis/go-redis/v9 v9.17.0 h1:K6E+ZlYN95KSMmZeEQPbU/c++wfmEvfFB17yEAq/VhM= +github.com/redis/go-redis/v9 v9.17.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8= -github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw= github.com/speakeasy-api/jsonpath v0.6.2 h1:Mys71yd6u8kuowNCR0gCVPlVAHCmKtoGXYoAtcEbqXQ= github.com/speakeasy-api/jsonpath v0.6.2/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw= -github.com/speakeasy-api/openapi-overlay v0.10.2 h1:VOdQ03eGKeiHnpb1boZCGm7x8Haj6gST0P3SGTX95GU= -github.com/speakeasy-api/openapi-overlay v0.10.2/go.mod h1:n0iOU7AqKpNFfEt6tq7qYITC4f0yzVVdFw0S7hukemg= github.com/speakeasy-api/openapi-overlay v0.10.3 h1:70een4vwHyslIp796vM+ox6VISClhtXsCjrQNhxwvWs= github.com/speakeasy-api/openapi-overlay v0.10.3/go.mod h1:RJjV0jbUHqXLS0/Mxv5XE7LAnJHqHw+01RDdpoGqiyY= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -245,10 +191,14 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s= @@ -279,8 +229,6 @@ go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6 go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= -go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= -go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -289,71 +237,40 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s= -go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= +go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go= +go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= -golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= -golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= @@ -361,10 +278,7 @@ golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -373,24 +287,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= -google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= -google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846 h1:ZdyUkS9po3H7G0tuh955QVyyotWvOD4W0aEapeGeUYk= +google.golang.org/genproto/googleapis/api v0.0.0-20251124214823-79d6a2a48846/go.mod h1:Fk4kyraUvqD7i5H6S43sj2W98fbZa75lpZz/eUyhfO0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -407,36 +309,29 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= -k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= -k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= -k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= +sigs.k8s.io/structured-merge-diff/v6 v6.3.1/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/k6/README.md b/k6/README.md index 0818791..17a4648 100644 --- a/k6/README.md +++ b/k6/README.md @@ -18,10 +18,10 @@ This directory contains a k6 script (`cart_load_test.js`) to stress and observe The script exercises (per iteration): -1. `GET /cart/` – ensure / fetch cart state (creates cart if missing; sets `cartid` & `cartowner` cookies) -2. `POST /cart/` – add item mutation (random SKU & quantity) -3. `GET /cart/` – fetch after mutations -4. `GET /cart/checkout` – occasionally (~2% of iterations) to simulate checkout start +1. `GET /cart` – ensure / fetch cart state (creates cart if missing; sets `cartid` & `cartowner` cookies) +2. `POST /cart` – add item mutation (random SKU & quantity) +3. `GET /cart` – fetch after mutations +4. `GET /checkout` – occasionally (~2% of iterations) to simulate checkout start You can extend it easily to hit deliveries, quantity changes, or removal endpoints. @@ -40,9 +40,9 @@ Example run: ```bash k6 run \ - -e BASE_URL=https://cart.prod.example.com/cart \ + -e BASE_URL=https://cart.k6n.net/cart \ -e VUS=40 \ - -e DURATION=10m \ + -e DURATION=1m \ -e RAMP_TARGET=120 \ k6/cart_load_test.js ``` @@ -171,4 +171,4 @@ Feel free to request: - WebSocket / long poll integration (if added later) - Synthetic error injection harness -Happy load testing! \ No newline at end of file +Happy load testing! diff --git a/k6/cart_load_test.js b/k6/cart_load_test.js index 2fff92d..da44152 100644 --- a/k6/cart_load_test.js +++ b/k6/cart_load_test.js @@ -4,43 +4,39 @@ import { Counter, Trend } from "k6/metrics"; // ---------------- Configuration ---------------- export const options = { - // Adjust vus/duration for your environment - scenarios: { - steady_mutations: { - executor: "constant-vus", - vus: __ENV.VUS ? parseInt(__ENV.VUS, 10) : 20, - duration: __ENV.DURATION || "5m", - gracefulStop: "30s", - }, - ramp_up: { - executor: "ramping-vus", - startVUs: 0, - stages: [ - { - duration: "1m", - target: __ENV.RAMP_TARGET - ? parseInt(__ENV.RAMP_TARGET, 10) - : 50, - }, - { - duration: "1m", - target: __ENV.RAMP_TARGET - ? parseInt(__ENV.RAMP_TARGET, 10) - : 50, - }, - { duration: "1m", target: 0 }, - ], - gracefulStop: "30s", - startTime: "30s", - }, + // Adjust vus/duration for your environment + scenarios: { + steady_mutations: { + executor: "constant-vus", + vus: __ENV.VUS ? parseInt(__ENV.VUS, 10) : 20, + duration: __ENV.DURATION || "5m", + gracefulStop: "30s", }, - thresholds: { - http_req_failed: ["rate<0.02"], // < 2% failures - http_req_duration: ["p(90)<800", "p(99)<1500"], // latency SLO - "cart_add_item_duration{op:add}": ["p(90)<800"], - "cart_fetch_duration{op:get}": ["p(90)<600"], + ramp_up: { + executor: "ramping-vus", + startVUs: 0, + stages: [ + { + duration: "1m", + target: __ENV.RAMP_TARGET ? parseInt(__ENV.RAMP_TARGET, 10) : 50, + }, + { + duration: "1m", + target: __ENV.RAMP_TARGET ? parseInt(__ENV.RAMP_TARGET, 10) : 50, + }, + { duration: "1m", target: 0 }, + ], + gracefulStop: "30s", + startTime: "30s", }, - summaryTrendStats: ["avg", "min", "med", "max", "p(90)", "p(95)", "p(99)"], + }, + thresholds: { + http_req_failed: ["rate<0.02"], // < 2% failures + http_req_duration: ["p(90)<800", "p(99)<1500"], // latency SLO + "cart_add_item_duration{op:add}": ["p(90)<800"], + "cart_fetch_duration{op:get}": ["p(90)<600"], + }, + summaryTrendStats: ["avg", "min", "med", "max", "p(90)", "p(95)", "p(99)"], }; // ---------------- Metrics ---------------- @@ -52,197 +48,197 @@ const checkoutCounter = new Counter("cart_checkout_calls"); // ---------------- SKUs ---------------- const SKUS = [ - "778290", - "778345", - "778317", - "778277", - "778267", - "778376", - "778244", - "778384", - "778365", - "778377", - "778255", - "778286", - "778246", - "778270", - "778266", - "778285", - "778329", - "778425", - "778407", - "778418", - "778430", - "778469", - "778358", - "778351", - "778319", - "778307", - "778278", - "778251", - "778253", - "778261", - "778263", - "778273", - "778281", - "778294", - "778297", - "778302", + "778290", + "778345", + "778317", + "778277", + "778267", + "778376", + "778244", + "778384", + "778365", + "778377", + "778255", + "778286", + "778246", + "778270", + "778266", + "778285", + "778329", + "778425", + "778407", + "778418", + "778430", + "778469", + "778358", + "778351", + "778319", + "778307", + "778278", + "778251", + "778253", + "778261", + "778263", + "778273", + "778281", + "778294", + "778297", + "778302", ]; // ---------------- Helpers ---------------- function randomSku() { - return SKUS[Math.floor(Math.random() * SKUS.length)]; + return SKUS[Math.floor(Math.random() * SKUS.length)]; } function randomQty() { - return 1 + Math.floor(Math.random() * 3); // 1..3 + return 1 + Math.floor(Math.random() * 3); // 1..3 } function baseUrl() { - const u = __ENV.BASE_URL || "http://localhost:8080/cart"; - // Allow user to pass either root host or full /cart path - return u.endsWith("/cart") ? u : u.replace(/\/+$/, "") + "/cart"; + const u = __ENV.BASE_URL || "http://localhost:8080/cart"; + // Allow user to pass either root host or full /cart path + return u.endsWith("/cart") ? u : u.replace(/\/+$/, "") + "/cart"; } function extractCookie(res, name) { - const cookies = res.cookies[name]; - if (!cookies || cookies.length === 0) return null; - return cookies[0].value; + const cookies = res.cookies[name]; + if (!cookies || cookies.length === 0) return null; + return cookies[0].value; } function withCookies(headers, cookieJar) { - if (!cookieJar || Object.keys(cookieJar).length === 0) return headers; - const cookieStr = Object.entries(cookieJar) - .map(([k, v]) => `${k}=${v}`) - .join("; "); - return { ...headers, Cookie: cookieStr }; + if (!cookieJar || Object.keys(cookieJar).length === 0) return headers; + const cookieStr = Object.entries(cookieJar) + .map(([k, v]) => `${k}=${v}`) + .join("; "); + return { ...headers, Cookie: cookieStr }; } // Maintain cart + owner cookies per VU let cartState = { - cartid: null, - cartowner: null, + cartid: null, + cartowner: null, }; // Refresh cookies from response function updateCookies(res) { - const cid = extractCookie(res, "cartid"); - if (cid) cartState.cartid = cid; - const owner = extractCookie(res, "cartowner"); - if (owner) cartState.cartowner = owner; + const cid = extractCookie(res, "cartid"); + if (cid) cartState.cartid = cid; + const owner = extractCookie(res, "cartowner"); + if (owner) cartState.cartowner = owner; } // Build headers function headers() { - const h = { "Content-Type": "application/json" }; - const jar = {}; - if (cartState.cartid) jar["cartid"] = cartState.cartid; - if (cartState.cartowner) jar["cartowner"] = cartState.cartowner; - return withCookies(h, jar); + const h = { "Content-Type": "application/json" }; + const jar = {}; + if (cartState.cartid) jar["cartid"] = cartState.cartid; + if (cartState.cartowner) jar["cartowner"] = cartState.cartowner; + return withCookies(h, jar); } // Ensure cart exists (GET /) function ensureCart() { - if (cartState.cartid) return; - const res = http.get(baseUrl() + "/", { headers: headers() }); - updateCookies(res); - check(res, { - "ensure cart status 200": (r) => r.status === 200, - "ensure cart has id": () => !!cartState.cartid, - }); + if (cartState.cartid) return; + const res = http.get(baseUrl(), { headers: headers() }); + updateCookies(res); + check(res, { + "ensure cart status 200": (r) => r.status === 200, + "ensure cart has id": () => !!cartState.cartid, + }); } // Add random item function addRandomItem() { - const payload = JSON.stringify({ - sku: randomSku(), - quantity: randomQty(), - country: "no", - }); - const start = Date.now(); - const res = http.post(baseUrl(), payload, { headers: headers() }); - const dur = Date.now() - start; - addItemTrend.add(dur, { op: "add" }); - if (res.status === 200) { - addedItemsCounter.add(1); - } - updateCookies(res); - check(res, { - "add item status ok": (r) => r.status === 200, - }); + const payload = JSON.stringify({ + sku: randomSku(), + quantity: randomQty(), + country: "no", + }); + const start = Date.now(); + const res = http.post(baseUrl(), payload, { headers: headers() }); + const dur = Date.now() - start; + addItemTrend.add(dur, { op: "add" }); + if (res.status === 200) { + addedItemsCounter.add(1); + } + updateCookies(res); + check(res, { + "add item status ok": (r) => r.status === 200, + }); } // Fetch cart state function fetchCart() { - const start = Date.now(); - const res = http.get(baseUrl() + "/", { headers: headers() }); - const dur = Date.now() - start; - fetchTrend.add(dur, { op: "get" }); - updateCookies(res); - check(res, { "fetch status ok": (r) => r.status === 200 }); + const start = Date.now(); + const res = http.get(baseUrl(), { headers: headers() }); + const dur = Date.now() - start; + fetchTrend.add(dur, { op: "get" }); + updateCookies(res); + check(res, { "fetch status ok": (r) => r.status === 200 }); } // Occasional checkout trigger function maybeCheckout() { - if (!cartState.cartid) return; - // // Small probability - // if (Math.random() < 0.02) { - // const start = Date.now(); - // const res = http.get(baseUrl() + "/checkout", { headers: headers() }); - // const dur = Date.now() - start; - // checkoutTrend.add(dur, { op: "checkout" }); - // updateCookies(res); - // if (res.status === 200) checkoutCounter.add(1); - // check(res, { "checkout status ok": (r) => r.status === 200 }); - // } + if (!cartState.cartid) return; + // // Small probability + // if (Math.random() < 0.02) { + // const start = Date.now(); + // const res = http.get(baseUrl() + "/checkout", { headers: headers() }); + // const dur = Date.now() - start; + // checkoutTrend.add(dur, { op: "checkout" }); + // updateCookies(res); + // if (res.status === 200) checkoutCounter.add(1); + // check(res, { "checkout status ok": (r) => r.status === 200 }); + // } } // ---------------- k6 lifecycle ---------------- export function setup() { - // Provide SKU list length for summary - return { skuCount: SKUS.length }; + // Provide SKU list length for summary + return { skuCount: SKUS.length }; } export default function (data) { - group("cart flow", () => { - // Create or reuse cart - ensureCart(); + group("cart flow", () => { + // Create or reuse cart + ensureCart(); - // Random number of item mutations per iteration (1..5) - const ops = 1 + Math.floor(Math.random() * 5); - for (let i = 0; i < ops; i++) { - addRandomItem(); - } + // Random number of item mutations per iteration (1..5) + const ops = 1 + Math.floor(Math.random() * 5); + for (let i = 0; i < ops; i++) { + addRandomItem(); + } - // Fetch state - fetchCart(); + // Fetch state + fetchCart(); - // Optional checkout attempt - maybeCheckout(); - }); + // Optional checkout attempt + maybeCheckout(); + }); - // Small think time - sleep(Math.random() * 0.5); + // Small think time + sleep(Math.random() * 0.5); } export function teardown(data) { - // Optionally we could GET confirmation or clear cart cookie - // Not implemented for load purpose. - console.log(`Test complete. SKU count: ${data.skuCount}`); + // Optionally we could GET confirmation or clear cart cookie + // Not implemented for load purpose. + console.log(`Test complete. SKU count: ${data.skuCount}`); } // ---------------- Summary ---------------- export function handleSummary(data) { - return { - stdout: JSON.stringify( - { - metrics: { - mutations_avg: data.metrics.cart_add_item_duration?.avg, - mutations_p95: data.metrics.cart_add_item_duration?.p(95), - fetch_p95: data.metrics.cart_fetch_duration?.p(95), - checkout_count: data.metrics.cart_checkout_calls?.count, - }, - checks: data.root_checks, - }, - null, - 2, - ), - }; + return { + stdout: JSON.stringify( + { + metrics: { + mutations_avg: data.metrics.cart_add_item_duration?.avg, + mutations_p95: data.metrics.cart_add_item_duration?.p(95), + fetch_p95: data.metrics.cart_fetch_duration?.p(95), + checkout_count: data.metrics.cart_checkout_calls?.count, + }, + checks: data.root_checks, + }, + null, + 2, + ), + }; } diff --git a/pkg/actor/disk_storage.go b/pkg/actor/disk_storage.go index e65a601..75e7376 100644 --- a/pkg/actor/disk_storage.go +++ b/pkg/actor/disk_storage.go @@ -10,7 +10,7 @@ import ( "sync" "time" - "github.com/gogo/protobuf/proto" + "google.golang.org/protobuf/proto" ) type QueueEvent struct { @@ -27,6 +27,7 @@ type DiskStorage[V any] struct { type LogStorage[V any] interface { LoadEvents(ctx context.Context, id uint64, grain Grain[V]) error + LoadEventsFunc(ctx context.Context, id uint64, grain Grain[V], condition func(msg proto.Message, index int, timeStamp time.Time) bool) error AppendMutations(id uint64, msg ...proto.Message) error } @@ -87,6 +88,27 @@ func (s *DiskStorage[V]) logPath(id uint64) string { return filepath.Join(s.path, fmt.Sprintf("%d.events.log", id)) } +func (s *DiskStorage[V]) LoadEventsFunc(ctx context.Context, id uint64, grain Grain[V], condition func(msg proto.Message, index int, timeStamp time.Time) bool) error { + path := s.logPath(id) + if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { + // No log -> nothing to replay + return nil + } + + fh, err := os.Open(path) + if err != nil { + return fmt.Errorf("open replay file: %w", err) + } + defer fh.Close() + index := 0 + return s.Load(fh, func(msg proto.Message, when time.Time) { + if condition(msg, index, when) { + s.registry.Apply(ctx, grain, msg) + } + index++ + }) +} + func (s *DiskStorage[V]) LoadEvents(ctx context.Context, id uint64, grain Grain[V]) error { path := s.logPath(id) if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { @@ -99,7 +121,7 @@ func (s *DiskStorage[V]) LoadEvents(ctx context.Context, id uint64, grain Grain[ return fmt.Errorf("open replay file: %w", err) } defer fh.Close() - return s.Load(fh, func(msg proto.Message) { + return s.Load(fh, func(msg proto.Message, _ time.Time) { s.registry.Apply(ctx, grain, msg) }) } diff --git a/pkg/actor/grain_pool.go b/pkg/actor/grain_pool.go index f51c220..be048fb 100644 --- a/pkg/actor/grain_pool.go +++ b/pkg/actor/grain_pool.go @@ -2,9 +2,10 @@ package actor import ( "context" + "io" "net/http" - "github.com/gogo/protobuf/proto" + "google.golang.org/protobuf/proto" ) type MutationResult[V any] struct { @@ -34,7 +35,8 @@ type Host interface { AnnounceExpiry(ids []uint64) Negotiate(otherHosts []string) ([]string, error) Name() string - Proxy(id uint64, w http.ResponseWriter, r *http.Request) (bool, error) + Proxy(id uint64, w http.ResponseWriter, r *http.Request, customBody io.Reader) (bool, error) + Apply(ctx context.Context, id uint64, mutation ...proto.Message) (bool, error) GetActorIds() []uint64 Close() error Ping() bool diff --git a/pkg/actor/grpc_server.go b/pkg/actor/grpc_server.go index 06bd8a1..91b63e1 100644 --- a/pkg/actor/grpc_server.go +++ b/pkg/actor/grpc_server.go @@ -7,13 +7,14 @@ import ( "net" "time" - messages "git.tornberg.me/go-cart-actor/pkg/messages" + messages "git.k6n.net/go-cart-actor/pkg/messages" "go.opentelemetry.io/contrib/bridges/otelslog" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "google.golang.org/grpc" "google.golang.org/grpc/reflection" + "google.golang.org/protobuf/proto" ) // ControlServer implements the ControlPlane gRPC services. @@ -139,6 +140,23 @@ func (s *ControlServer[V]) Ping(ctx context.Context, _ *messages.Empty) (*messag }, nil } +func (s *ControlServer[V]) Apply(ctx context.Context, in *messages.ApplyRequest) (*messages.ApplyResult, error) { + msgs := make([]proto.Message, len(in.Messages)) + for i, anyMsg := range in.Messages { + msg, err := anyMsg.UnmarshalNew() + if err != nil { + return &messages.ApplyResult{Accepted: false}, fmt.Errorf("failed to unmarshal message: %w", err) + } + msgs[i] = msg + } + _, err := s.pool.Apply(ctx, in.Id, msgs...) + if err != nil { + return &messages.ApplyResult{Accepted: false}, err + } + + return &messages.ApplyResult{Accepted: true}, nil +} + // ControlPlane: Negotiate (merge host views) func (s *ControlServer[V]) Negotiate(ctx context.Context, req *messages.NegotiateRequest) (*messages.NegotiateReply, error) { ctx, span := tracer.Start(ctx, "grpc_negotiate") diff --git a/pkg/actor/grpc_server_test.go b/pkg/actor/grpc_server_test.go new file mode 100644 index 0000000..cf83f83 --- /dev/null +++ b/pkg/actor/grpc_server_test.go @@ -0,0 +1,104 @@ +package actor + +import ( + "context" + "testing" + + "git.k6n.net/go-cart-actor/pkg/messages" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" +) + +// MockGrainPool for testing +type mockGrainPool struct { + applied []proto.Message +} + +func (m *mockGrainPool) Apply(ctx context.Context, id uint64, mutations ...proto.Message) (*MutationResult[*mockGrain], error) { + m.applied = mutations + // Simulate successful application + return &MutationResult[*mockGrain]{ + Result: &mockGrain{}, + Mutations: []ApplyResult{ + {Error: nil}, // Assume success + {Error: nil}, + }, + }, nil +} + +func (m *mockGrainPool) Get(ctx context.Context, id uint64) (*mockGrain, error) { + return &mockGrain{}, nil +} + +func (m *mockGrainPool) OwnerHost(id uint64) (Host, bool) { + return nil, false +} + +func (m *mockGrainPool) TakeOwnership(id uint64) {} + +func (m *mockGrainPool) Hostname() string { return "test-host" } + +func (m *mockGrainPool) HandleOwnershipChange(host string, ids []uint64) error { return nil } +func (m *mockGrainPool) HandleRemoteExpiry(host string, ids []uint64) error { return nil } +func (m *mockGrainPool) Negotiate(hosts []string) {} +func (m *mockGrainPool) GetLocalIds() []uint64 { return []uint64{} } +func (m *mockGrainPool) RemoveHost(host string) {} +func (m *mockGrainPool) AddRemoteHost(host string) {} +func (m *mockGrainPool) IsHealthy() bool { return true } +func (m *mockGrainPool) IsKnown(host string) bool { return false } +func (m *mockGrainPool) Close() {} + +type mockGrain struct{} + +func TestApplyRequestWithMutations(t *testing.T) { + // Setup mock pool + pool := &mockGrainPool{} + + // Create gRPC server + server, err := NewControlServer[*mockGrain](DefaultServerConfig(), pool) + if err != nil { + t.Fatalf("failed to create server: %v", err) + } + defer server.GracefulStop() + + // Create client connection + conn, err := grpc.Dial("localhost:1337", grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + t.Fatalf("failed to dial: %v", err) + } + defer conn.Close() + + client := messages.NewControlPlaneClient(conn) + + // Prepare ApplyRequest with multiple Any messages + addItemAny, _ := anypb.New(&messages.AddItem{ItemId: 1, Quantity: 2}) + removeItemAny, _ := anypb.New(&messages.RemoveItem{Id: 1}) + req := &messages.ApplyRequest{ + Id: 123, + Messages: []*anypb.Any{addItemAny, removeItemAny}, + } + + // Call Apply + resp, err := client.Apply(context.Background(), req) + if err != nil { + t.Fatalf("Apply failed: %v", err) + } + + // Verify response + if !resp.Accepted { + t.Errorf("expected Accepted=true, got false") + } + + // Verify mutations were extracted and applied + if len(pool.applied) != 2 { + t.Errorf("expected 2 mutations applied, got %d", len(pool.applied)) + } + if addItem, ok := pool.applied[0].(*messages.AddItem); !ok || addItem.ItemId != 1 { + t.Errorf("expected AddItem with ItemId=1, got %v", pool.applied[0]) + } + if removeItem, ok := pool.applied[1].(*messages.RemoveItem); !ok || removeItem.Id != 1 { + t.Errorf("expected RemoveItem with Id=1, got %v", pool.applied[1]) + } +} diff --git a/pkg/actor/mutation_registry.go b/pkg/actor/mutation_registry.go index 41f7798..3b92cd2 100644 --- a/pkg/actor/mutation_registry.go +++ b/pkg/actor/mutation_registry.go @@ -7,8 +7,8 @@ import ( "reflect" "sync" - "github.com/gogo/protobuf/proto" "go.opentelemetry.io/otel/attribute" + "google.golang.org/protobuf/proto" ) type ApplyResult struct { @@ -212,9 +212,9 @@ func (r *ProtoMutationRegistry) Apply(ctx context.Context, grain any, msg ...pro } for _, m := range msg { - // Ignore nil mutation elements (untyped or typed nil pointers) silently; they carry no data. + // Error if any mutation element is nil. if m == nil { - continue + return results, fmt.Errorf("nil mutation message") } // Typed nil: interface holds concrete proto message type whose pointer value is nil. rv := reflect.ValueOf(m) @@ -251,6 +251,12 @@ func (r *ProtoMutationRegistry) Apply(ctx context.Context, grain any, msg ...pro } } } + // Return error for unregistered mutations + for _, res := range results { + if res.Error == ErrMutationNotRegistered { + return results, res.Error + } + } return results, nil } diff --git a/pkg/actor/mutation_registry_test.go b/pkg/actor/mutation_registry_test.go index 838baaa..911b86a 100644 --- a/pkg/actor/mutation_registry_test.go +++ b/pkg/actor/mutation_registry_test.go @@ -2,12 +2,11 @@ package actor import ( "context" - "errors" "reflect" "slices" "testing" - "git.tornberg.me/go-cart-actor/pkg/messages" + "git.k6n.net/go-cart-actor/pkg/messages" ) type cartState struct { @@ -90,7 +89,7 @@ func TestRegisteredMutationBasics(t *testing.T) { } // Apply nil grain - if _, err := reg.Apply(nil, add); err == nil { + if _, err := reg.Apply(context.Background(), nil, add); err == nil { t.Fatalf("expected error for nil grain") } @@ -100,7 +99,8 @@ func TestRegisteredMutationBasics(t *testing.T) { } // Apply unregistered message - if _, err := reg.Apply(context.Background(), state, &messages.Noop{}); !errors.Is(err, ErrMutationNotRegistered) { + _, err := reg.Apply(context.Background(), state, &messages.Noop{}) + if err != ErrMutationNotRegistered { t.Fatalf("expected ErrMutationNotRegistered, got %v", err) } } diff --git a/pkg/actor/pubsub.go b/pkg/actor/pubsub.go new file mode 100644 index 0000000..0b13aef --- /dev/null +++ b/pkg/actor/pubsub.go @@ -0,0 +1,78 @@ +package actor + +import ( + "iter" + "slices" + "sync" +) + +type ReceiverFunc[V any] func(event V) + +type PubSub[V any] struct { + subscribers []*ReceiverFunc[V] + mu sync.RWMutex +} + +// NewPubSub creates a new PubSub instance. +func NewPubSub[V any]() *PubSub[V] { + return &PubSub[V]{ + subscribers: make([]*ReceiverFunc[V], 0), + } +} + +// Subscribe adds a grain ID to the subscribers of a topic. +func (p *PubSub[V]) Subscribe(receiver ReceiverFunc[V]) { + if receiver == nil { + return + } + p.mu.Lock() + defer p.mu.Unlock() + // log.Printf("adding subscriber") + p.subscribers = append(p.subscribers, &receiver) +} + +// Unsubscribe removes a grain ID from the subscribers of a topic. +func (p *PubSub[V]) Unsubscribe(receiver ReceiverFunc[V]) { + p.mu.Lock() + defer p.mu.Unlock() + list := p.subscribers + prt := &receiver + for i, sub := range list { + if sub == nil { + continue + } + if sub == prt { + // log.Printf("removing subscriber") + p.subscribers = append(list[:i], list[i+1:]...) + break + } + } + p.subscribers = slices.DeleteFunc(p.subscribers, func(fn *ReceiverFunc[V]) bool { + return fn == nil + }) + // If list is empty, could delete, but not necessary +} + +// GetSubscribers returns a copy of the subscriber IDs for a topic. +func (p *PubSub[V]) GetSubscribers() iter.Seq[ReceiverFunc[V]] { + p.mu.RLock() + defer p.mu.RUnlock() + + return func(yield func(ReceiverFunc[V]) bool) { + for _, sub := range p.subscribers { + if sub == nil { + continue + } + if !yield(*sub) { + return + } + } + } +} + +// Publish sends an event to all subscribers of the topic. +func (p *PubSub[V]) Publish(event V) { + for notify := range p.GetSubscribers() { + notify(event) + } +} diff --git a/pkg/actor/simple_grain_pool.go b/pkg/actor/simple_grain_pool.go index f08dab4..ca1b277 100644 --- a/pkg/actor/simple_grain_pool.go +++ b/pkg/actor/simple_grain_pool.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "github.com/gogo/protobuf/proto" + "google.golang.org/protobuf/proto" ) type SimpleGrainPool[V any] struct { @@ -17,6 +17,7 @@ type SimpleGrainPool[V any] struct { grains map[uint64]Grain[V] mutationRegistry MutationRegistry spawn func(ctx context.Context, id uint64) (Grain[V], error) + destroy func(grain Grain[V]) error spawnHost func(host string) (Host, error) listeners []LogListener storage LogStorage[V] @@ -38,6 +39,7 @@ type GrainPoolConfig[V any] struct { Hostname string Spawn func(ctx context.Context, id uint64) (Grain[V], error) SpawnHost func(host string) (Host, error) + Destroy func(grain Grain[V]) error TTL time.Duration PoolSize int MutationRegistry MutationRegistry @@ -51,6 +53,7 @@ func NewSimpleGrainPool[V any](config GrainPoolConfig[V]) (*SimpleGrainPool[V], storage: config.Storage, spawn: config.Spawn, spawnHost: config.SpawnHost, + destroy: config.Destroy, ttl: config.TTL, poolSize: config.PoolSize, hostname: config.Hostname, @@ -88,6 +91,9 @@ func (p *SimpleGrainPool[V]) purge() { for id, grain := range p.grains { if grain.GetLastAccess().Before(purgeLimit) { purgedIds = append(purgedIds, id) + if err := p.destroy(grain); err != nil { + log.Printf("failed to destroy grain %d: %v", id, err) + } delete(p.grains, id) } @@ -414,6 +420,7 @@ func (p *SimpleGrainPool[V]) Apply(ctx context.Context, id uint64, mutation ...p if err != nil { return nil, err } + return &MutationResult[*V]{ Result: result, Mutations: mutations, diff --git a/pkg/actor/state.go b/pkg/actor/state.go index 14020d9..17483b7 100644 --- a/pkg/actor/state.go +++ b/pkg/actor/state.go @@ -7,7 +7,7 @@ import ( "io" "time" - "github.com/gogo/protobuf/proto" + "google.golang.org/protobuf/proto" ) type StateStorage struct { @@ -34,14 +34,14 @@ func NewState(registry MutationRegistry) *StateStorage { var ErrUnknownType = errors.New("unknown type") -func (s *StateStorage) Load(r io.Reader, onMessage func(msg proto.Message)) error { +func (s *StateStorage) Load(r io.Reader, onMessage func(msg proto.Message, timeStamp time.Time)) error { var err error var evt *StorageEvent scanner := bufio.NewScanner(r) for err == nil { evt, err = s.Read(scanner) if err == nil { - onMessage(evt.Mutation) + onMessage(evt.Mutation, evt.TimeStamp) } } if err == io.EOF { diff --git a/pkg/cart/cart-grain.go b/pkg/cart/cart-grain.go index f780bd4..c0555ee 100644 --- a/pkg/cart/cart-grain.go +++ b/pkg/cart/cart-grain.go @@ -6,8 +6,9 @@ import ( "sync" "time" - messages "git.tornberg.me/go-cart-actor/pkg/messages" - "git.tornberg.me/go-cart-actor/pkg/voucher" + messages "git.k6n.net/go-cart-actor/pkg/messages" + "git.k6n.net/go-cart-actor/pkg/voucher" + "github.com/matst80/go-redis-inventory/pkg/inventory" ) // Legacy padded [16]byte CartId and its helper methods removed. @@ -23,29 +24,35 @@ type ItemMeta struct { Category3 string `json:"category3,omitempty"` Category4 string `json:"category4,omitempty"` Category5 string `json:"category5,omitempty"` - SellerId string `json:"sellerId,omitempty"` SellerName string `json:"sellerName,omitempty"` Image string `json:"image,omitempty"` Outlet *string `json:"outlet,omitempty"` } type CartItem struct { - Id uint32 `json:"id"` - ItemId uint32 `json:"itemId,omitempty"` - ParentId *uint32 `json:"parentId,omitempty"` - Sku string `json:"sku"` - Price Price `json:"price"` - TotalPrice Price `json:"totalPrice"` - OrgPrice *Price `json:"orgPrice,omitempty"` - Tax int - Stock StockStatus `json:"stock"` - Quantity int `json:"qty"` - Discount *Price `json:"discount,omitempty"` - Disclaimer string `json:"disclaimer,omitempty"` - ArticleType string `json:"type,omitempty"` - StoreId *string `json:"storeId,omitempty"` - Meta *ItemMeta `json:"meta,omitempty"` - SaleStatus string `json:"saleStatus"` + Id uint32 `json:"id"` + ItemId uint32 `json:"itemId,omitempty"` + ParentId *uint32 `json:"parentId,omitempty"` + Sku string `json:"sku"` + Price Price `json:"price"` + TotalPrice Price `json:"totalPrice"` + SellerId string `json:"sellerId,omitempty"` + OrgPrice *Price `json:"orgPrice,omitempty"` + Cgm string `json:"cgm,omitempty"` + Tax int + Stock uint16 `json:"stock"` + Quantity uint16 `json:"qty"` + Discount *Price `json:"discount,omitempty"` + Disclaimer string `json:"disclaimer,omitempty"` + ArticleType string `json:"type,omitempty"` + StoreId *string `json:"storeId,omitempty"` + Meta *ItemMeta `json:"meta,omitempty"` + SaleStatus string `json:"saleStatus"` + Marking *Marking `json:"marking,omitempty"` + SubscriptionDetailsId string `json:"subscriptionDetailsId,omitempty"` + OrderReference string `json:"orderReference,omitempty"` + IsSubscribed bool `json:"isSubscribed,omitempty"` + ReservationEndTime *time.Time `json:"reservationEndTime,omitempty"` } type CartDelivery struct { @@ -71,26 +78,61 @@ type SubscriptionDetails struct { Meta json.RawMessage `json:"data,omitempty"` } +type Notice struct { + Timestamp time.Time `json:"timestamp"` + Message string `json:"message"` + Code *string `json:"code,omitempty"` +} + +type Marking struct { + Type uint32 `json:"type"` + Text string `json:"text"` +} + +type GiftcardItem struct { + Id uint32 `json:"id"` + Value Price `json:"value"` + DeliveryDate string `json:"deliveryDate"` + Recipient string `json:"recipient"` + RecipientType string `json:"recipientType"` + Message string `json:"message"` + DesignConfig json.RawMessage `json:"designConfig,omitempty"` +} + type CartGrain struct { - mu sync.RWMutex - lastItemId uint32 - lastDeliveryId uint32 - lastVoucherId uint32 - lastAccess time.Time - lastChange time.Time // unix seconds of last successful mutation (replay sets from event ts) - userId string - Id CartId `json:"id"` - Items []*CartItem `json:"items"` - TotalPrice *Price `json:"totalPrice"` - TotalDiscount *Price `json:"totalDiscount"` - Deliveries []*CartDelivery `json:"deliveries,omitempty"` - Processing bool `json:"processing"` - PaymentInProgress bool `json:"paymentInProgress"` - OrderReference string `json:"orderReference,omitempty"` - PaymentStatus string `json:"paymentStatus,omitempty"` - Vouchers []*Voucher `json:"vouchers,omitempty"` - Notifications []CartNotification `json:"cartNotification,omitempty"` - SubscriptionDetails map[string]*SubscriptionDetails `json:"subscriptionDetails,omitempty"` + mu sync.RWMutex + lastItemId uint32 + lastDeliveryId uint32 + lastVoucherId uint32 + lastGiftcardId uint32 + lastAccess time.Time + lastChange time.Time // unix seconds of last successful mutation (replay sets from event ts) + userId string + InventoryReserved bool `json:"inventoryReserved"` + Id CartId `json:"id"` + Items []*CartItem `json:"items"` + Giftcards []*GiftcardItem `json:"giftcards,omitempty"` + TotalPrice *Price `json:"totalPrice"` + TotalDiscount *Price `json:"totalDiscount"` + Deliveries []*CartDelivery `json:"deliveries,omitempty"` + Processing bool `json:"processing"` + PaymentInProgress bool `json:"paymentInProgress"` + OrderReference string `json:"orderReference,omitempty"` + PaymentStatus string `json:"paymentStatus,omitempty"` + Vouchers []*Voucher `json:"vouchers,omitempty"` + Notifications []CartNotification `json:"cartNotification,omitempty"` + SubscriptionDetails map[string]*SubscriptionDetails `json:"subscriptionDetails,omitempty"` + PaymentDeclinedNotices []Notice `json:"paymentDeclinedNotices,omitempty"` + Confirmation *ConfirmationStatus `json:"confirmation,omitempty"` + CheckoutOrderId string `json:"checkoutOrderId,omitempty"` + CheckoutStatus string `json:"checkoutStatus,omitempty"` + CheckoutCountry string `json:"checkoutCountry,omitempty"` +} + +type ConfirmationStatus struct { + Code *string `json:"code,omitempty"` + ViewCount int `json:"viewCount"` + LastViewedAt time.Time `json:"lastViewedAt"` } type Voucher struct { @@ -155,11 +197,13 @@ func NewCartGrain(id uint64, ts time.Time) *CartGrain { lastItemId: 0, lastDeliveryId: 0, lastVoucherId: 0, + lastGiftcardId: 0, lastAccess: ts, lastChange: ts, TotalDiscount: NewPrice(), Vouchers: []*Voucher{}, Deliveries: []*CartDelivery{}, + Giftcards: []*GiftcardItem{}, Id: CartId(id), Items: []*CartItem{}, TotalPrice: NewPrice(), @@ -184,6 +228,19 @@ func (c *CartGrain) GetCurrentState() (*CartGrain, error) { return c, nil } +func (c *CartGrain) HandleInventoryChange(change inventory.InventoryChange) { + for _, item := range c.Items { + l := "se" + if item.StoreId != nil { + l = *item.StoreId + } + if item.Sku == change.SKU && change.StockLocationID == l { + item.Stock = uint16(change.Value) + break + } + } +} + func (c *CartGrain) GetState() ([]byte, error) { return json.Marshal(c) } @@ -273,6 +330,9 @@ func (c *CartGrain) UpdateTotals() { for _, delivery := range c.Deliveries { c.TotalPrice.Add(delivery.Price) } + for _, giftcard := range c.Giftcards { + c.TotalPrice.Add(giftcard.Value) + } for _, voucher := range c.Vouchers { _, ok := voucher.AppliesTo(c) voucher.Applied = false diff --git a/pkg/cart/cart-mutation-helper.go b/pkg/cart/cart-mutation-helper.go index 70a973d..b1ac5c3 100644 --- a/pkg/cart/cart-mutation-helper.go +++ b/pkg/cart/cart-mutation-helper.go @@ -1,24 +1,76 @@ package cart import ( - "git.tornberg.me/go-cart-actor/pkg/actor" - messages "git.tornberg.me/go-cart-actor/pkg/messages" + "context" + "time" + + "git.k6n.net/go-cart-actor/pkg/actor" + messages "git.k6n.net/go-cart-actor/pkg/messages" + "github.com/matst80/go-redis-inventory/pkg/inventory" ) -func NewCartMultationRegistry() actor.MutationRegistry { +type CartMutationContext struct { + reservationService inventory.CartReservationService +} + +func NewCartMutationContext(reservationService inventory.CartReservationService) *CartMutationContext { + return &CartMutationContext{ + reservationService: reservationService, + } +} + +func (c *CartMutationContext) ReserveItem(ctx context.Context, cartId CartId, sku string, locationId *string, quantity uint16) (*time.Time, error) { + if quantity <= 0 || c.reservationService == nil { + return nil, nil + } + l := inventory.LocationID("se") + if locationId != nil { + l = inventory.LocationID(*locationId) + } + ttl := time.Minute * 15 + endTime := time.Now().Add(ttl) + err := c.reservationService.ReserveForCart(ctx, inventory.CartReserveRequest{ + CartID: inventory.CartID(cartId.String()), + InventoryReference: &inventory.InventoryReference{ + SKU: inventory.SKU(sku), + LocationID: l, + }, + TTL: ttl, + Quantity: uint32(quantity), + }) + if err != nil { + return nil, err + } + + return &endTime, nil + +} + +func (c *CartMutationContext) ReleaseItem(ctx context.Context, cartId CartId, sku string, locationId *string) error { + if c.reservationService == nil { + return nil + } + l := inventory.LocationID("se") + if locationId != nil { + l = inventory.LocationID(*locationId) + } + return c.reservationService.ReleaseForCart(ctx, inventory.SKU(sku), l, inventory.CartID(cartId.String())) +} + +func NewCartMultationRegistry(context *CartMutationContext) actor.MutationRegistry { reg := actor.NewMutationRegistry() reg.RegisterMutations( - actor.NewMutation(AddItem, func() *messages.AddItem { + actor.NewMutation(context.AddItem, func() *messages.AddItem { return &messages.AddItem{} }), - actor.NewMutation(ChangeQuantity, func() *messages.ChangeQuantity { + actor.NewMutation(context.ChangeQuantity, func() *messages.ChangeQuantity { return &messages.ChangeQuantity{} }), - actor.NewMutation(RemoveItem, func() *messages.RemoveItem { + actor.NewMutation(context.RemoveItem, func() *messages.RemoveItem { return &messages.RemoveItem{} }), - actor.NewMutation(InitializeCheckout, func() *messages.InitializeCheckout { + actor.NewMutation(context.InitializeCheckout, func() *messages.InitializeCheckout { return &messages.InitializeCheckout{} }), actor.NewMutation(OrderCreated, func() *messages.OrderCreated { @@ -45,9 +97,39 @@ func NewCartMultationRegistry() actor.MutationRegistry { actor.NewMutation(UpsertSubscriptionDetails, func() *messages.UpsertSubscriptionDetails { return &messages.UpsertSubscriptionDetails{} }), + actor.NewMutation(context.InventoryReserved, func() *messages.InventoryReserved { + return &messages.InventoryReserved{} + }), actor.NewMutation(PreConditionFailed, func() *messages.PreConditionFailed { return &messages.PreConditionFailed{} }), + actor.NewMutation(SetUserId, func() *messages.SetUserId { + return &messages.SetUserId{} + }), + actor.NewMutation(LineItemMarking, func() *messages.LineItemMarking { + return &messages.LineItemMarking{} + }), + actor.NewMutation(RemoveLineItemMarking, func() *messages.RemoveLineItemMarking { + return &messages.RemoveLineItemMarking{} + }), + actor.NewMutation(SubscriptionAdded, func() *messages.SubscriptionAdded { + return &messages.SubscriptionAdded{} + }), + actor.NewMutation(PaymentDeclined, func() *messages.PaymentDeclined { + return &messages.PaymentDeclined{} + }), + actor.NewMutation(ConfirmationViewed, func() *messages.ConfirmationViewed { + return &messages.ConfirmationViewed{} + }), + actor.NewMutation(CreateCheckoutOrder, func() *messages.CreateCheckoutOrder { + return &messages.CreateCheckoutOrder{} + }), + actor.NewMutation(AddGiftcard, func() *messages.AddGiftcard { + return &messages.AddGiftcard{} + }), + actor.NewMutation(RemoveGiftcard, func() *messages.RemoveGiftcard { + return &messages.RemoveGiftcard{} + }), ) return reg diff --git a/pkg/cart/mutation_add_giftcard.go b/pkg/cart/mutation_add_giftcard.go new file mode 100644 index 0000000..3ce1148 --- /dev/null +++ b/pkg/cart/mutation_add_giftcard.go @@ -0,0 +1,41 @@ +package cart + +import ( + "encoding/json" + "fmt" + + messages "git.k6n.net/go-cart-actor/pkg/messages" + "google.golang.org/protobuf/proto" +) + +func AddGiftcard(grain *CartGrain, req *messages.AddGiftcard) error { + if req.Giftcard == nil { + return fmt.Errorf("giftcard cannot be nil") + } + if req.Giftcard.Value <= 0 { + return fmt.Errorf("giftcard value must be positive") + } + grain.lastGiftcardId++ + designConfig := json.RawMessage{} + if req.Giftcard.DesignConfig != nil { + // Convert Any to RawMessage + data, err := proto.Marshal(req.Giftcard.DesignConfig) + if err != nil { + return fmt.Errorf("failed to marshal designConfig: %w", err) + } + designConfig = data + } + value := NewPriceFromIncVat(req.Giftcard.Value, 25) // Assuming 25% tax; adjust as needed + item := &GiftcardItem{ + Id: grain.lastGiftcardId, + Value: *value, + DeliveryDate: req.Giftcard.DeliveryDate, + Recipient: req.Giftcard.Recipient, + RecipientType: req.Giftcard.RecipientType, + Message: req.Giftcard.Message, + DesignConfig: designConfig, + } + grain.Giftcards = append(grain.Giftcards, item) + grain.UpdateTotals() + return nil +} diff --git a/pkg/cart/mutation_add_item.go b/pkg/cart/mutation_add_item.go index dd8cfc6..2e6501e 100644 --- a/pkg/cart/mutation_add_item.go +++ b/pkg/cart/mutation_add_item.go @@ -1,9 +1,13 @@ package cart import ( + "context" "fmt" + "log" + "time" - messages "git.tornberg.me/go-cart-actor/pkg/messages" + messages "git.k6n.net/go-cart-actor/pkg/messages" + "google.golang.org/protobuf/types/known/timestamppb" ) // mutation_add_item.go @@ -20,7 +24,8 @@ import ( // NOTE: Any future field additions in messages.AddItem that affect pricing / tax // must keep this handler in sync. -func AddItem(g *CartGrain, m *messages.AddItem) error { +func (c *CartMutationContext) AddItem(g *CartGrain, m *messages.AddItem) error { + ctx := context.Background() if m == nil { return fmt.Errorf("AddItem: nil payload") } @@ -38,12 +43,21 @@ func AddItem(g *CartGrain, m *messages.AddItem) error { if !sameStore { continue } - existing.Quantity += int(m.Quantity) - existing.Stock = StockStatus(m.Stock) + if err := c.ReleaseItem(ctx, g.Id, existing.Sku, existing.StoreId); err != nil { + log.Printf("failed to release item %d: %v", existing.Id, err) + } + endTime, err := c.ReserveItem(ctx, g.Id, existing.Sku, existing.StoreId, existing.Quantity+uint16(m.Quantity)) + if err != nil { + return err + } + existing.ReservationEndTime = endTime + existing.Quantity += uint16(m.Quantity) + existing.Stock = uint16(m.Stock) // If existing had nil store but new has one, adopt it. if existing.StoreId == nil && m.StoreId != nil { existing.StoreId = m.StoreId } + return nil } @@ -58,10 +72,25 @@ func AddItem(g *CartGrain, m *messages.AddItem) error { pricePerItem := NewPriceFromIncVat(m.Price, taxRate) - g.Items = append(g.Items, &CartItem{ + needsReservation := true + if m.ReservationEndTime != nil { + needsReservation = m.ReservationEndTime.AsTime().Before(time.Now()) + } + + if needsReservation { + endTime, err := c.ReserveItem(ctx, g.Id, m.Sku, m.StoreId, uint16(m.Quantity)) + if err != nil { + return err + } + if endTime != nil { + m.ReservationEndTime = timestamppb.New(*endTime) + } + } + + cartItem := &CartItem{ Id: g.lastItemId, ItemId: uint32(m.ItemId), - Quantity: int(m.Quantity), + Quantity: uint16(m.Quantity), Sku: m.Sku, Tax: int(taxRate * 100), Meta: &ItemMeta{ @@ -74,23 +103,29 @@ func AddItem(g *CartGrain, m *messages.AddItem) error { Category4: m.Category4, Category5: m.Category5, Outlet: m.Outlet, - SellerId: m.SellerId, SellerName: m.SellerName, }, + SellerId: m.SellerId, + Cgm: m.Cgm, SaleStatus: m.SaleStatus, ParentId: m.ParentId, Price: *pricePerItem, TotalPrice: *MultiplyPrice(*pricePerItem, int64(m.Quantity)), - Stock: StockStatus(m.Stock), + Stock: uint16(m.Stock), Disclaimer: m.Disclaimer, OrgPrice: getOrgPrice(m.OrgPrice, taxRate), ArticleType: m.ArticleType, StoreId: m.StoreId, - }) + } + if m.ReservationEndTime != nil { + t := m.ReservationEndTime.AsTime() + cartItem.ReservationEndTime = &t + } + g.Items = append(g.Items, cartItem) g.UpdateTotals() return nil } diff --git a/pkg/cart/mutation_add_voucher.go b/pkg/cart/mutation_add_voucher.go index 88743cc..2b87ace 100644 --- a/pkg/cart/mutation_add_voucher.go +++ b/pkg/cart/mutation_add_voucher.go @@ -3,8 +3,8 @@ package cart import ( "slices" - "git.tornberg.me/go-cart-actor/pkg/actor" - "git.tornberg.me/go-cart-actor/pkg/messages" + "git.k6n.net/go-cart-actor/pkg/actor" + "git.k6n.net/go-cart-actor/pkg/messages" ) func RemoveVoucher(g *CartGrain, m *messages.RemoveVoucher) error { diff --git a/pkg/cart/mutation_change_quantity.go b/pkg/cart/mutation_change_quantity.go index ecc7579..05f6b95 100644 --- a/pkg/cart/mutation_change_quantity.go +++ b/pkg/cart/mutation_change_quantity.go @@ -1,9 +1,11 @@ package cart import ( + "context" "fmt" + "log" - messages "git.tornberg.me/go-cart-actor/pkg/messages" + messages "git.k6n.net/go-cart-actor/pkg/messages" ) // mutation_change_quantity.go @@ -26,10 +28,11 @@ import ( // (If strict locking is required around every mutation, wrap logic in // an explicit g.mu.Lock()/Unlock(), but current model mirrors prior code.) -func ChangeQuantity(g *CartGrain, m *messages.ChangeQuantity) error { +func (c *CartMutationContext) ChangeQuantity(g *CartGrain, m *messages.ChangeQuantity) error { if m == nil { return fmt.Errorf("ChangeQuantity: nil payload") } + ctx := context.Background() foundIndex := -1 for i, it := range g.Items { @@ -44,12 +47,33 @@ func ChangeQuantity(g *CartGrain, m *messages.ChangeQuantity) error { if m.Quantity <= 0 { // Remove the item + itemToRemove := g.Items[foundIndex] + err := c.ReleaseItem(ctx, g.Id, itemToRemove.Sku, itemToRemove.StoreId) + if err != nil { + log.Printf("unable to release reservation for %s in location: %v", itemToRemove.Sku, itemToRemove.StoreId) + } g.Items = append(g.Items[:foundIndex], g.Items[foundIndex+1:]...) g.UpdateTotals() return nil } + item := g.Items[foundIndex] + if item == nil { + return fmt.Errorf("ChangeQuantity: item id %d not found", m.Id) + } + if item.ReservationEndTime != nil { + err := c.ReleaseItem(ctx, g.Id, item.Sku, item.StoreId) + if err != nil { + log.Printf("unable to release reservation for %s in location: %v", item.Sku, item.StoreId) + } - g.Items[foundIndex].Quantity = int(m.Quantity) + } + endTime, err := c.ReserveItem(ctx, g.Id, item.Sku, item.StoreId, uint16(m.Quantity)) + if err != nil { + return err + } + item.ReservationEndTime = endTime + item.Quantity = uint16(m.Quantity) g.UpdateTotals() + return nil } diff --git a/pkg/cart/mutation_confirmation_viewed.go b/pkg/cart/mutation_confirmation_viewed.go new file mode 100644 index 0000000..a6dc2a4 --- /dev/null +++ b/pkg/cart/mutation_confirmation_viewed.go @@ -0,0 +1,21 @@ +package cart + +import ( + "time" + + messages "git.k6n.net/go-cart-actor/pkg/messages" +) + +func ConfirmationViewed(grain *CartGrain, req *messages.ConfirmationViewed) error { + if grain.Confirmation == nil { + grain.Confirmation = &ConfirmationStatus{ + ViewCount: 1, + LastViewedAt: time.Now(), + } + } else { + grain.Confirmation.ViewCount++ + grain.Confirmation.LastViewedAt = time.Now() + } + + return nil +} diff --git a/pkg/cart/mutation_create_checkout_order.go b/pkg/cart/mutation_create_checkout_order.go new file mode 100644 index 0000000..569d4b8 --- /dev/null +++ b/pkg/cart/mutation_create_checkout_order.go @@ -0,0 +1,22 @@ +package cart + +import ( + "errors" + + messages "git.k6n.net/go-cart-actor/pkg/messages" + "github.com/google/uuid" +) + +func CreateCheckoutOrder(grain *CartGrain, req *messages.CreateCheckoutOrder) error { + if len(grain.Items) == 0 { + return errors.New("cannot checkout empty cart") + } + if req.Terms != "accepted" { + return errors.New("terms must be accepted") + } + // Validate other fields as needed + grain.CheckoutOrderId = uuid.New().String() + grain.CheckoutStatus = "pending" + grain.CheckoutCountry = req.Country + return nil +} diff --git a/pkg/cart/mutation_initialize_checkout.go b/pkg/cart/mutation_initialize_checkout.go index aa1a43c..e3d56dc 100644 --- a/pkg/cart/mutation_initialize_checkout.go +++ b/pkg/cart/mutation_initialize_checkout.go @@ -1,9 +1,11 @@ package cart import ( + "context" "fmt" + "time" - messages "git.tornberg.me/go-cart-actor/pkg/messages" + messages "git.k6n.net/go-cart-actor/pkg/messages" ) // mutation_initialize_checkout.go @@ -29,13 +31,26 @@ import ( // (e.g. reject if PaymentInProgress already true unless reusing // the same OrderReference). -func InitializeCheckout(g *CartGrain, m *messages.InitializeCheckout) error { +func (c *CartMutationContext) InitializeCheckout(g *CartGrain, m *messages.InitializeCheckout) error { if m == nil { return fmt.Errorf("InitializeCheckout: nil payload") } if m.OrderId == "" { return fmt.Errorf("InitializeCheckout: missing orderId") } + ctx := context.Background() + now := time.Now() + for _, item := range g.Items { + if item.ReservationEndTime != nil { + if now.After(*item.ReservationEndTime) { + endTime, err := c.ReserveItem(ctx, g.Id, item.Sku, item.StoreId, item.Quantity) + if err != nil { + return err + } + item.ReservationEndTime = endTime + } + } + } g.OrderReference = m.OrderId g.PaymentStatus = m.Status diff --git a/pkg/cart/mutation_inventory_reserved.go b/pkg/cart/mutation_inventory_reserved.go new file mode 100644 index 0000000..bd9fff6 --- /dev/null +++ b/pkg/cart/mutation_inventory_reserved.go @@ -0,0 +1,22 @@ +package cart + +import ( + "context" + "log" + "time" + + "git.k6n.net/go-cart-actor/pkg/messages" +) + +func (c *CartMutationContext) InventoryReserved(g *CartGrain, m *messages.InventoryReserved) error { + for _, item := range g.Items { + if item.ReservationEndTime != nil && item.ReservationEndTime.After(time.Now()) { + err := c.ReleaseItem(context.Background(), g.Id, item.Sku, item.StoreId) + if err != nil { + log.Printf("unable to release item reservation") + } + } + } + g.InventoryReserved = true + return nil +} diff --git a/pkg/cart/mutation_line_item_marking.go b/pkg/cart/mutation_line_item_marking.go new file mode 100644 index 0000000..841b395 --- /dev/null +++ b/pkg/cart/mutation_line_item_marking.go @@ -0,0 +1,20 @@ +package cart + +import ( + "fmt" + + messages "git.k6n.net/go-cart-actor/pkg/messages" +) + +func LineItemMarking(grain *CartGrain, req *messages.LineItemMarking) error { + for i, item := range grain.Items { + if item.Id == req.Id { + grain.Items[i].Marking = &Marking{ + Type: req.Type, + Text: req.Marking, + } + return nil + } + } + return fmt.Errorf("item with ID %d not found", req.Id) +} diff --git a/pkg/cart/mutation_order_created.go b/pkg/cart/mutation_order_created.go index 00d6914..357dcdd 100644 --- a/pkg/cart/mutation_order_created.go +++ b/pkg/cart/mutation_order_created.go @@ -3,7 +3,7 @@ package cart import ( "fmt" - messages "git.tornberg.me/go-cart-actor/pkg/messages" + messages "git.k6n.net/go-cart-actor/pkg/messages" ) // mutation_order_created.go diff --git a/pkg/cart/mutation_payment_declined.go b/pkg/cart/mutation_payment_declined.go new file mode 100644 index 0000000..24d24e5 --- /dev/null +++ b/pkg/cart/mutation_payment_declined.go @@ -0,0 +1,21 @@ +package cart + +import ( + "time" + + messages "git.k6n.net/go-cart-actor/pkg/messages" +) + +func PaymentDeclined(grain *CartGrain, req *messages.PaymentDeclined) error { + grain.PaymentStatus = "declined" + grain.PaymentDeclinedNotices = append(grain.PaymentDeclinedNotices, Notice{ + Timestamp: time.Now(), + Message: req.Message, + Code: req.Code, + }) + // Optionally clear checkout order if in progress + if grain.CheckoutOrderId != "" { + grain.CheckoutOrderId = "" + } + return nil +} diff --git a/pkg/cart/mutation_precondition.go b/pkg/cart/mutation_precondition.go index 8fa6d98..b008c69 100644 --- a/pkg/cart/mutation_precondition.go +++ b/pkg/cart/mutation_precondition.go @@ -1,6 +1,6 @@ package cart -import messages "git.tornberg.me/go-cart-actor/pkg/messages" +import messages "git.k6n.net/go-cart-actor/pkg/messages" func PreConditionFailed(g *CartGrain, m *messages.PreConditionFailed) error { return nil diff --git a/pkg/cart/mutation_remove_delivery.go b/pkg/cart/mutation_remove_delivery.go index dc38824..1d78473 100644 --- a/pkg/cart/mutation_remove_delivery.go +++ b/pkg/cart/mutation_remove_delivery.go @@ -3,7 +3,7 @@ package cart import ( "fmt" - messages "git.tornberg.me/go-cart-actor/pkg/messages" + messages "git.k6n.net/go-cart-actor/pkg/messages" ) // mutation_remove_delivery.go diff --git a/pkg/cart/mutation_remove_giftcard.go b/pkg/cart/mutation_remove_giftcard.go new file mode 100644 index 0000000..2e1028d --- /dev/null +++ b/pkg/cart/mutation_remove_giftcard.go @@ -0,0 +1,18 @@ +package cart + +import ( + "fmt" + + messages "git.k6n.net/go-cart-actor/pkg/messages" +) + +func RemoveGiftcard(grain *CartGrain, req *messages.RemoveGiftcard) error { + for i, item := range grain.Giftcards { + if item.Id == req.Id { + grain.Giftcards = append(grain.Giftcards[:i], grain.Giftcards[i+1:]...) + grain.UpdateTotals() + return nil + } + } + return fmt.Errorf("giftcard with ID %d not found", req.Id) +} diff --git a/pkg/cart/mutation_remove_item.go b/pkg/cart/mutation_remove_item.go index e12a647..d8baebe 100644 --- a/pkg/cart/mutation_remove_item.go +++ b/pkg/cart/mutation_remove_item.go @@ -1,9 +1,12 @@ package cart import ( + "context" "fmt" + "log" + "time" - messages "git.tornberg.me/go-cart-actor/pkg/messages" + messages "git.k6n.net/go-cart-actor/pkg/messages" ) // mutation_remove_item.go @@ -22,7 +25,7 @@ import ( // - If multiple lines somehow shared the same Id (should not happen), only // the first match would be removed—data integrity relies on unique line Ids. -func RemoveItem(g *CartGrain, m *messages.RemoveItem) error { +func (c *CartMutationContext) RemoveItem(g *CartGrain, m *messages.RemoveItem) error { if m == nil { return fmt.Errorf("RemoveItem: nil payload") } @@ -39,6 +42,14 @@ func RemoveItem(g *CartGrain, m *messages.RemoveItem) error { return fmt.Errorf("RemoveItem: item id %d not found", m.Id) } + item := g.Items[index] + if item.ReservationEndTime != nil && item.ReservationEndTime.After(time.Now()) { + err := c.ReleaseItem(context.Background(), g.Id, item.Sku, item.StoreId) + if err != nil { + log.Printf("unable to release item reservation") + } + } + g.Items = append(g.Items[:index], g.Items[index+1:]...) g.UpdateTotals() return nil diff --git a/pkg/cart/mutation_remove_line_item_marking.go b/pkg/cart/mutation_remove_line_item_marking.go new file mode 100644 index 0000000..0cacc0d --- /dev/null +++ b/pkg/cart/mutation_remove_line_item_marking.go @@ -0,0 +1,17 @@ +package cart + +import ( + "fmt" + + messages "git.k6n.net/go-cart-actor/pkg/messages" +) + +func RemoveLineItemMarking(grain *CartGrain, req *messages.RemoveLineItemMarking) error { + for i, item := range grain.Items { + if item.Id == req.Id { + grain.Items[i].Marking = nil + return nil + } + } + return fmt.Errorf("item with ID %d not found", req.Id) +} diff --git a/pkg/cart/mutation_set_delivery.go b/pkg/cart/mutation_set_delivery.go index a853958..1650a77 100644 --- a/pkg/cart/mutation_set_delivery.go +++ b/pkg/cart/mutation_set_delivery.go @@ -4,7 +4,7 @@ import ( "fmt" "slices" - messages "git.tornberg.me/go-cart-actor/pkg/messages" + messages "git.k6n.net/go-cart-actor/pkg/messages" ) // mutation_set_delivery.go diff --git a/pkg/cart/mutation_set_pickup_point.go b/pkg/cart/mutation_set_pickup_point.go index 057769b..f5288ad 100644 --- a/pkg/cart/mutation_set_pickup_point.go +++ b/pkg/cart/mutation_set_pickup_point.go @@ -3,7 +3,7 @@ package cart import ( "fmt" - messages "git.tornberg.me/go-cart-actor/pkg/messages" + messages "git.k6n.net/go-cart-actor/pkg/messages" ) // mutation_set_pickup_point.go diff --git a/pkg/cart/mutation_set_user_id.go b/pkg/cart/mutation_set_user_id.go new file mode 100644 index 0000000..222e633 --- /dev/null +++ b/pkg/cart/mutation_set_user_id.go @@ -0,0 +1,15 @@ +package cart + +import ( + "errors" + + messages "git.k6n.net/go-cart-actor/pkg/messages" +) + +func SetUserId(grain *CartGrain, req *messages.SetUserId) error { + if req.UserId == "" { + return errors.New("user ID cannot be empty") + } + grain.userId = req.UserId + return nil +} diff --git a/pkg/cart/mutation_subscription_added.go b/pkg/cart/mutation_subscription_added.go new file mode 100644 index 0000000..8bc70e6 --- /dev/null +++ b/pkg/cart/mutation_subscription_added.go @@ -0,0 +1,19 @@ +package cart + +import ( + "fmt" + + messages "git.k6n.net/go-cart-actor/pkg/messages" +) + +func SubscriptionAdded(grain *CartGrain, req *messages.SubscriptionAdded) error { + for i, item := range grain.Items { + if item.Id == req.ItemId { + grain.Items[i].SubscriptionDetailsId = req.DetailsId + grain.Items[i].OrderReference = req.OrderReference + grain.Items[i].IsSubscribed = true + return nil + } + } + return fmt.Errorf("item with ID %d not found", req.ItemId) +} diff --git a/pkg/cart/mutation_test.go b/pkg/cart/mutation_test.go index 302045c..5f129a2 100644 --- a/pkg/cart/mutation_test.go +++ b/pkg/cart/mutation_test.go @@ -10,10 +10,12 @@ import ( "testing" "time" - "github.com/gogo/protobuf/proto" + "github.com/matst80/go-redis-inventory/pkg/inventory" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" - "git.tornberg.me/go-cart-actor/pkg/actor" - messages "git.tornberg.me/go-cart-actor/pkg/messages" + "git.k6n.net/go-cart-actor/pkg/actor" + messages "git.k6n.net/go-cart-actor/pkg/messages" ) // ---------------------- @@ -24,8 +26,38 @@ func newTestGrain() *CartGrain { return NewCartGrain(123, time.Now()) } +type MockReservationService struct { +} + +func (m *MockReservationService) ReserveForCart(ctx context.Context, req inventory.CartReserveRequest) error { + return nil +} + +func (m *MockReservationService) ReleaseForCart(ctx context.Context, sku inventory.SKU, locationID inventory.LocationID, cartID inventory.CartID) error { + return nil +} + +func (m *MockReservationService) GetAvailableInventory(ctx context.Context, sku inventory.SKU, locationID inventory.LocationID) (int64, error) { + return 1000, nil +} + +func (m *MockReservationService) GetReservationExpiry(ctx context.Context, sku inventory.SKU, locationID inventory.LocationID, cartID inventory.CartID) (time.Time, error) { + return time.Time{}, nil +} + +func (m *MockReservationService) GetReservationStatus(ctx context.Context, sku inventory.SKU, locationID inventory.LocationID, cartID inventory.CartID) (*inventory.ReservationStatus, error) { + return nil, nil +} + +func (m *MockReservationService) GetReservationSummary(ctx context.Context, sku inventory.SKU, locationID inventory.LocationID) (*inventory.ReservationSummary, error) { + return nil, nil +} + func newRegistry() actor.MutationRegistry { - return NewCartMultationRegistry() + cartCtx := &CartMutationContext{ + reservationService: &MockReservationService{}, + } + return NewCartMultationRegistry(cartCtx) } func msgAddItem(sku string, price int64, qty int32, storePtr *string) *messages.AddItem { @@ -84,6 +116,51 @@ func msgOrderCreated(orderId, status string) *messages.OrderCreated { return &messages.OrderCreated{OrderId: orderId, Status: status} } +func msgSetUserId(userId string) *messages.SetUserId { + return &messages.SetUserId{UserId: userId} +} + +func msgLineItemMarking(id uint32, typ uint32, marking string) *messages.LineItemMarking { + return &messages.LineItemMarking{Id: id, Type: typ, Marking: marking} +} + +func msgRemoveLineItemMarking(id uint32) *messages.RemoveLineItemMarking { + return &messages.RemoveLineItemMarking{Id: id} +} + +func msgSubscriptionAdded(itemId uint32, detailsId, orderRef string) *messages.SubscriptionAdded { + return &messages.SubscriptionAdded{ItemId: itemId, DetailsId: detailsId, OrderReference: orderRef} +} + +func msgPaymentDeclined(message, code string) *messages.PaymentDeclined { + return &messages.PaymentDeclined{Message: message, Code: &code} +} + +func msgConfirmationViewed() *messages.ConfirmationViewed { + return &messages.ConfirmationViewed{} +} + +func msgCreateCheckoutOrder(terms, country string) *messages.CreateCheckoutOrder { + return &messages.CreateCheckoutOrder{Terms: terms, Country: country} +} + +func msgAddGiftcard(value int64, deliveryDate, recipient, recipientType, message string, designConfig *anypb.Any) *messages.AddGiftcard { + return &messages.AddGiftcard{ + Giftcard: &messages.GiftcardItem{ + Value: value, + DeliveryDate: deliveryDate, + Recipient: recipient, + RecipientType: recipientType, + Message: message, + DesignConfig: designConfig, + }, + } +} + +func msgRemoveGiftcard(id uint32) *messages.RemoveGiftcard { + return &messages.RemoveGiftcard{Id: id} +} + func ptr[T any](v T) *T { return &v } // ---------------------- @@ -143,6 +220,17 @@ func TestMutationRegistryCoverage(t *testing.T) { "AddVoucher", "RemoveVoucher", "UpsertSubscriptionDetails", + "InventoryReserved", + "PreConditionFailed", + "SetUserId", + "LineItemMarking", + "RemoveLineItemMarking", + "SubscriptionAdded", + "PaymentDeclined", + "ConfirmationViewed", + "CreateCheckoutOrder", + "AddGiftcard", + "RemoveGiftcard", } names := reg.(*actor.ProtoMutationRegistry).RegisteredMutations() @@ -179,10 +267,10 @@ func TestMutationRegistryCoverage(t *testing.T) { t.Fatalf("GetTypeName failed for AddItem, got (%q,%v)", nm, ok) } - // Apply unregistered message -> result should contain ErrMutationNotRegistered, no top-level error + // Apply unregistered message -> should return error results, err := reg.Apply(context.Background(), newTestGrain(), &messages.Noop{}) - if err != nil { - t.Fatalf("unexpected top-level error applying unregistered mutation: %v", err) + if err == nil { + t.Fatalf("expected error for unregistered mutation") } if len(results) != 1 || results[0].Error == nil || results[0].Error != actor.ErrMutationNotRegistered { t.Fatalf("expected ApplyResult with ErrMutationNotRegistered, got %#v", results) @@ -458,17 +546,34 @@ func TestRegistryDefensiveErrors(t *testing.T) { t.Fatalf("expected no results when message slice nil") } } + +type SubscriptionDetailsRequest struct { + Id *string `json:"id,omitempty"` + OfferingCode string `json:"offeringCode,omitempty"` + SigningType string `json:"signingType,omitempty"` + Data json.RawMessage `json:"data,omitempty"` +} + +func (sd *SubscriptionDetailsRequest) ToMessage() *messages.UpsertSubscriptionDetails { + return &messages.UpsertSubscriptionDetails{ + Id: sd.Id, + OfferingCode: sd.OfferingCode, + SigningType: sd.SigningType, + Data: &anypb.Any{Value: sd.Data}, + } +} + func TestSubscriptionDetailsJSONValidation(t *testing.T) { reg := newRegistry() g := newTestGrain() // Valid JSON on create - jsonStr := `{"offeringCode": "OFFJSON", "signingType": "TYPEJSON", "data": {"value": "eyJvayI6dHJ1ZX0="}}` - var validCreate messages.UpsertSubscriptionDetails + jsonStr := `{"offeringCode": "OFFJSON", "signingType": "TYPEJSON", "data": {"value":"test","a":1}}` + var validCreate SubscriptionDetailsRequest if err := json.Unmarshal([]byte(jsonStr), &validCreate); err != nil { t.Fatal(err) } - applyOK(t, reg, g, &validCreate) + applyOK(t, reg, g, validCreate.ToMessage()) if len(g.SubscriptionDetails) != 1 { t.Fatalf("expected one subscription detail after valid create, got %d", len(g.SubscriptionDetails)) } @@ -476,7 +581,7 @@ func TestSubscriptionDetailsJSONValidation(t *testing.T) { for k := range g.SubscriptionDetails { id = k } - if string(g.SubscriptionDetails[id].Meta) != `{"ok":true}` { + if string(g.SubscriptionDetails[id].Meta) != `{"value":"test","a":1}` { t.Fatalf("expected meta stored as valid json, got %s", string(g.SubscriptionDetails[id].Meta)) } @@ -514,7 +619,7 @@ func TestSubscriptionDetailsJSONValidation(t *testing.T) { } // Empty Data should not overwrite existing meta - jsonStr5 := fmt.Sprintf(`{"id": "%s", "data": {"value": ""}}`, id) + jsonStr5 := fmt.Sprintf(`{"id": "%s"}`, id) var emptyUpdate messages.UpsertSubscriptionDetails if err := json.Unmarshal([]byte(jsonStr5), &emptyUpdate); err != nil { t.Fatal(err) @@ -524,3 +629,181 @@ func TestSubscriptionDetailsJSONValidation(t *testing.T) { t.Fatalf("empty update should not change meta, got %s", string(g.SubscriptionDetails[id].Meta)) } } + +func TestSetUserId(t *testing.T) { + reg := newRegistry() + g := newTestGrain() + + applyOK(t, reg, g, msgSetUserId("user123")) + if g.userId != "user123" { + t.Fatalf("expected userId=user123, got %s", g.userId) + } + + applyErrorContains(t, reg, g, msgSetUserId(""), "cannot be empty") +} + +func TestLineItemMarking(t *testing.T) { + reg := newRegistry() + g := newTestGrain() + + applyOK(t, reg, g, msgAddItem("MARK", 1000, 1, nil)) + id := g.Items[0].Id + + applyOK(t, reg, g, msgLineItemMarking(id, 1, "Gift message")) + if g.Items[0].Marking == nil || g.Items[0].Marking.Type != 1 || g.Items[0].Marking.Text != "Gift message" { + t.Fatalf("marking not set correctly: %+v", g.Items[0].Marking) + } + + applyErrorContains(t, reg, g, msgLineItemMarking(9999, 2, "Test"), "not found") +} + +func TestRemoveLineItemMarking(t *testing.T) { + reg := newRegistry() + g := newTestGrain() + + applyOK(t, reg, g, msgAddItem("REMOVE", 1000, 1, nil)) + id := g.Items[0].Id + + // First set a marking + applyOK(t, reg, g, msgLineItemMarking(id, 1, "Test marking")) + if g.Items[0].Marking == nil || g.Items[0].Marking.Text != "Test marking" { + t.Fatalf("marking not set") + } + + // Now remove it + applyOK(t, reg, g, msgRemoveLineItemMarking(id)) + if g.Items[0].Marking != nil { + t.Fatalf("marking not removed") + } + + applyErrorContains(t, reg, g, msgRemoveLineItemMarking(9999), "not found") +} + +func TestSubscriptionAdded(t *testing.T) { + reg := newRegistry() + g := newTestGrain() + + applyOK(t, reg, g, msgAddItem("SUB", 1000, 1, nil)) + id := g.Items[0].Id + + applyOK(t, reg, g, msgSubscriptionAdded(id, "det123", "ord456")) + if g.Items[0].SubscriptionDetailsId != "det123" || g.Items[0].OrderReference != "ord456" || !g.Items[0].IsSubscribed { + t.Fatalf("subscription not added: detailsId=%s orderRef=%s isSubscribed=%v", + g.Items[0].SubscriptionDetailsId, g.Items[0].OrderReference, g.Items[0].IsSubscribed) + } + + applyErrorContains(t, reg, g, msgSubscriptionAdded(9999, "", ""), "not found") +} + +func TestPaymentDeclined(t *testing.T) { + reg := newRegistry() + g := newTestGrain() + + g.CheckoutOrderId = "test-order" + applyOK(t, reg, g, msgPaymentDeclined("Payment failed due to insufficient funds", "INSUFFICIENT_FUNDS")) + if g.PaymentStatus != "declined" || g.CheckoutOrderId != "" { + t.Fatalf("payment declined not handled: status=%s checkoutId=%s", g.PaymentStatus, g.CheckoutOrderId) + } + if len(g.PaymentDeclinedNotices) != 1 { + t.Fatalf("expected 1 notice, got %d", len(g.PaymentDeclinedNotices)) + } + notice := g.PaymentDeclinedNotices[0] + if notice.Message != "Payment failed due to insufficient funds" { + t.Fatalf("notice message not set correctly: %s", notice.Message) + } + if notice.Code == nil || *notice.Code != "INSUFFICIENT_FUNDS" { + t.Fatalf("notice code not set correctly: %v", notice.Code) + } + if notice.Timestamp.IsZero() { + t.Fatalf("notice timestamp not set") + } +} + +func TestConfirmationViewed(t *testing.T) { + reg := newRegistry() + g := newTestGrain() + + // Initial state + if g.Confirmation != nil { + t.Fatalf("confirmation should be nil, got %v", g.Confirmation) + } + + // First view + applyOK(t, reg, g, msgConfirmationViewed()) + if g.Confirmation.ViewCount != 1 { + t.Fatalf("view count should be 1, got %d", g.Confirmation.ViewCount) + } + if g.Confirmation.LastViewedAt.IsZero() { + t.Fatalf("ConfirmationLastViewedAt not set") + } + firstTime := g.Confirmation.LastViewedAt + + // Second view + applyOK(t, reg, g, msgConfirmationViewed()) + if g.Confirmation.ViewCount != 2 { + t.Fatalf("view count should be 2, got %d", g.Confirmation.ViewCount) + } + if g.Confirmation.LastViewedAt == firstTime { + t.Fatalf("ConfirmationLastViewedAt should have updated") + } +} + +func TestCreateCheckoutOrder(t *testing.T) { + reg := newRegistry() + g := newTestGrain() + + applyOK(t, reg, g, msgAddItem("CHECKOUT", 1000, 1, nil)) + + applyOK(t, reg, g, msgCreateCheckoutOrder("accepted", "SE")) + if g.CheckoutOrderId == "" || g.CheckoutStatus != "pending" || g.CheckoutCountry != "SE" { + t.Fatalf("checkout order not created: id=%s status=%s country=%s", + g.CheckoutOrderId, g.CheckoutStatus, g.CheckoutCountry) + } + + // Empty cart + g2 := newTestGrain() + applyErrorContains(t, reg, g2, msgCreateCheckoutOrder("accepted", ""), "empty cart") + + // Terms not accepted + applyErrorContains(t, reg, g, msgCreateCheckoutOrder("no", ""), "terms must be accepted") +} + +func TestAddGiftcard(t *testing.T) { + reg := newRegistry() + g := newTestGrain() + + designConfig, _ := anypb.New(&messages.AddItem{}) // example + applyOK(t, reg, g, msgAddGiftcard(5000, "2023-12-25", "John", "email", "Happy Birthday!", designConfig)) + + if len(g.Giftcards) != 1 { + t.Fatalf("expected 1 giftcard, got %d", len(g.Giftcards)) + } + gc := g.Giftcards[0] + if gc.Value.IncVat != 5000 || gc.DeliveryDate != "2023-12-25" || gc.Recipient != "John" || gc.RecipientType != "email" || gc.Message != "Happy Birthday!" { + t.Fatalf("giftcard not set correctly: %+v", gc) + } + if g.TotalPrice.IncVat != 5000 { + t.Fatalf("total price not updated, got %d", g.TotalPrice.IncVat) + } + + // Test invalid value + applyErrorContains(t, reg, g, msgAddGiftcard(0, "", "", "", "", nil), "must be positive") +} + +func TestRemoveGiftcard(t *testing.T) { + reg := newRegistry() + g := newTestGrain() + + applyOK(t, reg, g, msgAddGiftcard(1000, "2023-01-01", "Jane", "sms", "Cheers!", nil)) + id := g.Giftcards[0].Id + + applyOK(t, reg, g, msgRemoveGiftcard(id)) + if len(g.Giftcards) != 0 { + t.Fatalf("giftcard not removed") + } + if g.TotalPrice.IncVat != 0 { + t.Fatalf("total price not updated after removal, got %d", g.TotalPrice.IncVat) + } + + applyErrorContains(t, reg, g, msgRemoveGiftcard(id), "not found") +} diff --git a/pkg/cart/mutation_upsert_subscriptiondetails.go b/pkg/cart/mutation_upsert_subscriptiondetails.go index 308f0a7..2c0b432 100644 --- a/pkg/cart/mutation_upsert_subscriptiondetails.go +++ b/pkg/cart/mutation_upsert_subscriptiondetails.go @@ -4,42 +4,24 @@ import ( "encoding/json" "fmt" - messages "git.tornberg.me/go-cart-actor/pkg/messages" - "google.golang.org/protobuf/types/known/anypb" + messages "git.k6n.net/go-cart-actor/pkg/messages" ) func UpsertSubscriptionDetails(g *CartGrain, m *messages.UpsertSubscriptionDetails) error { if m == nil { return nil } - - // Helper to get data bytes from Data field - getDataBytes := func(data *anypb.Any) ([]byte, error) { - if data == nil { - return nil, nil - } - if len(data.Value) > 0 { - return data.Value, nil - } - if data.TypeUrl != "" { - return []byte(data.TypeUrl), nil - } - return nil, nil - } + metaBytes := m.Data.GetValue() // Create new subscription details when Id is nil. if m.Id == nil { // Validate JSON if provided. var meta json.RawMessage - dataBytes, err := getDataBytes(m.Data) - if err != nil { - return err - } - if dataBytes != nil { - if !json.Valid(dataBytes) { + if metaBytes != nil { + if !json.Valid(metaBytes) { return fmt.Errorf("subscription details invalid json") } - meta = json.RawMessage(dataBytes) + meta = json.RawMessage(metaBytes) } id := MustNewCartId().String() @@ -57,7 +39,7 @@ func UpsertSubscriptionDetails(g *CartGrain, m *messages.UpsertSubscriptionDetai // Update existing entry. existing, ok := g.SubscriptionDetails[*m.Id] if !ok { - return fmt.Errorf("subscription details not found") + return fmt.Errorf("subscription details with id %s not found", *m.Id) } changed := false if m.OfferingCode != "" { @@ -68,15 +50,11 @@ func UpsertSubscriptionDetails(g *CartGrain, m *messages.UpsertSubscriptionDetai existing.SigningType = m.SigningType changed = true } - dataBytes, err := getDataBytes(m.Data) - if err != nil { - return err - } - if dataBytes != nil { - if !json.Valid(dataBytes) { + if metaBytes != nil { + if !json.Valid(metaBytes) { return fmt.Errorf("subscription details invalid json") } - existing.Meta = dataBytes + existing.Meta = json.RawMessage(metaBytes) changed = true } if changed { diff --git a/pkg/messages/control_plane.pb.go b/pkg/messages/control_plane.pb.go index b2633a1..854a86a 100644 --- a/pkg/messages/control_plane.pb.go +++ b/pkg/messages/control_plane.pb.go @@ -9,6 +9,7 @@ package messages import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" reflect "reflect" sync "sync" unsafe "unsafe" @@ -451,55 +452,165 @@ func (x *ExpiryAnnounce) GetIds() []uint64 { return nil } +type ApplyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Messages []*anypb.Any `protobuf:"bytes,2,rep,name=messages,proto3" json:"messages,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ApplyRequest) Reset() { + *x = ApplyRequest{} + mi := &file_control_plane_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ApplyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApplyRequest) ProtoMessage() {} + +func (x *ApplyRequest) ProtoReflect() protoreflect.Message { + mi := &file_control_plane_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApplyRequest.ProtoReflect.Descriptor instead. +func (*ApplyRequest) Descriptor() ([]byte, []int) { + return file_control_plane_proto_rawDescGZIP(), []int{9} +} + +func (x *ApplyRequest) GetId() uint64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *ApplyRequest) GetMessages() []*anypb.Any { + if x != nil { + return x.Messages + } + return nil +} + +type ApplyResult struct { + state protoimpl.MessageState `protogen:"open.v1"` + Accepted bool `protobuf:"varint,1,opt,name=accepted,proto3" json:"accepted,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ApplyResult) Reset() { + *x = ApplyResult{} + mi := &file_control_plane_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ApplyResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApplyResult) ProtoMessage() {} + +func (x *ApplyResult) ProtoReflect() protoreflect.Message { + mi := &file_control_plane_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApplyResult.ProtoReflect.Descriptor instead. +func (*ApplyResult) Descriptor() ([]byte, []int) { + return file_control_plane_proto_rawDescGZIP(), []int{10} +} + +func (x *ApplyResult) GetAccepted() bool { + if x != nil { + return x.Accepted + } + return false +} + var File_control_plane_proto protoreflect.FileDescriptor var file_control_plane_proto_rawDesc = string([]byte{ 0x0a, 0x13, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, - 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x3c, 0x0a, 0x09, 0x50, 0x69, 0x6e, 0x67, - 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x6e, 0x69, - 0x78, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x75, 0x6e, - 0x69, 0x78, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x33, 0x0a, 0x10, 0x4e, 0x65, 0x67, 0x6f, 0x74, 0x69, - 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6b, 0x6e, - 0x6f, 0x77, 0x6e, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0a, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x22, 0x26, 0x0a, 0x0e, 0x4e, - 0x65, 0x67, 0x6f, 0x74, 0x69, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x14, 0x0a, - 0x05, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x68, 0x6f, - 0x73, 0x74, 0x73, 0x22, 0x21, 0x0a, 0x0d, 0x41, 0x63, 0x74, 0x6f, 0x72, 0x49, 0x64, 0x73, 0x52, - 0x65, 0x70, 0x6c, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x04, 0x52, 0x03, 0x69, 0x64, 0x73, 0x22, 0x46, 0x0a, 0x0e, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x43, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x63, 0x6b, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x65, - 0x70, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x63, 0x63, 0x65, - 0x70, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x23, - 0x0a, 0x0d, 0x43, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x12, - 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, - 0x6f, 0x73, 0x74, 0x22, 0x39, 0x0a, 0x11, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, - 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, - 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x04, 0x52, 0x03, 0x69, 0x64, 0x73, 0x22, 0x36, - 0x0a, 0x0e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x1a, + 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x22, 0x3c, 0x0a, 0x09, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x68, 0x6f, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x04, 0x52, 0x03, 0x69, 0x64, 0x73, 0x32, 0x8d, 0x03, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x12, 0x2c, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, - 0x0f, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x1a, 0x13, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x50, 0x69, 0x6e, 0x67, - 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x41, 0x0a, 0x09, 0x4e, 0x65, 0x67, 0x6f, 0x74, 0x69, 0x61, - 0x74, 0x65, 0x12, 0x1a, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x4e, 0x65, - 0x67, 0x6f, 0x74, 0x69, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, - 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x4e, 0x65, 0x67, 0x6f, 0x74, 0x69, - 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x3c, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4c, - 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x63, 0x74, 0x6f, 0x72, 0x49, 0x64, 0x73, 0x12, 0x0f, 0x2e, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x17, 0x2e, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x6f, 0x72, 0x49, 0x64, - 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x4a, 0x0a, 0x11, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, - 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x12, 0x1b, 0x2e, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, - 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x1a, 0x18, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x73, 0x2e, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, - 0x63, 0x6b, 0x12, 0x44, 0x0a, 0x0e, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x45, 0x78, + 0x68, 0x6f, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x75, 0x6e, 0x69, 0x78, 0x54, 0x69, 0x6d, + 0x65, 0x22, 0x33, 0x0a, 0x10, 0x4e, 0x65, 0x67, 0x6f, 0x74, 0x69, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x68, + 0x6f, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x6b, 0x6e, 0x6f, 0x77, + 0x6e, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x22, 0x26, 0x0a, 0x0e, 0x4e, 0x65, 0x67, 0x6f, 0x74, 0x69, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x68, 0x6f, 0x73, 0x74, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x22, 0x21, + 0x0a, 0x0d, 0x41, 0x63, 0x74, 0x6f, 0x72, 0x49, 0x64, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x03, 0x69, 0x64, + 0x73, 0x22, 0x46, 0x0a, 0x0e, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x41, 0x63, 0x6b, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, + 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x23, 0x0a, 0x0d, 0x43, 0x6c, 0x6f, + 0x73, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, + 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x22, 0x39, + 0x0a, 0x11, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x41, 0x6e, 0x6e, 0x6f, 0x75, + 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x04, 0x52, 0x03, 0x69, 0x64, 0x73, 0x22, 0x36, 0x0a, 0x0e, 0x45, 0x78, 0x70, + 0x69, 0x72, 0x79, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, + 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, + 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x04, 0x52, 0x03, 0x69, 0x64, + 0x73, 0x22, 0x50, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x30, 0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x73, 0x22, 0x29, 0x0a, 0x0b, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x32, 0xc5, + 0x03, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x12, + 0x2c, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x0f, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x73, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x13, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x73, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x41, 0x0a, + 0x09, 0x4e, 0x65, 0x67, 0x6f, 0x74, 0x69, 0x61, 0x74, 0x65, 0x12, 0x1a, 0x2e, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x4e, 0x65, 0x67, 0x6f, 0x74, 0x69, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x73, 0x2e, 0x4e, 0x65, 0x67, 0x6f, 0x74, 0x69, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, + 0x12, 0x3c, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x63, 0x74, 0x6f, + 0x72, 0x49, 0x64, 0x73, 0x12, 0x0f, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x17, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, + 0x2e, 0x41, 0x63, 0x74, 0x6f, 0x72, 0x49, 0x64, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x4a, + 0x0a, 0x11, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, + 0x68, 0x69, 0x70, 0x12, 0x1b, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x4f, + 0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, + 0x1a, 0x18, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x4f, 0x77, 0x6e, 0x65, + 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x63, 0x6b, 0x12, 0x36, 0x0a, 0x05, 0x41, 0x70, + 0x70, 0x6c, 0x79, 0x12, 0x16, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x41, + 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x12, 0x44, 0x0a, 0x0e, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x18, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x1a, 0x18, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x43, @@ -507,10 +618,10 @@ var file_control_plane_proto_rawDesc = string([]byte{ 0x69, 0x6e, 0x67, 0x12, 0x17, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x1a, 0x18, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x43, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x41, 0x63, 0x6b, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x2e, 0x74, 0x6f, - 0x72, 0x6e, 0x62, 0x65, 0x72, 0x67, 0x2e, 0x6d, 0x65, 0x2f, 0x67, 0x6f, 0x2d, 0x63, 0x61, 0x72, - 0x74, 0x2d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x67, 0x65, 0x41, 0x63, 0x6b, 0x42, 0x2a, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x2e, 0x6b, 0x36, + 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x67, 0x6f, 0x2d, 0x63, 0x61, 0x72, 0x74, 0x2d, 0x61, 0x63, + 0x74, 0x6f, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -525,7 +636,7 @@ func file_control_plane_proto_rawDescGZIP() []byte { return file_control_plane_proto_rawDescData } -var file_control_plane_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_control_plane_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_control_plane_proto_goTypes = []any{ (*Empty)(nil), // 0: messages.Empty (*PingReply)(nil), // 1: messages.PingReply @@ -536,25 +647,31 @@ var file_control_plane_proto_goTypes = []any{ (*ClosingNotice)(nil), // 6: messages.ClosingNotice (*OwnershipAnnounce)(nil), // 7: messages.OwnershipAnnounce (*ExpiryAnnounce)(nil), // 8: messages.ExpiryAnnounce + (*ApplyRequest)(nil), // 9: messages.ApplyRequest + (*ApplyResult)(nil), // 10: messages.ApplyResult + (*anypb.Any)(nil), // 11: google.protobuf.Any } var file_control_plane_proto_depIdxs = []int32{ - 0, // 0: messages.ControlPlane.Ping:input_type -> messages.Empty - 2, // 1: messages.ControlPlane.Negotiate:input_type -> messages.NegotiateRequest - 0, // 2: messages.ControlPlane.GetLocalActorIds:input_type -> messages.Empty - 7, // 3: messages.ControlPlane.AnnounceOwnership:input_type -> messages.OwnershipAnnounce - 8, // 4: messages.ControlPlane.AnnounceExpiry:input_type -> messages.ExpiryAnnounce - 6, // 5: messages.ControlPlane.Closing:input_type -> messages.ClosingNotice - 1, // 6: messages.ControlPlane.Ping:output_type -> messages.PingReply - 3, // 7: messages.ControlPlane.Negotiate:output_type -> messages.NegotiateReply - 4, // 8: messages.ControlPlane.GetLocalActorIds:output_type -> messages.ActorIdsReply - 5, // 9: messages.ControlPlane.AnnounceOwnership:output_type -> messages.OwnerChangeAck - 5, // 10: messages.ControlPlane.AnnounceExpiry:output_type -> messages.OwnerChangeAck - 5, // 11: messages.ControlPlane.Closing:output_type -> messages.OwnerChangeAck - 6, // [6:12] is the sub-list for method output_type - 0, // [0:6] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 11, // 0: messages.ApplyRequest.messages:type_name -> google.protobuf.Any + 0, // 1: messages.ControlPlane.Ping:input_type -> messages.Empty + 2, // 2: messages.ControlPlane.Negotiate:input_type -> messages.NegotiateRequest + 0, // 3: messages.ControlPlane.GetLocalActorIds:input_type -> messages.Empty + 7, // 4: messages.ControlPlane.AnnounceOwnership:input_type -> messages.OwnershipAnnounce + 9, // 5: messages.ControlPlane.Apply:input_type -> messages.ApplyRequest + 8, // 6: messages.ControlPlane.AnnounceExpiry:input_type -> messages.ExpiryAnnounce + 6, // 7: messages.ControlPlane.Closing:input_type -> messages.ClosingNotice + 1, // 8: messages.ControlPlane.Ping:output_type -> messages.PingReply + 3, // 9: messages.ControlPlane.Negotiate:output_type -> messages.NegotiateReply + 4, // 10: messages.ControlPlane.GetLocalActorIds:output_type -> messages.ActorIdsReply + 5, // 11: messages.ControlPlane.AnnounceOwnership:output_type -> messages.OwnerChangeAck + 10, // 12: messages.ControlPlane.Apply:output_type -> messages.ApplyResult + 5, // 13: messages.ControlPlane.AnnounceExpiry:output_type -> messages.OwnerChangeAck + 5, // 14: messages.ControlPlane.Closing:output_type -> messages.OwnerChangeAck + 8, // [8:15] is the sub-list for method output_type + 1, // [1:8] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name } func init() { file_control_plane_proto_init() } @@ -562,13 +679,14 @@ func file_control_plane_proto_init() { if File_control_plane_proto != nil { return } + file_messages_proto_init() type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_control_plane_proto_rawDesc), len(file_control_plane_proto_rawDesc)), NumEnums: 0, - NumMessages: 9, + NumMessages: 11, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/messages/control_plane_grpc.pb.go b/pkg/messages/control_plane_grpc.pb.go index c78a6a6..f328d33 100644 --- a/pkg/messages/control_plane_grpc.pb.go +++ b/pkg/messages/control_plane_grpc.pb.go @@ -23,6 +23,7 @@ const ( ControlPlane_Negotiate_FullMethodName = "/messages.ControlPlane/Negotiate" ControlPlane_GetLocalActorIds_FullMethodName = "/messages.ControlPlane/GetLocalActorIds" ControlPlane_AnnounceOwnership_FullMethodName = "/messages.ControlPlane/AnnounceOwnership" + ControlPlane_Apply_FullMethodName = "/messages.ControlPlane/Apply" ControlPlane_AnnounceExpiry_FullMethodName = "/messages.ControlPlane/AnnounceExpiry" ControlPlane_Closing_FullMethodName = "/messages.ControlPlane/Closing" ) @@ -41,6 +42,7 @@ type ControlPlaneClient interface { GetLocalActorIds(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ActorIdsReply, error) // Ownership announcement: first-touch claim broadcast (idempotent; best-effort). AnnounceOwnership(ctx context.Context, in *OwnershipAnnounce, opts ...grpc.CallOption) (*OwnerChangeAck, error) + Apply(ctx context.Context, in *ApplyRequest, opts ...grpc.CallOption) (*ApplyResult, error) // Expiry announcement: drop remote ownership hints when local TTL expires. AnnounceExpiry(ctx context.Context, in *ExpiryAnnounce, opts ...grpc.CallOption) (*OwnerChangeAck, error) // Closing announces graceful shutdown so peers can proactively adjust. @@ -95,6 +97,16 @@ func (c *controlPlaneClient) AnnounceOwnership(ctx context.Context, in *Ownershi return out, nil } +func (c *controlPlaneClient) Apply(ctx context.Context, in *ApplyRequest, opts ...grpc.CallOption) (*ApplyResult, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ApplyResult) + err := c.cc.Invoke(ctx, ControlPlane_Apply_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *controlPlaneClient) AnnounceExpiry(ctx context.Context, in *ExpiryAnnounce, opts ...grpc.CallOption) (*OwnerChangeAck, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(OwnerChangeAck) @@ -129,6 +141,7 @@ type ControlPlaneServer interface { GetLocalActorIds(context.Context, *Empty) (*ActorIdsReply, error) // Ownership announcement: first-touch claim broadcast (idempotent; best-effort). AnnounceOwnership(context.Context, *OwnershipAnnounce) (*OwnerChangeAck, error) + Apply(context.Context, *ApplyRequest) (*ApplyResult, error) // Expiry announcement: drop remote ownership hints when local TTL expires. AnnounceExpiry(context.Context, *ExpiryAnnounce) (*OwnerChangeAck, error) // Closing announces graceful shutdown so peers can proactively adjust. @@ -155,6 +168,9 @@ func (UnimplementedControlPlaneServer) GetLocalActorIds(context.Context, *Empty) func (UnimplementedControlPlaneServer) AnnounceOwnership(context.Context, *OwnershipAnnounce) (*OwnerChangeAck, error) { return nil, status.Errorf(codes.Unimplemented, "method AnnounceOwnership not implemented") } +func (UnimplementedControlPlaneServer) Apply(context.Context, *ApplyRequest) (*ApplyResult, error) { + return nil, status.Errorf(codes.Unimplemented, "method Apply not implemented") +} func (UnimplementedControlPlaneServer) AnnounceExpiry(context.Context, *ExpiryAnnounce) (*OwnerChangeAck, error) { return nil, status.Errorf(codes.Unimplemented, "method AnnounceExpiry not implemented") } @@ -254,6 +270,24 @@ func _ControlPlane_AnnounceOwnership_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _ControlPlane_Apply_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ControlPlaneServer).Apply(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ControlPlane_Apply_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ControlPlaneServer).Apply(ctx, req.(*ApplyRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _ControlPlane_AnnounceExpiry_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ExpiryAnnounce) if err := dec(in); err != nil { @@ -313,6 +347,10 @@ var ControlPlane_ServiceDesc = grpc.ServiceDesc{ MethodName: "AnnounceOwnership", Handler: _ControlPlane_AnnounceOwnership_Handler, }, + { + MethodName: "Apply", + Handler: _ControlPlane_Apply_Handler, + }, { MethodName: "AnnounceExpiry", Handler: _ControlPlane_AnnounceExpiry_Handler, diff --git a/pkg/messages/messages.pb.go b/pkg/messages/messages.pb.go index 08a49b4..bb6b6f9 100644 --- a/pkg/messages/messages.pb.go +++ b/pkg/messages/messages.pb.go @@ -10,6 +10,7 @@ import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" anypb "google.golang.org/protobuf/types/known/anypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" unsafe "unsafe" @@ -59,33 +60,35 @@ func (*ClearCartRequest) Descriptor() ([]byte, []int) { } type AddItem struct { - state protoimpl.MessageState `protogen:"open.v1"` - ItemId uint32 `protobuf:"varint,1,opt,name=item_id,json=itemId,proto3" json:"item_id,omitempty"` - Quantity int32 `protobuf:"varint,2,opt,name=quantity,proto3" json:"quantity,omitempty"` - Price int64 `protobuf:"varint,3,opt,name=price,proto3" json:"price,omitempty"` - OrgPrice int64 `protobuf:"varint,9,opt,name=orgPrice,proto3" json:"orgPrice,omitempty"` - Sku string `protobuf:"bytes,4,opt,name=sku,proto3" json:"sku,omitempty"` - Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"` - Image string `protobuf:"bytes,6,opt,name=image,proto3" json:"image,omitempty"` - Stock int32 `protobuf:"varint,7,opt,name=stock,proto3" json:"stock,omitempty"` - Tax int32 `protobuf:"varint,8,opt,name=tax,proto3" json:"tax,omitempty"` - Brand string `protobuf:"bytes,13,opt,name=brand,proto3" json:"brand,omitempty"` - Category string `protobuf:"bytes,14,opt,name=category,proto3" json:"category,omitempty"` - Category2 string `protobuf:"bytes,15,opt,name=category2,proto3" json:"category2,omitempty"` - Category3 string `protobuf:"bytes,16,opt,name=category3,proto3" json:"category3,omitempty"` - Category4 string `protobuf:"bytes,17,opt,name=category4,proto3" json:"category4,omitempty"` - Category5 string `protobuf:"bytes,18,opt,name=category5,proto3" json:"category5,omitempty"` - Disclaimer string `protobuf:"bytes,10,opt,name=disclaimer,proto3" json:"disclaimer,omitempty"` - ArticleType string `protobuf:"bytes,11,opt,name=articleType,proto3" json:"articleType,omitempty"` - SellerId string `protobuf:"bytes,19,opt,name=sellerId,proto3" json:"sellerId,omitempty"` - SellerName string `protobuf:"bytes,20,opt,name=sellerName,proto3" json:"sellerName,omitempty"` - Country string `protobuf:"bytes,21,opt,name=country,proto3" json:"country,omitempty"` - SaleStatus string `protobuf:"bytes,24,opt,name=saleStatus,proto3" json:"saleStatus,omitempty"` - Outlet *string `protobuf:"bytes,12,opt,name=outlet,proto3,oneof" json:"outlet,omitempty"` - StoreId *string `protobuf:"bytes,22,opt,name=storeId,proto3,oneof" json:"storeId,omitempty"` - ParentId *uint32 `protobuf:"varint,23,opt,name=parentId,proto3,oneof" json:"parentId,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + ItemId uint32 `protobuf:"varint,1,opt,name=item_id,json=itemId,proto3" json:"item_id,omitempty"` + Quantity int32 `protobuf:"varint,2,opt,name=quantity,proto3" json:"quantity,omitempty"` + Price int64 `protobuf:"varint,3,opt,name=price,proto3" json:"price,omitempty"` + OrgPrice int64 `protobuf:"varint,9,opt,name=orgPrice,proto3" json:"orgPrice,omitempty"` + Sku string `protobuf:"bytes,4,opt,name=sku,proto3" json:"sku,omitempty"` + Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"` + Image string `protobuf:"bytes,6,opt,name=image,proto3" json:"image,omitempty"` + Stock int32 `protobuf:"varint,7,opt,name=stock,proto3" json:"stock,omitempty"` + Tax int32 `protobuf:"varint,8,opt,name=tax,proto3" json:"tax,omitempty"` + Brand string `protobuf:"bytes,13,opt,name=brand,proto3" json:"brand,omitempty"` + Category string `protobuf:"bytes,14,opt,name=category,proto3" json:"category,omitempty"` + Category2 string `protobuf:"bytes,15,opt,name=category2,proto3" json:"category2,omitempty"` + Category3 string `protobuf:"bytes,16,opt,name=category3,proto3" json:"category3,omitempty"` + Category4 string `protobuf:"bytes,17,opt,name=category4,proto3" json:"category4,omitempty"` + Category5 string `protobuf:"bytes,18,opt,name=category5,proto3" json:"category5,omitempty"` + Disclaimer string `protobuf:"bytes,10,opt,name=disclaimer,proto3" json:"disclaimer,omitempty"` + ArticleType string `protobuf:"bytes,11,opt,name=articleType,proto3" json:"articleType,omitempty"` + SellerId string `protobuf:"bytes,19,opt,name=sellerId,proto3" json:"sellerId,omitempty"` + SellerName string `protobuf:"bytes,20,opt,name=sellerName,proto3" json:"sellerName,omitempty"` + Country string `protobuf:"bytes,21,opt,name=country,proto3" json:"country,omitempty"` + SaleStatus string `protobuf:"bytes,24,opt,name=saleStatus,proto3" json:"saleStatus,omitempty"` + Outlet *string `protobuf:"bytes,12,opt,name=outlet,proto3,oneof" json:"outlet,omitempty"` + StoreId *string `protobuf:"bytes,22,opt,name=storeId,proto3,oneof" json:"storeId,omitempty"` + ParentId *uint32 `protobuf:"varint,23,opt,name=parentId,proto3,oneof" json:"parentId,omitempty"` + Cgm string `protobuf:"bytes,25,opt,name=cgm,proto3" json:"cgm,omitempty"` + ReservationEndTime *timestamppb.Timestamp `protobuf:"bytes,26,opt,name=reservationEndTime,proto3,oneof" json:"reservationEndTime,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AddItem) Reset() { @@ -286,6 +289,20 @@ func (x *AddItem) GetParentId() uint32 { return 0 } +func (x *AddItem) GetCgm() string { + if x != nil { + return x.Cgm + } + return "" +} + +func (x *AddItem) GetReservationEndTime() *timestamppb.Timestamp { + if x != nil { + return x.ReservationEndTime + } + return nil +} + type RemoveItem struct { state protoimpl.MessageState `protogen:"open.v1"` Id uint32 `protobuf:"varint,1,opt,name=Id,proto3" json:"Id,omitempty"` @@ -694,6 +711,302 @@ func (x *RemoveDelivery) GetId() uint32 { return 0 } +type SetUserId struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetUserId) Reset() { + *x = SetUserId{} + mi := &file_messages_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetUserId) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetUserId) ProtoMessage() {} + +func (x *SetUserId) ProtoReflect() protoreflect.Message { + mi := &file_messages_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetUserId.ProtoReflect.Descriptor instead. +func (*SetUserId) Descriptor() ([]byte, []int) { + return file_messages_proto_rawDescGZIP(), []int{8} +} + +func (x *SetUserId) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +type LineItemMarking struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Type uint32 `protobuf:"varint,2,opt,name=type,proto3" json:"type,omitempty"` + Marking string `protobuf:"bytes,3,opt,name=marking,proto3" json:"marking,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LineItemMarking) Reset() { + *x = LineItemMarking{} + mi := &file_messages_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LineItemMarking) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LineItemMarking) ProtoMessage() {} + +func (x *LineItemMarking) ProtoReflect() protoreflect.Message { + mi := &file_messages_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LineItemMarking.ProtoReflect.Descriptor instead. +func (*LineItemMarking) Descriptor() ([]byte, []int) { + return file_messages_proto_rawDescGZIP(), []int{9} +} + +func (x *LineItemMarking) GetId() uint32 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *LineItemMarking) GetType() uint32 { + if x != nil { + return x.Type + } + return 0 +} + +func (x *LineItemMarking) GetMarking() string { + if x != nil { + return x.Marking + } + return "" +} + +type RemoveLineItemMarking struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RemoveLineItemMarking) Reset() { + *x = RemoveLineItemMarking{} + mi := &file_messages_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoveLineItemMarking) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveLineItemMarking) ProtoMessage() {} + +func (x *RemoveLineItemMarking) ProtoReflect() protoreflect.Message { + mi := &file_messages_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveLineItemMarking.ProtoReflect.Descriptor instead. +func (*RemoveLineItemMarking) Descriptor() ([]byte, []int) { + return file_messages_proto_rawDescGZIP(), []int{10} +} + +func (x *RemoveLineItemMarking) GetId() uint32 { + if x != nil { + return x.Id + } + return 0 +} + +type SubscriptionAdded struct { + state protoimpl.MessageState `protogen:"open.v1"` + ItemId uint32 `protobuf:"varint,1,opt,name=itemId,proto3" json:"itemId,omitempty"` + DetailsId string `protobuf:"bytes,3,opt,name=detailsId,proto3" json:"detailsId,omitempty"` + OrderReference string `protobuf:"bytes,4,opt,name=orderReference,proto3" json:"orderReference,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubscriptionAdded) Reset() { + *x = SubscriptionAdded{} + mi := &file_messages_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubscriptionAdded) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubscriptionAdded) ProtoMessage() {} + +func (x *SubscriptionAdded) ProtoReflect() protoreflect.Message { + mi := &file_messages_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubscriptionAdded.ProtoReflect.Descriptor instead. +func (*SubscriptionAdded) Descriptor() ([]byte, []int) { + return file_messages_proto_rawDescGZIP(), []int{11} +} + +func (x *SubscriptionAdded) GetItemId() uint32 { + if x != nil { + return x.ItemId + } + return 0 +} + +func (x *SubscriptionAdded) GetDetailsId() string { + if x != nil { + return x.DetailsId + } + return "" +} + +func (x *SubscriptionAdded) GetOrderReference() string { + if x != nil { + return x.OrderReference + } + return "" +} + +type PaymentDeclined struct { + state protoimpl.MessageState `protogen:"open.v1"` + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + Code *string `protobuf:"bytes,2,opt,name=code,proto3,oneof" json:"code,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PaymentDeclined) Reset() { + *x = PaymentDeclined{} + mi := &file_messages_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PaymentDeclined) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PaymentDeclined) ProtoMessage() {} + +func (x *PaymentDeclined) ProtoReflect() protoreflect.Message { + mi := &file_messages_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PaymentDeclined.ProtoReflect.Descriptor instead. +func (*PaymentDeclined) Descriptor() ([]byte, []int) { + return file_messages_proto_rawDescGZIP(), []int{12} +} + +func (x *PaymentDeclined) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *PaymentDeclined) GetCode() string { + if x != nil && x.Code != nil { + return *x.Code + } + return "" +} + +type ConfirmationViewed struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConfirmationViewed) Reset() { + *x = ConfirmationViewed{} + mi := &file_messages_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConfirmationViewed) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfirmationViewed) ProtoMessage() {} + +func (x *ConfirmationViewed) ProtoReflect() protoreflect.Message { + mi := &file_messages_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfirmationViewed.ProtoReflect.Descriptor instead. +func (*ConfirmationViewed) Descriptor() ([]byte, []int) { + return file_messages_proto_rawDescGZIP(), []int{13} +} + type CreateCheckoutOrder struct { state protoimpl.MessageState `protogen:"open.v1"` Terms string `protobuf:"bytes,1,opt,name=terms,proto3" json:"terms,omitempty"` @@ -708,7 +1021,7 @@ type CreateCheckoutOrder struct { func (x *CreateCheckoutOrder) Reset() { *x = CreateCheckoutOrder{} - mi := &file_messages_proto_msgTypes[8] + mi := &file_messages_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -720,7 +1033,7 @@ func (x *CreateCheckoutOrder) String() string { func (*CreateCheckoutOrder) ProtoMessage() {} func (x *CreateCheckoutOrder) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[8] + mi := &file_messages_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -733,7 +1046,7 @@ func (x *CreateCheckoutOrder) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateCheckoutOrder.ProtoReflect.Descriptor instead. func (*CreateCheckoutOrder) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{8} + return file_messages_proto_rawDescGZIP(), []int{14} } func (x *CreateCheckoutOrder) GetTerms() string { @@ -788,7 +1101,7 @@ type OrderCreated struct { func (x *OrderCreated) Reset() { *x = OrderCreated{} - mi := &file_messages_proto_msgTypes[9] + mi := &file_messages_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -800,7 +1113,7 @@ func (x *OrderCreated) String() string { func (*OrderCreated) ProtoMessage() {} func (x *OrderCreated) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[9] + mi := &file_messages_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -813,7 +1126,7 @@ func (x *OrderCreated) ProtoReflect() protoreflect.Message { // Deprecated: Use OrderCreated.ProtoReflect.Descriptor instead. func (*OrderCreated) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{9} + return file_messages_proto_rawDescGZIP(), []int{15} } func (x *OrderCreated) GetOrderId() string { @@ -838,7 +1151,7 @@ type Noop struct { func (x *Noop) Reset() { *x = Noop{} - mi := &file_messages_proto_msgTypes[10] + mi := &file_messages_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -850,7 +1163,7 @@ func (x *Noop) String() string { func (*Noop) ProtoMessage() {} func (x *Noop) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[10] + mi := &file_messages_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -863,7 +1176,7 @@ func (x *Noop) ProtoReflect() protoreflect.Message { // Deprecated: Use Noop.ProtoReflect.Descriptor instead. func (*Noop) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{10} + return file_messages_proto_rawDescGZIP(), []int{16} } type InitializeCheckout struct { @@ -877,7 +1190,7 @@ type InitializeCheckout struct { func (x *InitializeCheckout) Reset() { *x = InitializeCheckout{} - mi := &file_messages_proto_msgTypes[11] + mi := &file_messages_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -889,7 +1202,7 @@ func (x *InitializeCheckout) String() string { func (*InitializeCheckout) ProtoMessage() {} func (x *InitializeCheckout) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[11] + mi := &file_messages_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -902,7 +1215,7 @@ func (x *InitializeCheckout) ProtoReflect() protoreflect.Message { // Deprecated: Use InitializeCheckout.ProtoReflect.Descriptor instead. func (*InitializeCheckout) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{11} + return file_messages_proto_rawDescGZIP(), []int{17} } func (x *InitializeCheckout) GetOrderId() string { @@ -937,7 +1250,7 @@ type InventoryReserved struct { func (x *InventoryReserved) Reset() { *x = InventoryReserved{} - mi := &file_messages_proto_msgTypes[12] + mi := &file_messages_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -949,7 +1262,7 @@ func (x *InventoryReserved) String() string { func (*InventoryReserved) ProtoMessage() {} func (x *InventoryReserved) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[12] + mi := &file_messages_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -962,7 +1275,7 @@ func (x *InventoryReserved) ProtoReflect() protoreflect.Message { // Deprecated: Use InventoryReserved.ProtoReflect.Descriptor instead. func (*InventoryReserved) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{12} + return file_messages_proto_rawDescGZIP(), []int{18} } func (x *InventoryReserved) GetId() string { @@ -998,7 +1311,7 @@ type AddVoucher struct { func (x *AddVoucher) Reset() { *x = AddVoucher{} - mi := &file_messages_proto_msgTypes[13] + mi := &file_messages_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1010,7 +1323,7 @@ func (x *AddVoucher) String() string { func (*AddVoucher) ProtoMessage() {} func (x *AddVoucher) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[13] + mi := &file_messages_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1023,7 +1336,7 @@ func (x *AddVoucher) ProtoReflect() protoreflect.Message { // Deprecated: Use AddVoucher.ProtoReflect.Descriptor instead. func (*AddVoucher) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{13} + return file_messages_proto_rawDescGZIP(), []int{19} } func (x *AddVoucher) GetCode() string { @@ -1063,7 +1376,7 @@ type RemoveVoucher struct { func (x *RemoveVoucher) Reset() { *x = RemoveVoucher{} - mi := &file_messages_proto_msgTypes[14] + mi := &file_messages_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1075,7 +1388,7 @@ func (x *RemoveVoucher) String() string { func (*RemoveVoucher) ProtoMessage() {} func (x *RemoveVoucher) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[14] + mi := &file_messages_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1088,7 +1401,7 @@ func (x *RemoveVoucher) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveVoucher.ProtoReflect.Descriptor instead. func (*RemoveVoucher) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{14} + return file_messages_proto_rawDescGZIP(), []int{20} } func (x *RemoveVoucher) GetId() uint32 { @@ -1110,7 +1423,7 @@ type UpsertSubscriptionDetails struct { func (x *UpsertSubscriptionDetails) Reset() { *x = UpsertSubscriptionDetails{} - mi := &file_messages_proto_msgTypes[15] + mi := &file_messages_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1122,7 +1435,7 @@ func (x *UpsertSubscriptionDetails) String() string { func (*UpsertSubscriptionDetails) ProtoMessage() {} func (x *UpsertSubscriptionDetails) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[15] + mi := &file_messages_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1135,7 +1448,7 @@ func (x *UpsertSubscriptionDetails) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertSubscriptionDetails.ProtoReflect.Descriptor instead. func (*UpsertSubscriptionDetails) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{15} + return file_messages_proto_rawDescGZIP(), []int{21} } func (x *UpsertSubscriptionDetails) GetId() string { @@ -1177,7 +1490,7 @@ type PreConditionFailed struct { func (x *PreConditionFailed) Reset() { *x = PreConditionFailed{} - mi := &file_messages_proto_msgTypes[16] + mi := &file_messages_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1189,7 +1502,7 @@ func (x *PreConditionFailed) String() string { func (*PreConditionFailed) ProtoMessage() {} func (x *PreConditionFailed) ProtoReflect() protoreflect.Message { - mi := &file_messages_proto_msgTypes[16] + mi := &file_messages_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1202,7 +1515,7 @@ func (x *PreConditionFailed) ProtoReflect() protoreflect.Message { // Deprecated: Use PreConditionFailed.ProtoReflect.Descriptor instead. func (*PreConditionFailed) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{16} + return file_messages_proto_rawDescGZIP(), []int{22} } func (x *PreConditionFailed) GetOperation() string { @@ -1226,173 +1539,939 @@ func (x *PreConditionFailed) GetInput() *anypb.Any { return nil } +type GiftcardItem struct { + state protoimpl.MessageState `protogen:"open.v1"` + Value int64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"` + DeliveryDate string `protobuf:"bytes,2,opt,name=deliveryDate,proto3" json:"deliveryDate,omitempty"` + Recipient string `protobuf:"bytes,3,opt,name=recipient,proto3" json:"recipient,omitempty"` + RecipientType string `protobuf:"bytes,4,opt,name=recipientType,proto3" json:"recipientType,omitempty"` + Message string `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"` + DesignConfig *anypb.Any `protobuf:"bytes,6,opt,name=designConfig,proto3,oneof" json:"designConfig,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GiftcardItem) Reset() { + *x = GiftcardItem{} + mi := &file_messages_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GiftcardItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GiftcardItem) ProtoMessage() {} + +func (x *GiftcardItem) ProtoReflect() protoreflect.Message { + mi := &file_messages_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GiftcardItem.ProtoReflect.Descriptor instead. +func (*GiftcardItem) Descriptor() ([]byte, []int) { + return file_messages_proto_rawDescGZIP(), []int{23} +} + +func (x *GiftcardItem) GetValue() int64 { + if x != nil { + return x.Value + } + return 0 +} + +func (x *GiftcardItem) GetDeliveryDate() string { + if x != nil { + return x.DeliveryDate + } + return "" +} + +func (x *GiftcardItem) GetRecipient() string { + if x != nil { + return x.Recipient + } + return "" +} + +func (x *GiftcardItem) GetRecipientType() string { + if x != nil { + return x.RecipientType + } + return "" +} + +func (x *GiftcardItem) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *GiftcardItem) GetDesignConfig() *anypb.Any { + if x != nil { + return x.DesignConfig + } + return nil +} + +type AddGiftcard struct { + state protoimpl.MessageState `protogen:"open.v1"` + Giftcard *GiftcardItem `protobuf:"bytes,1,opt,name=giftcard,proto3" json:"giftcard,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AddGiftcard) Reset() { + *x = AddGiftcard{} + mi := &file_messages_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AddGiftcard) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddGiftcard) ProtoMessage() {} + +func (x *AddGiftcard) ProtoReflect() protoreflect.Message { + mi := &file_messages_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddGiftcard.ProtoReflect.Descriptor instead. +func (*AddGiftcard) Descriptor() ([]byte, []int) { + return file_messages_proto_rawDescGZIP(), []int{24} +} + +func (x *AddGiftcard) GetGiftcard() *GiftcardItem { + if x != nil { + return x.Giftcard + } + return nil +} + +type RemoveGiftcard struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RemoveGiftcard) Reset() { + *x = RemoveGiftcard{} + mi := &file_messages_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoveGiftcard) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveGiftcard) ProtoMessage() {} + +func (x *RemoveGiftcard) ProtoReflect() protoreflect.Message { + mi := &file_messages_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveGiftcard.ProtoReflect.Descriptor instead. +func (*RemoveGiftcard) Descriptor() ([]byte, []int) { + return file_messages_proto_rawDescGZIP(), []int{25} +} + +func (x *RemoveGiftcard) GetId() uint32 { + if x != nil { + return x.Id + } + return 0 +} + +type Mutation struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Type: + // + // *Mutation_ClearCart + // *Mutation_AddItem + // *Mutation_RemoveItem + // *Mutation_ChangeQuantity + // *Mutation_SetDelivery + // *Mutation_SetPickupPoint + // *Mutation_RemoveDelivery + // *Mutation_SetUserId + // *Mutation_LineItemMarking + // *Mutation_RemoveLineItemMarking + // *Mutation_SubscriptionAdded + // *Mutation_PaymentDeclined + // *Mutation_ConfirmationViewed + // *Mutation_CreateCheckoutOrder + // *Mutation_OrderCreated + // *Mutation_Noop + // *Mutation_InitializeCheckout + // *Mutation_InventoryReserved + // *Mutation_AddVoucher + // *Mutation_RemoveVoucher + // *Mutation_UpsertSubscriptionDetails + // *Mutation_PreConditionFailed + // *Mutation_AddGiftcard + // *Mutation_RemoveGiftcard + Type isMutation_Type `protobuf_oneof:"type"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Mutation) Reset() { + *x = Mutation{} + mi := &file_messages_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Mutation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Mutation) ProtoMessage() {} + +func (x *Mutation) ProtoReflect() protoreflect.Message { + mi := &file_messages_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Mutation.ProtoReflect.Descriptor instead. +func (*Mutation) Descriptor() ([]byte, []int) { + return file_messages_proto_rawDescGZIP(), []int{26} +} + +func (x *Mutation) GetType() isMutation_Type { + if x != nil { + return x.Type + } + return nil +} + +func (x *Mutation) GetClearCart() *ClearCartRequest { + if x != nil { + if x, ok := x.Type.(*Mutation_ClearCart); ok { + return x.ClearCart + } + } + return nil +} + +func (x *Mutation) GetAddItem() *AddItem { + if x != nil { + if x, ok := x.Type.(*Mutation_AddItem); ok { + return x.AddItem + } + } + return nil +} + +func (x *Mutation) GetRemoveItem() *RemoveItem { + if x != nil { + if x, ok := x.Type.(*Mutation_RemoveItem); ok { + return x.RemoveItem + } + } + return nil +} + +func (x *Mutation) GetChangeQuantity() *ChangeQuantity { + if x != nil { + if x, ok := x.Type.(*Mutation_ChangeQuantity); ok { + return x.ChangeQuantity + } + } + return nil +} + +func (x *Mutation) GetSetDelivery() *SetDelivery { + if x != nil { + if x, ok := x.Type.(*Mutation_SetDelivery); ok { + return x.SetDelivery + } + } + return nil +} + +func (x *Mutation) GetSetPickupPoint() *SetPickupPoint { + if x != nil { + if x, ok := x.Type.(*Mutation_SetPickupPoint); ok { + return x.SetPickupPoint + } + } + return nil +} + +func (x *Mutation) GetRemoveDelivery() *RemoveDelivery { + if x != nil { + if x, ok := x.Type.(*Mutation_RemoveDelivery); ok { + return x.RemoveDelivery + } + } + return nil +} + +func (x *Mutation) GetSetUserId() *SetUserId { + if x != nil { + if x, ok := x.Type.(*Mutation_SetUserId); ok { + return x.SetUserId + } + } + return nil +} + +func (x *Mutation) GetLineItemMarking() *LineItemMarking { + if x != nil { + if x, ok := x.Type.(*Mutation_LineItemMarking); ok { + return x.LineItemMarking + } + } + return nil +} + +func (x *Mutation) GetRemoveLineItemMarking() *RemoveLineItemMarking { + if x != nil { + if x, ok := x.Type.(*Mutation_RemoveLineItemMarking); ok { + return x.RemoveLineItemMarking + } + } + return nil +} + +func (x *Mutation) GetSubscriptionAdded() *SubscriptionAdded { + if x != nil { + if x, ok := x.Type.(*Mutation_SubscriptionAdded); ok { + return x.SubscriptionAdded + } + } + return nil +} + +func (x *Mutation) GetPaymentDeclined() *PaymentDeclined { + if x != nil { + if x, ok := x.Type.(*Mutation_PaymentDeclined); ok { + return x.PaymentDeclined + } + } + return nil +} + +func (x *Mutation) GetConfirmationViewed() *ConfirmationViewed { + if x != nil { + if x, ok := x.Type.(*Mutation_ConfirmationViewed); ok { + return x.ConfirmationViewed + } + } + return nil +} + +func (x *Mutation) GetCreateCheckoutOrder() *CreateCheckoutOrder { + if x != nil { + if x, ok := x.Type.(*Mutation_CreateCheckoutOrder); ok { + return x.CreateCheckoutOrder + } + } + return nil +} + +func (x *Mutation) GetOrderCreated() *OrderCreated { + if x != nil { + if x, ok := x.Type.(*Mutation_OrderCreated); ok { + return x.OrderCreated + } + } + return nil +} + +func (x *Mutation) GetNoop() *Noop { + if x != nil { + if x, ok := x.Type.(*Mutation_Noop); ok { + return x.Noop + } + } + return nil +} + +func (x *Mutation) GetInitializeCheckout() *InitializeCheckout { + if x != nil { + if x, ok := x.Type.(*Mutation_InitializeCheckout); ok { + return x.InitializeCheckout + } + } + return nil +} + +func (x *Mutation) GetInventoryReserved() *InventoryReserved { + if x != nil { + if x, ok := x.Type.(*Mutation_InventoryReserved); ok { + return x.InventoryReserved + } + } + return nil +} + +func (x *Mutation) GetAddVoucher() *AddVoucher { + if x != nil { + if x, ok := x.Type.(*Mutation_AddVoucher); ok { + return x.AddVoucher + } + } + return nil +} + +func (x *Mutation) GetRemoveVoucher() *RemoveVoucher { + if x != nil { + if x, ok := x.Type.(*Mutation_RemoveVoucher); ok { + return x.RemoveVoucher + } + } + return nil +} + +func (x *Mutation) GetUpsertSubscriptionDetails() *UpsertSubscriptionDetails { + if x != nil { + if x, ok := x.Type.(*Mutation_UpsertSubscriptionDetails); ok { + return x.UpsertSubscriptionDetails + } + } + return nil +} + +func (x *Mutation) GetPreConditionFailed() *PreConditionFailed { + if x != nil { + if x, ok := x.Type.(*Mutation_PreConditionFailed); ok { + return x.PreConditionFailed + } + } + return nil +} + +func (x *Mutation) GetAddGiftcard() *AddGiftcard { + if x != nil { + if x, ok := x.Type.(*Mutation_AddGiftcard); ok { + return x.AddGiftcard + } + } + return nil +} + +func (x *Mutation) GetRemoveGiftcard() *RemoveGiftcard { + if x != nil { + if x, ok := x.Type.(*Mutation_RemoveGiftcard); ok { + return x.RemoveGiftcard + } + } + return nil +} + +type isMutation_Type interface { + isMutation_Type() +} + +type Mutation_ClearCart struct { + ClearCart *ClearCartRequest `protobuf:"bytes,1,opt,name=clear_cart,json=clearCart,proto3,oneof"` +} + +type Mutation_AddItem struct { + AddItem *AddItem `protobuf:"bytes,2,opt,name=add_item,json=addItem,proto3,oneof"` +} + +type Mutation_RemoveItem struct { + RemoveItem *RemoveItem `protobuf:"bytes,3,opt,name=remove_item,json=removeItem,proto3,oneof"` +} + +type Mutation_ChangeQuantity struct { + ChangeQuantity *ChangeQuantity `protobuf:"bytes,4,opt,name=change_quantity,json=changeQuantity,proto3,oneof"` +} + +type Mutation_SetDelivery struct { + SetDelivery *SetDelivery `protobuf:"bytes,5,opt,name=set_delivery,json=setDelivery,proto3,oneof"` +} + +type Mutation_SetPickupPoint struct { + SetPickupPoint *SetPickupPoint `protobuf:"bytes,6,opt,name=set_pickup_point,json=setPickupPoint,proto3,oneof"` +} + +type Mutation_RemoveDelivery struct { + RemoveDelivery *RemoveDelivery `protobuf:"bytes,7,opt,name=remove_delivery,json=removeDelivery,proto3,oneof"` +} + +type Mutation_SetUserId struct { + SetUserId *SetUserId `protobuf:"bytes,8,opt,name=set_user_id,json=setUserId,proto3,oneof"` +} + +type Mutation_LineItemMarking struct { + LineItemMarking *LineItemMarking `protobuf:"bytes,9,opt,name=line_item_marking,json=lineItemMarking,proto3,oneof"` +} + +type Mutation_RemoveLineItemMarking struct { + RemoveLineItemMarking *RemoveLineItemMarking `protobuf:"bytes,10,opt,name=remove_line_item_marking,json=removeLineItemMarking,proto3,oneof"` +} + +type Mutation_SubscriptionAdded struct { + SubscriptionAdded *SubscriptionAdded `protobuf:"bytes,11,opt,name=subscription_added,json=subscriptionAdded,proto3,oneof"` +} + +type Mutation_PaymentDeclined struct { + PaymentDeclined *PaymentDeclined `protobuf:"bytes,12,opt,name=payment_declined,json=paymentDeclined,proto3,oneof"` +} + +type Mutation_ConfirmationViewed struct { + ConfirmationViewed *ConfirmationViewed `protobuf:"bytes,13,opt,name=confirmation_viewed,json=confirmationViewed,proto3,oneof"` +} + +type Mutation_CreateCheckoutOrder struct { + CreateCheckoutOrder *CreateCheckoutOrder `protobuf:"bytes,14,opt,name=create_checkout_order,json=createCheckoutOrder,proto3,oneof"` +} + +type Mutation_OrderCreated struct { + OrderCreated *OrderCreated `protobuf:"bytes,15,opt,name=order_created,json=orderCreated,proto3,oneof"` +} + +type Mutation_Noop struct { + Noop *Noop `protobuf:"bytes,16,opt,name=noop,proto3,oneof"` +} + +type Mutation_InitializeCheckout struct { + InitializeCheckout *InitializeCheckout `protobuf:"bytes,17,opt,name=initialize_checkout,json=initializeCheckout,proto3,oneof"` +} + +type Mutation_InventoryReserved struct { + InventoryReserved *InventoryReserved `protobuf:"bytes,18,opt,name=inventory_reserved,json=inventoryReserved,proto3,oneof"` +} + +type Mutation_AddVoucher struct { + AddVoucher *AddVoucher `protobuf:"bytes,19,opt,name=add_voucher,json=addVoucher,proto3,oneof"` +} + +type Mutation_RemoveVoucher struct { + RemoveVoucher *RemoveVoucher `protobuf:"bytes,20,opt,name=remove_voucher,json=removeVoucher,proto3,oneof"` +} + +type Mutation_UpsertSubscriptionDetails struct { + UpsertSubscriptionDetails *UpsertSubscriptionDetails `protobuf:"bytes,21,opt,name=upsert_subscription_details,json=upsertSubscriptionDetails,proto3,oneof"` +} + +type Mutation_PreConditionFailed struct { + PreConditionFailed *PreConditionFailed `protobuf:"bytes,22,opt,name=pre_condition_failed,json=preConditionFailed,proto3,oneof"` +} + +type Mutation_AddGiftcard struct { + AddGiftcard *AddGiftcard `protobuf:"bytes,23,opt,name=add_giftcard,json=addGiftcard,proto3,oneof"` +} + +type Mutation_RemoveGiftcard struct { + RemoveGiftcard *RemoveGiftcard `protobuf:"bytes,24,opt,name=remove_giftcard,json=removeGiftcard,proto3,oneof"` +} + +func (*Mutation_ClearCart) isMutation_Type() {} + +func (*Mutation_AddItem) isMutation_Type() {} + +func (*Mutation_RemoveItem) isMutation_Type() {} + +func (*Mutation_ChangeQuantity) isMutation_Type() {} + +func (*Mutation_SetDelivery) isMutation_Type() {} + +func (*Mutation_SetPickupPoint) isMutation_Type() {} + +func (*Mutation_RemoveDelivery) isMutation_Type() {} + +func (*Mutation_SetUserId) isMutation_Type() {} + +func (*Mutation_LineItemMarking) isMutation_Type() {} + +func (*Mutation_RemoveLineItemMarking) isMutation_Type() {} + +func (*Mutation_SubscriptionAdded) isMutation_Type() {} + +func (*Mutation_PaymentDeclined) isMutation_Type() {} + +func (*Mutation_ConfirmationViewed) isMutation_Type() {} + +func (*Mutation_CreateCheckoutOrder) isMutation_Type() {} + +func (*Mutation_OrderCreated) isMutation_Type() {} + +func (*Mutation_Noop) isMutation_Type() {} + +func (*Mutation_InitializeCheckout) isMutation_Type() {} + +func (*Mutation_InventoryReserved) isMutation_Type() {} + +func (*Mutation_AddVoucher) isMutation_Type() {} + +func (*Mutation_RemoveVoucher) isMutation_Type() {} + +func (*Mutation_UpsertSubscriptionDetails) isMutation_Type() {} + +func (*Mutation_PreConditionFailed) isMutation_Type() {} + +func (*Mutation_AddGiftcard) isMutation_Type() {} + +func (*Mutation_RemoveGiftcard) isMutation_Type() {} + var File_messages_proto protoreflect.FileDescriptor var file_messages_proto_rawDesc = string([]byte{ 0x0a, 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x12, 0x0a, 0x10, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x43, 0x61, - 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb7, 0x05, 0x0a, 0x07, 0x41, 0x64, - 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x69, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, 0x1a, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x12, 0x0a, 0x10, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x43, + 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb1, 0x06, 0x0a, 0x07, 0x41, + 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x69, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, + 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x70, + 0x72, 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, + 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x72, 0x67, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x08, 0x6f, 0x72, 0x67, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x10, 0x0a, + 0x03, 0x73, 0x6b, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x6b, 0x75, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x6f, + 0x63, 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x73, 0x74, 0x6f, 0x63, 0x6b, 0x12, + 0x10, 0x0a, 0x03, 0x74, 0x61, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x74, 0x61, + 0x78, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x72, 0x61, 0x6e, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x62, 0x72, 0x61, 0x6e, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, + 0x6f, 0x72, 0x79, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, + 0x6f, 0x72, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x32, + 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, + 0x32, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x33, 0x18, 0x10, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x33, 0x12, + 0x1c, 0x0a, 0x09, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x34, 0x18, 0x11, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x34, 0x12, 0x1c, 0x0a, + 0x09, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x35, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x35, 0x12, 0x1e, 0x0a, 0x0a, 0x64, + 0x69, 0x73, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x64, 0x69, 0x73, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x61, + 0x72, 0x74, 0x69, 0x63, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x61, 0x72, 0x74, 0x69, 0x63, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, + 0x08, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x64, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x6c, + 0x6c, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, + 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x72, 0x79, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x61, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x61, 0x6c, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x6c, 0x65, 0x74, 0x18, 0x0c, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x6c, 0x65, 0x74, 0x88, 0x01, 0x01, + 0x12, 0x1d, 0x0a, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x49, 0x64, 0x18, 0x16, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x01, 0x52, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, + 0x1f, 0x0a, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x17, 0x20, 0x01, 0x28, + 0x0d, 0x48, 0x02, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x88, 0x01, 0x01, + 0x12, 0x10, 0x0a, 0x03, 0x63, 0x67, 0x6d, 0x18, 0x19, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, + 0x67, 0x6d, 0x12, 0x4f, 0x0a, 0x12, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x45, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, 0x03, 0x52, 0x12, 0x72, 0x65, + 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, + 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6f, 0x75, 0x74, 0x6c, 0x65, 0x74, 0x42, 0x0a, + 0x0a, 0x08, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x49, 0x64, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x70, + 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x42, 0x15, 0x0a, 0x13, 0x5f, 0x72, 0x65, 0x73, 0x65, + 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x1c, + 0x0a, 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x0e, 0x0a, 0x02, + 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x49, 0x64, 0x22, 0x3c, 0x0a, 0x0e, + 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x0e, + 0x0a, 0x02, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, - 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, - 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x72, 0x67, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x08, 0x6f, 0x72, 0x67, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, - 0x73, 0x6b, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x6b, 0x75, 0x12, 0x12, - 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x6f, 0x63, - 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x73, 0x74, 0x6f, 0x63, 0x6b, 0x12, 0x10, - 0x0a, 0x03, 0x74, 0x61, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x74, 0x61, 0x78, - 0x12, 0x14, 0x0a, 0x05, 0x62, 0x72, 0x61, 0x6e, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x62, 0x72, 0x61, 0x6e, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, - 0x72, 0x79, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, - 0x72, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x32, 0x18, - 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x32, - 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x33, 0x18, 0x10, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x33, 0x12, 0x1c, - 0x0a, 0x09, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x34, 0x18, 0x11, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x34, 0x12, 0x1c, 0x0a, 0x09, - 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x35, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x35, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x69, - 0x73, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x64, 0x69, 0x73, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x72, - 0x74, 0x69, 0x63, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x61, 0x72, 0x74, 0x69, 0x63, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, - 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x64, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x6c, 0x6c, - 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, - 0x6c, 0x6c, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x79, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x61, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x61, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x6c, 0x65, 0x74, 0x18, 0x0c, 0x20, 0x01, - 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x6c, 0x65, 0x74, 0x88, 0x01, 0x01, 0x12, - 0x1d, 0x0a, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x49, 0x64, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, - 0x48, 0x01, 0x52, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x1f, - 0x0a, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0d, - 0x48, 0x02, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, - 0x09, 0x0a, 0x07, 0x5f, 0x6f, 0x75, 0x74, 0x6c, 0x65, 0x74, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x49, 0x64, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x70, 0x61, 0x72, 0x65, 0x6e, - 0x74, 0x49, 0x64, 0x22, 0x1c, 0x0a, 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x49, 0x74, 0x65, - 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x49, - 0x64, 0x22, 0x3c, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x51, 0x75, 0x61, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x02, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, - 0x86, 0x02, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x12, - 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x69, - 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, - 0x73, 0x12, 0x3c, 0x0a, 0x0b, 0x70, 0x69, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x6f, 0x69, 0x6e, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x73, 0x2e, 0x50, 0x69, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, - 0x0b, 0x70, 0x69, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x12, - 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x7a, 0x69, 0x70, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x7a, 0x69, 0x70, 0x12, 0x1d, 0x0a, 0x07, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, - 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x63, 0x69, - 0x74, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x04, 0x63, 0x69, 0x74, 0x79, - 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x70, 0x69, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x6f, - 0x69, 0x6e, 0x74, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, - 0x07, 0x0a, 0x05, 0x5f, 0x63, 0x69, 0x74, 0x79, 0x22, 0xf9, 0x01, 0x0a, 0x0e, 0x53, 0x65, 0x74, - 0x50, 0x69, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x64, - 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x0a, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x02, 0x52, 0x04, 0x63, 0x69, 0x74, 0x79, 0x88, 0x01, 0x01, 0x12, 0x15, 0x0a, 0x03, - 0x7a, 0x69, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x03, 0x7a, 0x69, 0x70, - 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x04, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x88, - 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, - 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x63, 0x69, 0x74, 0x79, - 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x7a, 0x69, 0x70, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x72, 0x79, 0x22, 0xd6, 0x01, 0x0a, 0x0b, 0x50, 0x69, 0x63, 0x6b, 0x75, 0x70, 0x50, - 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, - 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, - 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, - 0x63, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x04, 0x63, 0x69, - 0x74, 0x79, 0x88, 0x01, 0x01, 0x12, 0x15, 0x0a, 0x03, 0x7a, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x48, 0x03, 0x52, 0x03, 0x7a, 0x69, 0x70, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x04, 0x52, - 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x63, 0x69, 0x74, 0x79, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x7a, 0x69, - 0x70, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x20, 0x0a, - 0x0e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x22, - 0xb9, 0x01, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, - 0x75, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x65, 0x72, 0x6d, 0x73, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x12, 0x1a, 0x0a, - 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, - 0x04, 0x70, 0x75, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x75, 0x73, - 0x68, 0x12, 0x1e, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x40, 0x0a, 0x0c, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6f, - 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x06, 0x0a, - 0x04, 0x4e, 0x6f, 0x6f, 0x70, 0x22, 0x74, 0x0a, 0x12, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, - 0x69, 0x7a, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6f, - 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2c, 0x0a, - 0x11, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, - 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x49, 0x6e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x22, 0x66, 0x0a, 0x11, 0x49, - 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x07, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x22, 0x7c, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x56, 0x6f, 0x75, 0x63, 0x68, 0x65, - 0x72, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x76, - 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x0c, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, - 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x22, 0x1f, 0x0a, 0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x56, 0x6f, 0x75, 0x63, 0x68, - 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, - 0x69, 0x64, 0x22, 0xa7, 0x01, 0x0a, 0x19, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, 0x62, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, - 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x02, - 0x69, 0x64, 0x88, 0x01, 0x01, 0x12, 0x22, 0x0a, 0x0c, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x69, 0x6e, - 0x67, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x66, 0x66, - 0x65, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x73, 0x69, 0x67, - 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x64, - 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, - 0x04, 0x64, 0x61, 0x74, 0x61, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x69, 0x64, 0x22, 0x74, 0x0a, 0x12, - 0x50, 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, - 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x05, 0x69, 0x6e, 0x70, - 0x75, 0x74, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x2e, 0x74, 0x6f, 0x72, 0x6e, 0x62, 0x65, - 0x72, 0x67, 0x2e, 0x6d, 0x65, 0x2f, 0x67, 0x6f, 0x2d, 0x63, 0x61, 0x72, 0x74, 0x2d, 0x61, 0x63, - 0x74, 0x6f, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x86, 0x02, 0x0a, 0x0b, 0x53, + 0x65, 0x74, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x3c, 0x0a, 0x0b, + 0x70, 0x69, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x50, 0x69, 0x63, + 0x6b, 0x75, 0x70, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x70, 0x69, 0x63, 0x6b, + 0x75, 0x70, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x7a, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x7a, 0x69, 0x70, 0x12, 0x1d, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x04, 0x63, 0x69, 0x74, 0x79, 0x88, 0x01, 0x01, 0x42, 0x0e, + 0x0a, 0x0c, 0x5f, 0x70, 0x69, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x42, 0x0a, + 0x0a, 0x08, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x63, + 0x69, 0x74, 0x79, 0x22, 0xf9, 0x01, 0x0a, 0x0e, 0x53, 0x65, 0x74, 0x50, 0x69, 0x63, 0x6b, 0x75, + 0x70, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, + 0x72, 0x79, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x64, 0x65, 0x6c, 0x69, + 0x76, 0x65, 0x72, 0x79, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, + 0x1d, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x01, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, 0x12, 0x17, + 0x0a, 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x04, + 0x63, 0x69, 0x74, 0x79, 0x88, 0x01, 0x01, 0x12, 0x15, 0x0a, 0x03, 0x7a, 0x69, 0x70, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x03, 0x7a, 0x69, 0x70, 0x88, 0x01, 0x01, 0x12, 0x1d, + 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x04, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, + 0x05, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x63, 0x69, 0x74, 0x79, 0x42, 0x06, 0x0a, 0x04, 0x5f, + 0x7a, 0x69, 0x70, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x22, + 0xd6, 0x01, 0x0a, 0x0b, 0x50, 0x69, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x04, 0x63, 0x69, 0x74, 0x79, 0x88, 0x01, 0x01, + 0x12, 0x15, 0x0a, 0x03, 0x7a, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, + 0x03, 0x7a, 0x69, 0x70, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x04, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x72, 0x79, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, + 0x0a, 0x0a, 0x08, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, + 0x63, 0x69, 0x74, 0x79, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x7a, 0x69, 0x70, 0x42, 0x0a, 0x0a, 0x08, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x20, 0x0a, 0x0e, 0x52, 0x65, 0x6d, 0x6f, + 0x76, 0x65, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x22, 0x23, 0x0a, 0x09, 0x53, 0x65, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, + 0x4f, 0x0a, 0x0f, 0x4c, 0x69, 0x6e, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x4d, 0x61, 0x72, 0x6b, 0x69, + 0x6e, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x61, 0x72, 0x6b, 0x69, 0x6e, + 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x61, 0x72, 0x6b, 0x69, 0x6e, 0x67, + 0x22, 0x27, 0x0a, 0x15, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4c, 0x69, 0x6e, 0x65, 0x49, 0x74, + 0x65, 0x6d, 0x4d, 0x61, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x22, 0x71, 0x0a, 0x11, 0x53, 0x75, 0x62, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x65, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x69, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, + 0x69, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, + 0x73, 0x49, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, 0x74, 0x61, 0x69, + 0x6c, 0x73, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0e, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x66, + 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x4d, 0x0a, 0x0f, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x63, 0x6c, 0x69, 0x6e, 0x65, 0x64, 0x12, + 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x17, 0x0a, 0x04, 0x63, 0x6f, 0x64, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x88, + 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x14, 0x0a, 0x12, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x65, 0x77, 0x65, + 0x64, 0x22, 0xb9, 0x01, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x6f, 0x75, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x65, 0x72, + 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x12, + 0x1a, 0x0a, 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x12, 0x0a, 0x04, 0x70, 0x75, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, + 0x75, 0x73, 0x68, 0x12, 0x1e, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x40, 0x0a, + 0x0c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a, + 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, + 0x06, 0x0a, 0x04, 0x4e, 0x6f, 0x6f, 0x70, 0x22, 0x74, 0x0a, 0x12, 0x49, 0x6e, 0x69, 0x74, 0x69, + 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x12, 0x18, 0x0a, + 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x2c, 0x0a, 0x11, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x50, 0x72, 0x6f, 0x67, + 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x70, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x22, 0x66, 0x0a, + 0x11, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x07, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x7c, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x56, 0x6f, 0x75, 0x63, + 0x68, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x22, 0x0a, + 0x0c, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x0c, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x52, 0x75, 0x6c, 0x65, + 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x22, 0x1f, 0x0a, 0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x56, 0x6f, 0x75, + 0x63, 0x68, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x02, 0x69, 0x64, 0x22, 0xa7, 0x01, 0x0a, 0x19, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x53, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, + 0x6c, 0x73, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, + 0x52, 0x02, 0x69, 0x64, 0x88, 0x01, 0x01, 0x12, 0x22, 0x0a, 0x0c, 0x6f, 0x66, 0x66, 0x65, 0x72, + 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, + 0x66, 0x66, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x73, + 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, + 0x79, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x69, 0x64, 0x22, 0x74, + 0x0a, 0x12, 0x50, 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, + 0x69, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x05, 0x69, + 0x6e, 0x70, 0x75, 0x74, 0x22, 0xf6, 0x01, 0x0a, 0x0c, 0x47, 0x69, 0x66, 0x74, 0x63, 0x61, 0x72, + 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x64, + 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x44, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x44, 0x61, 0x74, 0x65, 0x12, + 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x24, 0x0a, + 0x0d, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x3d, 0x0a, + 0x0c, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x48, 0x00, 0x52, 0x0c, 0x64, 0x65, 0x73, + 0x69, 0x67, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x88, 0x01, 0x01, 0x42, 0x0f, 0x0a, 0x0d, + 0x5f, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x41, 0x0a, + 0x0b, 0x41, 0x64, 0x64, 0x47, 0x69, 0x66, 0x74, 0x63, 0x61, 0x72, 0x64, 0x12, 0x32, 0x0a, 0x08, + 0x67, 0x69, 0x66, 0x74, 0x63, 0x61, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, + 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x47, 0x69, 0x66, 0x74, 0x63, 0x61, + 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x63, 0x61, 0x72, 0x64, + 0x22, 0x20, 0x0a, 0x0e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x47, 0x69, 0x66, 0x74, 0x63, 0x61, + 0x72, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, + 0x69, 0x64, 0x22, 0x95, 0x0d, 0x0a, 0x08, 0x4d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x3b, 0x0a, 0x0a, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x5f, 0x63, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x43, + 0x6c, 0x65, 0x61, 0x72, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, + 0x00, 0x52, 0x09, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x43, 0x61, 0x72, 0x74, 0x12, 0x2e, 0x0a, 0x08, + 0x61, 0x64, 0x64, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, + 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x74, 0x65, + 0x6d, 0x48, 0x00, 0x52, 0x07, 0x61, 0x64, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x37, 0x0a, 0x0b, + 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x14, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x52, 0x65, 0x6d, + 0x6f, 0x76, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x48, 0x00, 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x76, + 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x43, 0x0a, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, + 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, + 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x48, 0x00, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x0c, 0x73, 0x65, + 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x15, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x53, 0x65, 0x74, 0x44, + 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x65, 0x74, 0x44, 0x65, + 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x12, 0x44, 0x0a, 0x10, 0x73, 0x65, 0x74, 0x5f, 0x70, 0x69, + 0x63, 0x6b, 0x75, 0x70, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x18, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x53, 0x65, 0x74, 0x50, + 0x69, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x73, 0x65, + 0x74, 0x50, 0x69, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x43, 0x0a, 0x0f, + 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, + 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x48, + 0x00, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, + 0x79, 0x12, 0x35, 0x0a, 0x0b, 0x73, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x73, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x48, 0x00, 0x52, 0x09, 0x73, + 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x47, 0x0a, 0x11, 0x6c, 0x69, 0x6e, 0x65, + 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x6d, 0x61, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x4c, + 0x69, 0x6e, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x4d, 0x61, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x48, 0x00, + 0x52, 0x0f, 0x6c, 0x69, 0x6e, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x4d, 0x61, 0x72, 0x6b, 0x69, 0x6e, + 0x67, 0x12, 0x5a, 0x0a, 0x18, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x6c, 0x69, 0x6e, 0x65, + 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x6d, 0x61, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x52, + 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4c, 0x69, 0x6e, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x4d, 0x61, 0x72, + 0x6b, 0x69, 0x6e, 0x67, 0x48, 0x00, 0x52, 0x15, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4c, 0x69, + 0x6e, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x4d, 0x61, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x12, 0x4c, 0x0a, + 0x12, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x64, + 0x64, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x73, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x41, 0x64, 0x64, 0x65, 0x64, 0x48, 0x00, 0x52, 0x11, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x65, 0x64, 0x12, 0x46, 0x0a, 0x10, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x65, 0x63, 0x6c, 0x69, 0x6e, 0x65, 0x64, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, + 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x63, 0x6c, 0x69, 0x6e, 0x65, 0x64, + 0x48, 0x00, 0x52, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x63, 0x6c, 0x69, + 0x6e, 0x65, 0x64, 0x12, 0x4f, 0x0a, 0x13, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x65, 0x77, 0x65, 0x64, 0x48, 0x00, + 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, + 0x65, 0x77, 0x65, 0x64, 0x12, 0x53, 0x0a, 0x15, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0e, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x48, 0x00, 0x52, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x6f, 0x75, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x0d, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0c, 0x6f, 0x72, 0x64, 0x65, + 0x72, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x04, 0x6e, 0x6f, 0x6f, 0x70, + 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x73, 0x2e, 0x4e, 0x6f, 0x6f, 0x70, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x6f, 0x6f, 0x70, 0x12, 0x4f, + 0x0a, 0x13, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x5f, 0x63, 0x68, 0x65, + 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, + 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x48, 0x00, 0x52, 0x12, 0x69, 0x6e, 0x69, + 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x12, + 0x4c, 0x0a, 0x12, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x72, 0x65, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, + 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x48, 0x00, 0x52, 0x11, 0x69, 0x6e, 0x76, 0x65, + 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x12, 0x37, 0x0a, + 0x0b, 0x61, 0x64, 0x64, 0x5f, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x18, 0x13, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x41, 0x64, + 0x64, 0x56, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x48, 0x00, 0x52, 0x0a, 0x61, 0x64, 0x64, 0x56, + 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x12, 0x40, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, + 0x5f, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, + 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, + 0x56, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x48, 0x00, 0x52, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x76, + 0x65, 0x56, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x12, 0x65, 0x0a, 0x1b, 0x75, 0x70, 0x73, 0x65, + 0x72, 0x74, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x53, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, + 0x6c, 0x73, 0x48, 0x00, 0x52, 0x19, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x53, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, + 0x50, 0x0a, 0x14, 0x70, 0x72, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x50, 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x48, 0x00, 0x52, 0x12, 0x70, + 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, + 0x64, 0x12, 0x3a, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x63, 0x61, 0x72, + 0x64, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x47, 0x69, 0x66, 0x74, 0x63, 0x61, 0x72, 0x64, 0x48, 0x00, + 0x52, 0x0b, 0x61, 0x64, 0x64, 0x47, 0x69, 0x66, 0x74, 0x63, 0x61, 0x72, 0x64, 0x12, 0x43, 0x0a, + 0x0f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x63, 0x61, 0x72, 0x64, + 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x73, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x47, 0x69, 0x66, 0x74, 0x63, 0x61, 0x72, 0x64, + 0x48, 0x00, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x47, 0x69, 0x66, 0x74, 0x63, 0x61, + 0x72, 0x64, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x42, 0x2a, 0x5a, 0x28, 0x67, 0x69, + 0x74, 0x2e, 0x6b, 0x36, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x67, 0x6f, 0x2d, 0x63, 0x61, 0x72, + 0x74, 0x2d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -1407,7 +2486,7 @@ func file_messages_proto_rawDescGZIP() []byte { return file_messages_proto_rawDescData } -var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 17) +var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 27) var file_messages_proto_goTypes = []any{ (*ClearCartRequest)(nil), // 0: messages.ClearCartRequest (*AddItem)(nil), // 1: messages.AddItem @@ -1417,26 +2496,64 @@ var file_messages_proto_goTypes = []any{ (*SetPickupPoint)(nil), // 5: messages.SetPickupPoint (*PickupPoint)(nil), // 6: messages.PickupPoint (*RemoveDelivery)(nil), // 7: messages.RemoveDelivery - (*CreateCheckoutOrder)(nil), // 8: messages.CreateCheckoutOrder - (*OrderCreated)(nil), // 9: messages.OrderCreated - (*Noop)(nil), // 10: messages.Noop - (*InitializeCheckout)(nil), // 11: messages.InitializeCheckout - (*InventoryReserved)(nil), // 12: messages.InventoryReserved - (*AddVoucher)(nil), // 13: messages.AddVoucher - (*RemoveVoucher)(nil), // 14: messages.RemoveVoucher - (*UpsertSubscriptionDetails)(nil), // 15: messages.UpsertSubscriptionDetails - (*PreConditionFailed)(nil), // 16: messages.PreConditionFailed - (*anypb.Any)(nil), // 17: google.protobuf.Any + (*SetUserId)(nil), // 8: messages.SetUserId + (*LineItemMarking)(nil), // 9: messages.LineItemMarking + (*RemoveLineItemMarking)(nil), // 10: messages.RemoveLineItemMarking + (*SubscriptionAdded)(nil), // 11: messages.SubscriptionAdded + (*PaymentDeclined)(nil), // 12: messages.PaymentDeclined + (*ConfirmationViewed)(nil), // 13: messages.ConfirmationViewed + (*CreateCheckoutOrder)(nil), // 14: messages.CreateCheckoutOrder + (*OrderCreated)(nil), // 15: messages.OrderCreated + (*Noop)(nil), // 16: messages.Noop + (*InitializeCheckout)(nil), // 17: messages.InitializeCheckout + (*InventoryReserved)(nil), // 18: messages.InventoryReserved + (*AddVoucher)(nil), // 19: messages.AddVoucher + (*RemoveVoucher)(nil), // 20: messages.RemoveVoucher + (*UpsertSubscriptionDetails)(nil), // 21: messages.UpsertSubscriptionDetails + (*PreConditionFailed)(nil), // 22: messages.PreConditionFailed + (*GiftcardItem)(nil), // 23: messages.GiftcardItem + (*AddGiftcard)(nil), // 24: messages.AddGiftcard + (*RemoveGiftcard)(nil), // 25: messages.RemoveGiftcard + (*Mutation)(nil), // 26: messages.Mutation + (*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp + (*anypb.Any)(nil), // 28: google.protobuf.Any } var file_messages_proto_depIdxs = []int32{ - 6, // 0: messages.SetDelivery.pickupPoint:type_name -> messages.PickupPoint - 17, // 1: messages.UpsertSubscriptionDetails.data:type_name -> google.protobuf.Any - 17, // 2: messages.PreConditionFailed.input:type_name -> google.protobuf.Any - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 27, // 0: messages.AddItem.reservationEndTime:type_name -> google.protobuf.Timestamp + 6, // 1: messages.SetDelivery.pickupPoint:type_name -> messages.PickupPoint + 28, // 2: messages.UpsertSubscriptionDetails.data:type_name -> google.protobuf.Any + 28, // 3: messages.PreConditionFailed.input:type_name -> google.protobuf.Any + 28, // 4: messages.GiftcardItem.designConfig:type_name -> google.protobuf.Any + 23, // 5: messages.AddGiftcard.giftcard:type_name -> messages.GiftcardItem + 0, // 6: messages.Mutation.clear_cart:type_name -> messages.ClearCartRequest + 1, // 7: messages.Mutation.add_item:type_name -> messages.AddItem + 2, // 8: messages.Mutation.remove_item:type_name -> messages.RemoveItem + 3, // 9: messages.Mutation.change_quantity:type_name -> messages.ChangeQuantity + 4, // 10: messages.Mutation.set_delivery:type_name -> messages.SetDelivery + 5, // 11: messages.Mutation.set_pickup_point:type_name -> messages.SetPickupPoint + 7, // 12: messages.Mutation.remove_delivery:type_name -> messages.RemoveDelivery + 8, // 13: messages.Mutation.set_user_id:type_name -> messages.SetUserId + 9, // 14: messages.Mutation.line_item_marking:type_name -> messages.LineItemMarking + 10, // 15: messages.Mutation.remove_line_item_marking:type_name -> messages.RemoveLineItemMarking + 11, // 16: messages.Mutation.subscription_added:type_name -> messages.SubscriptionAdded + 12, // 17: messages.Mutation.payment_declined:type_name -> messages.PaymentDeclined + 13, // 18: messages.Mutation.confirmation_viewed:type_name -> messages.ConfirmationViewed + 14, // 19: messages.Mutation.create_checkout_order:type_name -> messages.CreateCheckoutOrder + 15, // 20: messages.Mutation.order_created:type_name -> messages.OrderCreated + 16, // 21: messages.Mutation.noop:type_name -> messages.Noop + 17, // 22: messages.Mutation.initialize_checkout:type_name -> messages.InitializeCheckout + 18, // 23: messages.Mutation.inventory_reserved:type_name -> messages.InventoryReserved + 19, // 24: messages.Mutation.add_voucher:type_name -> messages.AddVoucher + 20, // 25: messages.Mutation.remove_voucher:type_name -> messages.RemoveVoucher + 21, // 26: messages.Mutation.upsert_subscription_details:type_name -> messages.UpsertSubscriptionDetails + 22, // 27: messages.Mutation.pre_condition_failed:type_name -> messages.PreConditionFailed + 24, // 28: messages.Mutation.add_giftcard:type_name -> messages.AddGiftcard + 25, // 29: messages.Mutation.remove_giftcard:type_name -> messages.RemoveGiftcard + 30, // [30:30] is the sub-list for method output_type + 30, // [30:30] is the sub-list for method input_type + 30, // [30:30] is the sub-list for extension type_name + 30, // [30:30] is the sub-list for extension extendee + 0, // [0:30] is the sub-list for field type_name } func init() { file_messages_proto_init() } @@ -1449,14 +2566,42 @@ func file_messages_proto_init() { file_messages_proto_msgTypes[5].OneofWrappers = []any{} file_messages_proto_msgTypes[6].OneofWrappers = []any{} file_messages_proto_msgTypes[12].OneofWrappers = []any{} - file_messages_proto_msgTypes[15].OneofWrappers = []any{} + file_messages_proto_msgTypes[18].OneofWrappers = []any{} + file_messages_proto_msgTypes[21].OneofWrappers = []any{} + file_messages_proto_msgTypes[23].OneofWrappers = []any{} + file_messages_proto_msgTypes[26].OneofWrappers = []any{ + (*Mutation_ClearCart)(nil), + (*Mutation_AddItem)(nil), + (*Mutation_RemoveItem)(nil), + (*Mutation_ChangeQuantity)(nil), + (*Mutation_SetDelivery)(nil), + (*Mutation_SetPickupPoint)(nil), + (*Mutation_RemoveDelivery)(nil), + (*Mutation_SetUserId)(nil), + (*Mutation_LineItemMarking)(nil), + (*Mutation_RemoveLineItemMarking)(nil), + (*Mutation_SubscriptionAdded)(nil), + (*Mutation_PaymentDeclined)(nil), + (*Mutation_ConfirmationViewed)(nil), + (*Mutation_CreateCheckoutOrder)(nil), + (*Mutation_OrderCreated)(nil), + (*Mutation_Noop)(nil), + (*Mutation_InitializeCheckout)(nil), + (*Mutation_InventoryReserved)(nil), + (*Mutation_AddVoucher)(nil), + (*Mutation_RemoveVoucher)(nil), + (*Mutation_UpsertSubscriptionDetails)(nil), + (*Mutation_PreConditionFailed)(nil), + (*Mutation_AddGiftcard)(nil), + (*Mutation_RemoveGiftcard)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_messages_proto_rawDesc), len(file_messages_proto_rawDesc)), NumEnums: 0, - NumMessages: 17, + NumMessages: 27, NumExtensions: 0, NumServices: 0, }, diff --git a/pkg/promotions/eval.go b/pkg/promotions/eval.go index aebb66a..6923b56 100644 --- a/pkg/promotions/eval.go +++ b/pkg/promotions/eval.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "git.tornberg.me/go-cart-actor/pkg/cart" + "git.k6n.net/go-cart-actor/pkg/cart" ) var errInvalidTimeFormat = errors.New("invalid time format") @@ -26,7 +26,7 @@ func (NoopTracer) Trace(string, map[string]any) {} // for the purpose of promotion condition evaluation. type PromotionItem struct { SKU string - Quantity int + Quantity uint16 Category string PriceIncVat int64 } @@ -36,7 +36,7 @@ type PromotionItem struct { // customer/order metadata. type PromotionEvalContext struct { CartTotalIncVat int64 - TotalItemQuantity int + TotalItemQuantity uint32 Items []PromotionItem CustomerSegment string CustomerLifetimeValue float64 @@ -89,7 +89,7 @@ func NewContextFromCart(g *cart.CartGrain, opts ...ContextOption) *PromotionEval Category: strings.ToLower(category), PriceIncVat: it.Price.IncVat, }) - ctx.TotalItemQuantity += it.Quantity + ctx.TotalItemQuantity += uint32(it.Quantity) } for _, o := range opts { o(ctx) diff --git a/pkg/promotions/eval_test.go b/pkg/promotions/eval_test.go index 8352fa5..be353f2 100644 --- a/pkg/promotions/eval_test.go +++ b/pkg/promotions/eval_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "git.tornberg.me/go-cart-actor/pkg/cart" + "git.k6n.net/go-cart-actor/pkg/cart" ) // --- Helpers --------------------------------------------------------------- @@ -58,7 +58,7 @@ func (t *testTracer) Count(name string) int { func makeCart(totalOverride int64, items []struct { sku string category string - qty int + qty uint16 priceInc int64 }) *cart.CartGrain { g := cart.NewCartGrain(1, time.Now()) @@ -92,7 +92,7 @@ func TestEvaluateRuleBasicAND(t *testing.T) { g := makeCart(12000, []struct { sku string category string - qty int + qty uint16 priceInc int64 }{ {"SKU-1", "summer", 2, 3000}, @@ -226,7 +226,7 @@ func TestEvaluateProductCategoryIN(t *testing.T) { g := makeCart(-1, []struct { sku string category string - qty int + qty uint16 priceInc int64 }{ {"A", "shoes", 1, 5000}, @@ -268,7 +268,7 @@ func TestEvaluateGroupOR(t *testing.T) { g := makeCart(3000, []struct { sku string category string - qty int + qty uint16 priceInc int64 }{ {"ONE", "x", 1, 3000}, diff --git a/pkg/proxy/remotehost.go b/pkg/proxy/remotehost.go index a5854a5..bc42e2b 100644 --- a/pkg/proxy/remotehost.go +++ b/pkg/proxy/remotehost.go @@ -1,6 +1,7 @@ package proxy import ( + "bytes" "context" "errors" "fmt" @@ -9,12 +10,14 @@ import ( "net/http" "time" - messages "git.tornberg.me/go-cart-actor/pkg/messages" + "git.k6n.net/go-cart-actor/pkg/messages" "go.opentelemetry.io/contrib/bridges/otelslog" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" ) // RemoteHost mirrors the lightweight controller used for remote node @@ -37,6 +40,33 @@ var ( logger = otelslog.NewLogger(name) ) +// MockResponseWriter implements http.ResponseWriter to capture responses for proxy calls. +type MockResponseWriter struct { + StatusCode int + HeaderMap http.Header + Body *bytes.Buffer +} + +func NewMockResponseWriter() *MockResponseWriter { + return &MockResponseWriter{ + StatusCode: 200, + HeaderMap: make(http.Header), + Body: &bytes.Buffer{}, + } +} + +func (m *MockResponseWriter) Header() http.Header { + return m.HeaderMap +} + +func (m *MockResponseWriter) Write(data []byte) (int, error) { + return m.Body.Write(data) +} + +func (m *MockResponseWriter) WriteHeader(statusCode int) { + m.StatusCode = statusCode +} + func NewRemoteHost(host string) (*RemoteHost, error) { target := fmt.Sprintf("%s:1337", host) @@ -100,6 +130,32 @@ func (h *RemoteHost) Ping() bool { return true } +func (h *RemoteHost) Apply(ctx context.Context, id uint64, mutation ...proto.Message) (bool, error) { + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + toSend := make([]*anypb.Any, len(mutation)) + for i, msg := range mutation { + anyMsg, err := anypb.New(msg) + if err != nil { + return false, fmt.Errorf("failed to pack message: %w", err) + } + toSend[i] = anyMsg + } + + resp, err := h.controlClient.Apply(ctx, &messages.ApplyRequest{ + Id: id, + Messages: toSend, + }) + if err != nil { + h.missedPings++ + log.Printf("Apply %s failed: %v", h.host, err) + return false, err + } + h.missedPings = 0 + return resp.Accepted, nil +} + func (h *RemoteHost) Negotiate(knownHosts []string) ([]string, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -154,7 +210,7 @@ func (h *RemoteHost) AnnounceExpiry(uids []uint64) { h.missedPings = 0 } -func (h *RemoteHost) Proxy(id uint64, w http.ResponseWriter, r *http.Request) (bool, error) { +func (h *RemoteHost) Proxy(id uint64, w http.ResponseWriter, r *http.Request, customBody io.Reader) (bool, error) { target := fmt.Sprintf("%s%s", h.httpBase, r.URL.RequestURI()) ctx, span := tracer.Start(r.Context(), "remote_proxy") @@ -168,7 +224,11 @@ func (h *RemoteHost) Proxy(id uint64, w http.ResponseWriter, r *http.Request) (b ) logger.InfoContext(ctx, "proxying request", "cartid", id, "host", h.host, "method", r.Method) - req, err := http.NewRequestWithContext(ctx, r.Method, target, r.Body) + var bdy io.Reader = r.Body + if customBody != nil { + bdy = customBody + } + req, err := http.NewRequestWithContext(ctx, r.Method, target, bdy) if err != nil { span.RecordError(err) http.Error(w, "proxy build error", http.StatusBadGateway) diff --git a/pkg/voucher/service.go b/pkg/voucher/service.go index f65cd7c..342a716 100644 --- a/pkg/voucher/service.go +++ b/pkg/voucher/service.go @@ -6,7 +6,7 @@ import ( "fmt" "os" - "git.tornberg.me/go-cart-actor/pkg/messages" + "git.k6n.net/go-cart-actor/pkg/messages" ) type Rule struct { diff --git a/proto/control_plane.proto b/proto/control_plane.proto index 69570fd..cdeb587 100644 --- a/proto/control_plane.proto +++ b/proto/control_plane.proto @@ -2,7 +2,10 @@ syntax = "proto3"; package messages; -option go_package = "git.tornberg.me/go-cart-actor/proto;messages"; +option go_package = "git.k6n.net/go-cart-actor/proto;messages"; + +import "messages.proto"; +import "google/protobuf/any.proto"; // ----------------------------------------------------------------------------- // Control Plane gRPC API @@ -65,6 +68,16 @@ message ExpiryAnnounce { repeated uint64 ids = 2; } +message ApplyRequest { + + uint64 id = 1; + repeated google.protobuf.Any messages = 2; +} + +message ApplyResult { + bool accepted = 1; +} + // ControlPlane defines cluster coordination and ownership operations. service ControlPlane { // Ping for liveness; lightweight health signal. @@ -80,6 +93,7 @@ service ControlPlane { // Ownership announcement: first-touch claim broadcast (idempotent; best-effort). rpc AnnounceOwnership(OwnershipAnnounce) returns (OwnerChangeAck); + rpc Apply(ApplyRequest) returns (ApplyResult); // Expiry announcement: drop remote ownership hints when local TTL expires. rpc AnnounceExpiry(ExpiryAnnounce) returns (OwnerChangeAck); diff --git a/proto/messages.proto b/proto/messages.proto index 1c237a8..6e71140 100644 --- a/proto/messages.proto +++ b/proto/messages.proto @@ -1,8 +1,9 @@ syntax = "proto3"; package messages; -option go_package = "git.tornberg.me/go-cart-actor/proto;messages"; +option go_package = "git.k6n.net/go-cart-actor/proto;messages"; import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; message ClearCartRequest {} @@ -31,6 +32,8 @@ message AddItem { optional string outlet = 12; optional string storeId = 22; optional uint32 parentId = 23; + string cgm = 25; + optional google.protobuf.Timestamp reservationEndTime = 26; } message RemoveItem { uint32 Id = 1; } @@ -71,6 +74,35 @@ message PickupPoint { message RemoveDelivery { uint32 id = 1; } +message SetUserId { + string userId = 1; +} + +message LineItemMarking { + uint32 id = 1; + uint32 type = 2; + string marking = 3; +} + +message RemoveLineItemMarking { + uint32 id = 1; +} + +message SubscriptionAdded { + uint32 itemId = 1; + string detailsId = 3; + string orderReference = 4; +} + +message PaymentDeclined { + string message = 1; + optional string code = 2; +} + +message ConfirmationViewed { + +} + message CreateCheckoutOrder { string terms = 1; string checkout = 2; @@ -122,3 +154,49 @@ message PreConditionFailed { string error = 2; google.protobuf.Any input = 3; } + +message GiftcardItem { + int64 value = 1; + string deliveryDate = 2; + string recipient = 3; + string recipientType = 4; + string message = 5; + optional google.protobuf.Any designConfig = 6; +} + +message AddGiftcard { + GiftcardItem giftcard = 1; +} + +message RemoveGiftcard { + uint32 id = 1; +} + +message Mutation { + oneof type { + ClearCartRequest clear_cart = 1; + AddItem add_item = 2; + RemoveItem remove_item = 3; + ChangeQuantity change_quantity = 4; + SetDelivery set_delivery = 5; + SetPickupPoint set_pickup_point = 6; + RemoveDelivery remove_delivery = 7; + SetUserId set_user_id = 8; + LineItemMarking line_item_marking = 9; + RemoveLineItemMarking remove_line_item_marking = 10; + SubscriptionAdded subscription_added = 11; + PaymentDeclined payment_declined = 12; + ConfirmationViewed confirmation_viewed = 13; + CreateCheckoutOrder create_checkout_order = 14; + OrderCreated order_created = 15; + Noop noop = 16; + InitializeCheckout initialize_checkout = 17; + InventoryReserved inventory_reserved = 18; + AddVoucher add_voucher = 19; + RemoveVoucher remove_voucher = 20; + UpsertSubscriptionDetails upsert_subscription_details = 21; + PreConditionFailed pre_condition_failed = 22; + AddGiftcard add_giftcard = 23; + RemoveGiftcard remove_giftcard = 24; + } +}