From 4439f7e58b24fd70539282164082d0ae2d4b6518 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 12 Nov 2025 12:53:12 +0400 Subject: [PATCH 1/3] improve output --- src/Simplex/Chat/Library/Subscriber.hs | 3 +-- src/Simplex/Chat/View.hs | 24 +++++++++++++++++------- tests/ChatTests/Groups.hs | 6 +++--- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 57c39ac21a..423534032f 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -803,8 +803,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = pure gInfo {membership = membership {memberStatus = GSMemConnected}} else pure gInfo pure (m {memberStatus = GSMemConnected}, gInfo') - -- TODO [relays] member: only show "joined group" event/output once - -- TODO - or different output "connected to relay" + -- TODO [relays] member: don't duplicate e2ee and descr chat items for each relay toView $ CEvtUserJoinedGroup user gInfo' m' (gInfo'', m'', scopeInfo) <- mkGroupChatScope gInfo' m' let cd = CDGroupRcv gInfo'' scopeInfo m'' diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 5874256876..3a4731ea6d 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -416,7 +416,7 @@ chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView} CEvtAcceptingBusinessRequest u g -> ttyUser u $ viewAcceptingBusinessRequest g CEvtContactRequestAlreadyAccepted u c -> ttyUser u [ttyFullContact c <> ": sent you a duplicate contact request, but you are already connected, no action needed"] CEvtBusinessRequestAlreadyAccepted u g -> ttyUser u [ttyFullGroup g <> ": sent you a duplicate connection request, but you are already connected, no action needed"] - CEvtGroupLinkConnecting u g _ -> ttyUser u [ttyGroup' g <> ": joining the group..."] + CEvtGroupLinkConnecting u g m -> ttyUser u $ viewUserJoiningGroup g m CEvtBusinessLinkConnecting u g _ _ -> ttyUser u [ttyGroup' g <> ": joining the group..."] CEvtUnknownMemberCreated u g fwdM um -> ttyUser u [ttyGroup' g <> ": " <> ttyMember fwdM <> " forwarded a message from an unknown member, creating unknown member record " <> ttyMember um] CEvtUnknownMemberBlocked u g byM um -> ttyUser u [ttyGroup' g <> ": " <> ttyMember byM <> " blocked an unknown member, creating unknown member record " <> ttyMember um] @@ -458,7 +458,7 @@ chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView} in ttyUser u [sShow connId <> ": END"] CEvtSubscriptionStatus srv status conns -> [plain $ subStatusStr status <> " " <> show (length conns) <> " connections on server " <> showSMPServer srv] CEvtReceivedGroupInvitation {user = u, groupInfo = g, contact = c, memberRole = r} -> ttyUser u $ viewReceivedGroupInvitation g c r - CEvtUserJoinedGroup u g _ -> ttyUser u $ viewUserJoinedGroup g + CEvtUserJoinedGroup u g m -> ttyUser u $ viewUserJoinedGroup g m CEvtRelayJoined u g relayMem groupLink relays -> ttyUser u $ viewRelayJoined g relayMem groupLink relays CEvtJoinedGroupMember u g m -> ttyUser u $ viewJoinedGroupMember g m CEvtHostConnected p h -> [plain $ "connected to " <> viewHostEvent p h] @@ -1199,12 +1199,22 @@ viewDirectMessagesProhibited :: MsgDirection -> Contact -> [StyledString] viewDirectMessagesProhibited MDSnd c = ["direct messages to indirect contact " <> ttyContact' c <> " are prohibited"] viewDirectMessagesProhibited MDRcv c = ["received prohibited direct message from indirect contact " <> ttyContact' c <> " (discarded)"] -viewUserJoinedGroup :: GroupInfo -> [StyledString] -viewUserJoinedGroup g@GroupInfo {membership} = - case incognitoMembershipProfile g of - Just mp -> [ttyGroup' g <> ": you joined the group incognito as " <> incognitoProfile' (fromLocalProfile mp) <> pendingApproval_] - Nothing -> [ttyGroup' g <> ": you joined the group" <> pendingApproval_] +viewUserJoiningGroup :: GroupInfo -> GroupMember -> [StyledString] +viewUserJoiningGroup g m + | isRelay' m = [ttyGroup' g <> ": joining the group (connecting to relay)..."] + | otherwise = [ttyGroup' g <> ": joining the group..."] + +viewUserJoinedGroup :: GroupInfo -> GroupMember -> [StyledString] +viewUserJoinedGroup g@GroupInfo {membership} m + | isRelay' membership = [ttyGroup' g <> ": you joined the group as relay"] + | otherwise = + case incognitoMembershipProfile g of + Just mp -> [ttyGroup' g <> ": you joined the group" <> connectedToRelay_ <> " incognito as " <> incognitoProfile' (fromLocalProfile mp) <> pendingApproval_] + Nothing -> [ttyGroup' g <> ": you joined the group" <> connectedToRelay_ <> pendingApproval_] where + connectedToRelay_ + | isRelay' m = " (connected to relay)" + | otherwise = "" pendingApproval_ = case memberStatus membership of GSMemPendingApproval -> ", pending approval" GSMemPendingReview -> ", connecting to group moderators for admission to group" diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 87f5e7d023..45d8204c70 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -8215,7 +8215,7 @@ createChannel1Relay gName owner relay = do owner <## "group link:" _ <- getTermLine owner pure (), - relay <## ("#" <> gName <> ": you joined the group") + relay <## ("#" <> gName <> ": you joined the group as relay") ] owner ##> ("/show link #" <> gName) @@ -8237,8 +8237,8 @@ memberJoinChannel gName relay shortLink fullLink member = do member <## "ok" concurrentlyN_ [ do - member <## ("#" <> gName <> ": joining the group...") - member <## ("#" <> gName <> ": you joined the group"), + member <## ("#" <> gName <> ": joining the group (connecting to relay)...") + member <## ("#" <> gName <> ": you joined the group (connected to relay)"), do relay <## (mFullName <> ": accepting request to join group #team...") relay <## ("#" <> gName <> ": " <> mName <> " joined the group") From 2465382aff43973236d1ee8f03e45a44022a50e1 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:34:58 +0400 Subject: [PATCH 2/3] invert relay fkey --- bots/api/TYPES.md | 6 +- .../types/typescript/src/types.ts | 9 ++- src/Simplex/Chat/Library/Commands.hs | 4 +- src/Simplex/Chat/Library/Subscriber.hs | 37 +++++-------- src/Simplex/Chat/Store/Connections.hs | 5 +- src/Simplex/Chat/Store/Groups.hs | 55 ++++++++++--------- src/Simplex/Chat/Store/Messages.hs | 12 ++-- .../Migrations/M20251018_chat_relays.hs | 22 ++------ .../Store/SQLite/Migrations/chat_schema.sql | 6 +- src/Simplex/Chat/Store/Shared.hs | 14 ++--- src/Simplex/Chat/Types.hs | 4 +- 11 files changed, 80 insertions(+), 94 deletions(-) diff --git a/bots/api/TYPES.md b/bots/api/TYPES.md index 62511e7a85..91ecb35e60 100644 --- a/bots/api/TYPES.md +++ b/bots/api/TYPES.md @@ -2223,7 +2223,6 @@ Known: - updatedAt: UTCTime - supportChat: [GroupSupportChat](#groupsupportchat)? - isRelay: bool -- relayData: [GroupRelay](#grouprelay)? --- @@ -2345,6 +2344,7 @@ Known: **Record type**: - groupRelayId: int64 +- groupMemberId: int64 - userChatRelayId: int64 - relayStatus: [RelayStatus](#relaystatus) - relayLink: string? @@ -3598,6 +3598,10 @@ GroupRelayNotFound: - type: "groupRelayNotFound" - groupRelayId: int64 +GroupRelayNotFoundByMemberId: +- type: "groupRelayNotFoundByMemberId" +- groupMemberId: int64 + InvalidQuote: - type: "invalidQuote" diff --git a/packages/simplex-chat-client/types/typescript/src/types.ts b/packages/simplex-chat-client/types/typescript/src/types.ts index 7e0905caab..b3ebdd3fc7 100644 --- a/packages/simplex-chat-client/types/typescript/src/types.ts +++ b/packages/simplex-chat-client/types/typescript/src/types.ts @@ -2513,7 +2513,6 @@ export interface GroupMember { updatedAt: string // ISO-8601 timestamp supportChat?: GroupSupportChat isRelay: boolean - relayData?: GroupRelay } export interface GroupMemberAdmission { @@ -2595,6 +2594,7 @@ export interface GroupProfile { export interface GroupRelay { groupRelayId: number // int64 + groupMemberId: number // int64 userChatRelayId: number // int64 relayStatus: RelayStatus relayLink?: string @@ -3816,6 +3816,7 @@ export type StoreError = | StoreError.UsageConditionsNotFound | StoreError.UserChatRelayNotFound | StoreError.GroupRelayNotFound + | StoreError.GroupRelayNotFoundByMemberId | StoreError.InvalidQuote | StoreError.InvalidMention | StoreError.InvalidDeliveryTask @@ -3903,6 +3904,7 @@ export namespace StoreError { | "usageConditionsNotFound" | "userChatRelayNotFound" | "groupRelayNotFound" + | "groupRelayNotFoundByMemberId" | "invalidQuote" | "invalidMention" | "invalidDeliveryTask" @@ -4290,6 +4292,11 @@ export namespace StoreError { groupRelayId: number // int64 } + export interface GroupRelayNotFoundByMemberId extends Interface { + type: "groupRelayNotFoundByMemberId" + groupMemberId: number // int64 + } + export interface InvalidQuote extends Interface { type: "invalidQuote" } diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 9876ac2452..47224a219e 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -3625,8 +3625,8 @@ processChatCommand vr nm = \case -- TODO - or make "add relays" api retriable, via prepared connection connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq PQSupportOff (relayMember, conn, groupRelay) <- withFastStore $ \db -> do - groupRelay <- createGroupRelayRecord db gInfo relay - relayMember <- createRelayForOwner db vr gVar user gInfo relay groupRelay + relayMember <- createRelayForOwner db vr gVar user gInfo relay + groupRelay <- createGroupRelayRecord db gInfo relayMember relay conn <- createRelayConnection db vr user (groupMemberId' relayMember) connId ConnPrepared chatV subMode pure (relayMember, conn, groupRelay) let GroupMember {memberRole = userRole, memberId = userMemberId} = membership diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 423534032f..eb6578a8a0 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -728,18 +728,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- [async agent commands] no continuation needed, but command should be asynchronous for stability allowAgentConnectionAsync user conn' confId XOk | otherwise -> messageError "x.grp.acpt: memberId is different from expected" - -- TODO [relays] owner: XGrpRelayAcpt processing branch - -- TODO - TBC relay category XGrpRelayAcpt relayLink - | memberRole' membership == GROwner -> - case relayData m of - Just relay -> do - -- TODO [relays] owner: check current relay status? - withStore' $ \db -> do - updateGroupMemberStatus db userId m GSMemAccepted - void $ setRelayLinkAccepted db relay relayLink - allowAgentConnectionAsync user conn' confId XOk - Nothing -> messageError "x.grp.relay.acpt: member is not saved as relay" + | memberRole' membership == GROwner && isRelay' m -> do + withStore $ \db -> do + relay <- getGroupRelayByGMId db (groupMemberId' m) + liftIO $ updateGroupMemberStatus db userId m GSMemAccepted + void $ liftIO $ setRelayLinkAccepted db relay relayLink + allowAgentConnectionAsync user conn' confId XOk | otherwise -> messageError "x.grp.relay.acpt: only owner can add relay" _ -> messageError "CONF from invited member must have x.grp.acpt" GCHostMember -> @@ -813,23 +808,21 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = memberConnectedChatItem gInfo'' scopeInfo m'' let welcomeMsgId_ = (\PreparedGroup {welcomeSharedMsgId = mId} -> mId) <$> prepared unless (memberPending membership || isJust welcomeMsgId_) $ maybeCreateGroupDescrLocal gInfo'' m'' - GCInviteeMember -> - case relayData m of - -- TODO [relays] owner: relay CON processing branch - -- TODO - TBC relay category - Just relay -> do - -- TODO [relays] owner: agent async setConnShortLink api + GCInviteeMember + | isRelay' m -> do withStore' $ \db -> updateGroupMemberStatus db userId m GSMemConnected let m' = m {memberStatus = GSMemConnected} + -- TODO [relays] owner: agent async setConnShortLink api setGroupLinkData' NRMBackground user gInfo >>= \case Just gLink -> do - (relay', relays) <- withStore' $ \db -> - (,) <$> updateRelayStatus db relay RSActive <*> getGroupRelays db gInfo - let m'' = m' {relayData = Just relay'} + relays <- withStore $ \db -> do + relay <- getGroupRelayByGMId db (groupMemberId' m) + void $ liftIO $ updateRelayStatus db relay RSActive + liftIO $ getGroupRelays db gInfo -- TODO [relays] owner: relay added chat item? - toView $ CEvtRelayJoined user gInfo m'' gLink relays + toView $ CEvtRelayJoined user gInfo m' gLink relays Nothing -> messageError "x.grp.relay.acpt: group link not updated" - Nothing -> do + | otherwise -> do (gInfo', mStatus) <- if not (memberPending m) then do diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs index 6c7424dc71..fcbbe5a154 100644 --- a/src/Simplex/Chat/Store/Connections.hs +++ b/src/Simplex/Chat/Store/Connections.hs @@ -149,16 +149,15 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences, mu.created_at, mu.updated_at, mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts, - mu.is_relay, NULL, NULL, NULL, NULL, + mu.is_relay, -- from GroupMember m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, - m.is_relay, r.group_relay_id, r.chat_relay_id, r.relay_status, r.relay_link + m.is_relay FROM group_members m JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) - LEFT JOIN group_relays r ON r.group_relay_id = m.group_relay_id JOIN groups g ON g.group_id = m.group_id JOIN group_profiles gp USING (group_profile_id) JOIN group_members mu ON g.group_id = mu.group_id diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index f22e9e5a41..d44f679aba 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -76,6 +76,7 @@ module Simplex.Chat.Store.Groups createNewContactMember, createGroupRelayRecord, getGroupRelayById, + getGroupRelayByGMId, getGroupRelays, createRelayForOwner, createRelayForMember, @@ -204,11 +205,11 @@ import Database.SQLite.Simple (Only (..), Query, (:.) (..)) import Database.SQLite.Simple.QQ (sql) #endif -type MaybeGroupMemberRow = (Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId) :. (Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, Maybe LocalAlias, Maybe Preferences) :. (Maybe UTCTime, Maybe UTCTime) :. (Maybe UTCTime, Maybe Int64, Maybe Int64, Maybe Int64, Maybe UTCTime) :. (Maybe BoolInt, Maybe Int64, Maybe Int64, Maybe RelayStatus, Maybe ShortLinkContact) +type MaybeGroupMemberRow = (Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId) :. (Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, Maybe LocalAlias, Maybe Preferences) :. (Maybe UTCTime, Maybe UTCTime) :. (Maybe UTCTime, Maybe Int64, Maybe Int64, Maybe Int64, Maybe UTCTime, Maybe BoolInt) toMaybeGroupMember :: Int64 -> MaybeGroupMemberRow -> Maybe GroupMember -toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just minVer, Just maxVer, Just memberRole, Just memberCategory, Just memberStatus, Just showMessages, memberBlocked') :. (invitedById, invitedByGroupMemberId, Just localDisplayName, memberContactId, Just memberContactProfileId) :. (Just profileId, Just displayName, Just fullName, shortDescr, image, contactLink, peerType, Just localAlias, contactPreferences) :. (Just createdAt, Just updatedAt) :. (supportChatTs, Just supportChatUnread, Just supportChatUnanswered, Just supportChatMentions, supportChatLastMsgFromMemberTs) :. (Just isRelay, groupRelayId, chatRelayId, relayStatus, relayLink)) = - Just $ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberBlocked') :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId) :. (profileId, displayName, fullName, shortDescr, image, contactLink, peerType, localAlias, contactPreferences) :. (createdAt, updatedAt) :. (supportChatTs, supportChatUnread, supportChatUnanswered, supportChatMentions, supportChatLastMsgFromMemberTs) :. (isRelay, groupRelayId, chatRelayId, relayStatus, relayLink)) +toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just minVer, Just maxVer, Just memberRole, Just memberCategory, Just memberStatus, Just showMessages, memberBlocked') :. (invitedById, invitedByGroupMemberId, Just localDisplayName, memberContactId, Just memberContactProfileId) :. (Just profileId, Just displayName, Just fullName, shortDescr, image, contactLink, peerType, Just localAlias, contactPreferences) :. (Just createdAt, Just updatedAt) :. (supportChatTs, Just supportChatUnread, Just supportChatUnanswered, Just supportChatMentions, supportChatLastMsgFromMemberTs, Just isRelay)) = + Just $ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberBlocked') :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId) :. (profileId, displayName, fullName, shortDescr, image, contactLink, peerType, localAlias, contactPreferences) :. (createdAt, updatedAt) :. (supportChatTs, supportChatUnread, supportChatUnanswered, supportChatMentions, supportChatLastMsgFromMemberTs, isRelay)) toMaybeGroupMember _ _ = Nothing createGroupLink :: DB.Connection -> TVar ChaChaDRG -> User -> GroupInfo -> ConnId -> CreatedLinkContact -> GroupLinkId -> GroupMemberRole -> SubscriptionMode -> ExceptT StoreError IO GroupLink @@ -497,8 +498,7 @@ createContactMemberInv_ db User {userId, userContactId} groupId invitedByGroupMe createdAt, updatedAt = createdAt, supportChat = Nothing, - isRelay = BoolDef isRelay, - relayData = Nothing + isRelay = BoolDef isRelay } where memberChatVRange@(VersionRange minV maxV) = vr @@ -1116,8 +1116,7 @@ createNewContactMember db gVar User {userId, userContactId} GroupInfo {groupId, createdAt, updatedAt = createdAt, supportChat = Nothing, - isRelay = BoolDef False, - relayData = Nothing + isRelay = BoolDef False } where insertMember_ = @@ -1135,18 +1134,18 @@ createNewContactMember db gVar User {userId, userContactId} GroupInfo {groupId, :. (minV, maxV) ) -createGroupRelayRecord :: DB.Connection -> GroupInfo -> UserChatRelay -> ExceptT StoreError IO GroupRelay -createGroupRelayRecord db GroupInfo {groupId} UserChatRelay {chatRelayId} = do +createGroupRelayRecord :: DB.Connection -> GroupInfo -> GroupMember -> UserChatRelay -> ExceptT StoreError IO GroupRelay +createGroupRelayRecord db GroupInfo {groupId} GroupMember {groupMemberId} UserChatRelay {chatRelayId} = do currentTs <- liftIO getCurrentTime liftIO $ DB.execute db [sql| INSERT INTO group_relays - (group_id, chat_relay_id, relay_status, created_at, updated_at) - VALUES (?,?,?,?,?) + (group_id, group_member_id, chat_relay_id, relay_status, created_at, updated_at) + VALUES (?,?,?,?,?,?) |] - (groupId, chatRelayId, RSNew, currentTs, currentTs) + (groupId, groupMemberId, chatRelayId, RSNew, currentTs, currentTs) relayId <- liftIO $ insertedRowId db getGroupRelayById db relayId @@ -1158,6 +1157,14 @@ getGroupRelayById db relayId = (groupRelayQuery <> " WHERE group_relay_id = ?") (Only relayId) +getGroupRelayByGMId :: DB.Connection -> GroupMemberId -> ExceptT StoreError IO GroupRelay +getGroupRelayByGMId db groupMemberId = + ExceptT . firstRow toGroupRelay (SEGroupRelayNotFoundByMemberId groupMemberId) $ + DB.query + db + (groupRelayQuery <> " WHERE group_member_id = ?") + (Only groupMemberId) + getGroupRelays :: DB.Connection -> GroupInfo -> IO [GroupRelay] getGroupRelays db GroupInfo {groupId} = map toGroupRelay @@ -1169,21 +1176,21 @@ getGroupRelays db GroupInfo {groupId} = groupRelayQuery :: Query groupRelayQuery = [sql| - SELECT group_relay_id, chat_relay_id, relay_status, relay_link + SELECT group_relay_id, group_member_id, chat_relay_id, relay_status, relay_link FROM group_relays |] -toGroupRelay :: (Int64, Int64, RelayStatus, Maybe ShortLinkContact) -> GroupRelay -toGroupRelay (groupRelayId, userChatRelayId, relayStatus, relayLink) = - GroupRelay {groupRelayId, userChatRelayId, relayStatus, relayLink} +toGroupRelay :: (Int64, GroupMemberId, Int64, RelayStatus, Maybe ShortLinkContact) -> GroupRelay +toGroupRelay (groupRelayId, groupMemberId, userChatRelayId, relayStatus, relayLink) = + GroupRelay {groupRelayId, groupMemberId, userChatRelayId, relayStatus, relayLink} -- TODO [relays] TBC role, category, relay profile -- TODO - GCInviteeMember -> GCRelayMember? -- TODO - GRMember -> GRRelay? -- TODO - create 1 profile per relay, link to chat_relays? -- TODO - retrieve profile from relay address -createRelayForOwner :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> GroupInfo -> UserChatRelay -> GroupRelay -> ExceptT StoreError IO GroupMember -createRelayForOwner db vr gVar user@User {userId, userContactId} GroupInfo {groupId, membership} UserChatRelay {name} GroupRelay {groupRelayId} = do +createRelayForOwner :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> GroupInfo -> UserChatRelay -> ExceptT StoreError IO GroupMember +createRelayForOwner db vr gVar user@User {userId, userContactId} GroupInfo {groupId, membership} UserChatRelay {name} = do currentTs <- liftIO getCurrentTime let relayProfile = profileFromName name (localDisplayName, memProfileId) <- createNewMemberProfile_ db user relayProfile currentTs @@ -1193,14 +1200,11 @@ createRelayForOwner db vr gVar user@User {userId, userContactId} GroupInfo {grou [sql| INSERT INTO group_members ( group_id, member_id, member_role, member_category, member_status, invited_by, invited_by_group_member_id, - user_id, local_display_name, contact_profile_id, created_at, updated_at, - is_relay, group_relay_id - ) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) + user_id, local_display_name, contact_profile_id, created_at, updated_at, is_relay) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) |] ( (groupId, MemberId memId, GRMember, GCInviteeMember, GSMemInvited, fromInvitedBy userContactId IBUser, groupMemberId' membership) - :. (userId, localDisplayName, memProfileId, currentTs, currentTs) - :. (BI True, groupRelayId) + :. (userId, localDisplayName, memProfileId, currentTs, currentTs, BI True) ) insertedRowId db getGroupMemberById db vr user groupMemberId @@ -1689,8 +1693,7 @@ createNewMember_ createdAt, updatedAt = createdAt, supportChat = Nothing, - isRelay = BoolDef isRelay, - relayData = Nothing + isRelay = BoolDef isRelay } checkGroupMemberHasItems :: DB.Connection -> User -> GroupMember -> IO (Maybe ChatItemId) diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 2f7828e493..ab2c04e796 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -679,10 +679,9 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, - m.is_relay, r.group_relay_id, r.chat_relay_id, r.relay_status, r.relay_link + m.is_relay FROM group_members m JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) - LEFT JOIN group_relays r ON r.group_relay_id = m.group_relay_id LEFT JOIN contacts c ON m.contact_id = c.contact_id LEFT JOIN chat_items i ON i.user_id = m.user_id AND i.group_id = m.group_id @@ -3005,7 +3004,7 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, - m.is_relay, r.group_relay_id, r.chat_relay_id, r.relay_status, r.relay_link, + m.is_relay, -- quoted ChatItem ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent, -- quoted GroupMember @@ -3014,26 +3013,23 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do rp.display_name, rp.full_name, rp.short_descr, rp.image, rp.contact_link, rp.chat_peer_type, rp.local_alias, rp.preferences, rm.created_at, rm.updated_at, rm.support_chat_ts, rm.support_chat_items_unread, rm.support_chat_items_member_attention, rm.support_chat_items_mentions, rm.support_chat_last_msg_from_member_ts, - rm.is_relay, rr.group_relay_id, rr.chat_relay_id, rr.relay_status, rr.relay_link, + rm.is_relay, -- deleted by GroupMember dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.peer_chat_min_version, dbm.peer_chat_max_version, dbm.member_role, dbm.member_category, dbm.member_status, dbm.show_messages, dbm.member_restriction, dbm.invited_by, dbm.invited_by_group_member_id, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id, dbp.display_name, dbp.full_name, dbp.short_descr, dbp.image, dbp.contact_link, dbp.chat_peer_type, dbp.local_alias, dbp.preferences, dbm.created_at, dbm.updated_at, dbm.support_chat_ts, dbm.support_chat_items_unread, dbm.support_chat_items_member_attention, dbm.support_chat_items_mentions, dbm.support_chat_last_msg_from_member_ts, - dbm.is_relay, dbr.group_relay_id, dbr.chat_relay_id, dbr.relay_status, dbr.relay_link + dbm.is_relay FROM chat_items i LEFT JOIN files f ON f.chat_item_id = i.chat_item_id LEFT JOIN group_members m ON m.group_member_id = i.group_member_id LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) - LEFT JOIN group_relays r ON r.group_relay_id = m.group_relay_id LEFT JOIN chat_items ri ON ri.shared_msg_id = i.quoted_shared_msg_id AND ri.group_id = i.group_id LEFT JOIN group_members rm ON rm.group_member_id = ri.group_member_id LEFT JOIN contact_profiles rp ON rp.contact_profile_id = COALESCE(rm.member_profile_id, rm.contact_profile_id) - LEFT JOIN group_relays rr ON rr.group_relay_id = rm.group_relay_id LEFT JOIN group_members dbm ON dbm.group_member_id = i.item_deleted_by_group_member_id LEFT JOIN contact_profiles dbp ON dbp.contact_profile_id = COALESCE(dbm.member_profile_id, dbm.contact_profile_id) - LEFT JOIN group_relays dbr ON dbr.group_relay_id = dbm.group_relay_id WHERE i.user_id = ? AND i.group_id = ? AND i.chat_item_id = ? |] (userId, groupId, itemId) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20251018_chat_relays.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20251018_chat_relays.hs index fe72e5baa1..8b81ccafe0 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/M20251018_chat_relays.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20251018_chat_relays.hs @@ -5,13 +5,6 @@ module Simplex.Chat.Store.SQLite.Migrations.M20251018_chat_relays where import Database.SQLite.Simple (Query) import Database.SQLite.Simple.QQ (sql) --- TODO [relays] owner: consider flipping group_members.group_relay_id fkey to group_relays.group_member_id --- TODO - pros: less joins in all member queries, --- TODO - can discern relay by is_relay, --- TODO - little places need additional relay data --- TODO - cons: in some places will need extra query: --- TODO - SELECT ... FROM group_relays WHERE group_member_id = ? - -- - chat_relays - user's list of chat relays to choose from (similar to protocol_servers) -- - users.is_user_chat_relay - indicates that the user can serve as a chat relay -- (TBC usage, e.g. agree to invitations to be relay) @@ -21,11 +14,7 @@ import Database.SQLite.Simple.QQ (sql) -- - group_relays.chat_relay_id - associates group_relays record with a chat_relays record, -- chat_relays.deleted is to keep associated record if user removes chat relay from configuration, -- but has group relays using it --- - group_members.is_relay - indicates that the member is a chat relay (to all group members) --- - group_members.group_relay_id - associates group_members record with a group_relays record for a group owner; --- receiving event to member connection, owner can match it to the relay; --- TBC inverse association - from group_relays to group_members? --- - TBC also inverse link from group_relays to group_members? (group_relays.group_member_id) +-- - group_members.is_relay - indicates that the member is a chat relay -- - groups.relay_own_status - indicates for a relay client that it is chat relay for the group (RelayStatus) m20251018_chat_relays :: Query m20251018_chat_relays = @@ -58,6 +47,7 @@ ALTER TABLE group_profiles ADD COLUMN group_link BLOB; CREATE TABLE group_relays( group_relay_id INTEGER PRIMARY KEY, group_id INTEGER NOT NULL REFERENCES groups ON DELETE CASCADE, + group_member_id INTEGER NOT NULL REFERENCES group_members ON DELETE CASCADE, chat_relay_id INTEGER NOT NULL REFERENCES chat_relays ON DELETE CASCADE, relay_status TEXT NOT NULL, relay_link BLOB, @@ -65,12 +55,10 @@ CREATE TABLE group_relays( updated_at TEXT NOT NULL DEFAULT(datetime('now')) ); CREATE INDEX idx_group_relays_group_id ON group_relays(group_id); +CREATE UNIQUE INDEX idx_group_relays_group_member_id ON group_relays(group_member_id); CREATE INDEX idx_group_relays_chat_relay_id ON group_relays(chat_relay_id); ALTER TABLE group_members ADD COLUMN is_relay INTEGER NOT NULL DEFAULT 0; - -ALTER TABLE group_members ADD COLUMN group_relay_id INTEGER REFERENCES group_relays ON DELETE SET NULL; -CREATE INDEX idx_group_members_group_relay_id ON group_members(group_relay_id); |] down_m20251018_chat_relays :: Query @@ -88,11 +76,9 @@ ALTER TABLE groups DROP COLUMN relay_own_status; ALTER TABLE group_profiles DROP COLUMN group_link; DROP INDEX idx_group_relays_group_id; +DROP INDEX idx_group_relays_group_member_id; DROP INDEX idx_group_relays_chat_relay_id; DROP TABLE group_relays; ALTER TABLE group_members DROP COLUMN is_relay; - -DROP INDEX idx_group_members_group_relay_id; -ALTER TABLE group_members DROP COLUMN group_relay_id; |] diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index 052d506c81..77ada6b3d5 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -200,7 +200,6 @@ CREATE TABLE group_members( member_xcontact_id BLOB, member_welcome_shared_msg_id BLOB, is_relay INTEGER NOT NULL DEFAULT 0, - group_relay_id INTEGER REFERENCES group_relays ON DELETE SET NULL, FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON DELETE CASCADE @@ -745,6 +744,7 @@ CREATE TABLE chat_relays( CREATE TABLE group_relays( group_relay_id INTEGER PRIMARY KEY, group_id INTEGER NOT NULL REFERENCES groups ON DELETE CASCADE, + group_member_id INTEGER NOT NULL REFERENCES group_members ON DELETE CASCADE, chat_relay_id INTEGER NOT NULL REFERENCES chat_relays ON DELETE CASCADE, relay_status TEXT NOT NULL, relay_link BLOB, @@ -1216,8 +1216,10 @@ CREATE INDEX idx_connections_to_subscribe ON connections( ); CREATE INDEX idx_chat_relays_user_id ON chat_relays(user_id); CREATE INDEX idx_group_relays_group_id ON group_relays(group_id); +CREATE UNIQUE INDEX idx_group_relays_group_member_id ON group_relays( + group_member_id +); CREATE INDEX idx_group_relays_chat_relay_id ON group_relays(chat_relay_id); -CREATE INDEX idx_group_members_group_relay_id ON group_members(group_relay_id); CREATE TRIGGER on_group_members_insert_update_summary AFTER INSERT ON group_members FOR EACH ROW diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index 291102cb78..6d9487e075 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -149,6 +149,7 @@ data StoreError | SEUsageConditionsNotFound | SEUserChatRelayNotFound {chatRelayId :: Int64} | SEGroupRelayNotFound {groupRelayId :: Int64} + | SEGroupRelayNotFoundByMemberId {groupMemberId :: GroupMemberId} | SEInvalidQuote | SEInvalidMention | SEInvalidDeliveryTask {taskId :: Int64} @@ -658,7 +659,7 @@ type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe Member type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Text, Maybe Text, Maybe ImageData, Maybe ShortLinkContact) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences, Maybe GroupMemberAdmission) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. PreparedGroupRow :. BusinessChatInfoRow :. (BoolInt, Maybe RelayStatus, Maybe UIThemeEntityOverrides, Int64, Maybe CustomData, Maybe Int64, Int, Maybe ConnReqContact) :. GroupMemberRow -type GroupMemberRow = (Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId) :. ProfileRow :. (UTCTime, UTCTime) :. (Maybe UTCTime, Int64, Int64, Int64, Maybe UTCTime) :. (BoolInt, Maybe Int64, Maybe Int64, Maybe RelayStatus, Maybe ShortLinkContact) +type GroupMemberRow = (Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId) :. ProfileRow :. (UTCTime, UTCTime) :. (Maybe UTCTime, Int64, Int64, Int64, Maybe UTCTime, BoolInt) type ProfileRow = (ProfileId, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias, Maybe Preferences) @@ -680,7 +681,7 @@ toPreparedGroup = \case _ -> Nothing toGroupMember :: Int64 -> GroupMemberRow -> GroupMember -toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, BI showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId) :. profileRow :. (createdAt, updatedAt) :. (supportChatTs_, supportChatUnread, supportChatMemberAttention, supportChatMentions, supportChatLastMsgFromMemberTs) :. (BI isRel, groupRelayId_, chatRelayId_, relayStatus_, relayLink)) = +toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, BI showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId) :. profileRow :. (createdAt, updatedAt) :. (supportChatTs_, supportChatUnread, supportChatMemberAttention, supportChatMentions, supportChatLastMsgFromMemberTs, BI isRel)) = let memberProfile = rowToLocalProfile profileRow memberSettings = GroupMemberSettings {showMessages} blockedByAdmin = maybe False mrsBlocked memberRestriction_ @@ -699,9 +700,6 @@ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, } _ -> Nothing isRelay = BoolDef isRel - relayData = case (groupRelayId_, chatRelayId_, relayStatus_) of - (Just groupRelayId, Just userChatRelayId, Just relayStatus) -> Just GroupRelay {groupRelayId, userChatRelayId, relayStatus, relayLink} - _ -> Nothing in GroupMember {..} groupMemberQuery :: Query @@ -712,14 +710,13 @@ groupMemberQuery = m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, - m.is_relay, r.group_relay_id, r.chat_relay_id, r.relay_status, r.relay_link, + m.is_relay, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter, c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version FROM group_members m JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) - LEFT JOIN group_relays r ON r.group_relay_id = m.group_relay_id LEFT JOIN connections c ON c.group_member_id = m.group_member_id |] @@ -738,7 +735,6 @@ toBusinessChatInfo _ = Nothing groupInfoQuery :: Query groupInfoQuery = groupInfoQueryFields <> " " <> groupInfoQueryFrom --- membership "member" never references group_relays, therefore `NULL, NULL, NULL, NULL` which avoids extra join groupInfoQueryFields :: Query groupInfoQueryFields = [sql| @@ -757,7 +753,7 @@ groupInfoQueryFields = pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences, mu.created_at, mu.updated_at, mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts, - mu.is_relay, NULL, NULL, NULL, NULL + mu.is_relay |] groupInfoQueryFrom :: Query diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 771425aa0d..414eb7fd9c 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -962,8 +962,7 @@ data GroupMember = GroupMember createdAt :: UTCTime, updatedAt :: UTCTime, supportChat :: Maybe GroupSupportChat, - isRelay :: BoolDef, -- marker for all members that this member is a chat relay - relayData :: Maybe GroupRelay -- owner's additional data for a chat relay + isRelay :: BoolDef } deriving (Eq, Show) @@ -972,6 +971,7 @@ memberRole' GroupMember {memberRole} = memberRole data GroupRelay = GroupRelay { groupRelayId :: Int64, + groupMemberId :: GroupMemberId, userChatRelayId :: Int64, -- ID of configured UserChatRelay relayStatus :: RelayStatus, relayLink :: Maybe ShortLinkContact From 35e8dea29d2c0511fee0176242836335ed24b75d Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 12 Nov 2025 18:01:50 +0400 Subject: [PATCH 3/3] postgres schema --- .../Migrations/M20251018_chat_relays.hs | 48 +++++-- .../Store/Postgres/Migrations/chat_schema.sql | 118 +++++++++++++++++- .../Migrations/M20251018_chat_relays.hs | 10 +- 3 files changed, 160 insertions(+), 16 deletions(-) diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20251018_chat_relays.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20251018_chat_relays.hs index 542dde5707..647011f640 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/M20251018_chat_relays.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20251018_chat_relays.hs @@ -12,23 +12,43 @@ m20251018_chat_relays = [r| CREATE TABLE chat_relays( chat_relay_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, - address TEXT NOT NULL, + address BYTEA NOT NULL, name TEXT NOT NULL, domains TEXT NOT NULL, preset SMALLINT NOT NULL DEFAULT 0, tested SMALLINT, enabled SMALLINT NOT NULL DEFAULT 1, user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, + deleted SMALLINT NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (now()), - updated_at TEXT NOT NULL DEFAULT (now()), - UNIQUE(user_id, address), - UNIQUE(user_id, name) + updated_at TEXT NOT NULL DEFAULT (now()) ); - CREATE INDEX idx_chat_relays_user_id ON chat_relays(user_id); +CREATE UNIQUE INDEX idx_chat_relays_user_id_address ON chat_relays(user_id, address); +CREATE UNIQUE INDEX idx_chat_relays_user_id_name ON chat_relays(user_id, name); ALTER TABLE users ADD COLUMN is_user_chat_relay SMALLINT NOT NULL DEFAULT 0; +ALTER TABLE groups ADD COLUMN use_relays SMALLINT NOT NULL DEFAULT 0; + +ALTER TABLE groups ADD COLUMN relay_own_status TEXT; + +ALTER TABLE group_profiles ADD COLUMN group_link BYTEA; + +CREATE TABLE group_relays( + group_relay_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + group_id BIGINT NOT NULL REFERENCES groups ON DELETE CASCADE, + group_member_id BIGINT NOT NULL REFERENCES group_members ON DELETE CASCADE, + chat_relay_id BIGINT NOT NULL REFERENCES chat_relays ON DELETE CASCADE, + relay_status TEXT NOT NULL, + relay_link BYTEA, + created_at TEXT NOT NULL DEFAULT (now()), + updated_at TEXT NOT NULL DEFAULT (now()) +); +CREATE INDEX idx_group_relays_group_id ON group_relays(group_id); +CREATE UNIQUE INDEX idx_group_relays_group_member_id ON group_relays(group_member_id); +CREATE INDEX idx_group_relays_chat_relay_id ON group_relays(chat_relay_id); + ALTER TABLE group_members ADD COLUMN is_relay SMALLINT NOT NULL DEFAULT 0; |] @@ -36,11 +56,23 @@ down_m20251018_chat_relays :: Text down_m20251018_chat_relays = T.pack [r| -ALTER TABLE group_members DROP COLUMN is_relay; +DROP INDEX idx_chat_relays_user_id; +DROP INDEX idx_chat_relays_user_id_address; +DROP INDEX idx_chat_relays_user_id_name; +DROP TABLE chat_relays; ALTER TABLE users DROP COLUMN is_user_chat_relay; -DROP INDEX idx_chat_relays_user_id; +ALTER TABLE groups DROP COLUMN use_relays; -DROP TABLE chat_relays; +ALTER TABLE groups DROP COLUMN relay_own_status; + +ALTER TABLE group_profiles DROP COLUMN group_link; + +DROP INDEX idx_group_relays_group_id; +DROP INDEX idx_group_relays_group_member_id; +DROP INDEX idx_group_relays_chat_relay_id; +DROP TABLE group_relays; + +ALTER TABLE group_members DROP COLUMN is_relay; |] diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql index 601dd97a6e..69f716bc9c 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql @@ -284,6 +284,33 @@ ALTER TABLE test_chat_schema.chat_items ALTER COLUMN chat_item_id ADD GENERATED +CREATE TABLE test_chat_schema.chat_relays ( + chat_relay_id bigint NOT NULL, + address bytea NOT NULL, + name text NOT NULL, + domains text NOT NULL, + preset smallint DEFAULT 0 NOT NULL, + tested smallint, + enabled smallint DEFAULT 1 NOT NULL, + user_id bigint NOT NULL, + deleted smallint DEFAULT 0 NOT NULL, + created_at text DEFAULT now() NOT NULL, + updated_at text DEFAULT now() NOT NULL +); + + + +ALTER TABLE test_chat_schema.chat_relays ALTER COLUMN chat_relay_id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME test_chat_schema.chat_relays_chat_relay_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + + CREATE TABLE test_chat_schema.chat_tags ( chat_tag_id bigint NOT NULL, user_id bigint, @@ -706,7 +733,8 @@ CREATE TABLE test_chat_schema.group_members ( support_chat_items_mentions bigint DEFAULT 0 NOT NULL, support_chat_last_msg_from_member_ts timestamp with time zone, member_xcontact_id bytea, - member_welcome_shared_msg_id bytea + member_welcome_shared_msg_id bytea, + is_relay smallint DEFAULT 0 NOT NULL ); @@ -734,7 +762,8 @@ CREATE TABLE test_chat_schema.group_profiles ( preferences text, description text, member_admission text, - short_descr text + short_descr text, + group_link bytea ); @@ -750,6 +779,30 @@ ALTER TABLE test_chat_schema.group_profiles ALTER COLUMN group_profile_id ADD GE +CREATE TABLE test_chat_schema.group_relays ( + group_relay_id bigint NOT NULL, + group_id bigint NOT NULL, + group_member_id bigint NOT NULL, + chat_relay_id bigint NOT NULL, + relay_status text NOT NULL, + relay_link bytea, + created_at text DEFAULT now() NOT NULL, + updated_at text DEFAULT now() NOT NULL +); + + + +ALTER TABLE test_chat_schema.group_relays ALTER COLUMN group_relay_id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME test_chat_schema.group_relays_group_relay_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + + CREATE TABLE test_chat_schema.group_snd_item_statuses ( group_snd_item_status_id bigint NOT NULL, chat_item_id bigint NOT NULL, @@ -805,7 +858,9 @@ CREATE TABLE test_chat_schema.groups ( request_shared_msg_id bytea, conn_link_prepared_connection smallint DEFAULT 0 NOT NULL, via_group_link_uri bytea, - summary_current_members_count bigint DEFAULT 0 NOT NULL + summary_current_members_count bigint DEFAULT 0 NOT NULL, + use_relays smallint DEFAULT 0 NOT NULL, + relay_own_status text ); @@ -1273,7 +1328,8 @@ CREATE TABLE test_chat_schema.users ( user_member_profile_updated_at timestamp with time zone, ui_themes text, active_order bigint DEFAULT 0 NOT NULL, - auto_accept_member_contacts smallint DEFAULT 0 NOT NULL + auto_accept_member_contacts smallint DEFAULT 0 NOT NULL, + is_user_chat_relay smallint DEFAULT 0 NOT NULL ); @@ -1357,6 +1413,11 @@ ALTER TABLE ONLY test_chat_schema.chat_items +ALTER TABLE ONLY test_chat_schema.chat_relays + ADD CONSTRAINT chat_relays_pkey PRIMARY KEY (chat_relay_id); + + + ALTER TABLE ONLY test_chat_schema.chat_tags ADD CONSTRAINT chat_tags_pkey PRIMARY KEY (chat_tag_id); @@ -1472,6 +1533,11 @@ ALTER TABLE ONLY test_chat_schema.group_profiles +ALTER TABLE ONLY test_chat_schema.group_relays + ADD CONSTRAINT group_relays_pkey PRIMARY KEY (group_relay_id); + + + ALTER TABLE ONLY test_chat_schema.group_snd_item_statuses ADD CONSTRAINT group_snd_item_statuses_pkey PRIMARY KEY (group_snd_item_status_id); @@ -1837,6 +1903,18 @@ CREATE INDEX idx_chat_items_user_id_item_status ON test_chat_schema.chat_items U +CREATE INDEX idx_chat_relays_user_id ON test_chat_schema.chat_relays USING btree (user_id); + + + +CREATE UNIQUE INDEX idx_chat_relays_user_id_address ON test_chat_schema.chat_relays USING btree (user_id, address); + + + +CREATE UNIQUE INDEX idx_chat_relays_user_id_name ON test_chat_schema.chat_relays USING btree (user_id, name); + + + CREATE INDEX idx_chat_tags_chats_chat_tag_id ON test_chat_schema.chat_tags_chats USING btree (chat_tag_id); @@ -2105,6 +2183,18 @@ CREATE INDEX idx_group_profiles_user_id ON test_chat_schema.group_profiles USING +CREATE INDEX idx_group_relays_chat_relay_id ON test_chat_schema.group_relays USING btree (chat_relay_id); + + + +CREATE INDEX idx_group_relays_group_id ON test_chat_schema.group_relays USING btree (group_id); + + + +CREATE UNIQUE INDEX idx_group_relays_group_member_id ON test_chat_schema.group_relays USING btree (group_member_id); + + + CREATE INDEX idx_group_snd_item_statuses_chat_item_id ON test_chat_schema.group_snd_item_statuses USING btree (chat_item_id); @@ -2463,6 +2553,11 @@ ALTER TABLE ONLY test_chat_schema.chat_items +ALTER TABLE ONLY test_chat_schema.chat_relays + ADD CONSTRAINT chat_relays_user_id_fkey FOREIGN KEY (user_id) REFERENCES test_chat_schema.users(user_id) ON DELETE CASCADE; + + + ALTER TABLE ONLY test_chat_schema.chat_tags_chats ADD CONSTRAINT chat_tags_chats_chat_tag_id_fkey FOREIGN KEY (chat_tag_id) REFERENCES test_chat_schema.chat_tags(chat_tag_id) ON DELETE CASCADE; @@ -2783,6 +2878,21 @@ ALTER TABLE ONLY test_chat_schema.group_profiles +ALTER TABLE ONLY test_chat_schema.group_relays + ADD CONSTRAINT group_relays_chat_relay_id_fkey FOREIGN KEY (chat_relay_id) REFERENCES test_chat_schema.chat_relays(chat_relay_id) ON DELETE CASCADE; + + + +ALTER TABLE ONLY test_chat_schema.group_relays + ADD CONSTRAINT group_relays_group_id_fkey FOREIGN KEY (group_id) REFERENCES test_chat_schema.groups(group_id) ON DELETE CASCADE; + + + +ALTER TABLE ONLY test_chat_schema.group_relays + ADD CONSTRAINT group_relays_group_member_id_fkey FOREIGN KEY (group_member_id) REFERENCES test_chat_schema.group_members(group_member_id) ON DELETE CASCADE; + + + ALTER TABLE ONLY test_chat_schema.group_snd_item_statuses ADD CONSTRAINT group_snd_item_statuses_chat_item_id_fkey FOREIGN KEY (chat_item_id) REFERENCES test_chat_schema.chat_items(chat_item_id) ON DELETE CASCADE; diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20251018_chat_relays.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20251018_chat_relays.hs index 8b81ccafe0..d3d7174be2 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/M20251018_chat_relays.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20251018_chat_relays.hs @@ -21,7 +21,7 @@ m20251018_chat_relays = [sql| CREATE TABLE chat_relays( chat_relay_id INTEGER PRIMARY KEY, - address TEXT NOT NULL, + address BLOB NOT NULL, name TEXT NOT NULL, domains TEXT NOT NULL, preset INTEGER NOT NULL DEFAULT 0, @@ -30,11 +30,11 @@ CREATE TABLE chat_relays( user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE, deleted INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT(datetime('now')), - updated_at TEXT NOT NULL DEFAULT(datetime('now')), - UNIQUE(user_id, address), - UNIQUE(user_id, name) + updated_at TEXT NOT NULL DEFAULT(datetime('now')) ); CREATE INDEX idx_chat_relays_user_id ON chat_relays(user_id); +CREATE UNIQUE INDEX idx_chat_relays_user_id_address ON chat_relays(user_id, address); +CREATE UNIQUE INDEX idx_chat_relays_user_id_name ON chat_relays(user_id, name); ALTER TABLE users ADD COLUMN is_user_chat_relay INTEGER NOT NULL DEFAULT 0; @@ -65,6 +65,8 @@ down_m20251018_chat_relays :: Query down_m20251018_chat_relays = [sql| DROP INDEX idx_chat_relays_user_id; +DROP INDEX idx_chat_relays_user_id_address; +DROP INDEX idx_chat_relays_user_id_name; DROP TABLE chat_relays; ALTER TABLE users DROP COLUMN is_user_chat_relay;