core: only update business chat preferences (#5325)

* core: only update business chat preferences

* rework

* migration

* test, bump protocol version

* fix tests
This commit is contained in:
Evgeny
2024-12-05 18:32:00 +00:00
committed by GitHub
parent de76e271a8
commit 60e0e454e8
12 changed files with 201 additions and 60 deletions
+51 -20
View File
@@ -2929,11 +2929,22 @@ processChatCommand' vr = \case
lift . when (directOrUsed ct') $ createSndFeatureItems user ct ct'
pure $ CRContactPrefsUpdated user ct ct'
runUpdateGroupProfile :: User -> Group -> GroupProfile -> CM ChatResponse
runUpdateGroupProfile user (Group g@GroupInfo {groupProfile = p@GroupProfile {displayName = n}} ms) p'@GroupProfile {displayName = n'} = do
runUpdateGroupProfile user (Group g@GroupInfo {businessChat, groupProfile = p@GroupProfile {displayName = n}} ms) p'@GroupProfile {displayName = n'} = do
assertUserGroupRole g GROwner
when (n /= n') $ checkValidName n'
g' <- withStore $ \db -> updateGroupProfile db user g p'
msg <- sendGroupMessage user g' ms (XGrpInfo p')
msg <- case businessChat of
Just BusinessChatInfo {businessId} -> do
let (newMs, oldMs) = partition (\m -> maxVersion (memberChatVRange m) >= businessChatPrefsVersion) ms
-- this is a fallback to send the members with the old version correct profile of the business when preferences change
unless (null oldMs) $ do
GroupMember {memberProfile = LocalProfile {displayName, fullName, image}} <-
withStore $ \db -> getGroupMemberByMemberId db vr user g businessId
let p'' = p' {displayName, fullName, image} :: GroupProfile
void $ sendGroupMessage user g' oldMs (XGrpInfo p'')
let ps' = fromMaybe defaultBusinessGroupPrefs $ groupPreferences p'
sendGroupMessage user g' newMs $ XGrpPrefs ps'
Nothing -> sendGroupMessage user g' ms (XGrpInfo p')
let cd = CDGroupSnd g'
unless (sameGroupProfileInfo p p') $ do
ci <- saveSndChatItem user cd msg (CISndGroupEvent $ SGEGroupUpdated p')
@@ -3026,7 +3037,7 @@ processChatCommand' vr = \case
invitedMember = MemberIdRole memberId memRole,
connRequest = cReq,
groupProfile,
businessChat,
business = businessChat,
groupLinkId = Nothing,
groupSize = Just currentMemCount
}
@@ -4003,7 +4014,7 @@ acceptGroupJoinRequestAsync
fromMemberName = displayName,
invitedMember = MemberIdRole memberId gLinkMemRole,
groupProfile,
businessChat,
business = businessChat,
groupSize = Just currentMemCount
}
subMode <- chatReadVar subscriptionMode
@@ -4038,7 +4049,7 @@ acceptBusinessJoinRequestAsync
-- This refers to the "title member" that defines the group name and profile.
-- This coincides with fromMember to be current user when accepting the connecting user,
-- but it will be different when inviting somebody else.
businessChat = Just $ BusinessChatInfo userMemberId BCBusiness,
business = Just $ BusinessChatInfo {chatType = BCBusiness, businessId = userMemberId, customerId = memberId},
groupSize = Just 1
}
subMode <- chatReadVar subscriptionMode
@@ -5042,7 +5053,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
invitedMember = MemberIdRole memberId memRole,
connRequest = cReq,
groupProfile,
businessChat = Nothing,
business = Nothing,
groupLinkId = groupLinkId,
groupSize = Just currentMemCount
}
@@ -5299,6 +5310,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
XGrpLeave -> xGrpLeave gInfo m' msg brokerTs
XGrpDel -> xGrpDel gInfo m' msg brokerTs
XGrpInfo p' -> xGrpInfo gInfo m' p' msg brokerTs
XGrpPrefs ps' -> xGrpPrefs gInfo m' ps'
XGrpDirectInv connReq mContent_ -> memberCanSend m' $ xGrpDirectInv gInfo m' conn' connReq mContent_ msg brokerTs
XGrpMsgForward memberId msg' msgTs -> xGrpMsgForward gInfo m' memberId msg' msgTs
XInfoProbe probe -> xInfoProbe (COMGroupMember m') probe
@@ -5416,8 +5428,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
let GroupInfo {businessChat} = gInfo
GroupMember {memberId = joiningMemberId} = m
case businessChat of
Just BusinessChatInfo {memberId, chatType = BCCustomer}
| joiningMemberId == memberId -> useReply <$> withStore (`getUserAddress` user)
Just BusinessChatInfo {customerId, chatType = BCCustomer}
| joiningMemberId == customerId -> useReply <$> withStore (`getUserAddress` user)
where
useReply UserContactLink {autoAccept} = case autoAccept of
Just AutoAccept {businessAddress, autoReply} | businessAddress -> autoReply
@@ -6465,7 +6477,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
processMemberProfileUpdate :: GroupInfo -> GroupMember -> Profile -> Bool -> Maybe UTCTime -> CM GroupMember
processMemberProfileUpdate gInfo m@GroupMember {memberProfile = p, memberContactId} p' createItems itemTs_
| redactedMemberProfile (fromLocalProfile p) /= redactedMemberProfile p' = do
updateBusinessChatProfile gInfo m
updateBusinessChatProfile gInfo
case memberContactId of
Nothing -> do
m' <- withStore $ \db -> updateMemberProfile db user m p'
@@ -6491,11 +6503,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
| otherwise =
pure m
where
updateBusinessChatProfile g@GroupInfo {businessChat} GroupMember {memberId} = case businessChat of
Just BusinessChatInfo {memberId = mId} | mId == memberId -> do
updateBusinessChatProfile g@GroupInfo {businessChat} = case businessChat of
Just bc | isMainBusinessMember bc m -> do
g' <- withStore $ \db -> updateGroupProfileFromMember db user g p'
toView $ CRGroupUpdated user g g' (Just m)
_ -> pure ()
isMainBusinessMember BusinessChatInfo {chatType, businessId, customerId} GroupMember {memberId} = case chatType of
BCBusiness -> businessId == memberId
BCCustomer -> customerId == memberId
createProfileUpdatedItem m' =
when createItems $ do
let ciContent = CIRcvGroupEvent $ RGEMemberProfileUpdated (fromLocalProfile p) p'
@@ -7008,16 +7023,31 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
toView $ CRGroupDeleted user gInfo {membership = membership {memberStatus = GSMemGroupDeleted}} m
xGrpInfo :: GroupInfo -> GroupMember -> GroupProfile -> RcvMessage -> UTCTime -> CM ()
xGrpInfo g@GroupInfo {groupProfile = p} m@GroupMember {memberRole} p' msg brokerTs
xGrpInfo g@GroupInfo {groupProfile = p, businessChat} m@GroupMember {memberRole} p' msg brokerTs
| memberRole < GROwner = messageError "x.grp.info with insufficient member permissions"
| otherwise = unless (p == p') $ do
g' <- withStore $ \db -> updateGroupProfile db user g p'
toView $ CRGroupUpdated user g g' (Just m)
let cd = CDGroupRcv g' m
unless (sameGroupProfileInfo p p') $ do
ci <- saveRcvChatItem user cd msg brokerTs (CIRcvGroupEvent $ RGEGroupUpdated p')
groupMsgToView g' ci
createGroupFeatureChangedItems user cd CIRcvGroupFeature g g'
| otherwise = case businessChat of
Nothing -> unless (p == p') $ do
g' <- withStore $ \db -> updateGroupProfile db user g p'
toView $ CRGroupUpdated user g g' (Just m)
let cd = CDGroupRcv g' m
unless (sameGroupProfileInfo p p') $ do
ci <- saveRcvChatItem user cd msg brokerTs (CIRcvGroupEvent $ RGEGroupUpdated p')
groupMsgToView g' ci
createGroupFeatureChangedItems user cd CIRcvGroupFeature g g'
Just _ -> updateGroupPrefs_ g m $ fromMaybe defaultBusinessGroupPrefs $ groupPreferences p'
xGrpPrefs :: GroupInfo -> GroupMember -> GroupPreferences -> CM ()
xGrpPrefs g m@GroupMember {memberRole} ps'
| memberRole < GROwner = messageError "x.grp.prefs with insufficient member permissions"
| otherwise = updateGroupPrefs_ g m ps'
updateGroupPrefs_ :: GroupInfo -> GroupMember -> GroupPreferences -> CM ()
updateGroupPrefs_ g@GroupInfo {groupProfile = p} m ps' =
unless (groupPreferences p == Just ps') $ do
g' <- withStore' $ \db -> updateGroupPreferences db user g ps'
toView $ CRGroupUpdated user g g' (Just m)
let cd = CDGroupRcv g' m
createGroupFeatureChangedItems user cd CIRcvGroupFeature g g'
xGrpDirectInv :: GroupInfo -> GroupMember -> Connection -> ConnReqInvitation -> Maybe MsgContent -> RcvMessage -> UTCTime -> CM ()
xGrpDirectInv g m mConn connReq mContent_ msg brokerTs = do
@@ -7098,6 +7128,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
XGrpLeave -> xGrpLeave gInfo author rcvMsg msgTs
XGrpDel -> xGrpDel gInfo author rcvMsg msgTs
XGrpInfo p' -> xGrpInfo gInfo author p' rcvMsg msgTs
XGrpPrefs ps' -> xGrpPrefs gInfo author ps'
_ -> messageError $ "x.grp.msg.forward: unsupported forwarded event " <> T.pack (show $ toCMEventTag event)
createUnknownMember :: GroupInfo -> MemberId -> CM GroupMember
@@ -0,0 +1,18 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20241205_business_chat_members where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
m20241205_business_chat_members :: Query
m20241205_business_chat_members =
[sql|
ALTER TABLE groups ADD COLUMN customer_member_id BLOB NULL;
|]
down_m20241205_business_chat_members :: Query
down_m20241205_business_chat_members =
[sql|
ALTER TABLE groups DROP COLUMN customer_member_id;
|]
+2 -1
View File
@@ -130,7 +130,8 @@ CREATE TABLE groups(
ui_themes TEXT,
business_member_id BLOB NULL,
business_chat TEXT NULL,
business_xcontact_id BLOB NULL, -- received
business_xcontact_id BLOB NULL,
customer_member_id BLOB NULL, -- received
FOREIGN KEY(user_id, local_display_name)
REFERENCES display_names(user_id, local_display_name)
ON DELETE CASCADE
+15 -1
View File
@@ -46,6 +46,7 @@ import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
import Simplex.Chat.Call
import Simplex.Chat.Types
import Simplex.Chat.Types.Preferences
import Simplex.Chat.Types.Shared
import Simplex.Messaging.Agent.Protocol (VersionSMPA, pqdrSMPAgentVersion)
import Simplex.Messaging.Compression (Compressed, compress1, decompress1)
@@ -67,12 +68,13 @@ import Simplex.Messaging.Version hiding (version)
-- 8 - compress messages and PQ e2e encryption (2024-03-08)
-- 9 - batch sending in direct connections (2024-07-24)
-- 10 - business chats (2024-11-29)
-- 11 - fix profile update in business chats (2024-12-05)
-- This should not be used directly in code, instead use `maxVersion chatVRange` from ChatConfig.
-- This indirection is needed for backward/forward compatibility testing.
-- Testing with real app versions is still needed, as tests use the current code with different version ranges, not the old code.
currentChatVersion :: VersionChat
currentChatVersion = VersionChat 10
currentChatVersion = VersionChat 11
-- This should not be used directly in code, instead use `chatVRange` from ChatConfig (see comment above)
supportedChatVRange :: VersionRangeChat
@@ -115,6 +117,10 @@ batchSend2Version = VersionChat 9
businessChatsVersion :: VersionChat
businessChatsVersion = VersionChat 10
-- support updating preferences in business chats (XGrpPrefs message)
businessChatPrefsVersion :: VersionChat
businessChatPrefsVersion = VersionChat 11
agentToChatVersion :: VersionSMPA -> VersionChat
agentToChatVersion v
| v < pqdrSMPAgentVersion = initialChatVersion
@@ -299,6 +305,7 @@ data ChatMsgEvent (e :: MsgEncoding) where
XGrpLeave :: ChatMsgEvent 'Json
XGrpDel :: ChatMsgEvent 'Json
XGrpInfo :: GroupProfile -> ChatMsgEvent 'Json
XGrpPrefs :: GroupPreferences -> ChatMsgEvent 'Json
XGrpDirectInv :: ConnReqInvitation -> Maybe MsgContent -> ChatMsgEvent 'Json
XGrpMsgForward :: MemberId -> ChatMessage 'Json -> UTCTime -> ChatMsgEvent 'Json
XInfoProbe :: Probe -> ChatMsgEvent 'Json
@@ -339,6 +346,7 @@ isForwardedGroupMsg ev = case ev of
XGrpLeave -> True
XGrpDel -> True -- TODO there should be a special logic - host should forward before deleting connections
XGrpInfo _ -> True
XGrpPrefs _ -> True
_ -> False
forwardedGroupMsg :: forall e. MsgEncodingI e => ChatMessage e -> Maybe (ChatMessage 'Json)
@@ -721,6 +729,7 @@ data CMEventTag (e :: MsgEncoding) where
XGrpLeave_ :: CMEventTag 'Json
XGrpDel_ :: CMEventTag 'Json
XGrpInfo_ :: CMEventTag 'Json
XGrpPrefs_ :: CMEventTag 'Json
XGrpDirectInv_ :: CMEventTag 'Json
XGrpMsgForward_ :: CMEventTag 'Json
XInfoProbe_ :: CMEventTag 'Json
@@ -771,6 +780,7 @@ instance MsgEncodingI e => StrEncoding (CMEventTag e) where
XGrpLeave_ -> "x.grp.leave"
XGrpDel_ -> "x.grp.del"
XGrpInfo_ -> "x.grp.info"
XGrpPrefs_ -> "x.grp.prefs"
XGrpDirectInv_ -> "x.grp.direct.inv"
XGrpMsgForward_ -> "x.grp.msg.forward"
XInfoProbe_ -> "x.info.probe"
@@ -822,6 +832,7 @@ instance StrEncoding ACMEventTag where
"x.grp.leave" -> XGrpLeave_
"x.grp.del" -> XGrpDel_
"x.grp.info" -> XGrpInfo_
"x.grp.prefs" -> XGrpPrefs_
"x.grp.direct.inv" -> XGrpDirectInv_
"x.grp.msg.forward" -> XGrpMsgForward_
"x.info.probe" -> XInfoProbe_
@@ -869,6 +880,7 @@ toCMEventTag msg = case msg of
XGrpLeave -> XGrpLeave_
XGrpDel -> XGrpDel_
XGrpInfo _ -> XGrpInfo_
XGrpPrefs _ -> XGrpPrefs_
XGrpDirectInv _ _ -> XGrpDirectInv_
XGrpMsgForward {} -> XGrpMsgForward_
XInfoProbe _ -> XInfoProbe_
@@ -969,6 +981,7 @@ appJsonToCM AppMessageJson {v, msgId, event, params} = do
XGrpLeave_ -> pure XGrpLeave
XGrpDel_ -> pure XGrpDel
XGrpInfo_ -> XGrpInfo <$> p "groupProfile"
XGrpPrefs_ -> XGrpPrefs <$> p "groupPreferences"
XGrpDirectInv_ -> XGrpDirectInv <$> p "connReq" <*> opt "content"
XGrpMsgForward_ -> XGrpMsgForward <$> p "memberId" <*> p "msg" <*> p "msgTs"
XInfoProbe_ -> XInfoProbe <$> p "probe"
@@ -1030,6 +1043,7 @@ chatToAppMessage ChatMessage {chatVRange, msgId, chatMsgEvent} = case encoding @
XGrpLeave -> JM.empty
XGrpDel -> JM.empty
XGrpInfo p -> o ["groupProfile" .= p]
XGrpPrefs p -> o ["groupPreferences" .= p]
XGrpDirectInv connReq content -> o $ ("content" .=? content) ["connReq" .= connReq]
XGrpMsgForward memberId msg msgTs -> o ["memberId" .= memberId, "msg" .= msg, "msgTs" .= msgTs]
XInfoProbe probe -> o ["probe" .= probe]
+1 -1
View File
@@ -123,7 +123,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
+35 -17
View File
@@ -39,6 +39,7 @@ module Simplex.Chat.Store.Groups
getGroupInfoByUserContactLinkConnReq,
getGroupInfoByGroupLinkHash,
updateGroupProfile,
updateGroupPreferences,
updateGroupProfileFromMember,
getGroupIdByName,
getGroupMemberIdByName,
@@ -257,7 +258,7 @@ getGroupAndMember db User {userId, userContactId} groupMemberId vr =
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
@@ -339,7 +340,7 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc
-- | creates a new group record for the group the current user was invited to, or returns an existing one
createGroupInvitation :: DB.Connection -> VersionRangeChat -> User -> Contact -> GroupInvitation -> Maybe ProfileId -> ExceptT StoreError IO (GroupInfo, GroupMemberId)
createGroupInvitation _ _ _ Contact {localDisplayName, activeConn = Nothing} _ _ = throwError $ SEContactNotReady localDisplayName
createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activeConn = Just Connection {customUserProfileId, peerChatVRange}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile, businessChat} incognitoProfileId = do
createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activeConn = Just Connection {customUserProfileId, peerChatVRange}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile, business} incognitoProfileId = do
liftIO getInvitationGroupId_ >>= \case
Nothing -> createGroupInvitation_
Just gId -> do
@@ -377,10 +378,10 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
[sql|
INSERT INTO groups
(group_profile_id, local_display_name, inv_queue_info, host_conn_custom_user_profile_id, user_id, enable_ntfs,
created_at, updated_at, chat_ts, user_member_profile_sent_at, business_member_id, business_chat)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_member_id, customer_member_id)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
((profileId, localDisplayName, connRequest, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) :. businessChatTuple businessChat)
((profileId, localDisplayName, connRequest, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) :. businessChatInfoRow business)
insertedRowId db
let hostVRange = adjustedMemberVRange vr peerChatVRange
GroupMember {groupMemberId} <- createContactMemberInv_ db user groupId Nothing contact fromMember GCHostMember GSMemInvited IBUnknown Nothing currentTs hostVRange
@@ -406,10 +407,10 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
groupMemberId
)
businessChatTuple :: Maybe BusinessChatInfo -> (Maybe MemberId, Maybe BusinessChatType)
businessChatTuple = \case
Just BusinessChatInfo {memberId, chatType} -> (Just memberId, Just chatType)
Nothing -> (Nothing, Nothing)
businessChatInfoRow :: Maybe BusinessChatInfo -> BusinessChatInfoRow
businessChatInfoRow = \case
Just BusinessChatInfo {chatType, businessId, customerId} -> (Just chatType, Just businessId, Just customerId)
Nothing -> (Nothing, Nothing, Nothing)
adjustedMemberVRange :: VersionRangeChat -> VersionRangeChat -> VersionRangeChat
adjustedMemberVRange chatVR vr@(VersionRange minV maxV) =
@@ -497,7 +498,7 @@ createGroupInvitedViaLink
vr
user@User {userId, userContactId}
Connection {connId, customUserProfileId}
GroupLinkInvitation {fromMember, fromMemberName, invitedMember, groupProfile, businessChat} = do
GroupLinkInvitation {fromMember, fromMemberName, invitedMember, groupProfile, business} = do
currentTs <- liftIO getCurrentTime
groupId <- insertGroup_ currentTs
hostMemberId <- insertHost_ currentTs groupId
@@ -521,10 +522,10 @@ createGroupInvitedViaLink
[sql|
INSERT INTO groups
(group_profile_id, local_display_name, host_conn_custom_user_profile_id, user_id, enable_ntfs,
created_at, updated_at, chat_ts, user_member_profile_sent_at, business_member_id, business_chat)
VALUES (?,?,?,?,?,?,?,?,?,?,?)
created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_member_id, customer_member_id)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
|]
((profileId, localDisplayName, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) :. businessChatTuple businessChat)
((profileId, localDisplayName, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) :. businessChatInfoRow business)
insertedRowId db
insertHost_ currentTs groupId = do
let fromMemberProfile = profileFromName fromMemberName
@@ -631,7 +632,7 @@ getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ =
SELECT
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction,
mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences
FROM groups g
@@ -918,9 +919,9 @@ createBusinessRequestGroup
UserContactRequest {cReqChatVRange, xContactId, profile = Profile {displayName, fullName, image, contactLink, preferences}}
groupPreferences = do
currentTs <- liftIO getCurrentTime
(groupId, membership) <- insertGroup_ currentTs
(groupId, membership@GroupMember {memberId = userMemberId}) <- insertGroup_ currentTs
(groupMemberId, memberId) <- insertClientMember_ currentTs groupId membership
liftIO $ DB.execute db "UPDATE groups SET business_member_id = ? WHERE group_id = ?" (memberId, groupId)
liftIO $ DB.execute db "UPDATE groups SET business_member_id = ?, customer_member_id = ? WHERE group_id = ?" (userMemberId, memberId, groupId)
groupInfo <- getGroupInfo db vr user groupId
clientMember <- getGroupMemberById db vr user groupMemberId
pure (groupInfo, clientMember)
@@ -1370,7 +1371,7 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} =
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
@@ -1456,6 +1457,23 @@ updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName,
(ldn, currentTs, userId, groupId)
safeDeleteLDN db user localDisplayName
updateGroupPreferences :: DB.Connection -> User -> GroupInfo -> GroupPreferences -> IO GroupInfo
updateGroupPreferences db User {userId} g@GroupInfo {groupId, groupProfile = p} ps = do
currentTs <- getCurrentTime
DB.execute
db
[sql|
UPDATE group_profiles
SET preferences = ?, updated_at = ?
WHERE group_profile_id IN (
SELECT group_profile_id
FROM groups
WHERE user_id = ? AND group_id = ?
)
|]
(ps, currentTs, userId, groupId)
pure (g :: GroupInfo) {groupProfile = p {groupPreferences = Just ps}}
updateGroupProfileFromMember :: DB.Connection -> User -> GroupInfo -> Profile -> ExceptT StoreError IO GroupInfo
updateGroupProfileFromMember db user g@GroupInfo {groupId} Profile {displayName = n, fullName = fn, image = img} = do
p <- getGroupProfile -- to avoid any race conditions with UI
+3 -1
View File
@@ -118,6 +118,7 @@ import Simplex.Chat.Migrations.M20241023_chat_item_autoincrement_id
import Simplex.Chat.Migrations.M20241027_server_operators
import Simplex.Chat.Migrations.M20241125_indexes
import Simplex.Chat.Migrations.M20241128_business_chats
import Simplex.Chat.Migrations.M20241205_business_chat_members
import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..))
schemaMigrations :: [(String, Query, Maybe Query)]
@@ -235,7 +236,8 @@ schemaMigrations =
("20241023_chat_item_autoincrement_id", m20241023_chat_item_autoincrement_id, Just down_m20241023_chat_item_autoincrement_id),
("20241027_server_operators", m20241027_server_operators, Just down_m20241027_server_operators),
("20241125_indexes", m20241125_indexes, Just down_m20241125_indexes),
("20241128_business_chats", m20241128_business_chats, Just down_m20241128_business_chats)
("20241128_business_chats", m20241128_business_chats, Just down_m20241128_business_chats),
("20241205_business_chat_members", m20241205_business_chat_members, Just down_m20241205_business_chat_members)
]
-- | The list of migrations in ascending order by date
+10 -4
View File
@@ -546,17 +546,19 @@ safeDeleteLDN db User {userId} localDisplayName = do
|]
(userId, localDisplayName, userId)
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime, Maybe MemberId, Maybe BusinessChatType, Maybe UIThemeEntityOverrides, Maybe CustomData) :. GroupMemberRow
type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe MemberId)
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData) :. GroupMemberRow
type GroupMemberRow = ((Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences))
toGroupInfo :: VersionRangeChat -> Int64 -> GroupInfoRow -> GroupInfo
toGroupInfo vr userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt, businessMemberId, businessChatType, uiThemes, customData) :. userMemberRow) =
toGroupInfo vr userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. businessRow :. (uiThemes, customData) :. userMemberRow) =
let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr}
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite}
fullGroupPreferences = mergeGroupPreferences groupPreferences
groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences}
businessChat = BusinessChatInfo <$> businessMemberId <*> businessChatType
businessChat = toBusinessChatInfo businessRow
in GroupInfo {groupId, localDisplayName, groupProfile, businessChat, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, uiThemes, customData}
toGroupMember :: Int64 -> GroupMemberRow -> GroupMember
@@ -569,6 +571,10 @@ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer,
memberChatVRange = fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer
in GroupMember {..}
toBusinessChatInfo :: BusinessChatInfoRow -> Maybe BusinessChatInfo
toBusinessChatInfo (Just chatType, Just businessId, Just customerId) = Just BusinessChatInfo {chatType, businessId, customerId}
toBusinessChatInfo _ = Nothing
groupInfoQuery :: Query
groupInfoQuery =
[sql|
@@ -576,7 +582,7 @@ groupInfoQuery =
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
-- GroupMember - membership
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
+5 -4
View File
@@ -617,7 +617,7 @@ data GroupInvitation = GroupInvitation
invitedMember :: MemberIdRole,
connRequest :: ConnReqInvitation,
groupProfile :: GroupProfile,
businessChat :: Maybe BusinessChatInfo,
business :: Maybe BusinessChatInfo,
groupLinkId :: Maybe GroupLinkId,
groupSize :: Maybe Int
}
@@ -628,7 +628,7 @@ data GroupLinkInvitation = GroupLinkInvitation
fromMemberName :: ContactName,
invitedMember :: MemberIdRole,
groupProfile :: GroupProfile,
businessChat :: Maybe BusinessChatInfo,
business :: Maybe BusinessChatInfo,
groupSize :: Maybe Int
}
deriving (Eq, Show)
@@ -654,8 +654,9 @@ data MemberInfo = MemberInfo
deriving (Eq, Show)
data BusinessChatInfo = BusinessChatInfo
{ memberId :: MemberId,
chatType :: BusinessChatType
{ chatType :: BusinessChatType,
businessId :: MemberId,
customerId :: MemberId
}
deriving (Eq, Show)