// Package profile implements a user-profile actor grain (same event-driven // framework as the cart, checkout, and order grains). Mutations are the proto // messages in proto/profile; the grain's event log is the source of truth and // ProfileGrain is the projection rebuilt by replaying them. package profile import ( "encoding/json" "sync" "time" ) // LinkedCart records a cart id linked to this profile. type LinkedCart struct { CartId uint64 `json:"cartId"` Label string `json:"label,omitempty"` } // LinkedCheckout records a checkout id linked to this profile. type LinkedCheckout struct { CheckoutId uint64 `json:"checkoutId"` CartId uint64 `json:"cartId"` } // LinkedOrder records an order linked to this profile. type LinkedOrder struct { OrderReference string `json:"orderReference"` CartId uint64 `json:"cartId"` Status string `json:"status,omitempty"` } // StoredAddress is a saved address in the user's address book. type StoredAddress struct { Id uint32 `json:"id"` Label string `json:"label,omitempty"` FullName string `json:"fullName,omitempty"` AddressLine1 string `json:"addressLine1"` AddressLine2 string `json:"addressLine2,omitempty"` City string `json:"city"` State string `json:"state,omitempty"` Zip string `json:"zip"` Country string `json:"country"` Phone string `json:"phone,omitempty"` IsDefaultShipping bool `json:"isDefaultShipping,omitempty"` IsDefaultBilling bool `json:"isDefaultBilling,omitempty"` } // ProfileGrain is the projected current state of a user profile. It implements // actor.Grain[ProfileGrain]. type ProfileGrain struct { mu sync.RWMutex lastAccess time.Time lastChange time.Time nextAddrId uint32 Id uint64 `json:"id"` Name string `json:"name,omitempty"` Email string `json:"email,omitempty"` Phone string `json:"phone,omitempty"` Language string `json:"language,omitempty"` Currency string `json:"currency,omitempty"` AvatarUrl string `json:"avatarUrl,omitempty"` Carts []LinkedCart `json:"carts,omitempty"` Checkouts []LinkedCheckout `json:"checkouts,omitempty"` Orders []LinkedOrder `json:"orders,omitempty"` Addresses []StoredAddress `json:"addresses,omitempty"` } // NewProfileGrain returns an empty profile grain for id. func NewProfileGrain(id uint64, ts time.Time) *ProfileGrain { return &ProfileGrain{ Id: id, lastAccess: ts, lastChange: ts, } } // --- actor.Grain[ProfileGrain] --- func (p *ProfileGrain) GetId() uint64 { return p.Id } func (p *ProfileGrain) GetLastAccess() time.Time { return p.lastAccess } func (p *ProfileGrain) GetLastChange() time.Time { return p.lastChange } func (p *ProfileGrain) GetCurrentState() (*ProfileGrain, error) { p.lastAccess = time.Now() return p, nil } func (p *ProfileGrain) GetState() ([]byte, error) { return json.Marshal(p) } // NextAddrId returns the next address id and advances the counter. func (p *ProfileGrain) NextAddrId() uint32 { p.nextAddrId++ return p.nextAddrId } // findAddress returns a pointer to the address with the given id, or nil. func (p *ProfileGrain) findAddress(id uint32) *StoredAddress { for i := range p.Addresses { if p.Addresses[i].Id == id { return &p.Addresses[i] } } return nil }