From 71fc23bf50116d97d981aa87448b3d22b01f2e8d Mon Sep 17 00:00:00 2001 From: matst80 Date: Thu, 16 Oct 2025 22:49:12 +0200 Subject: [PATCH 01/13] update --- cmd/backoffice/fileserver.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/cmd/backoffice/fileserver.go b/cmd/backoffice/fileserver.go index dfe8d0b..6fe0f6c 100644 --- a/cmd/backoffice/fileserver.go +++ b/cmd/backoffice/fileserver.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "io/fs" "net/http" "os" @@ -156,6 +157,34 @@ func (fs *FileServer) CartsHandler(w http.ResponseWriter, r *http.Request) { }) } +func (fs *FileServer) PromotionsHandler(w http.ResponseWriter, r *http.Request) { + fileName := filepath.Join(fs.dataDir, "promotions.json") + if r.Method == http.MethodGet { + file, err := os.Open(fileName) + if err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + defer file.Close() + + io.Copy(w, file) + return + } + if r.Method == http.MethodPost { + file, err := os.Create(fileName) + if err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + defer file.Close() + + io.Copy(file, r.Body) + return + } + + w.WriteHeader(http.StatusMethodNotAllowed) +} + type JsonError struct { Error string `json:"error"` } From a1833d668518d07ed75a35bd996f0f919541f34d Mon Sep 17 00:00:00 2001 From: matst80 Date: Thu, 16 Oct 2025 22:54:08 +0200 Subject: [PATCH 02/13] missed save --- cmd/backoffice/main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/backoffice/main.go b/cmd/backoffice/main.go index 9d41b36..5f9ac87 100644 --- a/cmd/backoffice/main.go +++ b/cmd/backoffice/main.go @@ -89,6 +89,8 @@ func main() { mux := http.NewServeMux() mux.HandleFunc("GET /carts", fs.CartsHandler) mux.HandleFunc("GET /cart/{id}", fs.CartHandler) + mux.HandleFunc("/promotions", fs.PromotionsHandler) + mux.HandleFunc("/ws", hub.ServeWS) mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) From 918aa7d26528b67c9c2c7a59ae984cccb25668bc Mon Sep 17 00:00:00 2001 From: matst80 Date: Fri, 17 Oct 2025 09:23:05 +0200 Subject: [PATCH 03/13] promotion types --- cmd/backoffice/fileserver.go | 16 ++ cmd/backoffice/main.go | 1 + pkg/promotions/type_test.go | 443 +++++++++++++++++++++++++++++++++++ pkg/promotions/types.go | 371 +++++++++++++++++++++++++++++ 4 files changed, 831 insertions(+) create mode 100644 pkg/promotions/type_test.go create mode 100644 pkg/promotions/types.go diff --git a/cmd/backoffice/fileserver.go b/cmd/backoffice/fileserver.go index 15dbe62..21106b0 100644 --- a/cmd/backoffice/fileserver.go +++ b/cmd/backoffice/fileserver.go @@ -184,6 +184,22 @@ func (fs *FileServer) PromotionsHandler(w http.ResponseWriter, r *http.Request) w.WriteHeader(http.StatusMethodNotAllowed) } +func (fs *FileServer) PromotionPartHandler(w http.ResponseWriter, r *http.Request) { + idStr := r.PathValue("id") + if idStr == "" { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "missing id") + return + } + _, ok := isValidId(idStr) + if !ok { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "invalid id %s", idStr) + return + } + w.WriteHeader(http.StatusNotImplemented) +} + type JsonError struct { Error string `json:"error"` } diff --git a/cmd/backoffice/main.go b/cmd/backoffice/main.go index 5f9ac87..7f489ce 100644 --- a/cmd/backoffice/main.go +++ b/cmd/backoffice/main.go @@ -90,6 +90,7 @@ func main() { mux.HandleFunc("GET /carts", fs.CartsHandler) mux.HandleFunc("GET /cart/{id}", fs.CartHandler) mux.HandleFunc("/promotions", fs.PromotionsHandler) + mux.HandleFunc("/promotion/{id}", fs.PromotionPartHandler) mux.HandleFunc("/ws", hub.ServeWS) mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/promotions/type_test.go b/pkg/promotions/type_test.go new file mode 100644 index 0000000..e65efed --- /dev/null +++ b/pkg/promotions/type_test.go @@ -0,0 +1,443 @@ +package promotions + +import ( + "encoding/json" + "testing" +) + +// sampleJSON mirrors the user's full example data (all three rules) +var sampleJSON = []byte(`[ + { + "id": "1", + "name": "Summer Sale 2024", + "description": "20% off on all summer items", + "status": "active", + "priority": 1, + "startDate": "2024-06-01", + "endDate": "2024-08-31", + "conditions": [ + { + "id": "group1", + "type": "group", + "operator": "AND", + "conditions": [ + { + "id": "c1", + "type": "product_category", + "operator": "in", + "value": ["summer", "beachwear"], + "label": "Product category is Summer or Beachwear" + }, + { + "id": "c2", + "type": "cart_total", + "operator": "greater_or_equal", + "value": 50, + "label": "Cart total is at least $50" + } + ] + } + ], + "actions": [ + { + "id": "a1", + "type": "percentage_discount", + "value": 20, + "label": "20% discount" + } + ], + "usageLimit": 1000, + "usageCount": 342, + "createdAt": "2024-05-15T10:00:00Z", + "updatedAt": "2024-05-20T14:30:00Z", + "createdBy": "admin@example.com", + "tags": ["seasonal", "summer"] + }, + { + "id": "2", + "name": "VIP Customer Exclusive", + "description": "Free shipping for VIP customers", + "status": "active", + "priority": 2, + "startDate": "2024-01-01", + "endDate": null, + "conditions": [ + { + "id": "c3", + "type": "customer_segment", + "operator": "equals", + "value": "vip", + "label": "Customer segment is VIP" + } + ], + "actions": [ + { + "id": "a2", + "type": "free_shipping", + "value": 0, + "label": "Free shipping" + } + ], + "usageCount": 1523, + "createdAt": "2023-12-15T09:00:00Z", + "updatedAt": "2024-01-05T11:20:00Z", + "createdBy": "marketing@example.com", + "tags": ["vip", "loyalty"] + }, + { + "id": "3", + "name": "Buy 2 Get 1 Free", + "description": "Buy 2 items, get the cheapest one free", + "status": "scheduled", + "priority": 3, + "startDate": "2024-12-01", + "endDate": "2024-12-25", + "conditions": [ + { + "id": "c4", + "type": "item_quantity", + "operator": "greater_or_equal", + "value": 3, + "label": "Cart has at least 3 items" + } + ], + "actions": [ + { + "id": "a3", + "type": "buy_x_get_y", + "value": 0, + "config": { "buy": 2, "get": 1, "discount": 100 }, + "label": "Buy 2 Get 1 Free" + } + ], + "usageCount": 0, + "createdAt": "2024-11-01T08:00:00Z", + "updatedAt": "2024-11-01T08:00:00Z", + "createdBy": "admin@example.com", + "tags": ["holiday", "christmas"] + } +]`) + +func TestDecodePromotionRulesBasic(t *testing.T) { + rules, err := DecodePromotionRules(sampleJSON) + if err != nil { + t.Fatalf("DecodePromotionRules failed: %v", err) + } + if len(rules) != 3 { + t.Fatalf("expected 3 rules, got %d", len(rules)) + } + + // Rule 1 checks + r1 := rules[0] + if r1.ID != "1" { + t.Errorf("rule[0].ID = %s, want 1", r1.ID) + } + if r1.Status != StatusActive { + t.Errorf("rule[0].Status = %s, want %s", r1.Status, StatusActive) + } + if r1.EndDate == nil || *r1.EndDate != "2024-08-31" { + t.Errorf("rule[0].EndDate = %v, want 2024-08-31", r1.EndDate) + } + if r1.UsageLimit == nil || *r1.UsageLimit != 1000 { + t.Errorf("rule[0].UsageLimit = %v, want 1000", r1.UsageLimit) + } + + // Rule 2 checks + r2 := rules[1] + if r2.ID != "2" { + t.Errorf("rule[1].ID = %s, want 2", r2.ID) + } + if r2.EndDate != nil { + t.Errorf("rule[1].EndDate should be nil (from null), got %v", *r2.EndDate) + } + if r2.UsageLimit != nil { + t.Errorf("rule[1].UsageLimit should be nil (missing), got %v", *r2.UsageLimit) + } +} + +func TestConditionDecoding(t *testing.T) { + rules, err := DecodePromotionRules(sampleJSON) + if err != nil { + t.Fatalf("DecodePromotionRules failed: %v", err) + } + r1 := rules[0] + if len(r1.Conditions) != 1 { + t.Fatalf("expected 1 top-level condition group, got %d", len(r1.Conditions)) + } + + grp, ok := r1.Conditions[0].(ConditionGroup) + if !ok { + t.Fatalf("top-level condition is not a group, type=%T", r1.Conditions[0]) + } + if grp.Operator != LogicAND { + t.Errorf("group operator = %s, want AND", grp.Operator) + } + + if len(grp.Conditions) != 2 { + t.Fatalf("expected 2 child conditions, got %d", len(grp.Conditions)) + } + + // First child: product_category condition with slice value + c0, ok := grp.Conditions[0].(BaseCondition) + if !ok { + t.Fatalf("first child not BaseCondition, got %T", grp.Conditions[0]) + } + if c0.Type != CondProductCategory { + t.Errorf("first child type = %s, want %s", c0.Type, CondProductCategory) + } + if c0.Operator != OpIn { + t.Errorf("first child operator = %s, want %s", c0.Operator, OpIn) + } + if arr, ok := c0.Value.AsStringSlice(); !ok || len(arr) != 2 || arr[0] != "summer" { + t.Errorf("expected string slice value [summer,...], got %v", arr) + } + + // Second child: cart_total numeric + c1, ok := grp.Conditions[1].(BaseCondition) + if !ok { + t.Fatalf("second child not BaseCondition, got %T", grp.Conditions[1]) + } + if c1.Type != CondCartTotal { + t.Errorf("second child type = %s, want %s", c1.Type, CondCartTotal) + } + if val, ok := c1.Value.AsFloat64(); !ok || val != 50 { + t.Errorf("expected numeric value 50, got %v (ok=%v)", val, ok) + } +} + +func TestConditionValueHelpers(t *testing.T) { + tests := []struct { + name string + jsonVal string + wantStr string + wantNum *float64 + wantSS []string + wantFS []float64 + }{ + { + name: "string value", + jsonVal: `"vip"`, + wantStr: "vip", + }, + { + name: "number value", + jsonVal: `42`, + wantNum: floatPtr(42), + }, + { + name: "string slice", + jsonVal: `["a","b"]`, + wantSS: []string{"a", "b"}, + }, + { + name: "number slice int", + jsonVal: `[1,2,3]`, + wantFS: []float64{1, 2, 3}, + }, + { + name: "number slice float", + jsonVal: `[1.5,2.25]`, + wantFS: []float64{1.5, 2.25}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var cv ConditionValue + if err := json.Unmarshal([]byte(tc.jsonVal), &cv); err != nil { + t.Fatalf("unmarshal value failed: %v", err) + } + if tc.wantStr != "" { + if got, ok := cv.AsString(); !ok || got != tc.wantStr { + t.Errorf("AsString got=%q ok=%v want=%q", got, ok, tc.wantStr) + } + } + if tc.wantNum != nil { + if got, ok := cv.AsFloat64(); !ok || got != *tc.wantNum { + t.Errorf("AsFloat64 got=%v ok=%v want=%v", got, ok, *tc.wantNum) + } + } + if tc.wantSS != nil { + if got, ok := cv.AsStringSlice(); !ok || len(got) != len(tc.wantSS) || got[0] != tc.wantSS[0] { + t.Errorf("AsStringSlice got=%v ok=%v want=%v", got, ok, tc.wantSS) + } + } + if tc.wantFS != nil { + if got, ok := cv.AsFloat64Slice(); !ok || len(got) != len(tc.wantFS) { + t.Errorf("AsFloat64Slice got=%v ok=%v want=%v", got, ok, tc.wantFS) + } else { + for i := range got { + if got[i] != tc.wantFS[i] { + t.Errorf("AsFloat64Slice[%d]=%v want=%v", i, got[i], tc.wantFS[i]) + } + } + } + } + }) + } +} + +func TestWalkConditionsTraversal(t *testing.T) { + rules, err := DecodePromotionRules(sampleJSON) + if err != nil { + t.Fatalf("DecodePromotionRules failed: %v", err) + } + visited := 0 + WalkConditions(rules[0].Conditions, func(c Condition) bool { + visited++ + return true + }) + // group + 2 children + if visited != 3 { + t.Errorf("expected 3 visited conditions, got %d", visited) + } +} + +func TestActionBundleConfigParsing(t *testing.T) { + jsonData := []byte(`[ + { + "id": "bundle-1", + "name": "Bundle Deal", + "description": "Fixed price bundle", + "status": "active", + "priority": 1, + "startDate": "2024-01-01", + "endDate": null, + "conditions": [], + "actions": [ + { + "id": "act-bundle", + "type": "bundle_discount", + "value": 0, + "bundleConfig": { + "containers": [ + { + "id": "cont1", + "name": "Shoes", + "quantity": 2, + "selectionType": "any", + "qualifyingRules": { + "type": "category", + "value": "shoes" + } + }, + { + "id": "cont2", + "name": "Socks", + "quantity": 3, + "selectionType": "specific", + "qualifyingRules": { + "type": "product_ids", + "value": ["sock-1", "sock-2"] + }, + "allowedProducts": ["sock-1","sock-2"] + } + ], + "pricing": { + "type": "fixed_price", + "value": 49.99 + }, + "requireAllContainers": true + }, + "config": { "note": "Bundle applies to footwear + socks" } + } + ], + "usageCount": 0, + "createdAt": "2024-01-01T00:00:00Z", + "updatedAt": "2024-01-01T00:00:00Z", + "createdBy": "bundle@example.com", + "tags": ["bundle","footwear"] + } + ]`) + rules, err := DecodePromotionRules(jsonData) + if err != nil { + t.Fatalf("decode failed: %v", err) + } + if len(rules) != 1 { + t.Fatalf("expected 1 rule, got %d", len(rules)) + } + if len(rules[0].Actions) != 1 { + t.Fatalf("expected 1 action, got %d", len(rules[0].Actions)) + } + act := rules[0].Actions[0] + if act.Type != ActionBundleDiscount { + t.Fatalf("action type = %s, want %s", act.Type, ActionBundleDiscount) + } + if act.BundleConfig == nil { + t.Fatalf("bundleConfig nil") + } + if act.BundleConfig.Pricing.Type != "fixed_price" { + t.Errorf("pricing.type = %s, want fixed_price", act.BundleConfig.Pricing.Type) + } + if act.BundleConfig.Pricing.Value != 49.99 { + t.Errorf("pricing.value = %v, want 49.99", act.BundleConfig.Pricing.Value) + } + if !act.BundleConfig.RequireAllContainers { + t.Errorf("RequireAllContainers expected true") + } + if len(act.BundleConfig.Containers) != 2 { + t.Fatalf("expected 2 containers, got %d", len(act.BundleConfig.Containers)) + } + if act.Config == nil || act.Config["note"] != "Bundle applies to footwear + socks" { + t.Errorf("config.note mismatch: %v", act.Config) + } +} + +func TestConditionValueInvalidTypes(t *testing.T) { + cases := []struct { + name string + raw string + }{ + {"object", `{}`}, + {"booleanTrue", `true`}, + {"booleanFalse", `false`}, + {"null", `null`}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + var cv ConditionValue + if err := json.Unmarshal([]byte(tc.raw), &cv); err != nil { + t.Fatalf("unmarshal failed: %v", err) + } + if s, ok := cv.AsString(); ok { + t.Errorf("AsString unexpectedly succeeded (%q) for %s", s, tc.name) + } + if n, ok := cv.AsFloat64(); ok { + t.Errorf("AsFloat64 unexpectedly succeeded (%v) for %s", n, tc.name) + } + if ss, ok := cv.AsStringSlice(); ok { + t.Errorf("AsStringSlice unexpectedly succeeded (%v) for %s", ss, tc.name) + } + if fs, ok := cv.AsFloat64Slice(); ok { + t.Errorf("AsFloat64Slice unexpectedly succeeded (%v) for %s", fs, tc.name) + } + }) + } +} + +func TestPromotionRuleRoundTrip(t *testing.T) { + orig, err := DecodePromotionRules(sampleJSON) + if err != nil { + t.Fatalf("initial decode failed: %v", err) + } + data, err := json.Marshal(orig) + if err != nil { + t.Fatalf("marshal failed: %v", err) + } + round, err := DecodePromotionRules(data) + if err != nil { + t.Fatalf("round-trip decode failed: %v", err) + } + if len(orig) != len(round) { + t.Fatalf("rule count mismatch: orig=%d round=%d", len(orig), len(round)) + } + // spot-check first rule + if orig[0].Name != round[0].Name { + t.Errorf("first rule name mismatch: %s vs %s", orig[0].Name, round[0].Name) + } + if len(orig[0].Conditions) != len(round[0].Conditions) { + t.Errorf("first rule condition count mismatch: %d vs %d", len(orig[0].Conditions), len(round[0].Conditions)) + } +} + +func floatPtr(f float64) *float64 { return &f } diff --git a/pkg/promotions/types.go b/pkg/promotions/types.go new file mode 100644 index 0000000..3646c60 --- /dev/null +++ b/pkg/promotions/types.go @@ -0,0 +1,371 @@ +package promotions + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" +) + +// ----------------------------- +// Enum-like string types +// ----------------------------- + +type ConditionOperator string + +const ( + OpEquals ConditionOperator = "equals" + OpNotEquals ConditionOperator = "not_equals" + OpGreaterThan ConditionOperator = "greater_than" + OpLessThan ConditionOperator = "less_than" + OpGreaterOrEqual ConditionOperator = "greater_or_equal" + OpLessOrEqual ConditionOperator = "less_or_equal" + OpContains ConditionOperator = "contains" + OpNotContains ConditionOperator = "not_contains" + OpIn ConditionOperator = "in" + OpNotIn ConditionOperator = "not_in" +) + +type ConditionType string + +const ( + CondCartTotal ConditionType = "cart_total" + CondItemQuantity ConditionType = "item_quantity" + CondCustomerSegment ConditionType = "customer_segment" + CondProductCategory ConditionType = "product_category" + CondProductID ConditionType = "product_id" + CondCustomerLifetime ConditionType = "customer_lifetime_value" + CondOrderCount ConditionType = "order_count" + CondDateRange ConditionType = "date_range" + CondDayOfWeek ConditionType = "day_of_week" + CondTimeOfDay ConditionType = "time_of_day" + CondGroup ConditionType = "group" // synthetic value for groups +) + +type ActionType string + +const ( + ActionPercentageDiscount ActionType = "percentage_discount" + ActionFixedDiscount ActionType = "fixed_discount" + ActionFreeShipping ActionType = "free_shipping" + ActionBuyXGetY ActionType = "buy_x_get_y" + ActionTieredDiscount ActionType = "tiered_discount" + ActionBundleDiscount ActionType = "bundle_discount" +) + +type LogicOperator string + +const ( + LogicAND LogicOperator = "AND" + LogicOR LogicOperator = "OR" +) + +type PromotionStatus string + +const ( + StatusActive PromotionStatus = "active" + StatusInactive PromotionStatus = "inactive" + StatusScheduled PromotionStatus = "scheduled" + StatusExpired PromotionStatus = "expired" +) + +// ----------------------------- +// Condition Value (union) +// ----------------------------- +// +// Represents: string | number | []string | []number +// We store raw JSON and lazily interpret. + +type ConditionValue struct { + Raw json.RawMessage +} + +func (cv *ConditionValue) UnmarshalJSON(b []byte) error { + if len(b) == 0 { + return errors.New("empty ConditionValue") + } + // Just store raw; interpretation happens via helpers. + cv.Raw = append(cv.Raw[0:0], b...) + return nil +} + +// Helpers to interpret value: +func (cv ConditionValue) AsString() (string, bool) { + // Treat explicit JSON null as invalid + if string(cv.Raw) == "null" { + return "", false + } + var s string + if err := json.Unmarshal(cv.Raw, &s); err == nil { + return s, true + } + return "", false +} + +func (cv ConditionValue) AsFloat64() (float64, bool) { + if string(cv.Raw) == "null" { + return 0, false + } + var f float64 + if err := json.Unmarshal(cv.Raw, &f); err == nil { + return f, true + } + // Attempt integer decode into float64 + var i int64 + if err := json.Unmarshal(cv.Raw, &i); err == nil { + return float64(i), true + } + return 0, false +} + +func (cv ConditionValue) AsStringSlice() ([]string, bool) { + if string(cv.Raw) == "null" { + return nil, false + } + var arr []string + if err := json.Unmarshal(cv.Raw, &arr); err == nil { + return arr, true + } + return nil, false +} + +func (cv ConditionValue) AsFloat64Slice() ([]float64, bool) { + if string(cv.Raw) == "null" { + return nil, false + } + var arrNum []float64 + if err := json.Unmarshal(cv.Raw, &arrNum); err == nil { + return arrNum, true + } + // Try []int -> []float64 + var arrInt []int64 + if err := json.Unmarshal(cv.Raw, &arrInt); err == nil { + out := make([]float64, len(arrInt)) + for i, v := range arrInt { + out[i] = float64(v) + } + return out, true + } + return nil, false +} + +func (cv ConditionValue) String() string { + if s, ok := cv.AsString(); ok { + return s + } + if f, ok := cv.AsFloat64(); ok { + return strconv.FormatFloat(f, 'f', -1, 64) + } + if ss, ok := cv.AsStringSlice(); ok { + return fmt.Sprintf("%v", ss) + } + if fs, ok := cv.AsFloat64Slice(); ok { + return fmt.Sprintf("%v", fs) + } + return string(cv.Raw) +} + +// ----------------------------- +// BaseCondition +// ----------------------------- + +type BaseCondition struct { + ID string `json:"id"` + Type ConditionType `json:"type"` + Operator ConditionOperator `json:"operator"` + Value ConditionValue `json:"value"` + Label *string `json:"label,omitempty"` +} + +func (b BaseCondition) IsGroup() bool { return false } + +// ----------------------------- +// ConditionGroup +// ----------------------------- + +type ConditionGroup struct { + ID string `json:"id"` + Type string `json:"type"` // always "group" + Operator LogicOperator `json:"operator"` + Conditions Conditions `json:"conditions"` +} + +// Custom unmarshaller ensures nested polymorphic conditions are decoded +// using the Conditions type (which applies the raw element discriminator). +func (g *ConditionGroup) UnmarshalJSON(b []byte) error { + type alias struct { + ID string `json:"id"` + Type string `json:"type"` + Operator LogicOperator `json:"operator"` + Conditions json.RawMessage `json:"conditions"` + } + var a alias + if err := json.Unmarshal(b, &a); err != nil { + return err + } + // Basic validation + if a.Type != "group" { + return fmt.Errorf("ConditionGroup expected type 'group', got %q", a.Type) + } + + var conds Conditions + if len(a.Conditions) > 0 { + if err := json.Unmarshal(a.Conditions, &conds); err != nil { + return err + } + } + + g.ID = a.ID + g.Type = a.Type + g.Operator = a.Operator + g.Conditions = conds + return nil +} + +func (g ConditionGroup) IsGroup() bool { return true } + +// ----------------------------- +// Condition interface + slice +// ----------------------------- + +type Condition interface { + IsGroup() bool +} + +// Internal wrapper to help decode each element. +type rawCond struct { + Type string `json:"type"` +} + +// Custom unmarshaler for a Condition slice +type Conditions []Condition + +func (cs *Conditions) UnmarshalJSON(b []byte) error { + var rawList []json.RawMessage + if err := json.Unmarshal(b, &rawList); err != nil { + return err + } + out := make([]Condition, 0, len(rawList)) + for _, elem := range rawList { + var hdr rawCond + if err := json.Unmarshal(elem, &hdr); err != nil { + return err + } + if hdr.Type == "group" { + var grp ConditionGroup + if err := json.Unmarshal(elem, &grp); err != nil { + return err + } + // Recursively decode grp.Conditions (already handled because field type is []Condition) + out = append(out, grp) + } else { + var bc BaseCondition + if err := json.Unmarshal(elem, &bc); err != nil { + return err + } + out = append(out, bc) + } + } + *cs = out + return nil +} + +// ----------------------------- +// Bundle Structures +// ----------------------------- + +type BundleQualifyingRules struct { + Type string `json:"type"` // "category" | "product_ids" | "tag" | "all" + Value interface{} `json:"value"` // string or []string +} + +type BundleContainer struct { + ID string `json:"id"` + Name string `json:"name"` + Quantity int `json:"quantity"` + SelectionType string `json:"selectionType"` // "any" | "specific" + QualifyingRules BundleQualifyingRules `json:"qualifyingRules"` + AllowedProducts []string `json:"allowedProducts,omitempty"` +} + +type BundlePricing struct { + Type string `json:"type"` // "fixed_price" | "percentage_discount" | "fixed_discount" + Value float64 `json:"value"` +} + +type BundleConfig struct { + Containers []BundleContainer `json:"containers"` + Pricing BundlePricing `json:"pricing"` + RequireAllContainers bool `json:"requireAllContainers"` +} + +// ----------------------------- +// Action +// ----------------------------- + +type Action struct { + ID string `json:"id"` + Type ActionType `json:"type"` + Value interface{} `json:"value"` // number or string + Config map[string]interface{} `json:"config,omitempty"` + BundleConfig *BundleConfig `json:"bundleConfig,omitempty"` + Label *string `json:"label,omitempty"` +} + +// ----------------------------- +// Promotion Rule +// ----------------------------- + +type PromotionRule struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Status PromotionStatus `json:"status"` + Priority int `json:"priority"` + StartDate string `json:"startDate"` + EndDate *string `json:"endDate"` // null -> nil + Conditions Conditions `json:"conditions"` + Actions []Action `json:"actions"` + UsageLimit *int `json:"usageLimit,omitempty"` + UsageCount int `json:"usageCount"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` + CreatedBy string `json:"createdBy"` + Tags []string `json:"tags"` +} + +// ----------------------------- +// Promotion Stats +// ----------------------------- + +type PromotionStats struct { + TotalPromotions int `json:"totalPromotions"` + ActivePromotions int `json:"activePromotions"` + TotalRevenue float64 `json:"totalRevenue"` + TotalOrders int `json:"totalOrders"` + AverageDiscount float64 `json:"averageDiscount"` +} + +// ----------------------------- +// Utility: Decode array of rules +// ----------------------------- + +func DecodePromotionRules(data []byte) ([]PromotionRule, error) { + var rules []PromotionRule + if err := json.Unmarshal(data, &rules); err != nil { + return nil, err + } + return rules, nil +} + +// Example helper to inspect conditions programmatically. +func WalkConditions(conds []Condition, fn func(c Condition) bool) { + for _, c := range conds { + if !fn(c) { + return + } + if grp, ok := c.(ConditionGroup); ok { + WalkConditions(grp.Conditions, fn) + } + } +} From 4a54661f24a48c5bb9e0db28d5bf325b2ecfbf7e Mon Sep 17 00:00:00 2001 From: matst80 Date: Fri, 17 Oct 2025 12:48:43 +0200 Subject: [PATCH 04/13] add voucher store --- cmd/backoffice/fileserver.go | 28 ++++++++++++++++++++++++++++ cmd/backoffice/main.go | 1 + 2 files changed, 29 insertions(+) diff --git a/cmd/backoffice/fileserver.go b/cmd/backoffice/fileserver.go index 21106b0..69da05c 100644 --- a/cmd/backoffice/fileserver.go +++ b/cmd/backoffice/fileserver.go @@ -184,6 +184,34 @@ func (fs *FileServer) PromotionsHandler(w http.ResponseWriter, r *http.Request) w.WriteHeader(http.StatusMethodNotAllowed) } +func (fs *FileServer) VoucherHandler(w http.ResponseWriter, r *http.Request) { + fileName := filepath.Join(fs.dataDir, "vouchers.json") + if r.Method == http.MethodGet { + file, err := os.Open(fileName) + if err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + defer file.Close() + + io.Copy(w, file) + return + } + if r.Method == http.MethodPost { + file, err := os.Create(fileName) + if err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + defer file.Close() + + io.Copy(file, r.Body) + return + } + + w.WriteHeader(http.StatusMethodNotAllowed) +} + func (fs *FileServer) PromotionPartHandler(w http.ResponseWriter, r *http.Request) { idStr := r.PathValue("id") if idStr == "" { diff --git a/cmd/backoffice/main.go b/cmd/backoffice/main.go index 7f489ce..402ec81 100644 --- a/cmd/backoffice/main.go +++ b/cmd/backoffice/main.go @@ -90,6 +90,7 @@ func main() { mux.HandleFunc("GET /carts", fs.CartsHandler) mux.HandleFunc("GET /cart/{id}", fs.CartHandler) mux.HandleFunc("/promotions", fs.PromotionsHandler) + mux.HandleFunc("/vouchers", fs.VoucherHandler) mux.HandleFunc("/promotion/{id}", fs.PromotionPartHandler) mux.HandleFunc("/ws", hub.ServeWS) From 915d845014daa1b3c3c5d7d6d1989c5aa8b5c75e Mon Sep 17 00:00:00 2001 From: matst80 Date: Sat, 18 Oct 2025 13:37:33 +0200 Subject: [PATCH 05/13] update --- cmd/cart/main.go | 117 +++++++++++++++++++++++++---------------------- 1 file changed, 62 insertions(+), 55 deletions(-) diff --git a/cmd/cart/main.go b/cmd/cart/main.go index c767309..03fb479 100644 --- a/cmd/cart/main.go +++ b/cmd/cart/main.go @@ -138,19 +138,69 @@ func main() { pool: pool, } - conn, err := amqp.Dial(amqpUrl) - if err != nil { - fmt.Errorf("failed to connect to RabbitMQ: %w", err) - } + klarnaClient := NewKlarnaClient(KlarnaPlaygroundUrl, os.Getenv("KLARNA_API_USERNAME"), os.Getenv("KLARNA_API_PASSWORD")) - amqpListener := actor.NewAmqpListener(conn, func(id uint64, msg []actor.ApplyResult) (any, error) { - return &CartChangeEvent{ - CartId: cart.CartId(id), - Mutations: msg, - }, nil - }) - amqpListener.DefineTopics() - pool.AddListener(amqpListener) + syncedServer := NewPoolServer(pool, fmt.Sprintf("%s, %s", name, podIp), klarnaClient) + + mux := http.NewServeMux() + + if amqpUrl == "" { + log.Printf("no connection to amqp defined") + } else { + conn, err := amqp.Dial(amqpUrl) + if err != nil { + fmt.Errorf("failed to connect to RabbitMQ: %w", err) + } + + amqpListener := actor.NewAmqpListener(conn, func(id uint64, msg []actor.ApplyResult) (any, error) { + return &CartChangeEvent{ + CartId: cart.CartId(id), + Mutations: msg, + }, nil + }) + amqpListener.DefineTopics() + pool.AddListener(amqpListener) + orderHandler := NewAmqpOrderHandler(conn) + orderHandler.DefineTopics() + + mux.HandleFunc("/push", func(w http.ResponseWriter, r *http.Request) { + + if r.Method != http.MethodPost { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + orderId := r.URL.Query().Get("order_id") + log.Printf("Order confirmation push: %s", orderId) + + order, err := klarnaClient.GetOrder(orderId) + + if err != nil { + log.Printf("Error creating request: %v\n", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + err = confirmOrder(order, orderHandler) + if err != nil { + log.Printf("Error confirming order: %v\n", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + err = triggerOrderCompleted(syncedServer, order) + if err != nil { + log.Printf("Error processing cart message: %v\n", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + err = klarnaClient.AcknowledgeOrder(orderId) + if err != nil { + log.Printf("Error acknowledging order: %v\n", err) + } + + w.WriteHeader(http.StatusOK) + }) + } grpcSrv, err := actor.NewControlServer[*cart.CartGrain](controlPlaneConfig, pool) if err != nil { @@ -188,13 +238,6 @@ func main() { } }(GetDiscovery()) - orderHandler := NewAmqpOrderHandler(conn) - orderHandler.DefineTopics() - klarnaClient := NewKlarnaClient(KlarnaPlaygroundUrl, os.Getenv("KLARNA_API_USERNAME"), os.Getenv("KLARNA_API_PASSWORD")) - - syncedServer := NewPoolServer(pool, fmt.Sprintf("%s, %s", name, podIp), klarnaClient) - - mux := http.NewServeMux() mux.Handle("/cart/", http.StripPrefix("/cart", syncedServer.Serve())) // only for local mux.HandleFunc("GET /add/remote/{host}", func(w http.ResponseWriter, r *http.Request) { @@ -295,43 +338,7 @@ func main() { //} w.WriteHeader(http.StatusOK) }) - mux.HandleFunc("/push", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - orderId := r.URL.Query().Get("order_id") - log.Printf("Order confirmation push: %s", orderId) - - order, err := klarnaClient.GetOrder(orderId) - - if err != nil { - log.Printf("Error creating request: %v\n", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - err = confirmOrder(order, orderHandler) - if err != nil { - log.Printf("Error confirming order: %v\n", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - err = triggerOrderCompleted(syncedServer, order) - if err != nil { - log.Printf("Error processing cart message: %v\n", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - err = klarnaClient.AcknowledgeOrder(orderId) - if err != nil { - log.Printf("Error acknowledging order: %v\n", err) - } - - w.WriteHeader(http.StatusOK) - }) mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("1.0.0")) From dc352e3b747be915a0390c0646fdfb6a4eb21379 Mon Sep 17 00:00:00 2001 From: matst80 Date: Sat, 18 Oct 2025 14:33:21 +0200 Subject: [PATCH 06/13] voucher change --- cmd/cart/main.go | 2 +- cmd/cart/pool-server.go | 4 + pkg/cart/cart-mutation-helper.go | 3 + pkg/cart/mutation_precondition.go | 7 + pkg/messages/control_plane.pb.go | 95 +++---- pkg/messages/control_plane_grpc.pb.go | 2 +- pkg/messages/messages.pb.go | 373 ++++++++++++++------------ pkg/voucher/service.go | 58 +++- proto/messages.proto | 16 +- 9 files changed, 313 insertions(+), 247 deletions(-) create mode 100644 pkg/cart/mutation_precondition.go diff --git a/cmd/cart/main.go b/cmd/cart/main.go index 03fb479..6400d78 100644 --- a/cmd/cart/main.go +++ b/cmd/cart/main.go @@ -149,7 +149,7 @@ func main() { } else { conn, err := amqp.Dial(amqpUrl) if err != nil { - fmt.Errorf("failed to connect to RabbitMQ: %w", err) + log.Fatalf("failed to connect to RabbitMQ: %w", err) } amqpListener := actor.NewAmqpListener(conn, func(id uint64, msg []actor.ApplyResult) (any, error) { diff --git a/cmd/cart/pool-server.go b/cmd/cart/pool-server.go index e6e0720..bef6074 100644 --- a/cmd/cart/pool-server.go +++ b/cmd/cart/pool-server.go @@ -456,6 +456,10 @@ func (s *PoolServer) AddVoucherHandler(w http.ResponseWriter, r *http.Request, c v := voucher.Service{} msg, err := v.GetVoucher(data.VoucherCode) if err != nil { + s.ApplyLocal(cartId, &messages.PreConditionFailed{ + Operation: "AddVoucher", + Error: err.Error(), + }) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return err diff --git a/pkg/cart/cart-mutation-helper.go b/pkg/cart/cart-mutation-helper.go index 69035bf..70a973d 100644 --- a/pkg/cart/cart-mutation-helper.go +++ b/pkg/cart/cart-mutation-helper.go @@ -45,6 +45,9 @@ func NewCartMultationRegistry() actor.MutationRegistry { actor.NewMutation(UpsertSubscriptionDetails, func() *messages.UpsertSubscriptionDetails { return &messages.UpsertSubscriptionDetails{} }), + actor.NewMutation(PreConditionFailed, func() *messages.PreConditionFailed { + return &messages.PreConditionFailed{} + }), ) return reg diff --git a/pkg/cart/mutation_precondition.go b/pkg/cart/mutation_precondition.go new file mode 100644 index 0000000..8fa6d98 --- /dev/null +++ b/pkg/cart/mutation_precondition.go @@ -0,0 +1,7 @@ +package cart + +import messages "git.tornberg.me/go-cart-actor/pkg/messages" + +func PreConditionFailed(g *CartGrain, m *messages.PreConditionFailed) error { + return nil +} diff --git a/pkg/messages/control_plane.pb.go b/pkg/messages/control_plane.pb.go index 95aed7d..1bd4ef8 100644 --- a/pkg/messages/control_plane.pb.go +++ b/pkg/messages/control_plane.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.5 -// protoc v5.29.3 +// protoc-gen-go v1.36.10 +// protoc v6.32.1 // source: control_plane.proto package messages @@ -453,65 +453,38 @@ func (x *ExpiryAnnounce) GetIds() []uint64 { 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, - 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, - 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, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x63, 0x6b, 0x12, 0x3c, 0x0a, 0x07, 0x43, 0x6c, 0x6f, 0x73, - 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, -}) +const file_control_plane_proto_rawDesc = "" + + "\n" + + "\x13control_plane.proto\x12\bmessages\"\a\n" + + "\x05Empty\"<\n" + + "\tPingReply\x12\x12\n" + + "\x04host\x18\x01 \x01(\tR\x04host\x12\x1b\n" + + "\tunix_time\x18\x02 \x01(\x03R\bunixTime\"3\n" + + "\x10NegotiateRequest\x12\x1f\n" + + "\vknown_hosts\x18\x01 \x03(\tR\n" + + "knownHosts\"&\n" + + "\x0eNegotiateReply\x12\x14\n" + + "\x05hosts\x18\x01 \x03(\tR\x05hosts\"!\n" + + "\rActorIdsReply\x12\x10\n" + + "\x03ids\x18\x01 \x03(\x04R\x03ids\"F\n" + + "\x0eOwnerChangeAck\x12\x1a\n" + + "\baccepted\x18\x01 \x01(\bR\baccepted\x12\x18\n" + + "\amessage\x18\x02 \x01(\tR\amessage\"#\n" + + "\rClosingNotice\x12\x12\n" + + "\x04host\x18\x01 \x01(\tR\x04host\"9\n" + + "\x11OwnershipAnnounce\x12\x12\n" + + "\x04host\x18\x01 \x01(\tR\x04host\x12\x10\n" + + "\x03ids\x18\x02 \x03(\x04R\x03ids\"6\n" + + "\x0eExpiryAnnounce\x12\x12\n" + + "\x04host\x18\x01 \x01(\tR\x04host\x12\x10\n" + + "\x03ids\x18\x02 \x03(\x04R\x03ids2\x8d\x03\n" + + "\fControlPlane\x12,\n" + + "\x04Ping\x12\x0f.messages.Empty\x1a\x13.messages.PingReply\x12A\n" + + "\tNegotiate\x12\x1a.messages.NegotiateRequest\x1a\x18.messages.NegotiateReply\x12<\n" + + "\x10GetLocalActorIds\x12\x0f.messages.Empty\x1a\x17.messages.ActorIdsReply\x12J\n" + + "\x11AnnounceOwnership\x12\x1b.messages.OwnershipAnnounce\x1a\x18.messages.OwnerChangeAck\x12D\n" + + "\x0eAnnounceExpiry\x12\x18.messages.ExpiryAnnounce\x1a\x18.messages.OwnerChangeAck\x12<\n" + + "\aClosing\x12\x17.messages.ClosingNotice\x1a\x18.messages.OwnerChangeAckB.Z,git.tornberg.me/go-cart-actor/proto;messagesb\x06proto3" var ( file_control_plane_proto_rawDescOnce sync.Once diff --git a/pkg/messages/control_plane_grpc.pb.go b/pkg/messages/control_plane_grpc.pb.go index d0c9cf5..08e2815 100644 --- a/pkg/messages/control_plane_grpc.pb.go +++ b/pkg/messages/control_plane_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.29.3 +// - protoc v6.32.1 // source: control_plane.proto package messages diff --git a/pkg/messages/messages.pb.go b/pkg/messages/messages.pb.go index 237cd4d..7303467 100644 --- a/pkg/messages/messages.pb.go +++ b/pkg/messages/messages.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.5 -// protoc v5.29.3 +// protoc-gen-go v1.36.10 +// protoc v6.32.1 // source: messages.proto package messages @@ -1166,167 +1166,198 @@ func (x *UpsertSubscriptionDetails) GetData() *anypb.Any { return nil } +type PreConditionFailed struct { + state protoimpl.MessageState `protogen:"open.v1"` + Operation string `protobuf:"bytes,1,opt,name=operation,proto3" json:"operation,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` + Input *anypb.Any `protobuf:"bytes,3,opt,name=input,proto3" json:"input,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PreConditionFailed) Reset() { + *x = PreConditionFailed{} + mi := &file_messages_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PreConditionFailed) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PreConditionFailed) ProtoMessage() {} + +func (x *PreConditionFailed) ProtoReflect() protoreflect.Message { + mi := &file_messages_proto_msgTypes[16] + 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 PreConditionFailed.ProtoReflect.Descriptor instead. +func (*PreConditionFailed) Descriptor() ([]byte, []int) { + return file_messages_proto_rawDescGZIP(), []int{16} +} + +func (x *PreConditionFailed) GetOperation() string { + if x != nil { + return x.Operation + } + return "" +} + +func (x *PreConditionFailed) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *PreConditionFailed) GetInput() *anypb.Any { + if x != nil { + return x.Input + } + return nil +} + 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, - 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, 0x79, 0x0a, 0x0b, 0x56, - 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x20, - 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, - 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x71, 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, 0x39, - 0x0a, 0x0c, 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, - 0x56, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0c, 0x76, 0x6f, 0x75, - 0x63, 0x68, 0x65, 0x72, 0x52, 0x75, 0x6c, 0x65, 0x73, 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, 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, -}) +const file_messages_proto_rawDesc = "" + + "\n" + + "\x0emessages.proto\x12\bmessages\x1a\x19google/protobuf/any.proto\"\x12\n" + + "\x10ClearCartRequest\"\xb7\x05\n" + + "\aAddItem\x12\x17\n" + + "\aitem_id\x18\x01 \x01(\rR\x06itemId\x12\x1a\n" + + "\bquantity\x18\x02 \x01(\x05R\bquantity\x12\x14\n" + + "\x05price\x18\x03 \x01(\x03R\x05price\x12\x1a\n" + + "\borgPrice\x18\t \x01(\x03R\borgPrice\x12\x10\n" + + "\x03sku\x18\x04 \x01(\tR\x03sku\x12\x12\n" + + "\x04name\x18\x05 \x01(\tR\x04name\x12\x14\n" + + "\x05image\x18\x06 \x01(\tR\x05image\x12\x14\n" + + "\x05stock\x18\a \x01(\x05R\x05stock\x12\x10\n" + + "\x03tax\x18\b \x01(\x05R\x03tax\x12\x14\n" + + "\x05brand\x18\r \x01(\tR\x05brand\x12\x1a\n" + + "\bcategory\x18\x0e \x01(\tR\bcategory\x12\x1c\n" + + "\tcategory2\x18\x0f \x01(\tR\tcategory2\x12\x1c\n" + + "\tcategory3\x18\x10 \x01(\tR\tcategory3\x12\x1c\n" + + "\tcategory4\x18\x11 \x01(\tR\tcategory4\x12\x1c\n" + + "\tcategory5\x18\x12 \x01(\tR\tcategory5\x12\x1e\n" + + "\n" + + "disclaimer\x18\n" + + " \x01(\tR\n" + + "disclaimer\x12 \n" + + "\varticleType\x18\v \x01(\tR\varticleType\x12\x1a\n" + + "\bsellerId\x18\x13 \x01(\tR\bsellerId\x12\x1e\n" + + "\n" + + "sellerName\x18\x14 \x01(\tR\n" + + "sellerName\x12\x18\n" + + "\acountry\x18\x15 \x01(\tR\acountry\x12\x1e\n" + + "\n" + + "saleStatus\x18\x18 \x01(\tR\n" + + "saleStatus\x12\x1b\n" + + "\x06outlet\x18\f \x01(\tH\x00R\x06outlet\x88\x01\x01\x12\x1d\n" + + "\astoreId\x18\x16 \x01(\tH\x01R\astoreId\x88\x01\x01\x12\x1f\n" + + "\bparentId\x18\x17 \x01(\rH\x02R\bparentId\x88\x01\x01B\t\n" + + "\a_outletB\n" + + "\n" + + "\b_storeIdB\v\n" + + "\t_parentId\"\x1c\n" + + "\n" + + "RemoveItem\x12\x0e\n" + + "\x02Id\x18\x01 \x01(\rR\x02Id\"<\n" + + "\x0eChangeQuantity\x12\x0e\n" + + "\x02Id\x18\x01 \x01(\rR\x02Id\x12\x1a\n" + + "\bquantity\x18\x02 \x01(\x05R\bquantity\"\x86\x02\n" + + "\vSetDelivery\x12\x1a\n" + + "\bprovider\x18\x01 \x01(\tR\bprovider\x12\x14\n" + + "\x05items\x18\x02 \x03(\rR\x05items\x12<\n" + + "\vpickupPoint\x18\x03 \x01(\v2\x15.messages.PickupPointH\x00R\vpickupPoint\x88\x01\x01\x12\x18\n" + + "\acountry\x18\x04 \x01(\tR\acountry\x12\x10\n" + + "\x03zip\x18\x05 \x01(\tR\x03zip\x12\x1d\n" + + "\aaddress\x18\x06 \x01(\tH\x01R\aaddress\x88\x01\x01\x12\x17\n" + + "\x04city\x18\a \x01(\tH\x02R\x04city\x88\x01\x01B\x0e\n" + + "\f_pickupPointB\n" + + "\n" + + "\b_addressB\a\n" + + "\x05_city\"\xf9\x01\n" + + "\x0eSetPickupPoint\x12\x1e\n" + + "\n" + + "deliveryId\x18\x01 \x01(\rR\n" + + "deliveryId\x12\x0e\n" + + "\x02id\x18\x02 \x01(\tR\x02id\x12\x17\n" + + "\x04name\x18\x03 \x01(\tH\x00R\x04name\x88\x01\x01\x12\x1d\n" + + "\aaddress\x18\x04 \x01(\tH\x01R\aaddress\x88\x01\x01\x12\x17\n" + + "\x04city\x18\x05 \x01(\tH\x02R\x04city\x88\x01\x01\x12\x15\n" + + "\x03zip\x18\x06 \x01(\tH\x03R\x03zip\x88\x01\x01\x12\x1d\n" + + "\acountry\x18\a \x01(\tH\x04R\acountry\x88\x01\x01B\a\n" + + "\x05_nameB\n" + + "\n" + + "\b_addressB\a\n" + + "\x05_cityB\x06\n" + + "\x04_zipB\n" + + "\n" + + "\b_country\"\xd6\x01\n" + + "\vPickupPoint\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x17\n" + + "\x04name\x18\x02 \x01(\tH\x00R\x04name\x88\x01\x01\x12\x1d\n" + + "\aaddress\x18\x03 \x01(\tH\x01R\aaddress\x88\x01\x01\x12\x17\n" + + "\x04city\x18\x04 \x01(\tH\x02R\x04city\x88\x01\x01\x12\x15\n" + + "\x03zip\x18\x05 \x01(\tH\x03R\x03zip\x88\x01\x01\x12\x1d\n" + + "\acountry\x18\x06 \x01(\tH\x04R\acountry\x88\x01\x01B\a\n" + + "\x05_nameB\n" + + "\n" + + "\b_addressB\a\n" + + "\x05_cityB\x06\n" + + "\x04_zipB\n" + + "\n" + + "\b_country\" \n" + + "\x0eRemoveDelivery\x12\x0e\n" + + "\x02id\x18\x01 \x01(\rR\x02id\"\xb9\x01\n" + + "\x13CreateCheckoutOrder\x12\x14\n" + + "\x05terms\x18\x01 \x01(\tR\x05terms\x12\x1a\n" + + "\bcheckout\x18\x02 \x01(\tR\bcheckout\x12\"\n" + + "\fconfirmation\x18\x03 \x01(\tR\fconfirmation\x12\x12\n" + + "\x04push\x18\x04 \x01(\tR\x04push\x12\x1e\n" + + "\n" + + "validation\x18\x05 \x01(\tR\n" + + "validation\x12\x18\n" + + "\acountry\x18\x06 \x01(\tR\acountry\"@\n" + + "\fOrderCreated\x12\x18\n" + + "\aorderId\x18\x01 \x01(\tR\aorderId\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status\"\x06\n" + + "\x04Noop\"t\n" + + "\x12InitializeCheckout\x12\x18\n" + + "\aorderId\x18\x01 \x01(\tR\aorderId\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status\x12,\n" + + "\x11paymentInProgress\x18\x03 \x01(\bR\x11paymentInProgress\"y\n" + + "\vVoucherRule\x12\x12\n" + + "\x04type\x18\x02 \x01(\tR\x04type\x12 \n" + + "\vdescription\x18\x03 \x01(\tR\vdescription\x12\x1c\n" + + "\tcondition\x18\x04 \x01(\tR\tcondition\x12\x16\n" + + "\x06action\x18\x05 \x01(\tR\x06action\"q\n" + + "\n" + + "AddVoucher\x12\x12\n" + + "\x04code\x18\x01 \x01(\tR\x04code\x12\x14\n" + + "\x05value\x18\x02 \x01(\x03R\x05value\x129\n" + + "\fvoucherRules\x18\x03 \x03(\v2\x15.messages.VoucherRuleR\fvoucherRules\"\x1f\n" + + "\rRemoveVoucher\x12\x0e\n" + + "\x02id\x18\x01 \x01(\rR\x02id\"\xa7\x01\n" + + "\x19UpsertSubscriptionDetails\x12\x13\n" + + "\x02id\x18\x01 \x01(\tH\x00R\x02id\x88\x01\x01\x12\"\n" + + "\fofferingCode\x18\x02 \x01(\tR\fofferingCode\x12 \n" + + "\vsigningType\x18\x03 \x01(\tR\vsigningType\x12(\n" + + "\x04data\x18\x04 \x01(\v2\x14.google.protobuf.AnyR\x04dataB\x05\n" + + "\x03_id\"t\n" + + "\x12PreConditionFailed\x12\x1c\n" + + "\toperation\x18\x01 \x01(\tR\toperation\x12\x14\n" + + "\x05error\x18\x02 \x01(\tR\x05error\x12*\n" + + "\x05input\x18\x03 \x01(\v2\x14.google.protobuf.AnyR\x05inputB.Z,git.tornberg.me/go-cart-actor/proto;messagesb\x06proto3" var ( file_messages_proto_rawDescOnce sync.Once @@ -1340,7 +1371,7 @@ func file_messages_proto_rawDescGZIP() []byte { return file_messages_proto_rawDescData } -var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_messages_proto_goTypes = []any{ (*ClearCartRequest)(nil), // 0: messages.ClearCartRequest (*AddItem)(nil), // 1: messages.AddItem @@ -1358,17 +1389,19 @@ var file_messages_proto_goTypes = []any{ (*AddVoucher)(nil), // 13: messages.AddVoucher (*RemoveVoucher)(nil), // 14: messages.RemoveVoucher (*UpsertSubscriptionDetails)(nil), // 15: messages.UpsertSubscriptionDetails - (*anypb.Any)(nil), // 16: google.protobuf.Any + (*PreConditionFailed)(nil), // 16: messages.PreConditionFailed + (*anypb.Any)(nil), // 17: google.protobuf.Any } var file_messages_proto_depIdxs = []int32{ 6, // 0: messages.SetDelivery.pickupPoint:type_name -> messages.PickupPoint 12, // 1: messages.AddVoucher.voucherRules:type_name -> messages.VoucherRule - 16, // 2: messages.UpsertSubscriptionDetails.data: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 + 17, // 2: messages.UpsertSubscriptionDetails.data:type_name -> google.protobuf.Any + 17, // 3: messages.PreConditionFailed.input:type_name -> google.protobuf.Any + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_messages_proto_init() } @@ -1387,7 +1420,7 @@ func file_messages_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_messages_proto_rawDesc), len(file_messages_proto_rawDesc)), NumEnums: 0, - NumMessages: 16, + NumMessages: 17, NumExtensions: 0, NumServices: 0, }, diff --git a/pkg/voucher/service.go b/pkg/voucher/service.go index d75877e..e4dc5f0 100644 --- a/pkg/voucher/service.go +++ b/pkg/voucher/service.go @@ -1,7 +1,10 @@ package voucher import ( + "encoding/json" "errors" + "fmt" + "os" "git.tornberg.me/go-cart-actor/pkg/messages" ) @@ -12,11 +15,9 @@ type Rule struct { } type Voucher struct { - Code string `json:"code"` - Value int64 `json:"discount"` - TaxValue int64 `json:"taxValue"` - TaxRate int `json:"taxRate"` - rules []Rule `json:"rules"` + Code string `json:"code"` + Value int64 `json:"discount"` + rules []*messages.VoucherRule `json:"rules"` } type Service struct { @@ -29,10 +30,51 @@ func (s *Service) GetVoucher(code string) (*messages.AddVoucher, error) { if code == "" { return nil, ErrInvalidCode } - value := int64(250_00) + sf, err := LoadStateFile("data/vouchers.json") + if err != nil { + return nil, err + } + v, ok := sf.GetVoucher(code) + if !ok { + return nil, fmt.Errorf("no voucher found for code: %s", code) + } + return &messages.AddVoucher{ Code: code, - Value: value, - VoucherRules: make([]*messages.VoucherRule, 0), + Value: v.Value, + VoucherRules: v.rules, }, nil } + +type State struct { + Vouchers []Voucher `json:"vouchers"` +} + +type StateFile struct { + State State `json:"state"` + Vercion int `json:"version"` +} + +func (sf *StateFile) GetVoucher(code string) (*Voucher, bool) { + for _, v := range sf.State.Vouchers { + if v.Code == code { + return &v, true + } + } + return nil, false +} + +func LoadStateFile(fileName string) (*StateFile, error) { + f, err := os.Open(fileName) + if err != nil { + return nil, err + } + defer f.Close() + dec := json.NewDecoder(f) + sf := &StateFile{} + err = dec.Decode(sf) + if err != nil { + return nil, err + } + return sf, nil +} diff --git a/proto/messages.proto b/proto/messages.proto index 1b281a1..0b9f827 100644 --- a/proto/messages.proto +++ b/proto/messages.proto @@ -119,10 +119,14 @@ message RemoveVoucher { } message UpsertSubscriptionDetails { - - optional string id = 1; - string offeringCode = 2; - string signingType = 3; - google.protobuf.Any data = 4; - + optional string id = 1; + string offeringCode = 2; + string signingType = 3; + google.protobuf.Any data = 4; +} + +message PreConditionFailed { + string operation = 1; + string error = 2; + google.protobuf.Any input = 3; } From 2ce45656d9df3cf93a949ba829b5709cbce8e2cc Mon Sep 17 00:00:00 2001 From: matst80 Date: Sat, 18 Oct 2025 14:42:52 +0200 Subject: [PATCH 07/13] test --- pkg/voucher/service.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/pkg/voucher/service.go b/pkg/voucher/service.go index e4dc5f0..e5913c3 100644 --- a/pkg/voucher/service.go +++ b/pkg/voucher/service.go @@ -15,9 +15,9 @@ type Rule struct { } type Voucher struct { - Code string `json:"code"` - Value int64 `json:"discount"` - rules []*messages.VoucherRule `json:"rules"` + Code string `json:"code"` + Value int64 `json:"value"` + rules string `json:"rules"` } type Service struct { @@ -40,9 +40,16 @@ func (s *Service) GetVoucher(code string) (*messages.AddVoucher, error) { } return &messages.AddVoucher{ - Code: code, - Value: v.Value, - VoucherRules: v.rules, + Code: code, + Value: v.Value, + VoucherRules: []*messages.VoucherRule{ + { + Condition: v.rules, + Type: "", + Description: "", + Action: "", + }, + }, }, nil } From d58409e3fcec8a2323dcb160b4e243d5a95fb919 Mon Sep 17 00:00:00 2001 From: matst80 Date: Sat, 18 Oct 2025 15:14:22 +0200 Subject: [PATCH 08/13] add total test --- pkg/voucher/parser_test.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/pkg/voucher/parser_test.go b/pkg/voucher/parser_test.go index c618e69..d9f4de4 100644 --- a/pkg/voucher/parser_test.go +++ b/pkg/voucher/parser_test.go @@ -28,6 +28,32 @@ func TestParseRules_SimpleSku(t *testing.T) { } } +func TestRuleCartTotal(t *testing.T) { + rs, err := ParseRules("min_total>=500000") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(rs.Conditions) != 1 { + t.Fatalf("expected 1 condition got %d", len(rs.Conditions)) + } + c := rs.Conditions[0] + if c.Kind != RuleMinTotal { + t.Fatalf("expected kind cart total got %s", c.Kind) + } + ctx := EvalContext{ + Items: []Item{ + Item{ + Sku: "123", + }, + }, + CartTotalInc: 400000, + } + applied := rs.Applies(ctx) + if applied { + t.Fatalf("expected") + } +} + func TestParseRules_CategoryAndSkuMixedSeparators(t *testing.T) { rs, err := ParseRules(" category=Shoes|Bags ; sku= A | B , min_total>=1000\nmin_item_price>=500") if err != nil { From b7f0990269802ae684392e185ff17be0a32baa5d Mon Sep 17 00:00:00 2001 From: matst80 Date: Sat, 18 Oct 2025 15:21:11 +0200 Subject: [PATCH 09/13] simpler rules --- pkg/cart/cart-grain.go | 12 +-- pkg/messages/messages.pb.go | 139 ++++++++----------------------- pkg/voucher/service.go | 13 +-- proto/messages.proto | 159 ++++++++++++++++-------------------- 4 files changed, 114 insertions(+), 209 deletions(-) diff --git a/pkg/cart/cart-grain.go b/pkg/cart/cart-grain.go index 712f20e..8682d4d 100644 --- a/pkg/cart/cart-grain.go +++ b/pkg/cart/cart-grain.go @@ -92,10 +92,10 @@ type CartGrain struct { } type Voucher struct { - Code string `json:"code"` - Rules []*messages.VoucherRule `json:"rules"` - Id uint32 `json:"id"` - Value int64 `json:"value"` + Code string `json:"code"` + Rules []string `json:"rules"` + Id uint32 `json:"id"` + Value int64 `json:"value"` } func (v *Voucher) AppliesTo(cart *CartGrain) ([]*CartItem, bool) { @@ -127,8 +127,8 @@ func (v *Voucher) AppliesTo(cart *CartGrain) ([]*CartItem, bool) { } // All voucher rules must pass (logical AND) - for _, rule := range v.Rules { - expr := rule.GetCondition() + for _, expr := range v.Rules { + if expr == "" { // Empty condition treated as pass (acts like a comment / placeholder) continue diff --git a/pkg/messages/messages.pb.go b/pkg/messages/messages.pb.go index 7303467..9f4f8a5 100644 --- a/pkg/messages/messages.pb.go +++ b/pkg/messages/messages.pb.go @@ -926,86 +926,18 @@ func (x *InitializeCheckout) GetPaymentInProgress() bool { return false } -type VoucherRule struct { - state protoimpl.MessageState `protogen:"open.v1"` - Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` - Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` - Condition string `protobuf:"bytes,4,opt,name=condition,proto3" json:"condition,omitempty"` - Action string `protobuf:"bytes,5,opt,name=action,proto3" json:"action,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *VoucherRule) Reset() { - *x = VoucherRule{} - mi := &file_messages_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *VoucherRule) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*VoucherRule) ProtoMessage() {} - -func (x *VoucherRule) 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 VoucherRule.ProtoReflect.Descriptor instead. -func (*VoucherRule) Descriptor() ([]byte, []int) { - return file_messages_proto_rawDescGZIP(), []int{12} -} - -func (x *VoucherRule) GetType() string { - if x != nil { - return x.Type - } - return "" -} - -func (x *VoucherRule) GetDescription() string { - if x != nil { - return x.Description - } - return "" -} - -func (x *VoucherRule) GetCondition() string { - if x != nil { - return x.Condition - } - return "" -} - -func (x *VoucherRule) GetAction() string { - if x != nil { - return x.Action - } - return "" -} - type AddVoucher struct { state protoimpl.MessageState `protogen:"open.v1"` Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` Value int64 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"` - VoucherRules []*VoucherRule `protobuf:"bytes,3,rep,name=voucherRules,proto3" json:"voucherRules,omitempty"` + VoucherRules []string `protobuf:"bytes,3,rep,name=voucherRules,proto3" json:"voucherRules,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *AddVoucher) Reset() { *x = AddVoucher{} - mi := &file_messages_proto_msgTypes[13] + mi := &file_messages_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1017,7 +949,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[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1030,7 +962,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{12} } func (x *AddVoucher) GetCode() string { @@ -1047,7 +979,7 @@ func (x *AddVoucher) GetValue() int64 { return 0 } -func (x *AddVoucher) GetVoucherRules() []*VoucherRule { +func (x *AddVoucher) GetVoucherRules() []string { if x != nil { return x.VoucherRules } @@ -1063,7 +995,7 @@ type RemoveVoucher struct { func (x *RemoveVoucher) Reset() { *x = RemoveVoucher{} - mi := &file_messages_proto_msgTypes[14] + mi := &file_messages_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1075,7 +1007,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[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1088,7 +1020,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{13} } func (x *RemoveVoucher) GetId() uint32 { @@ -1110,7 +1042,7 @@ type UpsertSubscriptionDetails struct { func (x *UpsertSubscriptionDetails) Reset() { *x = UpsertSubscriptionDetails{} - mi := &file_messages_proto_msgTypes[15] + mi := &file_messages_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1122,7 +1054,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[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1135,7 +1067,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{14} } func (x *UpsertSubscriptionDetails) GetId() string { @@ -1177,7 +1109,7 @@ type PreConditionFailed struct { func (x *PreConditionFailed) Reset() { *x = PreConditionFailed{} - mi := &file_messages_proto_msgTypes[16] + mi := &file_messages_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1189,7 +1121,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[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1202,7 +1134,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{15} } func (x *PreConditionFailed) GetOperation() string { @@ -1335,17 +1267,12 @@ const file_messages_proto_rawDesc = "" + "\x12InitializeCheckout\x12\x18\n" + "\aorderId\x18\x01 \x01(\tR\aorderId\x12\x16\n" + "\x06status\x18\x02 \x01(\tR\x06status\x12,\n" + - "\x11paymentInProgress\x18\x03 \x01(\bR\x11paymentInProgress\"y\n" + - "\vVoucherRule\x12\x12\n" + - "\x04type\x18\x02 \x01(\tR\x04type\x12 \n" + - "\vdescription\x18\x03 \x01(\tR\vdescription\x12\x1c\n" + - "\tcondition\x18\x04 \x01(\tR\tcondition\x12\x16\n" + - "\x06action\x18\x05 \x01(\tR\x06action\"q\n" + + "\x11paymentInProgress\x18\x03 \x01(\bR\x11paymentInProgress\"Z\n" + "\n" + "AddVoucher\x12\x12\n" + "\x04code\x18\x01 \x01(\tR\x04code\x12\x14\n" + - "\x05value\x18\x02 \x01(\x03R\x05value\x129\n" + - "\fvoucherRules\x18\x03 \x03(\v2\x15.messages.VoucherRuleR\fvoucherRules\"\x1f\n" + + "\x05value\x18\x02 \x01(\x03R\x05value\x12\"\n" + + "\fvoucherRules\x18\x03 \x03(\tR\fvoucherRules\"\x1f\n" + "\rRemoveVoucher\x12\x0e\n" + "\x02id\x18\x01 \x01(\rR\x02id\"\xa7\x01\n" + "\x19UpsertSubscriptionDetails\x12\x13\n" + @@ -1371,7 +1298,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, 16) var file_messages_proto_goTypes = []any{ (*ClearCartRequest)(nil), // 0: messages.ClearCartRequest (*AddItem)(nil), // 1: messages.AddItem @@ -1385,23 +1312,21 @@ var file_messages_proto_goTypes = []any{ (*OrderCreated)(nil), // 9: messages.OrderCreated (*Noop)(nil), // 10: messages.Noop (*InitializeCheckout)(nil), // 11: messages.InitializeCheckout - (*VoucherRule)(nil), // 12: messages.VoucherRule - (*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 + (*AddVoucher)(nil), // 12: messages.AddVoucher + (*RemoveVoucher)(nil), // 13: messages.RemoveVoucher + (*UpsertSubscriptionDetails)(nil), // 14: messages.UpsertSubscriptionDetails + (*PreConditionFailed)(nil), // 15: messages.PreConditionFailed + (*anypb.Any)(nil), // 16: google.protobuf.Any } var file_messages_proto_depIdxs = []int32{ 6, // 0: messages.SetDelivery.pickupPoint:type_name -> messages.PickupPoint - 12, // 1: messages.AddVoucher.voucherRules:type_name -> messages.VoucherRule - 17, // 2: messages.UpsertSubscriptionDetails.data:type_name -> google.protobuf.Any - 17, // 3: messages.PreConditionFailed.input:type_name -> google.protobuf.Any - 4, // [4:4] is the sub-list for method output_type - 4, // [4:4] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 16, // 1: messages.UpsertSubscriptionDetails.data:type_name -> google.protobuf.Any + 16, // 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 } func init() { file_messages_proto_init() } @@ -1413,14 +1338,14 @@ func file_messages_proto_init() { file_messages_proto_msgTypes[4].OneofWrappers = []any{} file_messages_proto_msgTypes[5].OneofWrappers = []any{} file_messages_proto_msgTypes[6].OneofWrappers = []any{} - file_messages_proto_msgTypes[15].OneofWrappers = []any{} + file_messages_proto_msgTypes[14].OneofWrappers = []any{} 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: 16, NumExtensions: 0, NumServices: 0, }, diff --git a/pkg/voucher/service.go b/pkg/voucher/service.go index e5913c3..01a9677 100644 --- a/pkg/voucher/service.go +++ b/pkg/voucher/service.go @@ -17,7 +17,7 @@ type Rule struct { type Voucher struct { Code string `json:"code"` Value int64 `json:"value"` - rules string `json:"rules"` + Rules string `json:"rules"` } type Service struct { @@ -42,13 +42,8 @@ func (s *Service) GetVoucher(code string) (*messages.AddVoucher, error) { return &messages.AddVoucher{ Code: code, Value: v.Value, - VoucherRules: []*messages.VoucherRule{ - { - Condition: v.rules, - Type: "", - Description: "", - Action: "", - }, + VoucherRules: []string{ + v.Rules, }, }, nil } @@ -59,7 +54,7 @@ type State struct { type StateFile struct { State State `json:"state"` - Vercion int `json:"version"` + Version int `json:"version"` } func (sf *StateFile) GetVoucher(code string) (*Voucher, bool) { diff --git a/proto/messages.proto b/proto/messages.proto index 0b9f827..4ec43f5 100644 --- a/proto/messages.proto +++ b/proto/messages.proto @@ -4,129 +4,114 @@ option go_package = "git.tornberg.me/go-cart-actor/proto;messages"; import "google/protobuf/any.proto"; -message ClearCartRequest { - -} +message ClearCartRequest {} message AddItem { - uint32 item_id = 1; - int32 quantity = 2; - int64 price = 3; - int64 orgPrice = 9; - string sku = 4; - string name = 5; - string image = 6; - int32 stock = 7; - int32 tax = 8; - string brand = 13; - string category = 14; - string category2 = 15; - string category3 = 16; - string category4 = 17; - string category5 = 18; - string disclaimer = 10; - string articleType = 11; - string sellerId = 19; - string sellerName = 20; - string country = 21; - string saleStatus = 24; - optional string outlet = 12; - optional string storeId = 22; - optional uint32 parentId = 23; + uint32 item_id = 1; + int32 quantity = 2; + int64 price = 3; + int64 orgPrice = 9; + string sku = 4; + string name = 5; + string image = 6; + int32 stock = 7; + int32 tax = 8; + string brand = 13; + string category = 14; + string category2 = 15; + string category3 = 16; + string category4 = 17; + string category5 = 18; + string disclaimer = 10; + string articleType = 11; + string sellerId = 19; + string sellerName = 20; + string country = 21; + string saleStatus = 24; + optional string outlet = 12; + optional string storeId = 22; + optional uint32 parentId = 23; } -message RemoveItem { - uint32 Id = 1; -} +message RemoveItem { uint32 Id = 1; } message ChangeQuantity { - uint32 Id = 1; - int32 quantity = 2; + uint32 Id = 1; + int32 quantity = 2; } message SetDelivery { - string provider = 1; - repeated uint32 items = 2; - optional PickupPoint pickupPoint = 3; - string country = 4; - string zip = 5; - optional string address = 6; - optional string city = 7; + string provider = 1; + repeated uint32 items = 2; + optional PickupPoint pickupPoint = 3; + string country = 4; + string zip = 5; + optional string address = 6; + optional string city = 7; } message SetPickupPoint { - uint32 deliveryId = 1; - string id = 2; - optional string name = 3; - optional string address = 4; - optional string city = 5; - optional string zip = 6; - optional string country = 7; + uint32 deliveryId = 1; + string id = 2; + optional string name = 3; + optional string address = 4; + optional string city = 5; + optional string zip = 6; + optional string country = 7; } message PickupPoint { - string id = 1; - optional string name = 2; - optional string address = 3; - optional string city = 4; - optional string zip = 5; - optional string country = 6; + string id = 1; + optional string name = 2; + optional string address = 3; + optional string city = 4; + optional string zip = 5; + optional string country = 6; } -message RemoveDelivery { - uint32 id = 1; -} +message RemoveDelivery { uint32 id = 1; } message CreateCheckoutOrder { - string terms = 1; - string checkout = 2; - string confirmation = 3; - string push = 4; - string validation = 5; - string country = 6; + string terms = 1; + string checkout = 2; + string confirmation = 3; + string push = 4; + string validation = 5; + string country = 6; } message OrderCreated { - string orderId = 1; - string status = 2; + string orderId = 1; + string status = 2; } message Noop { - // Intentionally empty - used for ownership acquisition or health pings + // Intentionally empty - used for ownership acquisition or health pings } message InitializeCheckout { - string orderId = 1; - string status = 2; - bool paymentInProgress = 3; -} - -message VoucherRule { - string type = 2; - string description = 3; - string condition = 4; - string action = 5; + string orderId = 1; + string status = 2; + bool paymentInProgress = 3; } message AddVoucher { - string code = 1; - int64 value = 2; - repeated VoucherRule voucherRules = 3; + string code = 1; + int64 value = 2; + repeated string voucherRules = 3; } -message RemoveVoucher { - uint32 id = 1; -} +message RemoveVoucher { uint32 id = 1; } message UpsertSubscriptionDetails { - optional string id = 1; - string offeringCode = 2; - string signingType = 3; - google.protobuf.Any data = 4; + optional string id = 1; + string offeringCode = 2; + string signingType = 3; + google.protobuf.Any data = 4; } message PreConditionFailed { - string operation = 1; - string error = 2; - google.protobuf.Any input = 3; + string operation = 1; + string error = 2; + google.protobuf.Any input = 3; } From e127251a60c8b6d52808b0ab7aa116a3479af3ae Mon Sep 17 00:00:00 2001 From: matst80 Date: Sat, 18 Oct 2025 15:25:44 +0200 Subject: [PATCH 10/13] add state file parser --- pkg/cart/mutation_test.go | 2 +- pkg/promotions/types.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/pkg/cart/mutation_test.go b/pkg/cart/mutation_test.go index 68c37a2..50fff32 100644 --- a/pkg/cart/mutation_test.go +++ b/pkg/cart/mutation_test.go @@ -66,7 +66,7 @@ func msgClearCart() *messages.ClearCartRequest { return &messages.ClearCartRequest{} } -func msgAddVoucher(code string, value int64, rules ...*messages.VoucherRule) *messages.AddVoucher { +func msgAddVoucher(code string, value int64, rules ...string) *messages.AddVoucher { return &messages.AddVoucher{Code: code, Value: value, VoucherRules: rules} } diff --git a/pkg/promotions/types.go b/pkg/promotions/types.go index 3646c60..bf5b370 100644 --- a/pkg/promotions/types.go +++ b/pkg/promotions/types.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "os" "strconv" ) @@ -369,3 +370,36 @@ func WalkConditions(conds []Condition, fn func(c Condition) bool) { } } } + +type PromotionState struct { + Vouchers []PromotionRule `json:"promotions"` +} + +type StateFile struct { + State PromotionState `json:"state"` + Version int `json:"version"` +} + +func (sf *StateFile) GetPromotion(id string) (*PromotionRule, bool) { + for _, v := range sf.State.Vouchers { + if v.ID == id { + return &v, true + } + } + return nil, false +} + +func LoadStateFile(fileName string) (*PromotionState, error) { + f, err := os.Open(fileName) + if err != nil { + return nil, err + } + defer f.Close() + dec := json.NewDecoder(f) + sf := &PromotionState{} + err = dec.Decode(sf) + if err != nil { + return nil, err + } + return sf, nil +} From 662b381a3429884ed5220e7c727b40a9b34e7439 Mon Sep 17 00:00:00 2001 From: matst80 Date: Sat, 18 Oct 2025 15:42:30 +0200 Subject: [PATCH 11/13] probably fix discounts --- pkg/cart/cart-grain.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pkg/cart/cart-grain.go b/pkg/cart/cart-grain.go index 8682d4d..a4fb4b3 100644 --- a/pkg/cart/cart-grain.go +++ b/pkg/cart/cart-grain.go @@ -249,19 +249,22 @@ func (c *CartGrain) UpdateTotals() { for _, item := range c.Items { rowTotal := MultiplyPrice(item.Price, int64(item.Quantity)) - item.TotalPrice = *rowTotal - - c.TotalPrice.Add(*rowTotal) - if item.OrgPrice != nil { diff := NewPrice() diff.Add(*item.OrgPrice) diff.Subtract(item.Price) + diff.Multiply(int64(item.Quantity)) + rowTotal.Subtract(*diff) + item.Discount = diff if diff.IncVat > 0 { c.TotalDiscount.Add(*diff) } } + item.TotalPrice = *rowTotal + + c.TotalPrice.Add(*rowTotal) + } for _, delivery := range c.Deliveries { c.TotalPrice.Add(delivery.Price) From 0c127e9d38685268194c957334a5828409d209f0 Mon Sep 17 00:00:00 2001 From: matst80 Date: Sat, 18 Oct 2025 15:50:38 +0200 Subject: [PATCH 12/13] add description all the way --- pkg/cart/cart-grain.go | 16 ++++++++++------ pkg/messages/messages.pb.go | 13 +++++++++++-- pkg/voucher/service.go | 12 +++++++----- proto/messages.proto | 1 + 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/pkg/cart/cart-grain.go b/pkg/cart/cart-grain.go index a4fb4b3..5c5816b 100644 --- a/pkg/cart/cart-grain.go +++ b/pkg/cart/cart-grain.go @@ -92,10 +92,12 @@ type CartGrain struct { } type Voucher struct { - Code string `json:"code"` - Rules []string `json:"rules"` - Id uint32 `json:"id"` - Value int64 `json:"value"` + Code string `json:"code"` + Applied bool `json:"applied"` + Rules []string `json:"rules"` + Description string `json:"description,omitempty"` + Id uint32 `json:"id"` + Value int64 `json:"value"` } func (v *Voucher) AppliesTo(cart *CartGrain) ([]*CartItem, bool) { @@ -270,13 +272,15 @@ func (c *CartGrain) UpdateTotals() { c.TotalPrice.Add(delivery.Price) } for _, voucher := range c.Vouchers { - if _, ok := voucher.AppliesTo(c); ok { + _, ok := voucher.AppliesTo(c) + voucher.Applied = false + if ok { value := NewPriceFromIncVat(voucher.Value, 25) if c.TotalPrice.IncVat <= value.IncVat { // don't apply discounts to more than the total price continue } - + voucher.Applied = true c.TotalDiscount.Add(*value) c.TotalPrice.Subtract(*value) } diff --git a/pkg/messages/messages.pb.go b/pkg/messages/messages.pb.go index 9f4f8a5..86ccfed 100644 --- a/pkg/messages/messages.pb.go +++ b/pkg/messages/messages.pb.go @@ -931,6 +931,7 @@ type AddVoucher struct { Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` Value int64 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"` VoucherRules []string `protobuf:"bytes,3,rep,name=voucherRules,proto3" json:"voucherRules,omitempty"` + Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -986,6 +987,13 @@ func (x *AddVoucher) GetVoucherRules() []string { return nil } +func (x *AddVoucher) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + type RemoveVoucher struct { state protoimpl.MessageState `protogen:"open.v1"` Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` @@ -1267,12 +1275,13 @@ const file_messages_proto_rawDesc = "" + "\x12InitializeCheckout\x12\x18\n" + "\aorderId\x18\x01 \x01(\tR\aorderId\x12\x16\n" + "\x06status\x18\x02 \x01(\tR\x06status\x12,\n" + - "\x11paymentInProgress\x18\x03 \x01(\bR\x11paymentInProgress\"Z\n" + + "\x11paymentInProgress\x18\x03 \x01(\bR\x11paymentInProgress\"|\n" + "\n" + "AddVoucher\x12\x12\n" + "\x04code\x18\x01 \x01(\tR\x04code\x12\x14\n" + "\x05value\x18\x02 \x01(\x03R\x05value\x12\"\n" + - "\fvoucherRules\x18\x03 \x03(\tR\fvoucherRules\"\x1f\n" + + "\fvoucherRules\x18\x03 \x03(\tR\fvoucherRules\x12 \n" + + "\vdescription\x18\x04 \x01(\tR\vdescription\"\x1f\n" + "\rRemoveVoucher\x12\x0e\n" + "\x02id\x18\x01 \x01(\rR\x02id\"\xa7\x01\n" + "\x19UpsertSubscriptionDetails\x12\x13\n" + diff --git a/pkg/voucher/service.go b/pkg/voucher/service.go index 01a9677..f65cd7c 100644 --- a/pkg/voucher/service.go +++ b/pkg/voucher/service.go @@ -15,9 +15,10 @@ type Rule struct { } type Voucher struct { - Code string `json:"code"` - Value int64 `json:"value"` - Rules string `json:"rules"` + Code string `json:"code"` + Value int64 `json:"value"` + Rules string `json:"rules"` + Description string `json:"description,omitempty"` } type Service struct { @@ -40,8 +41,9 @@ func (s *Service) GetVoucher(code string) (*messages.AddVoucher, error) { } return &messages.AddVoucher{ - Code: code, - Value: v.Value, + Code: code, + Value: v.Value, + Description: v.Description, VoucherRules: []string{ v.Rules, }, diff --git a/proto/messages.proto b/proto/messages.proto index 4ec43f5..772bfdd 100644 --- a/proto/messages.proto +++ b/proto/messages.proto @@ -99,6 +99,7 @@ message AddVoucher { string code = 1; int64 value = 2; repeated string voucherRules = 3; + string description = 4; } message RemoveVoucher { uint32 id = 1; } From a0c82dc35177e6c61528627f73d53dc93ed0ee7f Mon Sep 17 00:00:00 2001 From: matst80 Date: Sat, 18 Oct 2025 15:51:04 +0200 Subject: [PATCH 13/13] missing file --- pkg/cart/mutation_add_voucher.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/cart/mutation_add_voucher.go b/pkg/cart/mutation_add_voucher.go index 85bffe0..88743cc 100644 --- a/pkg/cart/mutation_add_voucher.go +++ b/pkg/cart/mutation_add_voucher.go @@ -54,10 +54,12 @@ func AddVoucher(g *CartGrain, m *messages.AddVoucher) error { g.lastVoucherId++ g.Vouchers = append(g.Vouchers, &Voucher{ - Id: g.lastVoucherId, - Code: m.Code, - Rules: m.VoucherRules, - Value: m.Value, + Id: g.lastVoucherId, + Applied: false, + Description: m.Description, + Code: m.Code, + Rules: m.VoucherRules, + Value: m.Value, }) g.UpdateTotals() return nil