diff --git a/chats/ops.go b/chats/ops.go index 0c8b5689..ea453cb3 100644 --- a/chats/ops.go +++ b/chats/ops.go @@ -46,6 +46,22 @@ func (o *ops) CreateGroup(ctx context.Context, userIDs []string, topic string, i return adapter.MapGraphChat(resp), nil } +func (o *ops) GetOneOnOneChat(ctx context.Context, chatID string) (*models.Chat, error) { + resp, requestErr := o.chatAPI.GetOneOnOneChat(ctx, chatID) + if requestErr != nil { + return nil, snd.MapError(requestErr, snd.WithResource(resources.OneOnOneChat, chatID)) + } + return adapter.MapGraphChat(resp), nil +} + +func (o *ops) GetGroupChat(ctx context.Context, chatID string) (*models.Chat, error) { + resp, requestErr := o.chatAPI.GetGroupChat(ctx, chatID) + if requestErr != nil { + return nil, snd.MapError(requestErr, snd.WithResource(resources.GroupChat, chatID)) + } + return adapter.MapGraphChat(resp), nil +} + func (o *ops) AddMemberToGroupChat(ctx context.Context, chatID, userID string) (*models.Member, error) { resp, requestErr := o.chatAPI.AddMemberToGroupChat(ctx, chatID, userID) if requestErr != nil { diff --git a/chats/ops_interface.go b/chats/ops_interface.go index 112a2e1c..fb92d9aa 100644 --- a/chats/ops_interface.go +++ b/chats/ops_interface.go @@ -11,6 +11,8 @@ import ( type chatOps interface { CreateOneOnOne(ctx context.Context, userID string) (*models.Chat, error) CreateGroup(ctx context.Context, userIDs []string, topic string, includeMe bool) (*models.Chat, error) + GetOneOnOneChat(ctx context.Context, chatID string) (*models.Chat, error) + GetGroupChat(ctx context.Context, chatID string) (*models.Chat, error) AddMemberToGroupChat(ctx context.Context, chatID, userID string) (*models.Member, error) RemoveMemberFromGroupChat(ctx context.Context, chatID, userID string) error ListGroupChatMembers(ctx context.Context, chatID string) ([]*models.Member, error) diff --git a/chats/ops_with_cache.go b/chats/ops_with_cache.go index 9730b98e..ed7c938b 100644 --- a/chats/ops_with_cache.go +++ b/chats/ops_with_cache.go @@ -56,6 +56,18 @@ func (o *opsWithCache) CreateGroup(ctx context.Context, userIDs []string, topic return chat, nil } +func (o *opsWithCache) GetOneOnOneChat(ctx context.Context, chatID string) (*models.Chat, error) { + return cacher.WithErrorClear(func() (*models.Chat, error) { + return o.chatOps.GetOneOnOneChat(ctx, chatID) + }, o.cacheHandler) +} + +func (o *opsWithCache) GetGroupChat(ctx context.Context, chatID string) (*models.Chat, error) { + return cacher.WithErrorClear(func() (*models.Chat, error) { + return o.chatOps.GetGroupChat(ctx, chatID) + }, o.cacheHandler) +} + func (o *opsWithCache) AddMemberToGroupChat(ctx context.Context, chatID, userID string) (*models.Member, error) { member, err := o.chatOps.AddMemberToGroupChat(ctx, chatID, userID) if err != nil { diff --git a/chats/service.go b/chats/service.go index 3cf6a55f..a9417366 100644 --- a/chats/service.go +++ b/chats/service.go @@ -44,6 +44,34 @@ func (s *service) CreateGroup(ctx context.Context, recipientRefs []string, topic return resp, nil } +func (s *service) GetChat(ctx context.Context, chatRef ChatRef) (*models.Chat, error) { + chatID, err := s.resolveChatIDFromRef(ctx, chatRef) + if err != nil { + return nil, snd.Wrap("GetChat", err, + snd.NewParam(resources.ChatRef, chatRef.get()), + ) + } + + var resp *models.Chat + switch chatRef.(type) { + case OneOnOneChatRef: + resp, err = s.chatOps.GetOneOnOneChat(ctx, chatID) + case GroupChatRef: + resp, err = s.chatOps.GetGroupChat(ctx, chatID) + default: + return nil, snd.Wrap("GetChat", fmt.Errorf("unknown chat reference type"), + snd.NewParam(resources.ChatRef, chatRef.get()), + ) + } + if err != nil { + return nil, snd.Wrap("GetChat", err, + snd.NewParam(resources.ChatRef, chatRef.get()), + ) + } + + return resp, nil +} + func (s *service) AddMemberToGroupChat(ctx context.Context, chatRef GroupChatRef, userRef string) (*models.Member, error) { chatID, err := s.resolveChatIDFromRef(ctx, chatRef) if err != nil { diff --git a/chats/service_interface.go b/chats/service_interface.go index dd8b78df..2d04cd9a 100644 --- a/chats/service_interface.go +++ b/chats/service_interface.go @@ -31,6 +31,9 @@ type Service interface { // The authenticated user may be included by setting includeMe to true. CreateGroup(ctx context.Context, recipientRefs []string, topic string, includeMe bool) (*models.Chat, error) + // GetChat retrieves a chat (one-on-one or group) by its reference. + GetChat(ctx context.Context, chatRef ChatRef) (*models.Chat, error) + // AddMemberToGroupChat adds a user to a group chat. AddMemberToGroupChat(ctx context.Context, chatRef GroupChatRef, userRef string) (*models.Member, error) diff --git a/internal/api/chat.go b/internal/api/chat.go index 8d895b7b..03193fd1 100644 --- a/internal/api/chat.go +++ b/internal/api/chat.go @@ -16,9 +16,11 @@ import ( type OneOnOneChatAPI interface { CreateOneOnOneChat(ctx context.Context, recipientRef string) (msmodels.Chatable, *sender.RequestError) + GetOneOnOneChat(ctx context.Context, chatID string) (msmodels.Chatable, *sender.RequestError) } type GroupChatAPI interface { + GetGroupChat(ctx context.Context, chatID string) (msmodels.Chatable, *sender.RequestError) CreateGroupChat(ctx context.Context, recipientRefs []string, topic string, includeMe bool) (msmodels.Chatable, *sender.RequestError) AddMemberToGroupChat(ctx context.Context, chatID, userRef string) (msmodels.ConversationMemberable, *sender.RequestError) RemoveMemberFromGroupChat(ctx context.Context, chatID, memberID string) *sender.RequestError @@ -52,6 +54,64 @@ func NewChat(client *graph.GraphServiceClient, senderCfg *config.SenderConfig, s return &chatsAPI{client, senderCfg, searchAPI} } +func (c *chatsAPI) GetOneOnOneChat(ctx context.Context, chatID string) (msmodels.Chatable, *sender.RequestError) { + me, err := GetMe(ctx, c.client, c.senderCfg) + if err != nil { + return nil, err + } + if me.GetId() == nil { + return nil, &sender.RequestError{Message: "cannot get current user ID"} + } + + requestParameters := &graphusers.ItemChatsChatItemRequestBuilderGetQueryParameters{ + Expand: []string{"members"}, + } + + configuration := &graphusers.ItemChatsChatItemRequestBuilderGetRequestConfiguration{ + QueryParameters: requestParameters, + } + + call := func(ctx context.Context) (sender.Response, error) { + return c.client. + Users(). + ByUserId(*me.GetId()). + Chats(). + ByChatId(chatID). + Get(ctx, configuration) + } + + resp, err := sender.SendRequest(ctx, call, c.senderCfg) + if err != nil { + return nil, err + } + + out, ok := resp.(msmodels.Chatable) + if !ok { + return nil, newTypeError("Chatable") + } + return out, nil +} + +func (c *chatsAPI) GetGroupChat(ctx context.Context, chatID string) (msmodels.Chatable, *sender.RequestError) { + call := func(ctx context.Context) (sender.Response, error) { + return c.client. + Chats(). + ByChatId(chatID). + Get(ctx, nil) + } + + resp, err := sender.SendRequest(ctx, call, c.senderCfg) + if err != nil { + return nil, err + } + + out, ok := resp.(msmodels.Chatable) + if !ok { + return nil, newTypeError("Chatable") + } + return out, nil +} + func (c *chatsAPI) CreateOneOnOneChat(ctx context.Context, userRef string) (msmodels.Chatable, *sender.RequestError) { body := msmodels.NewChat() chatType := msmodels.ONEONONE_CHATTYPE diff --git a/internal/testutil/mock_chat_api.go b/internal/testutil/mock_chat_api.go index a0dd422b..fef1b924 100644 --- a/internal/testutil/mock_chat_api.go +++ b/internal/testutil/mock_chat_api.go @@ -60,6 +60,21 @@ func (mr *MockOneOnOneChatAPIMockRecorder) CreateOneOnOneChat(ctx, recipientRef return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOneOnOneChat", reflect.TypeOf((*MockOneOnOneChatAPI)(nil).CreateOneOnOneChat), ctx, recipientRef) } +// GetOneOnOneChat mocks base method. +func (m *MockOneOnOneChatAPI) GetOneOnOneChat(ctx context.Context, chatID string) (models.Chatable, *sender.RequestError) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOneOnOneChat", ctx, chatID) + ret0, _ := ret[0].(models.Chatable) + ret1, _ := ret[1].(*sender.RequestError) + return ret0, ret1 +} + +// GetOneOnOneChat indicates an expected call of GetOneOnOneChat. +func (mr *MockOneOnOneChatAPIMockRecorder) GetOneOnOneChat(ctx, chatID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOneOnOneChat", reflect.TypeOf((*MockOneOnOneChatAPI)(nil).GetOneOnOneChat), ctx, chatID) +} + // MockGroupChatAPI is a mock of GroupChatAPI interface. type MockGroupChatAPI struct { ctrl *gomock.Controller @@ -114,6 +129,21 @@ func (mr *MockGroupChatAPIMockRecorder) CreateGroupChat(ctx, recipientRefs, topi return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateGroupChat", reflect.TypeOf((*MockGroupChatAPI)(nil).CreateGroupChat), ctx, recipientRefs, topic, includeMe) } +// GetGroupChat mocks base method. +func (m *MockGroupChatAPI) GetGroupChat(ctx context.Context, chatID string) (models.Chatable, *sender.RequestError) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetGroupChat", ctx, chatID) + ret0, _ := ret[0].(models.Chatable) + ret1, _ := ret[1].(*sender.RequestError) + return ret0, ret1 +} + +// GetGroupChat indicates an expected call of GetGroupChat. +func (mr *MockGroupChatAPIMockRecorder) GetGroupChat(ctx, chatID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGroupChat", reflect.TypeOf((*MockGroupChatAPI)(nil).GetGroupChat), ctx, chatID) +} + // ListGroupChatMembers mocks base method. func (m *MockGroupChatAPI) ListGroupChatMembers(ctx context.Context, chatID string) (models.ConversationMemberCollectionResponseable, *sender.RequestError) { m.ctrl.T.Helper() @@ -241,6 +271,21 @@ func (mr *MockChatAPIMockRecorder) DeleteMessage(ctx, chatID, messageID any) *go return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMessage", reflect.TypeOf((*MockChatAPI)(nil).DeleteMessage), ctx, chatID, messageID) } +// GetGroupChat mocks base method. +func (m *MockChatAPI) GetGroupChat(ctx context.Context, chatID string) (models.Chatable, *sender.RequestError) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetGroupChat", ctx, chatID) + ret0, _ := ret[0].(models.Chatable) + ret1, _ := ret[1].(*sender.RequestError) + return ret0, ret1 +} + +// GetGroupChat indicates an expected call of GetGroupChat. +func (mr *MockChatAPIMockRecorder) GetGroupChat(ctx, chatID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGroupChat", reflect.TypeOf((*MockChatAPI)(nil).GetGroupChat), ctx, chatID) +} + // GetMessage mocks base method. func (m *MockChatAPI) GetMessage(ctx context.Context, chatID, messageID string) (models.ChatMessageable, *sender.RequestError) { m.ctrl.T.Helper() @@ -256,6 +301,21 @@ func (mr *MockChatAPIMockRecorder) GetMessage(ctx, chatID, messageID any) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessage", reflect.TypeOf((*MockChatAPI)(nil).GetMessage), ctx, chatID, messageID) } +// GetOneOnOneChat mocks base method. +func (m *MockChatAPI) GetOneOnOneChat(ctx context.Context, chatID string) (models.Chatable, *sender.RequestError) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOneOnOneChat", ctx, chatID) + ret0, _ := ret[0].(models.Chatable) + ret1, _ := ret[1].(*sender.RequestError) + return ret0, ret1 +} + +// GetOneOnOneChat indicates an expected call of GetOneOnOneChat. +func (mr *MockChatAPIMockRecorder) GetOneOnOneChat(ctx, chatID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOneOnOneChat", reflect.TypeOf((*MockChatAPI)(nil).GetOneOnOneChat), ctx, chatID) +} + // ListAllMessages mocks base method. func (m *MockChatAPI) ListAllMessages(ctx context.Context, startTime, endTime *time.Time, top *int32) (models.ChatMessageCollectionResponseable, *sender.RequestError) { m.ctrl.T.Helper()