package main import ( "encoding/json" "fmt" "time" messages "git.tornberg.me/go-cart-actor/proto" ) type CartId [16]byte func (id CartId) MarshalJSON() ([]byte, error) { return json.Marshal(id.String()) } func (id *CartId) UnmarshalJSON(data []byte) error { var str string err := json.Unmarshal(data, &str) if err != nil { return err } copy(id[:], []byte(str)) return nil } type CartItem struct { Id int `json:"id"` Sku string `json:"sku"` Name string `json:"name"` Price int64 `json:"price"` Quantity int `json:"qty"` Image string `json:"image"` } type CartDelivery struct { Provider string `json:"provider"` Price int64 `json:"price"` Items []int `json:"items"` } type CartGrain struct { lastItemId int lastDeliveryId int storageMessages []Message Id CartId `json:"id"` Items []*CartItem `json:"items"` TotalPrice int64 `json:"totalPrice"` Deliveries []string `json:"deliveries,omitempty"` } type Grain interface { GetId() CartId HandleMessage(message *Message, isReplay bool) ([]byte, error) } func (c *CartGrain) GetId() CartId { return c.Id } func (c *CartGrain) GetLastChange() int64 { if len(c.storageMessages) == 0 { return 0 } return *c.storageMessages[len(c.storageMessages)-1].TimeStamp } func getItemData(sku string, qty int) (*messages.AddItem, error) { item, err := FetchItem(sku) if err != nil { return nil, err } price := 0 priceField, ok := item.Fields[4] if ok { priceFloat, ok := priceField.(float64) if !ok { price, ok = priceField.(int) if !ok { return nil, fmt.Errorf("invalid price type") } } else { price = int(priceFloat) } } if price == 0 { return nil, fmt.Errorf("invalid price") } return &messages.AddItem{ Quantity: int32(qty), Price: int64(price), Sku: sku, Name: item.Title, Image: item.Img, }, nil } func (c *CartGrain) AddItem(sku string, qty int) ([]byte, error) { cartItem, err := getItemData(sku, qty) if err != nil { return nil, err } return c.HandleMessage(&Message{ Type: 2, Content: cartItem, }, false) } func (c *CartGrain) GetStorageMessage(since int64) []StorableMessage { ret := make([]StorableMessage, 0) for _, message := range c.storageMessages { if *message.TimeStamp > since { ret = append(ret, message) } } return ret } func (c *CartGrain) HandleMessage(message *Message, isReplay bool) ([]byte, error) { if message.TimeStamp == nil { now := time.Now().Unix() message.TimeStamp = &now } grainMutations.Inc() var err error switch message.Type { case AddRequestType: msg, ok := message.Content.(*messages.AddRequest) if !ok { err = fmt.Errorf("expected AddRequest") } else { return c.AddItem(msg.Sku, 1) // extent AddRequest to include quantity } case AddItemType: msg, ok := message.Content.(*messages.AddItem) if !ok { err = fmt.Errorf("expected AddItem") } else { found := false if msg.Quantity < 1 { return nil, fmt.Errorf("invalid quantity") } for _, item := range c.Items { if item.Sku == msg.Sku { found = true item.Quantity += int(msg.Quantity) c.TotalPrice += item.Price * int64(msg.Quantity) break } } if !found { c.lastItemId++ c.Items = append(c.Items, &CartItem{ Id: c.lastItemId, Quantity: int(msg.Quantity), Sku: msg.Sku, Name: msg.Name, Price: msg.Price, Image: msg.Image, }) c.TotalPrice += msg.Price * int64(msg.Quantity) } } case RemoveItemType: //msg, ok := message.Content.(*messages.RemoveItem) case AddDeliveryType: c.lastDeliveryId++ //msg, ok := message.Content.(*messages.AddDelivery) case RemoveDeliveryType: default: err = fmt.Errorf("unknown message type %d", message.Type) } if err != nil { return nil, err } if !isReplay { c.storageMessages = append(c.storageMessages, *message) } return json.Marshal(c) }