diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..6b2550dc5d6180d126ada837e6dff32965cad4ea --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +.PHONY: update master release setup update_master update_release build clean version + +setup: + git config --global --add url."git@gitlab.com:".insteadOf "https://gitlab.com/" + +version: + go run main.go generate + mv version_vars.go cmd/version_vars.go + +clean: + rm -rf vendor/ + go mod vendor + +update: + -GOFLAGS="" go get -u all + +build: + go build ./... + go mod tidy + +update_release: + GOFLAGS="" go get gitlab.com/elixxir/primitives@release + GOFLAGS="" go get gitlab.com/xx_network/primitives@release + GOFLAGS="" go get gitlab.com/elixxir/crypto@release + GOFLAGS="" go get gitlab.com/xx_network/crypto@release + GOFLAGS="" go get gitlab.com/elixxir/comms@release + GOFLAGS="" go get gitlab.com/xx_network/comms@release + GOFLAGS="" go get gitlab.com/elixxir/client@release + +update_master: + GOFLAGS="" go get gitlab.com/elixxir/primitives@master + GOFLAGS="" go get gitlab.com/xx_network/primitives@release + GOFLAGS="" go get gitlab.com/elixxir/crypto@master + GOFLAGS="" go get gitlab.com/elixxir/comms@master + GOFLAGS="" go get gitlab.com/xx_network/comms@master + GOFLAGS="" go get gitlab.com/elixxir/client@master + +master: update_master clean build version + +release: update_release clean build version diff --git a/cmd/root.go b/cmd/root.go index ad65315ee8d36c76c9dc5526a6fd5a60ae393a1a..8ee2e7bff2c0eac5a7bf41c6fdec16d3d3e9392a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,6 +4,7 @@ import ( "fmt" "git.xx.network/elixxir/coupons/coupons" "git.xx.network/elixxir/coupons/storage" + "github.com/golang/protobuf/proto" "github.com/skip2/go-qrcode" "github.com/spf13/cobra" jww "github.com/spf13/jwalterweatherman" @@ -28,9 +29,9 @@ var ( // RootCmd represents the base command when called without any sub-commands var rootCmd = &cobra.Command{ - Use: "UDB", - Short: "Runs the cMix UDB server.", - Long: "The cMix UDB server handles user and fact registration for the network.", + Use: "Coins", + Short: "Runs the coins bot.", + Long: "The cMix coupon bot handles incoming requests to see coin redemption info", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { // Initialize config & logging @@ -64,26 +65,61 @@ var rootCmd = &cobra.Command{ // Get session parameters sessionPath := viper.GetString("sessionPath") + + // Only require proto user path if session does not exist + var protoUserJson []byte + protoUserPath, err := utils.ExpandPath(viper.GetString("protoUserPath")) + if err != nil { + jww.FATAL.Fatalf("Failed to read proto path: %+v", err) + } else if protoUserPath == "" { + jww.WARN.Printf("protoUserPath is blank - a new session will be generated") + } + sessionPass := viper.GetString("sessionPass") networkFollowerTimeout := time.Duration(viper.GetInt("networkFollowerTimeout")) * time.Second - // Create the client if there's no session - if _, err := os.Stat(sessionPath); os.IsNotExist(err) { - ndfPath := viper.GetString("ndf") - ndfJSON, err := ioutil.ReadFile(ndfPath) - if err != nil { - jww.FATAL.Panicf("Failed to read NDF: %+v", err) - } + ndfPath := viper.GetString("ndf") + ndfJSON, err := ioutil.ReadFile(ndfPath) + if err != nil { + jww.FATAL.Panicf("Failed to read NDF: %+v", err) + } + + nwParams := params.GetDefaultNetwork() + + useProto := protoUserPath != "" && utils.Exists(protoUserPath) + useSession := sessionPath != "" && utils.Exists(sessionPath) + + if !useProto && !useSession { err = api.NewClient(string(ndfJSON), sessionPath, []byte(sessionPass), "") if err != nil { jww.FATAL.Panicf("Failed to create new client: %+v", err) } + useSession = true } - // Create client object - cl, err := api.Login(sessionPath, []byte(sessionPass), params.GetDefaultNetwork()) - if err != nil { - jww.FATAL.Panicf("Failed to initialize client: %+v", err) + var cl *api.Client + if useSession { + // If the session exists, load & login + // Create client object + cl, err = api.Login(sessionPath, []byte(sessionPass), nwParams) + if err != nil { + jww.FATAL.Panicf("Failed to initialize client: %+v", err) + } + } else if useProto { + protoUserJson, err = utils.ReadFile(protoUserPath) + if err != nil { + jww.FATAL.Fatalf("Failed to read proto user at %s: %+v", protoUserPath, err) + } + + // If the session does not exist but we have a proto file + // Log in using the protofile (attempt to rebuild session) + cl, err = api.LoginWithProtoClient(sessionPath, + []byte(sessionPass), protoUserJson, string(ndfJSON), nwParams) + if err != nil { + jww.FATAL.Fatalf("Failed to create client: %+v", err) + } + } else { + jww.FATAL.Panicf("Cannot run with no session or proto info") } // Generate QR code @@ -91,7 +127,7 @@ var rootCmd = &cobra.Command{ qrLevel := qrcode.RecoveryLevel(viper.GetInt("qrLevel")) qrPath := viper.GetString("qrPath") me := cl.GetUser().GetContact() - couponsUsername, err := fact.NewFact(fact.Username, "xx Coupons Bot") + couponsUsername, err := fact.NewFact(fact.Username, "xx-bonus-coin-bot") if err != nil { jww.FATAL.Panicf("Failed to create username: %+v", err) } @@ -106,25 +142,62 @@ var rootCmd = &cobra.Command{ jww.FATAL.Panicf("Failed to write QR code: %+v", err) } - // Start network follower - err = cl.StartNetworkFollower(networkFollowerTimeout) - if err != nil { - jww.FATAL.Panicf("Failed to start network follower: %+v", err) - } - // Create & register callback to confirm any authenticated channel requests - rcb := func(requestor contact.Contact, message string) { + rcb := func(requestor contact.Contact) { rid, err := cl.ConfirmAuthenticatedChannel(requestor) if err != nil { jww.ERROR.Printf("Failed to confirm authenticated channel to %+v: %+v", requestor, err) } jww.DEBUG.Printf("Authenticated channel to %+v created over round %d", requestor, rid) + + time.Sleep(100 * time.Millisecond) + + intro := "Thank you for your interest in xx coin! This bot allows purchasers in the " + + "November Community Sale to claim their extra bonus 100% xx coins for using the xx messenger.\n" + + "You have received an email from the team with a code you will need to use in order to claim coins.\n" + + "Please send that code to this bot, which will confirm your receipt. The coins will be sent to your " + + "wallet within 2 weeks.\nCoins will be received in the wallet you received the initial purchase amount in.\n" + payload := &coupons.CMIXText{ + Version: 0, + Text: intro, + } + marshalled, err := proto.Marshal(payload) + if err != nil { + jww.ERROR.Printf("Failed to marshal payload: %+v", err) + return + } + + contact, err := cl.GetAuthenticatedChannelRequest(requestor.ID) + if err != nil { + jww.ERROR.Printf("Could not get authenticated channel request info: %+v", err) + return + } + + // Create response message + resp := message.Send{ + Recipient: contact.ID, + Payload: marshalled, + MessageType: message.XxMessage, + } + + rids, mid, t, err := cl.SendE2E(resp, params.GetDefaultE2E()) + if err != nil { + jww.ERROR.Printf("Failed to send message: %+v", err) + return + } + jww.INFO.Printf("Sent intro [%+v] to %+v on rounds %+v [%+v]", mid, requestor, rids, t) } cl.GetAuthRegistrar().AddGeneralRequestCallback(rcb) // Create coupons impl & register listener on zero user for text messages impl := coupons.New(s, cl) - cl.GetSwitchboard().RegisterListener(&id.ZeroUser, message.Text, impl) + cl.GetSwitchboard().RegisterListener(&id.ZeroUser, message.XxMessage, impl) + + // Start network follower + err = cl.StartNetworkFollower(networkFollowerTimeout) + if err != nil { + jww.FATAL.Panicf("Failed to start network follower: %+v", err) + } // Wait 5ever select {} @@ -141,8 +214,8 @@ func Execute() { func init() { rootCmd.Flags().StringVarP(&cfgFile, "config", "c", "", - "Path to load the UDB configuration file from. If not set, this "+ - "file must be named udb.yaml and must be located in "+ + "Path to load the coupons configuration file from. If not set, this "+ + "file must be named coupons.yaml and must be located in "+ "~/.xxnetwork/, /opt/xxnetwork, or /etc/xxnetwork.") } @@ -151,7 +224,7 @@ func initConfig() { validConfig = true var err error if cfgFile == "" { - cfgFile, err = utils.SearchDefaultLocations("udb.yaml", "xxnetwork") + cfgFile, err = utils.SearchDefaultLocations("coupons.yaml", "xxnetwork") if err != nil { validConfig = false jww.FATAL.Panicf("Failed to find config file: %+v", err) diff --git a/coupons/couponresp.pb.go b/coupons/couponresp.pb.go index bceaf08046a6f7d4d00d64d6e646a0a6a6715bb4..3e9bab1866b58b9a39418eb98dc1d1472bd1c464 100644 --- a/coupons/couponresp.pb.go +++ b/coupons/couponresp.pb.go @@ -21,12 +21,12 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package type CMIXText struct { - Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` - Reply *TextReply `protobuf:"bytes,2,opt,name=reply,proto3" json:"reply,omitempty"` - Preview *TextNetworkPreview `protobuf:"bytes,3,opt,name=preview,proto3" json:"preview,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` + Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` + Reply *TextReply `protobuf:"bytes,3,opt,name=reply,proto3" json:"reply,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *CMIXText) Reset() { *m = CMIXText{} } @@ -54,6 +54,13 @@ func (m *CMIXText) XXX_DiscardUnknown() { var xxx_messageInfo_CMIXText proto.InternalMessageInfo +func (m *CMIXText) GetVersion() uint32 { + if m != nil { + return m.Version + } + return 0 +} + func (m *CMIXText) GetText() string { if m != nil { return m.Text @@ -68,83 +75,19 @@ func (m *CMIXText) GetReply() *TextReply { return nil } -func (m *CMIXText) GetPreview() *TextNetworkPreview { - if m != nil { - return m.Preview - } - return nil -} - -type TextNetworkPreview struct { - Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` - Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"` - Image []byte `protobuf:"bytes,3,opt,name=image,proto3" json:"image,omitempty"` +type TextReply struct { + MessageId []byte `protobuf:"bytes,1,opt,name=messageId,proto3" json:"messageId,omitempty"` + SenderId []byte `protobuf:"bytes,2,opt,name=senderId,proto3" json:"senderId,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } -func (m *TextNetworkPreview) Reset() { *m = TextNetworkPreview{} } -func (m *TextNetworkPreview) String() string { return proto.CompactTextString(m) } -func (*TextNetworkPreview) ProtoMessage() {} -func (*TextNetworkPreview) Descriptor() ([]byte, []int) { - return fileDescriptor_0df6afd1dcee6c11, []int{1} -} - -func (m *TextNetworkPreview) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_TextNetworkPreview.Unmarshal(m, b) -} -func (m *TextNetworkPreview) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_TextNetworkPreview.Marshal(b, m, deterministic) -} -func (m *TextNetworkPreview) XXX_Merge(src proto.Message) { - xxx_messageInfo_TextNetworkPreview.Merge(m, src) -} -func (m *TextNetworkPreview) XXX_Size() int { - return xxx_messageInfo_TextNetworkPreview.Size(m) -} -func (m *TextNetworkPreview) XXX_DiscardUnknown() { - xxx_messageInfo_TextNetworkPreview.DiscardUnknown(m) -} - -var xxx_messageInfo_TextNetworkPreview proto.InternalMessageInfo - -func (m *TextNetworkPreview) GetUrl() string { - if m != nil { - return m.Url - } - return "" -} - -func (m *TextNetworkPreview) GetTitle() string { - if m != nil { - return m.Title - } - return "" -} - -func (m *TextNetworkPreview) GetImage() []byte { - if m != nil { - return m.Image - } - return nil -} - -type TextReply struct { - Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` - SenderId []byte `protobuf:"bytes,2,opt,name=senderId,proto3" json:"senderId,omitempty"` - UniqueId []byte `protobuf:"bytes,3,opt,name=uniqueId,proto3" json:"uniqueId,omitempty"` - Preview *TextNetworkPreview `protobuf:"bytes,4,opt,name=preview,proto3" json:"preview,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - func (m *TextReply) Reset() { *m = TextReply{} } func (m *TextReply) String() string { return proto.CompactTextString(m) } func (*TextReply) ProtoMessage() {} func (*TextReply) Descriptor() ([]byte, []int) { - return fileDescriptor_0df6afd1dcee6c11, []int{2} + return fileDescriptor_0df6afd1dcee6c11, []int{1} } func (m *TextReply) XXX_Unmarshal(b []byte) error { @@ -165,11 +108,11 @@ func (m *TextReply) XXX_DiscardUnknown() { var xxx_messageInfo_TextReply proto.InternalMessageInfo -func (m *TextReply) GetMessage() string { +func (m *TextReply) GetMessageId() []byte { if m != nil { - return m.Message + return m.MessageId } - return "" + return nil } func (m *TextReply) GetSenderId() []byte { @@ -179,23 +122,8 @@ func (m *TextReply) GetSenderId() []byte { return nil } -func (m *TextReply) GetUniqueId() []byte { - if m != nil { - return m.UniqueId - } - return nil -} - -func (m *TextReply) GetPreview() *TextNetworkPreview { - if m != nil { - return m.Preview - } - return nil -} - func init() { proto.RegisterType((*CMIXText)(nil), "coupons.CMIXText") - proto.RegisterType((*TextNetworkPreview)(nil), "coupons.TextNetworkPreview") proto.RegisterType((*TextReply)(nil), "coupons.TextReply") } @@ -204,21 +132,16 @@ func init() { } var fileDescriptor_0df6afd1dcee6c11 = []byte{ - // 246 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x90, 0xbf, 0x4e, 0xc3, 0x30, - 0x10, 0xc6, 0xe5, 0xfe, 0x21, 0xcd, 0xd1, 0xa1, 0x3a, 0x31, 0x44, 0xb0, 0x54, 0x99, 0x32, 0x65, - 0x00, 0xf1, 0x02, 0x30, 0x65, 0x00, 0x55, 0x27, 0x06, 0x56, 0x20, 0x27, 0x64, 0x91, 0xc6, 0xc6, - 0x7f, 0x68, 0x99, 0x78, 0x09, 0x1e, 0x18, 0xd9, 0x4e, 0x23, 0x10, 0x4b, 0xb7, 0xfb, 0xee, 0x7e, - 0xf7, 0xf9, 0x3b, 0xc3, 0xea, 0x45, 0x79, 0xad, 0x7a, 0xc3, 0x56, 0xd7, 0xda, 0x28, 0xa7, 0x30, - 0x4b, 0x1d, 0x5b, 0x7e, 0xc1, 0xe2, 0xf6, 0xae, 0x79, 0x7c, 0xe0, 0xbd, 0x43, 0x84, 0x99, 0xe3, - 0xbd, 0x2b, 0xc4, 0x5a, 0x54, 0x39, 0xc5, 0x1a, 0x2b, 0x98, 0x1b, 0xd6, 0xdd, 0x67, 0x31, 0x59, - 0x8b, 0xea, 0xf4, 0x12, 0xeb, 0x61, 0xb1, 0x0e, 0x1b, 0x14, 0x26, 0x94, 0x00, 0xbc, 0x86, 0x4c, - 0x1b, 0xfe, 0x90, 0xbc, 0x2b, 0xa6, 0x91, 0xbd, 0xf8, 0xc3, 0xde, 0xb3, 0xdb, 0x29, 0xf3, 0xb6, - 0x49, 0x08, 0x1d, 0xd8, 0x92, 0x00, 0xff, 0x8f, 0x71, 0x05, 0x53, 0x6f, 0xba, 0x21, 0x49, 0x28, - 0xf1, 0x0c, 0xe6, 0x4e, 0xba, 0x8e, 0x63, 0x90, 0x9c, 0x92, 0x08, 0x5d, 0xb9, 0x7d, 0x7a, 0xe5, - 0xf8, 0xe4, 0x92, 0x92, 0x28, 0xbf, 0x05, 0xe4, 0x63, 0x3e, 0x2c, 0x20, 0xdb, 0xb2, 0xb5, 0x81, - 0x4a, 0x7e, 0x07, 0x89, 0xe7, 0xb0, 0xb0, 0xdc, 0xb7, 0x6c, 0x9a, 0x36, 0xda, 0x2e, 0x69, 0xd4, - 0x61, 0xe6, 0x7b, 0xf9, 0xee, 0xb9, 0x69, 0x07, 0xf3, 0x51, 0xff, 0x3e, 0x75, 0x76, 0xfc, 0xa9, - 0x37, 0x93, 0x8d, 0x78, 0x3e, 0x89, 0xff, 0x7f, 0xf5, 0x13, 0x00, 0x00, 0xff, 0xff, 0xf3, 0xac, - 0x09, 0x0f, 0x93, 0x01, 0x00, 0x00, + // 173 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x48, 0xce, 0x2f, 0x2d, + 0xc8, 0xcf, 0x2b, 0x4a, 0x2d, 0x2e, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x87, 0x88, + 0x14, 0x2b, 0x25, 0x71, 0x71, 0x38, 0xfb, 0x7a, 0x46, 0x84, 0xa4, 0x56, 0x94, 0x08, 0x49, 0x70, + 0xb1, 0x97, 0xa5, 0x16, 0x15, 0x67, 0xe6, 0xe7, 0x49, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x06, 0xc1, + 0xb8, 0x42, 0x42, 0x5c, 0x2c, 0x25, 0xa9, 0x15, 0x25, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, + 0x60, 0xb6, 0x90, 0x06, 0x17, 0x6b, 0x51, 0x6a, 0x41, 0x4e, 0xa5, 0x04, 0xb3, 0x02, 0xa3, 0x06, + 0xb7, 0x91, 0x90, 0x1e, 0xd4, 0x48, 0x3d, 0x90, 0x59, 0x41, 0x20, 0x99, 0x20, 0x88, 0x02, 0x25, + 0x57, 0x2e, 0x4e, 0xb8, 0x98, 0x90, 0x0c, 0x17, 0x67, 0x6e, 0x6a, 0x71, 0x71, 0x62, 0x7a, 0xaa, + 0x67, 0x0a, 0xd8, 0x1a, 0x9e, 0x20, 0x84, 0x80, 0x90, 0x14, 0x17, 0x47, 0x71, 0x6a, 0x5e, 0x4a, + 0x6a, 0x91, 0x67, 0x0a, 0xd8, 0x32, 0x9e, 0x20, 0x38, 0x3f, 0x89, 0x0d, 0xec, 0x74, 0x63, 0x40, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x96, 0x78, 0xf4, 0x8f, 0xce, 0x00, 0x00, 0x00, } diff --git a/coupons/couponresp.proto b/coupons/couponresp.proto index 5437690084a5a310c02fc6ccdea105eb7a17b094..a28fe1ca95f429fc33f5d5d58cedb1775aaf9700 100644 --- a/coupons/couponresp.proto +++ b/coupons/couponresp.proto @@ -2,24 +2,14 @@ syntax = "proto3"; package coupons; -// message, enum should be define in package level -option java_multiple_files = true; - message CMIXText { - string text = 1; - TextReply reply = 2; - TextNetworkPreview preview = 3; -} - -message TextNetworkPreview { - string url = 1; - string title = 2; - bytes image = 3; + uint32 version = 1; + string text = 2; + TextReply reply = 3; } message TextReply { - string message = 1; + bytes messageId = 1; bytes senderId = 2; - bytes uniqueId = 3; - TextNetworkPreview preview = 4; } + diff --git a/coupons/listener.go b/coupons/listener.go index c710ec53fabb1f01f6e80f9dee768557b258af65..c66d5d9f2fefef1e141b0a834758a679f14ed5b5 100644 --- a/coupons/listener.go +++ b/coupons/listener.go @@ -1,63 +1,128 @@ package coupons import ( + "errors" + "fmt" "git.xx.network/elixxir/coupons/storage" "github.com/golang/protobuf/proto" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/api" "gitlab.com/elixxir/client/interfaces/message" "gitlab.com/elixxir/client/interfaces/params" + "gorm.io/gorm" + "strconv" + "strings" + "time" ) type listener struct { - s *storage.Storage - c *api.Client + delay time.Duration + s *storage.Storage + c *api.Client } +var validResponse = "Thank you! You will receive %d xx in %s within 2 weeks!" +var noWalletFound = "Thank you! You will receive %d xx. The xx team does not have a wallet address for your purchase, please contact %s." +var reusedCode = "Your code %s has already been used. Your wallet %s will receive %d xx within 2 weeks." +var invalidCode = "That is not a valid code. Please send the code you received in your email for participating in the November Community Sale. Please contact %s if you need support." +var accountUsed = "Your messenger account can only redeem once." +var supportEmail = "distribution@xx-coin.io" + // Hear messages from users to the coupon bot & respond appropriately func (l *listener) Hear(item message.Receive) { // Confirm that authenticated channels if !l.c.HasAuthenticatedChannel(item.Sender) { jww.ERROR.Printf("No authenticated channel exists to %+v", item.Sender) + return } // Parse the trigger in := &CMIXText{} - trigger := "" + var trigger string err := proto.Unmarshal(item.Payload, in) if err != nil { - jww.ERROR.Printf("Could not unmartial message from messenger: %+v", err) + jww.ERROR.Printf("Could not unmarshal message from messenger: %+v", err) + return } else { trigger = in.Text } + jww.INFO.Printf("Received trigger %s [%+v]", trigger, in) + var strResponse string + + // Check if the user is in the db, if it is return the trigger + usedTrigger, err := l.s.CheckUser(item.Sender.String()) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { // Exit if we get error other than record not found + jww.DEBUG.Printf("Failed to check user with DB: %+v", err) + return + } + // Retrieve coupon code for trigger if it exists - strResponse, err := l.s.GetCouponCode(trigger) + codeResp, uses, err := l.s.GetCouponCode(trigger) if err != nil { jww.DEBUG.Printf("No coupon code for trigger %s: %+v", trigger, err) - strResponse = "No coupon found for trigger " + trigger + strResponse = fmt.Sprintf(invalidCode, supportEmail) + } else { + // Split the data in code column + rl := strings.Split(codeResp, ",") + // Assign wallet to var + wallet := rl[1] + // Parse num coins + num, err := strconv.Atoi(rl[0]) + if err != nil { + jww.ERROR.Printf("Could not parse num coins: %+v", err) + return + } + jww.DEBUG.Printf("Found %s with %d uses", codeResp, uses) + + // Response logic + if usedTrigger != "" && usedTrigger != trigger { // If account has a code used that isn't the one passed in + jww.INFO.Println("Case 1") + strResponse = accountUsed + } else if uses < 1 { // If the code has been used + jww.INFO.Println("Case 2") + strResponse = fmt.Sprintf(reusedCode, trigger, wallet, num) + } else { // Code is good and can be used by this account + jww.INFO.Println("Case 3") + err = l.s.UseCode(item.Sender.String(), trigger) + if err != nil { + jww.ERROR.Printf("Failed to commit code use to db: %+v", err) + } + if wallet == "" { // + strResponse = fmt.Sprintf(noWalletFound, num, supportEmail) + } else { + strResponse = fmt.Sprintf(validResponse, num, wallet) + } + } } payload := &CMIXText{ - Text: strResponse, + Version: 0, + Text: strResponse, + Reply: &TextReply{ + MessageId: item.ID.Marshal(), + SenderId: item.Sender.Marshal(), + }, } marshalled, err := proto.Marshal(payload) if err != nil { jww.ERROR.Printf("Failed to marshal payload: %+v", err) + return } // Create response message resp := message.Send{ Recipient: item.Sender, Payload: marshalled, - MessageType: message.Text, + MessageType: message.XxMessage, } // Send response message to sender over cmix rids, mid, t, err := l.c.SendE2E(resp, params.GetDefaultE2E()) if err != nil { jww.ERROR.Printf("Failed to send message: %+v", err) + } else { + jww.INFO.Printf("Sent response %s [%+v] to %+v on rounds %+v [%+v]", strResponse, mid, item.Sender.String(), rids, t) } - jww.INFO.Printf("Sent response %s [%+v] to %+v on rounds %+v [%+v]", strResponse, mid, item.Sender.String(), rids, t) } // Name returns a name, used for debugging diff --git a/go.mod b/go.mod index fe20eaeffc70b71d726816e6f00ffeb38e7397ee..a57385d33c9a6bef32ee9b537501a7e04444517c 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,10 @@ require ( github.com/spf13/cobra v1.2.1 github.com/spf13/jwalterweatherman v1.1.0 github.com/spf13/viper v1.9.0 - gitlab.com/elixxir/client v1.5.1-0.20211108203412-047b81be6c67 - gitlab.com/elixxir/crypto v0.0.7-0.20211022013957-3a7899285c4c - gitlab.com/elixxir/primitives v0.0.3-0.20211014164029-06022665b576 - gitlab.com/xx_network/primitives v0.0.4-0.20211014163031-53405cf191fb + gitlab.com/elixxir/client v1.5.1-0.20220202192535-930327652941 + gitlab.com/elixxir/crypto v0.0.7-0.20220110170041-7e42f2e8b062 + gitlab.com/elixxir/primitives v0.0.3-0.20220104173924-275cb9d7834f + gitlab.com/xx_network/primitives v0.0.4-0.20211222205802-03e9d7d835b0 gorm.io/driver/postgres v1.1.2 gorm.io/gorm v1.21.15 ) diff --git a/go.sum b/go.sum index 94f82c9bbce1dfbadd5e0dab2960198322b01e56..52d55028e08157eecea5bc94681e918e39006f53 100644 --- a/go.sum +++ b/go.sum @@ -62,16 +62,24 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.0.1-0.20211008185751-59b49bc148ce h1:2s+cfEmFVdtV8Z85o6U0QxtNhCXDCMR2OLZKgL39ApI= +github.com/cloudflare/circl v1.0.1-0.20211008185751-59b49bc148ce/go.mod h1:tnEeRn/onb0b4Ew40H00boTlcVMHveaTzi6m+/iMruw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -89,6 +97,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/elliotchance/orderedmap v1.4.0 h1:wZtfeEONCbx6in1CZyE6bELEt/vFayMvsxqI5SgsR+A= +github.com/elliotchance/orderedmap v1.4.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -96,6 +106,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= @@ -351,6 +362,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -460,34 +473,34 @@ github.com/zeebo/pcg v1.0.0/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228 h1:Gi6rj4mAlK0BJIk1HIzBVMjWNjIUfstrsXC2VqLYPcA= gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228/go.mod h1:H6jztdm0k+wEV2QGK/KYA+MY9nj9Zzatux/qIvDDv3k= -gitlab.com/elixxir/client v1.5.1-0.20211108203412-047b81be6c67 h1:0eR95gzo2L5fJlWkRmasXmGb9155/Pr26GAoFEUzQI0= -gitlab.com/elixxir/client v1.5.1-0.20211108203412-047b81be6c67/go.mod h1:YYGrpEby2kGV26hzfRciqyOvTSZBtMiGi8QLg4jMymo= -gitlab.com/elixxir/comms v0.0.4-0.20211029171408-dcd58978e446 h1:wmrdEN8H+nCLkZ09S41OyYI/Nj/9Gg6ra6p8iuJ6pjg= -gitlab.com/elixxir/comms v0.0.4-0.20211029171408-dcd58978e446/go.mod h1:rQpTeFVSn08ocbQeEw5AbMhGWXHfXmQ0y1/ZprAIVVU= +gitlab.com/elixxir/client v1.5.1-0.20220202192535-930327652941 h1:YbVSJyx1T7nD5KY/cykpKE+sDctzWT2yS88GEfrEEXE= +gitlab.com/elixxir/client v1.5.1-0.20220202192535-930327652941/go.mod h1:Ji6FOGgiVckBV2vaWd5Qh3feoYiMFvro/4PZuPVPILg= +gitlab.com/elixxir/comms v0.0.4-0.20220128193157-34178165415d h1:207Okb8+amKnRzsKE/4ehl3eb6ZHkWXevq9TimOehzw= +gitlab.com/elixxir/comms v0.0.4-0.20220128193157-34178165415d/go.mod h1:pj1TXrpHKytF68y53BtCBRYhaiMJWquuVBk4iEN7wkk= gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo+K0N2GwRRpaNr+tKXMIOVWzmyUD0SbOu2c= gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA= -gitlab.com/elixxir/crypto v0.0.7-0.20211022013957-3a7899285c4c h1:HIr2HBhZqSAKdPRBdEY0/qravISL619O2yuTY/DQTdo= -gitlab.com/elixxir/crypto v0.0.7-0.20211022013957-3a7899285c4c/go.mod h1:teuTEXyqsqo4N/J1sshcTg9xYOt+wNTurop7pkZOiCg= -gitlab.com/elixxir/ekv v0.1.5 h1:R8M1PA5zRU1HVnTyrtwybdABh7gUJSCvt1JZwUSeTzk= -gitlab.com/elixxir/ekv v0.1.5/go.mod h1:e6WPUt97taFZe5PFLPb1Dupk7tqmDCTQu1kkstqJvw4= +gitlab.com/elixxir/crypto v0.0.7-0.20220110170041-7e42f2e8b062 h1:6LLdEX2U/jA3RakJh/cKtjckMYBBjrjyowoBEtIF9L4= +gitlab.com/elixxir/crypto v0.0.7-0.20220110170041-7e42f2e8b062/go.mod h1:qmW0OGPB21GcaGg1Jvt527/qUw7ke6W8DKCiYBfsx48= +gitlab.com/elixxir/ekv v0.1.6 h1:M2hUSNhH/ChxDd+s8xBqSEKgoPtmE6hOEBqQ73KbN6A= +gitlab.com/elixxir/ekv v0.1.6/go.mod h1:e6WPUt97taFZe5PFLPb1Dupk7tqmDCTQu1kkstqJvw4= gitlab.com/elixxir/primitives v0.0.0-20200731184040-494269b53b4d/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg= gitlab.com/elixxir/primitives v0.0.0-20200804170709-a1896d262cd9/go.mod h1:p0VelQda72OzoUckr1O+vPW0AiFe0nyKQ6gYcmFSuF8= gitlab.com/elixxir/primitives v0.0.0-20200804182913-788f47bded40/go.mod h1:tzdFFvb1ESmuTCOl1z6+yf6oAICDxH2NPUemVgoNLxc= gitlab.com/elixxir/primitives v0.0.1/go.mod h1:kNp47yPqja2lHSiS4DddTvFpB/4D9dB2YKnw5c+LJCE= -gitlab.com/elixxir/primitives v0.0.3-0.20211014164029-06022665b576 h1:sXX3/hewV4TQLxT2iKBfnfgW/A1eXoEfv5raJxTb79s= -gitlab.com/elixxir/primitives v0.0.3-0.20211014164029-06022665b576/go.mod h1:zZy8AlOISFm5IG4G4sylypnz7xNBfZ5mpXiibqJT8+8= +gitlab.com/elixxir/primitives v0.0.3-0.20220104173924-275cb9d7834f h1:zg3oYk+a6Wmq9tGRwka3GjJR1XRZIVCsOMpBGxtF2yc= +gitlab.com/elixxir/primitives v0.0.3-0.20220104173924-275cb9d7834f/go.mod h1:zA+1Lp9fGPo6pl1QxtMoNPLeZJ1O5m4kcH7HNxePQnQ= gitlab.com/xx_network/comms v0.0.0-20200805174823-841427dd5023/go.mod h1:owEcxTRl7gsoM8c3RQ5KAm5GstxrJp5tn+6JfQ4z5Hw= -gitlab.com/xx_network/comms v0.0.4-0.20211014163953-e774276b83ae h1:jmZWmSm8eH40SX5B5uOw2XaYoHYqVn8daTfa6B80AOs= -gitlab.com/xx_network/comms v0.0.4-0.20211014163953-e774276b83ae/go.mod h1:wR9Vx0KZLrIs0g2Efcp0UwFPStjcDRWkg/DJLVQI2vw= +gitlab.com/xx_network/comms v0.0.4-0.20220126231737-fe2338016cce h1:PCOf9nyehdz2IIUsssakxH+YT2q3YQ6OlAFpcOgsd88= +gitlab.com/xx_network/comms v0.0.4-0.20220126231737-fe2338016cce/go.mod h1:5arueRMa2MNa6dALnfJwyZOhqhV53Gqc+tlHRz+Ycjw= gitlab.com/xx_network/crypto v0.0.3/go.mod h1:DF2HYvvCw9wkBybXcXAgQMzX+MiGbFPjwt3t17VRqRE= gitlab.com/xx_network/crypto v0.0.4/go.mod h1:+lcQEy+Th4eswFgQDwT0EXKp4AXrlubxalwQFH5O0Mk= -gitlab.com/xx_network/crypto v0.0.5-0.20211014163843-57b345890686 h1:mEjKISxi9LrguYgz6evroFwsfxH78/hYmr32yws+WV0= -gitlab.com/xx_network/crypto v0.0.5-0.20211014163843-57b345890686/go.mod h1:GeUUB5eMlu7G1u7LXpClfOyUYsSDxAhiZBf+RZeGftc= +gitlab.com/xx_network/crypto v0.0.5-0.20211227194420-f311e8920467 h1:LkZtWBYrM2e7QRf5aaBAcy7s7CpYGhAqgXRFVCdBRy4= +gitlab.com/xx_network/crypto v0.0.5-0.20211227194420-f311e8920467/go.mod h1:c+x0w3Xk6QZe5w2Redn5SiaBpqAhgNSfwBr0JGa/yyo= gitlab.com/xx_network/primitives v0.0.0-20200803231956-9b192c57ea7c/go.mod h1:wtdCMr7DPePz9qwctNoAUzZtbOSHSedcK++3Df3psjA= gitlab.com/xx_network/primitives v0.0.0-20200804183002-f99f7a7284da/go.mod h1:OK9xevzWCaPO7b1wiluVJGk7R5ZsuC7pHY5hteZFQug= gitlab.com/xx_network/primitives v0.0.2/go.mod h1:cs0QlFpdMDI6lAo61lDRH2JZz+3aVkHy+QogOB6F/qc= -gitlab.com/xx_network/primitives v0.0.4-0.20211014163031-53405cf191fb h1:0K9dyxFpDYzH9jYLwzg3+bRj9a0uJjwjQkMeIdTxduQ= -gitlab.com/xx_network/primitives v0.0.4-0.20211014163031-53405cf191fb/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE= +gitlab.com/xx_network/primitives v0.0.4-0.20211222205802-03e9d7d835b0 h1:IHHb59DJEKk02HgfxddqK2ilvkRMAUdPIBFn8rmjjIg= +gitlab.com/xx_network/primitives v0.0.4-0.20211222205802-03e9d7d835b0/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE= gitlab.com/xx_network/ring v0.0.3-0.20210527191221-ce3f170aabd5 h1:FY+4Rh1Q2rgLyv10aKJjhWApuKRCR/054XhreudfAvw= gitlab.com/xx_network/ring v0.0.3-0.20210527191221-ce3f170aabd5/go.mod h1:aLzpP2TiZTQut/PVHR40EJAomzugDdHXetbieRClXIM= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -707,8 +720,9 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210902050250-f475640dd07b h1:S7hKs0Flbq0bbc9xgYt4stIEG1zNDFqyrPwAX2Wj/sE= +golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -906,8 +920,9 @@ google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= diff --git a/storage/database.go b/storage/database.go index d8c2c9b10ecbe7a55580eaa99e3567cd62bb8d82..eb7ec0cd3d3920153def64ad6a962977fb2fcc06 100644 --- a/storage/database.go +++ b/storage/database.go @@ -13,8 +13,10 @@ import ( // database interface holds function definitions for storage type database interface { - GetCouponCode(trigger string) (string, error) + CheckUser(id string) (string, error) + GetCouponCode(trigger string) (string, int, error) InsertCoupon(c Coupon) error + UseCode(id, trigger string) error } // DatabaseImpl struct implements the database interface with an underlying DB @@ -24,15 +26,22 @@ type DatabaseImpl struct { // MapImpl struct implements the database interface with an underlying Map type MapImpl struct { - coupons map[string]Coupon + coupons map[string]*Coupon + users map[string]*Coupon sync.RWMutex } +type User struct { + ID string `gorm:"primary_key"` + Trigger string + Coupon Coupon `gorm:"foreignkey:trigger;references:trigger"` +} + // Coupon struct defines coupons in the db type Coupon struct { Trigger string `gorm:"primary_key"` - Code string `gorm:"NOT NULL;unique;"` - Uses uint + Code string `gorm:"NOT NULL;"` + Uses int } // newDatabase initializes the database interface @@ -70,7 +79,8 @@ func newDatabase(username, password, dbName, address, defer jww.INFO.Println("Map backend initialized successfully!") mapImpl := &MapImpl{ - coupons: map[string]Coupon{}, + coupons: map[string]*Coupon{}, + users: map[string]*Coupon{}, } return database(mapImpl), nil @@ -92,7 +102,7 @@ func newDatabase(username, password, dbName, address, // Initialize the database schema // WARNING: Order is important. Do not change without database testing - models := []interface{}{Coupon{}} + models := []interface{}{Coupon{}, User{}} for _, model := range models { err = db.AutoMigrate(model) if err != nil { diff --git a/storage/database_test.go b/storage/database_test.go index de55cf774fe1bcd2b929a52da7c97e1d13344023..d9a6b9812b0ea8920d0a15984f7ce50ce4c09ad7 100644 --- a/storage/database_test.go +++ b/storage/database_test.go @@ -13,8 +13,9 @@ func TestDatabase(t *testing.T) { if err != nil { t.Errorf("Failed to initialize storage: %+v", err) } + trigger := "up up down down left right left right b a start" err = s.InsertCoupon(Coupon{ - Trigger: "up up down down left right left right b a start", + Trigger: trigger, Code: "helpimtrappedinacouponfactory", Uses: 1, }) @@ -22,7 +23,7 @@ func TestDatabase(t *testing.T) { t.Errorf("Failed to insert coupon: %+v", err) } - c, err := s.GetCouponCode("up up down down left right left right b a start") + c, _, err := s.GetCouponCode("up up down down left right left right b a start") if err != nil { t.Errorf("Failed to get coupon code: %+v", err) } @@ -31,8 +32,13 @@ func TestDatabase(t *testing.T) { t.Errorf("Did not get expected coupon") } - _, err = s.GetCouponCode("up up down down left right left right b a start") - if err == nil { - t.Error("Should have given error w/ no uses left") + err = s.UseCode("zezima", trigger) + if err != nil { + t.Errorf("Failed to use code: %+v", err) + } + + _, uses, err := s.GetCouponCode("up up down down left right left right b a start") + if uses > 0 { + t.Error("Should not have any uses left") } } diff --git a/storage/dbImpl.go b/storage/dbImpl.go index 928c75759cb216bf57e58f36411c4cac99f5131d..0d1e2807b5c6a38a4e1f4fbb644b8e1115f4a593 100644 --- a/storage/dbImpl.go +++ b/storage/dbImpl.go @@ -1,34 +1,56 @@ package storage import ( - "errors" + "github.com/pkg/errors" "gorm.io/gorm" ) -func (db *DatabaseImpl) GetCouponCode(trigger string) (string, error) { +func (db *DatabaseImpl) GetCouponCode(trigger string) (string, int, error) { var c = &Coupon{} err := db.db.Transaction(func(tx *gorm.DB) error { err := tx.First(&c, "trigger = ?", trigger).Error if err != nil { return err } - if c.Uses <= 0 { - return errors.New("requested code is out of uses") - } - err = tx.Model(&c).Where("trigger = ?", trigger). - Update("uses", gorm.Expr("uses - ?", 1)).Error - if err != nil { - return err - } return nil }) if err != nil { - return "", err + return "", 0, err } - return c.Code, nil + return c.Code, int(c.Uses), nil } func (db *DatabaseImpl) InsertCoupon(c Coupon) error { return db.db.Create(c).Error } + +func (db *DatabaseImpl) CheckUser(id string) (string, error) { + u := &User{} + err := db.db.Where("id = ?", id).Take(&u).Error + if err != nil { + return "", err + } + return u.Trigger, nil +} + +func (db *DatabaseImpl) UseCode(id, trigger string) error { + return db.db.Transaction(func(tx *gorm.DB) error { + u := &User{ + ID: id, + Trigger: trigger, + } + err := tx.Create(&u).Error + if err != nil { + return errors.WithMessage(err, "Failed to add user") + } + + c := &Coupon{} + err = tx.Model(&c).Where("trigger = ?", trigger). + Update("uses", gorm.Expr("uses - ?", 1)).Error + if err != nil { + return errors.WithMessage(err, "Failed to use code") + } + return nil + }) +} diff --git a/storage/mapImpl.go b/storage/mapImpl.go index 24489826072ced38f1f9e27fafe61c96a4c43723..667f892a6268f1ef1c27b666d91e8068eb00a911 100644 --- a/storage/mapImpl.go +++ b/storage/mapImpl.go @@ -3,22 +3,39 @@ package storage import ( "errors" "fmt" + "gorm.io/gorm" ) -func (m *MapImpl) GetCouponCode(trigger string) (string, error) { +func (m *MapImpl) GetCouponCode(trigger string) (string, int, error) { c, ok := m.coupons[trigger] if !ok { - return "", errors.New(fmt.Sprintf("No coupon for trigger %s", trigger)) + return "", 0, errors.New(fmt.Sprintf("No coupon for trigger %s", trigger)) } + uses := int(c.Uses) if c.Uses <= 0 { - return "", errors.New("No uses left for requested coupon") + return "", uses, errors.New("No uses left for requested coupon") } - c.Uses = c.Uses - 1 m.coupons[trigger] = c - return c.Code, nil + return c.Code, uses, nil } func (m *MapImpl) InsertCoupon(c Coupon) error { - m.coupons[c.Trigger] = c + m.coupons[c.Trigger] = &c + return nil +} + +func (m *MapImpl) CheckUser(id string) (string, error) { + u, ok := m.users[id] + if !ok { + return "", gorm.ErrRecordNotFound + } + return u.Trigger, nil +} + +func (m *MapImpl) UseCode(id, trigger string) error { + m.users[id] = m.coupons[trigger] + c := m.coupons[trigger] + fmt.Println(m.coupons) + c.Uses = c.Uses - 1 return nil }