status-im/status-go

View on GitHub
protocol/messenger_group_chat.go

Summary

Maintainability
A
0 mins
Test Coverage
F
51%
package protocol

import (
    "context"
    "crypto/ecdsa"
    "encoding/hex"
    "errors"

    "github.com/golang/protobuf/proto"
    "go.uber.org/zap"

    "github.com/status-im/status-go/eth-node/crypto"
    "github.com/status-im/status-go/eth-node/types"
    "github.com/status-im/status-go/images"
    "github.com/status-im/status-go/protocol/common"
    "github.com/status-im/status-go/protocol/protobuf"
    "github.com/status-im/status-go/protocol/requests"
    v1protocol "github.com/status-im/status-go/protocol/v1"
)

var ErrGroupChatAddedContacts = errors.New("group-chat: can't add members who are not mutual contacts")

func (m *Messenger) validateAddedGroupMembers(members []string) error {
    for _, memberPubkey := range members {
        contactID, err := contactIDFromPublicKeyString(memberPubkey)
        if err != nil {
            return err
        }

        contact, _ := m.allContacts.Load(contactID)
        if contact == nil || !contact.mutual() {
            return ErrGroupChatAddedContacts
        }
    }
    return nil
}

func (m *Messenger) CreateGroupChatWithMembers(ctx context.Context, name string, members []string) (*MessengerResponse, error) {
    var convertedKeyMembers []string
    for _, m := range members {
        k, err := requests.ConvertCompressedToLegacyKey(m)
        if err != nil {
            return nil, err
        }
        convertedKeyMembers = append(convertedKeyMembers, k)

    }
    if err := m.validateAddedGroupMembers(convertedKeyMembers); err != nil {
        return nil, err
    }

    var response MessengerResponse
    logger := m.logger.With(zap.String("site", "CreateGroupChatWithMembers"))
    logger.Info("Creating group chat", zap.String("name", name), zap.Any("members", convertedKeyMembers))
    chat := CreateGroupChat(m.getTimesource())

    clock, _ := chat.NextClockAndTimestamp(m.getTimesource())

    group, err := v1protocol.NewGroupWithCreator(name, chat.Color, clock, m.identity)
    if err != nil {
        return nil, err
    }
    chat.LastClockValue = clock

    chat.updateChatFromGroupMembershipChanges(group)
    chat.Joined = int64(m.getTimesource().GetCurrentTime())

    clock, _ = chat.NextClockAndTimestamp(m.getTimesource())

    // Add members
    if len(convertedKeyMembers) > 0 {
        event := v1protocol.NewMembersAddedEvent(convertedKeyMembers, clock)
        event.ChatID = chat.ID
        err = event.Sign(m.identity)
        if err != nil {
            return nil, err
        }

        err = group.ProcessEvent(event)
        if err != nil {
            return nil, err
        }
    }

    recipients, err := stringSliceToPublicKeys(group.Members())

    if err != nil {
        return nil, err
    }

    encodedMessage, err := m.sender.EncodeMembershipUpdate(group, nil)
    if err != nil {
        return nil, err
    }

    m.allChats.Store(chat.ID, &chat)

    _, err = m.dispatchMessage(ctx, common.RawMessage{
        LocalChatID: chat.ID,
        Payload:     encodedMessage,
        MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
        Recipients:  recipients,
    })

    if err != nil {
        return nil, err
    }

    chat.updateChatFromGroupMembershipChanges(group)

    return m.addMessagesAndChat(&chat, buildSystemMessages(chat.MembershipUpdates, m.systemMessagesTranslations), &response)
}

func (m *Messenger) CreateGroupChatFromInvitation(name string, chatID string, adminPK string) (*MessengerResponse, error) {
    var response MessengerResponse
    logger := m.logger.With(zap.String("site", "CreateGroupChatFromInvitation"))
    logger.Info("Creating group chat from invitation", zap.String("name", name))
    chat := CreateGroupChat(m.getTimesource())
    chat.ID = chatID
    chat.Name = name
    chat.InvitationAdmin = adminPK

    response.AddChat(&chat)

    return &response, m.saveChat(&chat)
}

type removeMembersFromGroupChatResponse struct {
    oldRecipients   []*ecdsa.PublicKey
    group           *v1protocol.Group
    encodedProtobuf []byte
}

func (m *Messenger) removeMembersFromGroupChat(ctx context.Context, chat *Chat, members []string) (*removeMembersFromGroupChatResponse, error) {
    chatID := chat.ID
    logger := m.logger.With(zap.String("site", "RemoveMembersFromGroupChat"))
    logger.Info("Removing members form group chat", zap.String("chatID", chatID), zap.Any("members", members))
    group, err := newProtocolGroupFromChat(chat)
    if err != nil {
        return nil, err
    }

    // We save the initial recipients as we want to send updates to also
    // the members kicked out
    oldRecipients, err := stringSliceToPublicKeys(group.Members())
    if err != nil {
        return nil, err
    }

    clock, _ := chat.NextClockAndTimestamp(m.getTimesource())

    for _, member := range members {
        // Remove member
        event := v1protocol.NewMemberRemovedEvent(member, clock)
        event.ChatID = chat.ID
        err = event.Sign(m.identity)
        if err != nil {
            return nil, err
        }

        err = group.ProcessEvent(event)
        if err != nil {
            return nil, err
        }
    }

    encoded, err := m.sender.EncodeMembershipUpdate(group, nil)
    if err != nil {
        return nil, err
    }

    return &removeMembersFromGroupChatResponse{
        oldRecipients:   oldRecipients,
        group:           group,
        encodedProtobuf: encoded,
    }, nil
}

func (m *Messenger) RemoveMembersFromGroupChat(ctx context.Context, chatID string, members []string) (*MessengerResponse, error) {
    var response MessengerResponse

    chat, ok := m.allChats.Load(chatID)
    if !ok {
        return nil, ErrChatNotFound
    }

    removeMembersResponse, err := m.removeMembersFromGroupChat(ctx, chat, members)
    if err != nil {
        return nil, err
    }

    _, err = m.dispatchMessage(ctx, common.RawMessage{
        LocalChatID: chat.ID,
        Payload:     removeMembersResponse.encodedProtobuf,
        MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
        Recipients:  removeMembersResponse.oldRecipients,
    })
    if err != nil {
        return nil, err
    }

    chat.updateChatFromGroupMembershipChanges(removeMembersResponse.group)

    return m.addMessagesAndChat(chat, buildSystemMessages(chat.MembershipUpdates, m.systemMessagesTranslations), &response)
}

func (m *Messenger) AddMembersToGroupChat(ctx context.Context, chatID string, members []string) (*MessengerResponse, error) {
    if err := m.validateAddedGroupMembers(members); err != nil {
        return nil, err
    }

    var response MessengerResponse
    logger := m.logger.With(zap.String("site", "AddMembersFromGroupChat"))
    logger.Info("Adding members form group chat", zap.String("chatID", chatID), zap.Any("members", members))
    chat, ok := m.allChats.Load(chatID)
    if !ok {
        return nil, ErrChatNotFound
    }

    group, err := newProtocolGroupFromChat(chat)
    if err != nil {
        return nil, err
    }

    clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
    // Add members
    event := v1protocol.NewMembersAddedEvent(members, clock)
    event.ChatID = chat.ID
    err = event.Sign(m.identity)
    if err != nil {
        return nil, err
    }

    //approve invitations
    for _, member := range members {
        logger.Info("ApproveInvitationByChatIdAndFrom", zap.String("chatID", chatID), zap.Any("member", member))

        groupChatInvitation := &GroupChatInvitation{
            GroupChatInvitation: &protobuf.GroupChatInvitation{
                ChatId: chat.ID,
            },
            From: member,
        }

        groupChatInvitation, err = m.persistence.InvitationByID(groupChatInvitation.ID())
        if err != nil && err != common.ErrRecordNotFound {
            return nil, err
        }
        if groupChatInvitation != nil {
            groupChatInvitation.State = protobuf.GroupChatInvitation_APPROVED

            err := m.persistence.SaveInvitation(groupChatInvitation)
            if err != nil {
                return nil, err
            }
            response.Invitations = append(response.Invitations, groupChatInvitation)
        }
    }

    err = group.ProcessEvent(event)
    if err != nil {
        return nil, err
    }

    recipients, err := stringSliceToPublicKeys(group.Members())
    if err != nil {
        return nil, err
    }

    encodedMessage, err := m.sender.EncodeMembershipUpdate(group, nil)
    if err != nil {
        return nil, err
    }
    _, err = m.dispatchMessage(ctx, common.RawMessage{
        LocalChatID: chat.ID,
        Payload:     encodedMessage,
        MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
        Recipients:  recipients,
    })

    if err != nil {
        return nil, err
    }

    chat.updateChatFromGroupMembershipChanges(group)

    return m.addMessagesAndChat(chat, buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations), &response)
}

func (m *Messenger) ChangeGroupChatName(ctx context.Context, chatID string, name string) (*MessengerResponse, error) {
    logger := m.logger.With(zap.String("site", "ChangeGroupChatName"))
    logger.Info("Changing group chat name", zap.String("chatID", chatID), zap.String("name", name))

    chat, ok := m.allChats.Load(chatID)
    if !ok {
        return nil, ErrChatNotFound
    }

    group, err := newProtocolGroupFromChat(chat)
    if err != nil {
        return nil, err
    }

    clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
    // Change name
    event := v1protocol.NewNameChangedEvent(name, clock)
    event.ChatID = chat.ID
    err = event.Sign(m.identity)
    if err != nil {
        return nil, err
    }

    // Update in-memory group
    err = group.ProcessEvent(event)
    if err != nil {
        return nil, err
    }

    recipients, err := stringSliceToPublicKeys(group.Members())
    if err != nil {
        return nil, err
    }

    encodedMessage, err := m.sender.EncodeMembershipUpdate(group, nil)
    if err != nil {
        return nil, err
    }
    _, err = m.dispatchMessage(ctx, common.RawMessage{
        LocalChatID: chat.ID,
        Payload:     encodedMessage,
        MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
        Recipients:  recipients,
    })

    if err != nil {
        return nil, err
    }

    chat.updateChatFromGroupMembershipChanges(group)

    var response MessengerResponse

    return m.addMessagesAndChat(chat, buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations), &response)
}

func (m *Messenger) EditGroupChat(ctx context.Context, chatID string, name string, color string, image images.CroppedImage) (*MessengerResponse, error) {
    logger := m.logger.With(zap.String("site", "EditGroupChat"))
    logger.Info("Editing group chat details", zap.String("chatID", chatID), zap.String("name", name), zap.String("color", color))

    chat, ok := m.allChats.Load(chatID)
    if !ok {
        return nil, ErrChatNotFound
    }

    group, err := newProtocolGroupFromChat(chat)
    if err != nil {
        return nil, err
    }

    signAndProcessEvent := func(m *Messenger, event *v1protocol.MembershipUpdateEvent) error {
        err := event.Sign(m.identity)
        if err != nil {
            return err
        }

        err = group.ProcessEvent(*event)
        if err != nil {
            return err
        }

        return nil
    }

    var events []v1protocol.MembershipUpdateEvent

    if chat.Name != name {
        clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
        event := v1protocol.NewNameChangedEvent(name, clock)
        event.ChatID = chat.ID
        err = signAndProcessEvent(m, &event)
        if err != nil {
            return nil, err
        }
        events = append(events, event)
    }

    if chat.Color != color {
        clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
        event := v1protocol.NewColorChangedEvent(color, clock)
        event.ChatID = chat.ID
        err = signAndProcessEvent(m, &event)
        if err != nil {
            return nil, err
        }
        events = append(events, event)
    }

    if len(image.ImagePath) > 0 {
        payload, err := images.OpenAndAdjustImage(image, true)

        if err != nil {
            return nil, err
        }

        // prepare event
        clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
        event := v1protocol.NewImageChangedEvent(payload, clock)
        event.ChatID = chat.ID
        err = signAndProcessEvent(m, &event)
        if err != nil {
            return nil, err
        }
        events = append(events, event)
    }

    recipients, err := stringSliceToPublicKeys(group.Members())
    if err != nil {
        return nil, err
    }

    encodedMessage, err := m.sender.EncodeMembershipUpdate(group, nil)
    if err != nil {
        return nil, err
    }
    _, err = m.dispatchMessage(ctx, common.RawMessage{
        LocalChatID: chat.ID,
        Payload:     encodedMessage,
        MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
        Recipients:  recipients,
    })

    if err != nil {
        return nil, err
    }

    chat.updateChatFromGroupMembershipChanges(group)

    var response MessengerResponse

    return m.addMessagesAndChat(chat, buildSystemMessages(events, m.systemMessagesTranslations), &response)
}

func (m *Messenger) SendGroupChatInvitationRequest(ctx context.Context, chatID string, adminPK string,
    message string) (*MessengerResponse, error) {
    logger := m.logger.With(zap.String("site", "SendGroupChatInvitationRequest"))
    logger.Info("Sending group chat invitation request", zap.String("chatID", chatID),
        zap.String("adminPK", adminPK), zap.String("message", message))

    var response MessengerResponse

    // Get chat and clock
    chat, ok := m.allChats.Load(chatID)
    if !ok {
        return nil, ErrChatNotFound
    }
    clock, _ := chat.NextClockAndTimestamp(m.getTimesource())

    invitationR := &GroupChatInvitation{
        GroupChatInvitation: &protobuf.GroupChatInvitation{
            Clock:               clock,
            ChatId:              chatID,
            IntroductionMessage: message,
            State:               protobuf.GroupChatInvitation_REQUEST,
        },
        From: types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey)),
    }

    encodedMessage, err := proto.Marshal(invitationR.GetProtobuf())
    if err != nil {
        return nil, err
    }

    spec := common.RawMessage{
        LocalChatID: adminPK,
        Payload:     encodedMessage,
        MessageType: protobuf.ApplicationMetadataMessage_GROUP_CHAT_INVITATION,
        ResendType:  common.ResendTypeDataSync,
    }

    pkey, err := hex.DecodeString(adminPK[2:])
    if err != nil {
        return nil, err
    }
    // Safety check, make sure is well formed
    adminpk, err := crypto.UnmarshalPubkey(pkey)
    if err != nil {
        return nil, err
    }

    id, err := m.sender.SendPrivate(ctx, adminpk, &spec)
    if err != nil {
        return nil, err
    }

    spec.ID = types.EncodeHex(id)
    spec.SendCount++
    err = m.persistence.SaveRawMessage(&spec)
    if err != nil {
        return nil, err
    }

    response.Invitations = []*GroupChatInvitation{invitationR}

    err = m.persistence.SaveInvitation(invitationR)
    if err != nil {
        return nil, err
    }

    return &response, nil
}

func (m *Messenger) GetGroupChatInvitations() ([]*GroupChatInvitation, error) {
    return m.persistence.GetGroupChatInvitations()
}

func (m *Messenger) SendGroupChatInvitationRejection(ctx context.Context, invitationRequestID string) (*MessengerResponse, error) {
    logger := m.logger.With(zap.String("site", "SendGroupChatInvitationRejection"))
    logger.Info("Sending group chat invitation reject", zap.String("invitationRequestID", invitationRequestID))

    invitationR, err := m.persistence.InvitationByID(invitationRequestID)
    if err != nil {
        return nil, err
    }

    invitationR.State = protobuf.GroupChatInvitation_REJECTED

    // Get chat and clock
    chat, ok := m.allChats.Load(invitationR.ChatId)
    if !ok {
        return nil, ErrChatNotFound
    }
    clock, _ := chat.NextClockAndTimestamp(m.getTimesource())

    invitationR.Clock = clock

    encodedMessage, err := proto.Marshal(invitationR.GetProtobuf())
    if err != nil {
        return nil, err
    }

    spec := common.RawMessage{
        LocalChatID: invitationR.From,
        Payload:     encodedMessage,
        MessageType: protobuf.ApplicationMetadataMessage_GROUP_CHAT_INVITATION,
        ResendType:  common.ResendTypeDataSync,
    }

    pkey, err := hex.DecodeString(invitationR.From[2:])
    if err != nil {
        return nil, err
    }
    // Safety check, make sure is well formed
    userpk, err := crypto.UnmarshalPubkey(pkey)
    if err != nil {
        return nil, err
    }

    id, err := m.sender.SendPrivate(ctx, userpk, &spec)
    if err != nil {
        return nil, err
    }

    spec.ID = types.EncodeHex(id)
    spec.SendCount++
    err = m.persistence.SaveRawMessage(&spec)
    if err != nil {
        return nil, err
    }

    var response MessengerResponse

    response.Invitations = []*GroupChatInvitation{invitationR}

    err = m.persistence.SaveInvitation(invitationR)
    if err != nil {
        return nil, err
    }

    return &response, nil
}

func (m *Messenger) AddAdminsToGroupChat(ctx context.Context, chatID string, members []string) (*MessengerResponse, error) {
    var response MessengerResponse
    logger := m.logger.With(zap.String("site", "AddAdminsToGroupChat"))
    logger.Info("Add admins to group chat", zap.String("chatID", chatID), zap.Any("members", members))

    chat, ok := m.allChats.Load(chatID)
    if !ok {
        return nil, ErrChatNotFound
    }

    group, err := newProtocolGroupFromChat(chat)
    if err != nil {
        return nil, err
    }

    clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
    // Add members
    event := v1protocol.NewAdminsAddedEvent(members, clock)
    event.ChatID = chat.ID
    err = event.Sign(m.identity)
    if err != nil {
        return nil, err
    }

    err = group.ProcessEvent(event)
    if err != nil {
        return nil, err
    }

    recipients, err := stringSliceToPublicKeys(group.Members())
    if err != nil {
        return nil, err
    }

    encodedMessage, err := m.sender.EncodeMembershipUpdate(group, nil)
    if err != nil {
        return nil, err
    }
    _, err = m.dispatchMessage(ctx, common.RawMessage{
        LocalChatID: chat.ID,
        Payload:     encodedMessage,
        MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
        Recipients:  recipients,
    })

    if err != nil {
        return nil, err
    }

    chat.updateChatFromGroupMembershipChanges(group)
    return m.addMessagesAndChat(chat, buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations), &response)
}

// Kept only for backward compatibility (auto-join), explicit join has been removed
func (m *Messenger) ConfirmJoiningGroup(ctx context.Context, chatID string) (*MessengerResponse, error) {
    var response MessengerResponse

    chat, ok := m.allChats.Load(chatID)
    if !ok {
        return nil, ErrChatNotFound
    }

    _, err := m.Join(chat)
    if err != nil {
        return nil, err
    }

    group, err := newProtocolGroupFromChat(chat)
    if err != nil {
        return nil, err
    }
    clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
    event := v1protocol.NewMemberJoinedEvent(
        clock,
    )
    event.ChatID = chat.ID
    err = event.Sign(m.identity)
    if err != nil {
        return nil, err
    }

    err = group.ProcessEvent(event)
    if err != nil {
        return nil, err
    }

    recipients, err := stringSliceToPublicKeys(group.Members())
    if err != nil {
        return nil, err
    }

    encodedMessage, err := m.sender.EncodeMembershipUpdate(group, nil)
    if err != nil {
        return nil, err
    }
    _, err = m.dispatchMessage(ctx, common.RawMessage{
        LocalChatID: chat.ID,
        Payload:     encodedMessage,
        MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
        Recipients:  recipients,
    })
    if err != nil {
        return nil, err
    }

    chat.updateChatFromGroupMembershipChanges(group)
    chat.Joined = int64(m.getTimesource().GetCurrentTime())

    return m.addMessagesAndChat(chat, buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations), &response)
}

func (m *Messenger) leaveGroupChat(ctx context.Context, response *MessengerResponse, chatID string, remove bool, shouldBeSynced bool) (*MessengerResponse, error) {
    chat, ok := m.allChats.Load(chatID)
    if !ok {
        return nil, ErrChatNotFound
    }

    amIMember := chat.HasMember(common.PubkeyToHex(&m.identity.PublicKey))

    if amIMember {
        chat.RemoveMember(common.PubkeyToHex(&m.identity.PublicKey))

        group, err := newProtocolGroupFromChat(chat)
        if err != nil {
            return nil, err
        }
        clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
        event := v1protocol.NewMemberRemovedEvent(
            contactIDFromPublicKey(&m.identity.PublicKey),
            clock,
        )
        event.ChatID = chat.ID
        err = event.Sign(m.identity)
        if err != nil {
            return nil, err
        }

        err = group.ProcessEvent(event)
        if err != nil {
            return nil, err
        }

        recipients, err := stringSliceToPublicKeys(group.Members())
        if err != nil {
            return nil, err
        }

        encodedMessage, err := m.sender.EncodeMembershipUpdate(group, nil)
        if err != nil {
            return nil, err
        }

        // shouldBeSynced is false if we got here because a synced client has already
        // sent the leave group message. In that case we don't need to send it again.
        if shouldBeSynced {
            _, err = m.dispatchMessage(ctx, common.RawMessage{
                LocalChatID: chat.ID,
                Payload:     encodedMessage,
                MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
                Recipients:  recipients,
            })
            if err != nil {
                return nil, err
            }
        }

        chat.updateChatFromGroupMembershipChanges(group)
        response.AddMessages(buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations))
        err = m.persistence.SaveMessages(response.Messages())
        if err != nil {
            return nil, err
        }
    }

    if remove {
        chat.Active = false
    }

    if remove && shouldBeSynced {
        err := m.syncChatRemoving(ctx, chat.ID, m.dispatchMessage)
        if err != nil {
            return nil, err
        }
    }

    response.AddChat(chat)

    return response, m.saveChat(chat)
}

func (m *Messenger) LeaveGroupChat(ctx context.Context, chatID string, remove bool) (*MessengerResponse, error) {
    _, err := m.DismissAllActivityCenterNotificationsFromChatID(ctx, chatID, m.GetCurrentTimeInMillis())
    if err != nil {
        return nil, err
    }
    var response MessengerResponse
    return m.leaveGroupChat(ctx, &response, chatID, remove, true)
}

// Decline all pending group invites from a user
func (m *Messenger) DeclineAllPendingGroupInvitesFromUser(ctx context.Context, response *MessengerResponse, userPublicKey string) (*MessengerResponse, error) {

    // Decline group invites from active chats
    chats, err := m.persistence.Chats()
    if err != nil {
        return nil, err
    }

    for _, chat := range chats {
        if chat.ChatType == ChatTypePrivateGroupChat &&
            chat.ReceivedInvitationAdmin == userPublicKey &&
            chat.Joined == 0 && chat.Active {
            response, err = m.leaveGroupChat(ctx, response, chat.ID, true, true)
            if err != nil {
                return nil, err
            }
        }
    }

    // Decline group invites from activity center notifications
    notifications, err := m.AcceptActivityCenterNotificationsForInvitesFromUser(ctx, userPublicKey, m.GetCurrentTimeInMillis())
    if err != nil {
        return nil, err
    }

    for _, notification := range notifications {
        response, err = m.leaveGroupChat(ctx, response, notification.ChatID, true, true)
        if err != nil {
            return nil, err
        }
    }
    return response, nil
}