@@ -2,25 +2,30 @@ package chat
22
33import (
44 "context"
5+ "database/sql"
56 "errors"
7+ "fmt"
68 "time"
79
810 "github.com/mr-tron/base58"
911 "google.golang.org/protobuf/proto"
1012
1113 chatpb "github.com/code-payments/code-protobuf-api/generated/go/chat/v1"
14+ chatv2pb "github.com/code-payments/code-protobuf-api/generated/go/chat/v2"
1215
1316 "github.com/code-payments/code-server/pkg/code/common"
1417 code_data "github.com/code-payments/code-server/pkg/code/data"
1518 chat_v1 "github.com/code-payments/code-server/pkg/code/data/chat/v1"
19+ chat_v2 "github.com/code-payments/code-server/pkg/code/data/chat/v2"
20+ chatserver "github.com/code-payments/code-server/pkg/code/server/grpc/chat/v2"
1621)
1722
18- // SendChatMessage sends a chat message to a receiving owner account.
23+ // SendNotificationChatMessageV1 sends a chat message to a receiving owner account.
1924//
2025// Note: This function is not responsible for push notifications. This method
2126// might be called within the context of a DB transaction, which might have
22- // unrelated failures. A hint as to whether a push should be sent is provided.
23- func SendChatMessage (
27+ // unrelated failures. A hint whether a push should be sent is provided.
28+ func SendNotificationChatMessageV1 (
2429 ctx context.Context ,
2530 data code_data.Provider ,
2631 chatTitle string ,
@@ -80,7 +85,7 @@ func SendChatMessage(
8085 }
8186
8287 err = data .PutChatV1 (ctx , chatRecord )
83- if err != nil && err != chat_v1 .ErrChatAlreadyExists {
88+ if err != nil && ! errors . Is ( err , chat_v1 .ErrChatAlreadyExists ) {
8489 return false , err
8590 }
8691 default :
@@ -115,3 +120,131 @@ func SendChatMessage(
115120
116121 return canPushMessage , nil
117122}
123+
124+ func SendNotificationChatMessageV2 (
125+ ctx context.Context ,
126+ data code_data.Provider ,
127+ notifier chatserver.Notifier ,
128+ chatTitle string ,
129+ isVerifiedChat bool ,
130+ receiver * common.Account ,
131+ protoMessage * chatv2pb.ChatMessage ,
132+ intentId string ,
133+ isSilentMessage bool ,
134+ ) (canPushMessage bool , err error ) {
135+ chatId := chat_v2 .GetChatId (chatTitle , receiver .PublicKey ().ToBase58 (), isVerifiedChat )
136+
137+ if protoMessage .Cursor != nil {
138+ // Let the utilities and GetMessages RPC handle cursors
139+ return false , errors .New ("cursor must not be set" )
140+ }
141+
142+ if err := protoMessage .Validate (); err != nil {
143+ return false , err
144+ }
145+
146+ messageId , err := chat_v2 .GetMessageIdFromProto (protoMessage .MessageId )
147+ if err != nil {
148+ return false , fmt .Errorf ("invalid message id: %w" , err )
149+ }
150+
151+ // Clear out extracted metadata as a space optimization
152+ cloned := proto .Clone (protoMessage ).(* chatv2pb.ChatMessage )
153+ cloned .MessageId = nil
154+ cloned .Ts = nil
155+ cloned .Cursor = nil
156+
157+ marshalled , err := proto .Marshal (cloned )
158+ if err != nil {
159+ return false , err
160+ }
161+
162+ canPersistMessage := true
163+ canPushMessage = ! isSilentMessage
164+
165+ //
166+ // Step 1: Check to see if we need to create the chat.
167+ //
168+ _ , err = data .GetChatByIdV2 (ctx , chatId )
169+ if errors .Is (err , chat_v2 .ErrChatNotFound ) {
170+ chatRecord := & chat_v2.ChatRecord {
171+ ChatId : chatId ,
172+ ChatType : chat_v2 .ChatTypeNotification ,
173+ ChatTitle : & chatTitle ,
174+ IsVerified : isVerifiedChat ,
175+
176+ CreatedAt : time .Now (),
177+ }
178+
179+ err = data .ExecuteInTx (ctx , sql .LevelDefault , func (ctx context.Context ) error {
180+ err = data .PutChatV2 (ctx , chatRecord )
181+ if err != nil && ! errors .Is (err , chat_v2 .ErrChatExists ) {
182+ return fmt .Errorf ("failed to initialize chat: %w" , err )
183+ }
184+
185+ err = data .PutChatMemberV2 (ctx , & chat_v2.MemberRecord {
186+ ChatId : chatId ,
187+ MemberId : chat_v2 .GenerateMemberId (),
188+ Platform : chat_v2 .PlatformCode ,
189+ PlatformId : receiver .PublicKey ().ToBase58 (),
190+ JoinedAt : time .Now (),
191+ })
192+ if err != nil {
193+ return fmt .Errorf ("failed to initialize chat with member: %w" , err )
194+ }
195+
196+ return nil
197+ })
198+ if err != nil {
199+ return false , err
200+ }
201+ } else if err != nil {
202+ return false , err
203+ }
204+
205+ //
206+ // Step 2: Ensure that there is exactly 1 member in the chat.
207+ //
208+ members , err := data .GetAllChatMembersV2 (ctx , chatId )
209+ if errors .Is (err , chat_v2 .ErrMemberNotFound ) { // TODO: This is a weird error...
210+ return false , nil
211+ } else if err != nil {
212+ return false , err
213+ }
214+ if len (members ) > 1 {
215+ // TODO: This _could_ get weird if client or someone else decides to join as another member.
216+ return false , errors .New ("notification chat should have at most 1 member" )
217+ }
218+
219+ canPersistMessage = ! members [0 ].IsUnsubscribed
220+ canPushMessage = canPushMessage && canPersistMessage && ! members [0 ].IsMuted
221+
222+ if canPersistMessage {
223+ refType := chat_v2 .ReferenceTypeIntent
224+ messageRecord := & chat_v2.MessageRecord {
225+ ChatId : chatId ,
226+ MessageId : messageId ,
227+
228+ Data : marshalled ,
229+ IsSilent : isSilentMessage ,
230+
231+ ReferenceType : & refType ,
232+ Reference : & intentId ,
233+ }
234+
235+ // TODO: Once we have a better idea on the data modeling around chatv2,
236+ // we may wish to have the server manage the creation of messages
237+ // (and chats?) as well. That would also put the
238+ err = data .PutChatMessageV2 (ctx , messageRecord )
239+ if err != nil {
240+ return false , err
241+ }
242+
243+ notifier .NotifyMessage (ctx , chatId , protoMessage )
244+ }
245+
246+ // TODO: Once we move more things over to chatv2, we will need to increment
247+ // badge count here. We don't currently, as it would result in a double
248+ // push.
249+ return canPushMessage , nil
250+ }
0 commit comments