From 8167f7c2ab2ae48bad4a254ee0679b297167df83 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 3 Apr 2026 13:42:43 +0100 Subject: [PATCH] core: add fields to chat relay profiles; remove unique name requirement; update relay profile in relay address link data (#6743) * core: add fields to chat relay profiles * wip * wip * fix * fix * fix * enable tests * schema * api --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- apps/ios/Shared/Model/AppAPITypes.swift | 1 - .../Shared/Views/NewChat/AddChannelView.swift | 2 +- .../NetworkAndServers/ChatRelayView.swift | 27 ++++++++--------- .../NetworkAndServers/NetworkAndServers.swift | 7 ----- .../NetworkAndServers/OperatorView.swift | 2 -- .../ProtocolServersView.swift | 4 +-- apps/ios/SimpleXChat/ChatTypes.swift | 13 ++++---- .../chat/simplex/common/model/ChatModel.kt | 9 ++++-- .../chat/simplex/common/model/SimpleXAPI.kt | 2 -- .../common/views/newchat/AddChannelView.kt | 2 +- .../networkAndServers/ChatRelayView.kt | 21 +++++++------ .../networkAndServers/NetworkAndServers.kt | 3 -- .../networkAndServers/OperatorView.kt | 3 +- .../networkAndServers/ProtocolServersView.kt | 5 ++-- bots/api/TYPES.md | 5 +++- .../types/typescript/src/types.ts | 5 +++- src/Simplex/Chat/Library/Commands.hs | 15 ++++++---- src/Simplex/Chat/Operators.hs | 18 +++++------ src/Simplex/Chat/Operators/Presets.hs | 7 +++-- src/Simplex/Chat/Protocol.hs | 13 +++++++- src/Simplex/Chat/Store/Groups.hs | 12 ++++---- .../Migrations/M20260222_chat_relays.hs | 7 +++-- .../Store/Postgres/Migrations/chat_schema.sql | 9 +++--- src/Simplex/Chat/Store/Profiles.hs | 30 +++++++++---------- .../Migrations/M20260222_chat_relays.hs | 7 +++-- .../Store/SQLite/Migrations/chat_schema.sql | 6 ++-- src/Simplex/Chat/View.hs | 4 +-- tests/ChatTests/ChatRelays.hs | 22 +++++++++++++- tests/OperatorTests.hs | 23 ++++++-------- 29 files changed, 153 insertions(+), 131 deletions(-) diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift index 9f3f9decb1..1131069d88 100644 --- a/apps/ios/Shared/Model/AppAPITypes.swift +++ b/apps/ios/Shared/Model/AppAPITypes.swift @@ -1802,7 +1802,6 @@ enum UserServersError: Decodable { case storageMissing(protocol: ServerProtocol, user: UserRef?) case proxyMissing(protocol: ServerProtocol, user: UserRef?) case duplicateServer(protocol: ServerProtocol, duplicateServer: String, duplicateHost: String) - case duplicateChatRelayName(duplicateChatRelay: String) case duplicateChatRelayAddress(duplicateChatRelay: String, duplicateAddress: String) var globalError: String? { diff --git a/apps/ios/Shared/Views/NewChat/AddChannelView.swift b/apps/ios/Shared/Views/NewChat/AddChannelView.swift index d7709ea31c..548387417b 100644 --- a/apps/ios/Shared/Views/NewChat/AddChannelView.swift +++ b/apps/ios/Shared/Views/NewChat/AddChannelView.swift @@ -422,7 +422,7 @@ struct AddChannelView: View { } func relayDisplayName(_ relay: GroupRelay) -> String { - if !relay.userChatRelay.name.isEmpty { return relay.userChatRelay.name } + if !relay.userChatRelay.displayName.isEmpty { return relay.userChatRelay.displayName } if let domain = relay.userChatRelay.domains.first { return domain } if let link = relay.relayLink { return hostFromRelayLink(link) } return "relay \(relay.groupRelayId)" diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ChatRelayView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ChatRelayView.swift index 2bcfb6ca87..4a5cbab184 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ChatRelayView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ChatRelayView.swift @@ -57,11 +57,11 @@ func addChatRelay( _ serverWarnings: Binding<[UserServersWarning]>? = nil, _ dismiss: DismissAction ) { - let nameEmpty = relay.name.trimmingCharacters(in: .whitespaces).isEmpty + let nameEmpty = relay.displayName.trimmingCharacters(in: .whitespaces).isEmpty let addressEmpty = relay.address.trimmingCharacters(in: .whitespaces).isEmpty if nameEmpty && addressEmpty { dismiss() - } else if !validRelayName(relay.name) { + } else if !validRelayName(relay.displayName) { dismiss() showAlert( NSLocalizedString("Invalid relay name!", comment: "alert title"), @@ -97,7 +97,7 @@ struct ChatRelayView: View { @State private var testFailure: RelayTestFailure? var body: some View { - let validName = validRelayName(relayToEdit.name) + let validName = validRelayName(relayToEdit.displayName) let validAddress = validRelayAddress(relayToEdit.address) ZStack { if relay.preset { @@ -137,7 +137,7 @@ struct ChatRelayView: View { .onChange(of: relayToEdit.address) { _ in if relayToEdit.address == relay.address { relayToEdit.tested = relay.tested - relayToEdit.name = relay.name + relayToEdit.displayName = relay.displayName } else { relayToEdit.tested = nil } @@ -150,7 +150,7 @@ struct ChatRelayView: View { if !validName { Spacer() Image(systemName: "exclamationmark.circle").foregroundColor(.red) - .onTapGesture { showInvalidRelayNameAlert($relayToEdit.name) } + .onTapGesture { showInvalidRelayNameAlert($relayToEdit.displayName) } } } } @@ -162,7 +162,7 @@ struct ChatRelayView: View { .textSelection(.enabled) } Section(header: Text("Preset relay name").foregroundColor(theme.colors.secondary)) { - Text(relayToEdit.name) + Text(relayToEdit.displayName) } useRelaySection() } @@ -190,7 +190,7 @@ struct ChatRelayView: View { } } Section { - TextField("Enter relay name…", text: $relayToEdit.name) + TextField("Enter relay name…", text: $relayToEdit.displayName) .autocorrectionDisabled(true) .disabled(relayToEdit.tested == true) } header: { @@ -243,7 +243,6 @@ struct ChatRelayViewLink: View { @Binding var serverErrors: [UserServersError] @Binding var serverWarnings: [UserServersWarning] @Binding var relay: UserChatRelay - var duplicateRelayNames: Set var duplicateRelayAddresses: Set var backLabel: LocalizedStringKey @Binding var selectedServer: String? @@ -264,7 +263,7 @@ struct ChatRelayViewLink: View { } label: { HStack { Group { - if duplicateRelayNames.contains(relay.name) || duplicateRelayAddresses.contains(relay.address) { + if duplicateRelayAddresses.contains(relay.address) { Image(systemName: "exclamationmark.circle").foregroundColor(.red) } else if !relay.enabled { Image(systemName: "slash.circle").foregroundColor(theme.colors.secondary) @@ -275,7 +274,7 @@ struct ChatRelayViewLink: View { .frame(width: 16, alignment: .center) .padding(.trailing, 4) - let displayName = !relay.name.isEmpty ? relay.name : relay.domains.first ?? relay.address + let displayName = !relay.displayName.isEmpty ? relay.displayName : relay.domains.first ?? relay.address let v = Text(displayName).lineLimit(1) if relay.enabled { v @@ -302,7 +301,7 @@ struct NewChatRelayView: View { @State private var testFailure: RelayTestFailure? var body: some View { - let validName = validRelayName(relayToEdit.name) + let validName = validRelayName(relayToEdit.displayName) let validAddress = validRelayAddress(relayToEdit.address) ZStack { List { @@ -326,7 +325,7 @@ struct NewChatRelayView: View { } } Section { - TextField("Enter relay name…", text: $relayToEdit.name) + TextField("Enter relay name…", text: $relayToEdit.displayName) .autocorrectionDisabled(true) .disabled(relayToEdit.tested == true) } header: { @@ -335,7 +334,7 @@ struct NewChatRelayView: View { if !validName { Spacer() Image(systemName: "exclamationmark.circle").foregroundColor(.red) - .onTapGesture { showInvalidRelayNameAlert($relayToEdit.name) } + .onTapGesture { showInvalidRelayNameAlert($relayToEdit.displayName) } } } } footer: { @@ -392,7 +391,7 @@ func testRelayConnection(relay: Binding) async -> RelayTestFailur await MainActor.run { relay.wrappedValue.tested = true if let relayProfile { - relay.wrappedValue.name = relayProfile.name + relay.wrappedValue.displayName = relayProfile.displayName } } return nil diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift index 5e9d558917..74b7374654 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift @@ -483,13 +483,6 @@ func findDuplicateHosts(_ serverErrors: [UserServersError]) -> Set { return Set(duplicateHostsList) } -func findDuplicateRelayNames(_ serverErrors: [UserServersError]) -> Set { - Set(serverErrors.compactMap { err in - if case let .duplicateChatRelayName(duplicateChatRelay) = err { return duplicateChatRelay } - else { return nil } - }) -} - func findDuplicateRelayAddresses(_ serverErrors: [UserServersError]) -> Set { Set(serverErrors.compactMap { err in if case let .duplicateChatRelayAddress(_, duplicateAddress) = err { return duplicateAddress } diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift index ea6c2ea40c..9d068d3b26 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift @@ -42,7 +42,6 @@ struct OperatorView: View { private func operatorView() -> some View { let duplicateHosts = findDuplicateHosts(serverErrors) - let duplicateRelayNames = findDuplicateRelayNames(serverErrors) let duplicateRelayAddresses = findDuplicateRelayAddresses(serverErrors) return VStack { List { @@ -83,7 +82,6 @@ struct OperatorView: View { serverErrors: $serverErrors, serverWarnings: $serverWarnings, relay: relay, - duplicateRelayNames: duplicateRelayNames, duplicateRelayAddresses: duplicateRelayAddresses, backLabel: "\(userServers[operatorIndex].operator_.tradeName) servers", selectedServer: $selectedServer diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift index 4e7b4040cd..e57df4c5dc 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift @@ -43,7 +43,6 @@ struct YourServersView: View { private func yourServersView() -> some View { let duplicateHosts = findDuplicateHosts(serverErrors) - let duplicateRelayNames = findDuplicateRelayNames(serverErrors) let duplicateRelayAddresses = findDuplicateRelayAddresses(serverErrors) return List { if !userServers[operatorIndex].chatRelays.filter({ !$0.deleted }).isEmpty { @@ -55,7 +54,6 @@ struct YourServersView: View { serverErrors: $serverErrors, serverWarnings: $serverWarnings, relay: relay, - duplicateRelayNames: duplicateRelayNames, duplicateRelayAddresses: duplicateRelayAddresses, backLabel: "Your servers", selectedServer: $selectedServer @@ -434,7 +432,7 @@ struct TestServersButton: View { for i in 0.. this.protocol is ProxyMissing -> this.protocol is DuplicateServer -> this.protocol - is DuplicateChatRelayName -> null is DuplicateChatRelayAddress -> null } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt index fc5b2f0640..cf10f5e545 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt @@ -539,7 +539,7 @@ private fun LinkStepView( } fun relayDisplayName(relay: GroupRelay): String { - if (relay.userChatRelay.name.isNotEmpty()) return relay.userChatRelay.name + if (relay.userChatRelay.displayName.isNotEmpty()) return relay.userChatRelay.displayName relay.userChatRelay.domains.firstOrNull()?.let { return it } relay.relayLink?.let { return hostFromRelayLink(it) } return "relay ${relay.groupRelayId}" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ChatRelayView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ChatRelayView.kt index 7886c3b8b8..1c68e780dc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ChatRelayView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ChatRelayView.kt @@ -72,11 +72,11 @@ fun addChatRelay( rhId: Long?, close: () -> Unit ) { - val nameEmpty = relay.name.trim().isEmpty() + val nameEmpty = relay.displayName.trim().isEmpty() val addressEmpty = relay.address.trim().isEmpty() if (nameEmpty && addressEmpty) { close() - } else if (!validRelayName(relay.name)) { + } else if (!validRelayName(relay.displayName)) { close() AlertManager.shared.showAlertMsg( title = generalGetString(MR.strings.invalid_relay_name), @@ -131,7 +131,7 @@ fun ChatRelayView( ModalView( close = { - val validName = validRelayName(relayToEdit.value.name) + val validName = validRelayName(relayToEdit.value.displayName) val validAddress = validRelayAddress(relayToEdit.value.address) if (validName && validAddress) { onUpdate(relayToEdit.value) @@ -194,7 +194,7 @@ private fun PresetRelay(relay: MutableState, testing: MutableStat SectionDividerSpaced() SectionView(stringResource(MR.strings.preset_relay_name).uppercase()) { SectionItemView { - Text(relay.value.name) + Text(relay.value.displayName) } } SectionDividerSpaced() @@ -207,7 +207,7 @@ private fun CustomRelay( onDelete: (() -> Unit)?, testing: MutableState ) { - val relayName = remember { mutableStateOf(relay.value.name) } + val relayName = remember { mutableStateOf(relay.value.displayName) } val relayAddress = remember { mutableStateOf(relay.value.address) } val validName = remember { derivedStateOf { validRelayName(relayName.value) } } val validAddress = remember { derivedStateOf { validRelayAddress(relayAddress.value) } } @@ -218,7 +218,7 @@ private fun CustomRelay( .collect { relay.value = relay.value.copyWithName(it) } } LaunchedEffect(Unit) { - snapshotFlow { relay.value.name } + snapshotFlow { relay.value.displayName } .distinctUntilChanged() .collect { relayName.value = it } } @@ -329,20 +329,19 @@ private fun UseRelaySection( @Composable fun ChatRelayViewLink( relay: UserChatRelay, - duplicateRelayNames: Set, duplicateRelayAddresses: Set, onClick: () -> Unit ) { SectionItemView(onClick) { Box(Modifier.width(16.dp)) { when { - relay.name in duplicateRelayNames || relay.address in duplicateRelayAddresses -> InvalidServer() + relay.address in duplicateRelayAddresses -> InvalidServer() !relay.enabled -> Icon(painterResource(MR.images.ic_do_not_disturb_on), null, tint = MaterialTheme.colors.secondary) else -> ShowRelayTestStatus(relay) } } Spacer(Modifier.padding(horizontal = 4.dp)) - val displayName = relay.name.ifEmpty { relay.domains.firstOrNull() ?: relay.address } + val displayName = relay.displayName.ifEmpty { relay.domains.firstOrNull() ?: relay.address } if (relay.enabled) { Text(displayName, color = MaterialTheme.colors.onBackground, maxLines = 1) } else { @@ -362,7 +361,7 @@ fun ModalData.NewChatRelayView( val relayToEdit = remember { mutableStateOf( UserChatRelay( - chatRelayId = null, address = "", relayProfile = RelayProfile(name = ""), domains = emptyList(), + chatRelayId = null, address = "", relayProfile = RelayProfile(displayName = "", fullName = ""), domains = emptyList(), preset = false, tested = null, enabled = true, deleted = false ) ) @@ -406,7 +405,7 @@ suspend fun testRelayConnection(relay: MutableState): RelayTestFa testFailure } else { relay.value = relay.value.copy(tested = true).let { - if (relayProfile != null) it.copyWithName(relayProfile.name) else it + if (relayProfile != null) it.copyWithName(relayProfile.displayName) else it } null } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt index d50946ec3b..bbd2a0af49 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt @@ -990,9 +990,6 @@ fun findDuplicateHosts(serverErrors: List): Set { return duplicateHostsList.toSet() } -fun findDuplicateRelayNames(serverErrors: List): Set = - serverErrors.mapNotNull { (it as? UserServersError.DuplicateChatRelayName)?.duplicateChatRelay }.toSet() - fun findDuplicateRelayAddresses(serverErrors: List): Set = serverErrors.mapNotNull { (it as? UserServersError.DuplicateChatRelayAddress)?.duplicateAddress }.toSet() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt index 8ed1d0910f..1449e0cd0d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt @@ -236,13 +236,12 @@ fun OperatorViewLayout( if (operator.enabled) { if (userServers.value[operatorIndex].chatRelays.any { !it.deleted }) { - val duplicateRelayNames = findDuplicateRelayNames(serverErrors.value) val duplicateRelayAddresses = findDuplicateRelayAddresses(serverErrors.value) SectionDividerSpaced() SectionView(generalGetString(MR.strings.chat_relays).uppercase()) { userServers.value[operatorIndex].chatRelays.forEachIndexed { index, relay -> if (!relay.deleted) { - ChatRelayViewLink(relay, duplicateRelayNames, duplicateRelayAddresses) { + ChatRelayViewLink(relay, duplicateRelayAddresses) { navigateToChatRelayView(userServers, serverErrors, serverWarnings, operatorIndex, index, relay, rhId) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt index 2f5a165eb5..b232c7994e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt @@ -85,12 +85,11 @@ fun YourServersViewLayout( Column { if (userServers.value[operatorIndex].chatRelays.any { !it.deleted }) { - val duplicateRelayNames = findDuplicateRelayNames(serverErrors.value) val duplicateRelayAddresses = findDuplicateRelayAddresses(serverErrors.value) SectionView(generalGetString(MR.strings.chat_relays).uppercase()) { userServers.value[operatorIndex].chatRelays.forEachIndexed { i, relay -> if (relay.deleted) return@forEachIndexed - ChatRelayViewLink(relay, duplicateRelayNames, duplicateRelayAddresses) { + ChatRelayViewLink(relay, duplicateRelayAddresses) { navigateToChatRelayView(userServers, serverErrors, serverWarnings, operatorIndex, i, relay, rhId) } } @@ -433,7 +432,7 @@ private suspend fun runRelaysTest(relays: List, onUpdated: (List< updatedRelays.add(index, relayState.value) onUpdated(updatedRelays.toList()) if (f != null) { - val name = relayState.value.name.ifEmpty { relayState.value.domains.firstOrNull() ?: relayState.value.address } + val name = relayState.value.displayName.ifEmpty { relayState.value.domains.firstOrNull() ?: relayState.value.address } fs[name] = f } } diff --git a/bots/api/TYPES.md b/bots/api/TYPES.md index 95d3c63509..c2303e9e6d 100644 --- a/bots/api/TYPES.md +++ b/bots/api/TYPES.md @@ -3184,7 +3184,10 @@ MsgBadSignature: ## RelayProfile **Record type**: -- name: string +- displayName: string +- fullName: string +- shortDescr: string? +- image: string? --- diff --git a/packages/simplex-chat-client/types/typescript/src/types.ts b/packages/simplex-chat-client/types/typescript/src/types.ts index 0b0d92b526..34b34ccbd5 100644 --- a/packages/simplex-chat-client/types/typescript/src/types.ts +++ b/packages/simplex-chat-client/types/typescript/src/types.ts @@ -3599,7 +3599,10 @@ export namespace RcvGroupEvent { } export interface RelayProfile { - name: string + displayName: string + fullName: string + shortDescr?: string + image?: string } export enum RelayStatus { diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 834839f1b9..7add9aad93 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1544,7 +1544,7 @@ processChatCommand vr nm = \case processChatCommand vr nm $ APISetUserServers userId $ L.map (updatedRelays relays') userServers where aUserRelay :: CLINewRelay -> AUserChatRelay - aUserRelay CLINewRelay {address, name} = AUCR SDBNew $ newChatRelay name [""] address + aUserRelay CLINewRelay {address, name} = AUCR SDBNew $ newChatRelay (relayProfileFromName name) [""] address APIGetServerOperators -> CRServerOperatorConditions <$> withFastStore getServerOperators APISetServerOperators operators -> do as <- asks randomAgentServers @@ -2202,7 +2202,7 @@ processChatCommand vr nm = \case CRContactsList user <$> withFastStore' (\db -> getUserContacts db vr user) ListContacts -> withUser $ \User {userId} -> processChatCommand vr nm $ APIListContacts userId - APICreateMyAddress userId -> withUserId userId $ \user@User {profile = LocalProfile {displayName}, userChatRelay} -> do + APICreateMyAddress userId -> withUserId userId $ \user@User {userChatRelay} -> do withFastStore' (\db -> runExceptT $ getUserAddress db user) >>= \case Left SEUserContactLinkNotFound -> pure () Left e -> throwError $ ChatErrorStore e @@ -2210,7 +2210,7 @@ processChatCommand vr nm = \case subMode <- chatReadVar subscriptionMode -- TODO [relays] relay: add identity, key to link data? let userData - | isTrue userChatRelay = encodeShortLinkData $ RelayAddressLinkData {relayProfile = RelayProfile {name = displayName}} + | isTrue userChatRelay = relayShortLinkData (userProfileDirect user Nothing Nothing True) | otherwise = contactShortLinkData (userProfileDirect user Nothing Nothing True) Nothing userLinkData = UserContactLinkData UserContactData {direct = True, owners = [], relays = [], userData} -- TODO [certs rcv] @@ -3583,11 +3583,13 @@ processChatCommand vr nm = \case fmap $ \SndMessage {msgId, msgBody} -> (conn, MsgFlags {notification = hasNotification XInfo_}, (vrValue msgBody, [msgId])) setMyAddressData :: User -> UserContactLink -> CM UserContactLink - setMyAddressData user ucl@UserContactLink {userContactLinkId, connLinkContact = CCLink connFullLink _sLnk_, addressSettings} = do + setMyAddressData user@User {userChatRelay} ucl@UserContactLink {userContactLinkId, connLinkContact = CCLink connFullLink _sLnk_, addressSettings} = do conn <- withFastStore $ \db -> getUserAddressConnection db vr user let shortLinkProfile = userProfileDirect user Nothing Nothing True -- TODO [short links] do not save address to server if data did not change, spinners, error handling - userData = contactShortLinkData shortLinkProfile $ Just addressSettings + userData + | isTrue userChatRelay = relayShortLinkData shortLinkProfile + | otherwise = contactShortLinkData shortLinkProfile $ Just addressSettings userLinkData = UserContactLinkData UserContactData {direct = True, owners = [], relays = [], userData} sLnk <- shortenShortLink' =<< withAgent (\a -> setConnShortLink a nm (aConnId conn) SCMContact userLinkData Nothing) withFastStore' $ \db -> setUserContactLinkShortLink db userContactLinkId sLnk @@ -4063,6 +4065,9 @@ processChatCommand vr nm = \case business = maybe False businessAddress settings contactData = ContactShortLinkData p msg business in encodeShortLinkData contactData + relayShortLinkData :: Profile -> UserLinkData + relayShortLinkData Profile {displayName, fullName, shortDescr, image} = + encodeShortLinkData $ RelayAddressLinkData {relayProfile = RelayProfile {displayName, fullName, shortDescr, image}} updatePCCShortLinkData :: PendingContactConnection -> Profile -> CM (Maybe ShortLinkInvitation) updatePCCShortLinkData conn@PendingContactConnection {connLinkInv} profile = forM (connShortLink =<< connLinkInv) $ \_ -> do diff --git a/src/Simplex/Chat/Operators.hs b/src/Simplex/Chat/Operators.hs index 3e7bc427e7..3bbfb02d0a 100644 --- a/src/Simplex/Chat/Operators.hs +++ b/src/Simplex/Chat/Operators.hs @@ -331,17 +331,17 @@ newUserServer_ :: Bool -> Bool -> ProtoServerWithAuth p -> NewUserServer p newUserServer_ preset enabled server = UserServer {serverId = DBNewEntity, server, preset, tested = Nothing, enabled, deleted = False} -presetChatRelay :: Bool -> Text -> [Text] -> ShortLinkContact -> NewUserChatRelay +presetChatRelay :: Bool -> RelayProfile -> [Text] -> ShortLinkContact -> NewUserChatRelay presetChatRelay = newChatRelay_ True {-# INLINE presetChatRelay #-} -newChatRelay :: Text -> [Text] -> ShortLinkContact -> NewUserChatRelay +newChatRelay :: RelayProfile -> [Text] -> ShortLinkContact -> NewUserChatRelay newChatRelay = newChatRelay_ False True {-# INLINE newChatRelay #-} -newChatRelay_ :: Bool -> Bool -> Text -> [Text] -> ShortLinkContact -> NewUserChatRelay -newChatRelay_ preset enabled name domains !address = - UserChatRelay {chatRelayId = DBNewEntity, address, relayProfile = RelayProfile {name}, domains, preset, tested = Nothing, enabled, deleted = False} +newChatRelay_ :: Bool -> Bool -> RelayProfile -> [Text] -> ShortLinkContact -> NewUserChatRelay +newChatRelay_ preset enabled relayProfile domains !address = + UserChatRelay {chatRelayId = DBNewEntity, address, relayProfile, domains, preset, tested = Nothing, enabled, deleted = False} -- This function should be used inside DB transaction to update conditions in the database -- it evaluates to (current conditions, and conditions to add) @@ -507,7 +507,6 @@ data UserServersError | USEStorageMissing {protocol :: AProtocolType, user :: Maybe User} | USEProxyMissing {protocol :: AProtocolType, user :: Maybe User} | USEDuplicateServer {protocol :: AProtocolType, duplicateServer :: Text, duplicateHost :: TransportHost} - | USEDuplicateChatRelayName {duplicateChatRelay :: Text} | USEDuplicateChatRelayAddress {duplicateChatRelay :: Text, duplicateAddress :: ShortLinkContact} deriving (Show) @@ -544,11 +543,8 @@ validateUserServers curr others = (currUserErrs <> concatMap otherUserErrs other chatRelayErrs uss = concatMap duplicateErrs_ cRelays where cRelays = filter (\(AUCR _ UserChatRelay {deleted}) -> not deleted) $ userChatRelays uss - duplicateErrs_ (AUCR _ UserChatRelay {relayProfile = RelayProfile {name}, address}) = - [USEDuplicateChatRelayName name | name `elem` duplicateNames] - <> [USEDuplicateChatRelayAddress name address | address `elem` duplicateAddresses] - duplicateNames = snd $ foldl' addDuplicate (S.empty, S.empty) allNames - allNames = map (\(AUCR _ UserChatRelay {relayProfile = RelayProfile {name}}) -> name) cRelays + duplicateErrs_ (AUCR _ UserChatRelay {relayProfile = RelayProfile {displayName}, address}) = + [USEDuplicateChatRelayAddress displayName address | address `elem` duplicateAddresses] duplicateAddresses = snd $ foldl' addAddress ([], []) allAddresses allAddresses = map (\(AUCR _ UserChatRelay {address}) -> address) cRelays addAddress :: ([ShortLinkContact], [ShortLinkContact]) -> ShortLinkContact -> ([ShortLinkContact], [ShortLinkContact]) diff --git a/src/Simplex/Chat/Operators/Presets.hs b/src/Simplex/Chat/Operators/Presets.hs index 682e0cff55..c51841d85c 100644 --- a/src/Simplex/Chat/Operators/Presets.hs +++ b/src/Simplex/Chat/Operators/Presets.hs @@ -8,6 +8,7 @@ module Simplex.Chat.Operators.Presets where import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as L import Simplex.Chat.Operators +import Simplex.Chat.Protocol (relayProfileFromName) import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..), allRoles) import Simplex.Messaging.Agent.Store.Entity import Simplex.Messaging.Encoding.String @@ -91,9 +92,9 @@ disabledSimplexChatSMPServers = -- TODO [relays] real chat relays simplexChatRelays :: [NewUserChatRelay] simplexChatRelays = - [ presetChatRelay True "chat_relay_1" ["simplex.im"] (either error id $ strDecode "https://smp111.simplex.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y"), - presetChatRelay True "chat_relay_2" ["simplex.im"] (either error id $ strDecode "https://smp222.simplex.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y"), - presetChatRelay True "chat_relay_3" ["simplex.im"] (either error id $ strDecode "https://smp333.simplex.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y") + [ presetChatRelay True (relayProfileFromName "chat_relay_1") ["simplex.im"] (either error id $ strDecode "https://smp111.simplex.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y"), + presetChatRelay True (relayProfileFromName "chat_relay_2") ["simplex.im"] (either error id $ strDecode "https://smp222.simplex.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y"), + presetChatRelay True (relayProfileFromName "chat_relay_3") ["simplex.im"] (either error id $ strDecode "https://smp333.simplex.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y") ] fluxSMPServers :: [NewUserServer 'PSMP] diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index a2ea54f39d..4657254f0c 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -1455,11 +1455,22 @@ data RelayShortLinkData = RelayShortLinkData $(JQ.deriveJSON defaultJSON ''RelayShortLinkData) -data RelayProfile = RelayProfile {name :: ContactName} +data RelayProfile = RelayProfile + { displayName :: ContactName, + fullName :: Text, + shortDescr :: Maybe Text, + image :: Maybe ImageData + } deriving (Eq, Show) $(JQ.deriveJSON defaultJSON ''RelayProfile) +toRelayProfile :: (ContactName, Text, Maybe Text, Maybe ImageData) -> RelayProfile +toRelayProfile (displayName, fullName, shortDescr, image) = RelayProfile {displayName, fullName, shortDescr, image} + +relayProfileFromName :: ContactName -> RelayProfile +relayProfileFromName displayName = RelayProfile {displayName, fullName = "", shortDescr = Nothing, image = Nothing} + data RelayAddressLinkData = RelayAddressLinkData {relayProfile :: RelayProfile} deriving (Show) diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 59dbd0f869..93fdf1868a 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -1329,21 +1329,21 @@ groupRelayQuery :: Query groupRelayQuery = [sql| SELECT gr.group_relay_id, gr.group_member_id, - cr.chat_relay_id, cr.address, cr.name, cr.domains, cr.preset, cr.tested, cr.enabled, cr.deleted, + cr.chat_relay_id, cr.address, cr.display_name, cr.full_name, cr.short_descr, cr.image, cr.domains, cr.preset, cr.tested, cr.enabled, cr.deleted, gr.relay_status, gr.relay_link FROM group_relays gr JOIN chat_relays cr ON cr.chat_relay_id = gr.chat_relay_id |] -toGroupRelay :: (Int64, GroupMemberId, DBEntityId, ShortLinkContact, Text, Text, BoolInt, Maybe BoolInt, BoolInt, BoolInt, RelayStatus, Maybe ShortLinkContact) -> GroupRelay -toGroupRelay (groupRelayId, groupMemberId, chatRelayId, address, name, domains, BI preset, tested, BI enabled, BI deleted, relayStatus, relayLink) = - let userChatRelay = UserChatRelay {chatRelayId, address, relayProfile = RelayProfile {name}, domains = T.splitOn "," domains, preset, tested = unBI <$> tested, enabled, deleted} +toGroupRelay :: (Int64, GroupMemberId, DBEntityId, ShortLinkContact, Text, Text, Maybe Text, Maybe ImageData, Text, BoolInt) :. (Maybe BoolInt, BoolInt, BoolInt, RelayStatus, Maybe ShortLinkContact) -> GroupRelay +toGroupRelay ((groupRelayId, groupMemberId, chatRelayId, address, displayName, fullName, shortDescr, image, domains, BI preset) :. (tested, BI enabled, BI deleted, relayStatus, relayLink)) = + let userChatRelay = UserChatRelay {chatRelayId, address, relayProfile = toRelayProfile (displayName, fullName, shortDescr, image), domains = T.splitOn "," domains, preset, tested = unBI <$> tested, enabled, deleted} in GroupRelay {groupRelayId, groupMemberId, userChatRelay, relayStatus, relayLink} 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 {relayProfile = RelayProfile {name}} = do +createRelayForOwner db vr gVar user@User {userId, userContactId} GroupInfo {groupId, membership} UserChatRelay {relayProfile = RelayProfile {displayName}} = do currentTs <- liftIO getCurrentTime - let relayProfile = profileFromName name + let relayProfile = profileFromName displayName (localDisplayName, memProfileId) <- createNewMemberProfile_ db user relayProfile currentTs groupMemberId <- createWithRandomId' db gVar $ \memId -> runExceptT $ do indexInGroup <- getUpdateNextIndexInGroup_ db groupId diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20260222_chat_relays.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20260222_chat_relays.hs index 135fc39eb2..da74c773f5 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/M20260222_chat_relays.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20260222_chat_relays.hs @@ -13,7 +13,10 @@ m20260222_chat_relays = CREATE TABLE chat_relays( chat_relay_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, address BYTEA NOT NULL, - name TEXT NOT NULL, + display_name TEXT NOT NULL, + full_name TEXT NOT NULL DEFAULT '', + short_descr TEXT, + image TEXT, domains TEXT NOT NULL, preset SMALLINT NOT NULL DEFAULT 0, tested SMALLINT, @@ -25,7 +28,6 @@ CREATE TABLE chat_relays( ); 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; @@ -111,7 +113,6 @@ DROP TABLE group_relays; 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 group_members diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql index b3ccca41d3..1e8809f168 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql @@ -363,7 +363,10 @@ 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, + display_name text NOT NULL, + full_name text DEFAULT ''::text NOT NULL, + short_descr text, + image text, domains text NOT NULL, preset smallint DEFAULT 0 NOT NULL, tested smallint, @@ -2027,10 +2030,6 @@ CREATE UNIQUE INDEX idx_chat_relays_user_id_address ON test_chat_schema.chat_rel -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); diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index 06443707fc..cff3e68234 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -626,15 +626,15 @@ getChatRelays db User {userId} = <$> DB.query db [sql| - SELECT chat_relay_id, address, name, domains, preset, tested, enabled + SELECT chat_relay_id, address, display_name, full_name, short_descr, image, domains, preset, tested, enabled FROM chat_relays WHERE user_id = ? AND deleted = 0 |] (Only userId) -toChatRelay :: (DBEntityId, ShortLinkContact, Text, Text, BoolInt, Maybe BoolInt, BoolInt) -> UserChatRelay -toChatRelay (chatRelayId, address, name, domains, BI preset, tested, BI enabled) = - UserChatRelay {chatRelayId, address, relayProfile = RelayProfile {name}, domains = T.splitOn "," domains, preset, tested = unBI <$> tested, enabled, deleted = False} +toChatRelay :: (DBEntityId, ShortLinkContact, Text, Text, Maybe Text, Maybe ImageData, Text, BoolInt, Maybe BoolInt, BoolInt) -> UserChatRelay +toChatRelay (chatRelayId, address, displayName, fullName, shortDescr, image, domains, BI preset, tested, BI enabled) = + UserChatRelay {chatRelayId, address, relayProfile = toRelayProfile (displayName, fullName, shortDescr, image), domains = T.splitOn "," domains, preset, tested = unBI <$> tested, enabled, deleted = False} getChatRelayById :: DB.Connection -> User -> Int64 -> ExceptT StoreError IO UserChatRelay getChatRelayById db User {userId} relayId = @@ -642,38 +642,38 @@ getChatRelayById db User {userId} relayId = DB.query db [sql| - SELECT chat_relay_id, address, name, domains, preset, tested, enabled + SELECT chat_relay_id, address, display_name, full_name, short_descr, image, domains, preset, tested, enabled FROM chat_relays WHERE user_id = ? AND chat_relay_id = ? AND deleted = 0 |] (userId, relayId) insertChatRelay :: DB.Connection -> User -> UTCTime -> NewUserChatRelay -> IO UserChatRelay -insertChatRelay db User {userId} ts relay@UserChatRelay {address, relayProfile = RelayProfile {name}, domains, preset, tested, enabled} = do +insertChatRelay db User {userId} ts relay@UserChatRelay {address, relayProfile = RelayProfile {displayName, fullName, shortDescr, image}, domains, preset, tested, enabled} = do crId <- fromOnly . head <$> DB.query db [sql| INSERT INTO chat_relays - (address, name, domains, preset, tested, enabled, user_id, created_at, updated_at) - VALUES (?,?,?,?,?,?,?,?,?) + (address, display_name, full_name, short_descr, image, domains, preset, tested, enabled, user_id, created_at, updated_at) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?) RETURNING chat_relay_id |] - (address, name, T.intercalate "," domains, BI preset, BI <$> tested, BI enabled, userId, ts, ts) + ((address, displayName, fullName, shortDescr, image, T.intercalate "," domains, BI preset, BI <$> tested, BI enabled, userId) :. (ts, ts)) pure (relay :: NewUserChatRelay) {chatRelayId = DBEntityId crId} updateChatRelay :: DB.Connection -> UTCTime -> UserChatRelay -> IO () -updateChatRelay db ts UserChatRelay {chatRelayId, address, relayProfile = RelayProfile {name}, domains, preset, tested, enabled} = +updateChatRelay db ts UserChatRelay {chatRelayId, address, relayProfile = RelayProfile {displayName, fullName, shortDescr, image}, domains, preset, tested, enabled} = DB.execute db [sql| UPDATE chat_relays - SET address = ?, name = ?, domains = ?, + SET address = ?, display_name = ?, full_name = ?, short_descr = ?, image = ?, domains = ?, preset = ?, tested = ?, enabled = ?, updated_at = ? WHERE chat_relay_id = ? |] - (address, name, T.intercalate "," domains, BI preset, BI <$> tested, BI enabled, ts, chatRelayId) + ((address, displayName, fullName, shortDescr, image, T.intercalate "," domains, BI preset, BI <$> tested, BI enabled, ts) :. Only chatRelayId) getServerOperators :: DB.Connection -> ExceptT StoreError IO ServerOperatorConditions getServerOperators db = do @@ -948,15 +948,15 @@ setUserServers' db user@User {userId} ts UpdatedUserOperatorServers {operator, s | otherwise -> Just relay <$ updateChatRelay db ts relay -- Un-delete soft-deleted relay, updating name and settings but keeping the address unchanged. undeleteRelay :: Int64 -> NewUserChatRelay -> IO () - undeleteRelay existingId UserChatRelay {relayProfile = RelayProfile {name = nm}, domains, preset, tested, enabled} = + undeleteRelay existingId UserChatRelay {relayProfile = RelayProfile {displayName, fullName, shortDescr, image}, domains, preset, tested, enabled} = DB.execute db [sql| UPDATE chat_relays - SET name = ?, domains = ?, + SET display_name = ?, full_name = ?, short_descr = ?, image = ?, domains = ?, preset = ?, tested = ?, enabled = ?, deleted = 0, updated_at = ? WHERE chat_relay_id = ? |] - (nm, T.intercalate "," domains, BI preset, BI <$> tested, BI enabled, ts, existingId) + (displayName, fullName, shortDescr, image, T.intercalate "," domains, BI preset, BI <$> tested, BI enabled, ts, existingId) createCall :: DB.Connection -> User -> Call -> UTCTime -> IO () createCall db user@User {userId} Call {contactId, callId, callUUID, chatItemId, callState} callTs = do diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20260222_chat_relays.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20260222_chat_relays.hs index e5e083efd0..a106e184d1 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/M20260222_chat_relays.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20260222_chat_relays.hs @@ -24,7 +24,10 @@ m20260222_chat_relays = CREATE TABLE chat_relays( chat_relay_id INTEGER PRIMARY KEY, address BLOB NOT NULL, - name TEXT NOT NULL, + display_name TEXT NOT NULL, + full_name TEXT NOT NULL DEFAULT '', + short_descr TEXT, + image TEXT, domains TEXT NOT NULL, preset INTEGER NOT NULL DEFAULT 0, tested INTEGER, @@ -36,7 +39,6 @@ CREATE TABLE chat_relays( ) STRICT; 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; @@ -116,7 +118,6 @@ DROP TABLE group_relays; 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 group_members DROP COLUMN relay_link; diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index c9a4c38de0..ef8c18234b 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -750,7 +750,10 @@ CREATE TABLE connections_sync( CREATE TABLE chat_relays( chat_relay_id INTEGER PRIMARY KEY, address BLOB NOT NULL, - name TEXT NOT NULL, + display_name TEXT NOT NULL, + full_name TEXT NOT NULL DEFAULT '', + short_descr TEXT, + image TEXT, domains TEXT NOT NULL, preset INTEGER NOT NULL DEFAULT 0, tested INTEGER, @@ -1270,7 +1273,6 @@ 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); 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 diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 87a796663c..6959d3e562 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -1579,7 +1579,7 @@ viewUserServers UserOperatorServers {operator, smpServers, xftpServers, chatRela [" Chat relays"] <> map (plain . (" " <>) . viewChatRelay) cRelays | otherwise = [] where - viewChatRelay UserChatRelay {relayProfile = RelayProfile {name}, address, preset, tested, enabled} = name <> relayAddress <> relayInfo + viewChatRelay UserChatRelay {relayProfile = RelayProfile {displayName, fullName, shortDescr}, address, preset, tested, enabled} = displayName <> optionalFullName displayName fullName shortDescr <> relayAddress <> relayInfo where relayAddress = ": " <> safeDecodeUtf8 (strEncode address) relayInfo = if null relayInfo_ then "" else parens $ T.intercalate ", " relayInfo_ @@ -1619,7 +1619,7 @@ viewRelayTestResult relayProfile_ = \case Just RelayTestFailure {rtfStep, rtfError} -> ["relay test failed at " <> plain (show rtfStep) <> ", error: " <> plain (show rtfError)] Nothing -> case relayProfile_ of - Just RelayProfile {name} -> ["relay test passed, profile: " <> plain (T.unpack name)] + Just RelayProfile {displayName, fullName, shortDescr} -> ["relay test passed, profile: " <> ttyFullName displayName fullName shortDescr] Nothing -> ["relay test passed"] viewServerOperators :: [ServerOperator] -> Maybe UsageConditionsAction -> [StyledString] diff --git a/tests/ChatTests/ChatRelays.hs b/tests/ChatTests/ChatRelays.hs index 9767539441..721d71d0e0 100644 --- a/tests/ChatTests/ChatRelays.hs +++ b/tests/ChatTests/ChatRelays.hs @@ -3,6 +3,7 @@ module ChatTests.ChatRelays where import ChatClient import ChatTests.DBUtils import ChatTests.Utils +import Control.Concurrent (threadDelay) import Test.Hspec hiding (it) chatRelayTests :: SpecWith TestParams @@ -12,6 +13,7 @@ chatRelayTests = do it "re-add soft-deleted relay by same address" testReAddRelaySameAddress it "re-add soft-deleted relay by same name" testReAddRelaySameName it "test chat relay" testChatRelayTest + it "relay profile updated in address" testRelayProfileUpdateInAddress testGetSetChatRelays :: HasCallStack => TestParams -> IO () testGetSetChatRelays ps = @@ -131,7 +133,7 @@ testChatRelayTest ps = -- Scenario 1: Happy path - test relay address succeeds alice ##> ("/relay test " <> bobSLink) - alice <## "relay test passed, profile: bob" + alice <## "relay test passed, profile: bob (Bob)" -- Scenario 2: Non-relay address - cath is not a relay user, -- her address has ContactShortLinkData, not RelayAddressLinkData @@ -145,6 +147,24 @@ testChatRelayTest ps = alice ##> ("/relay test " <> bobSLink) alice <##. "relay test failed at RTSGetLink, error: " +testRelayProfileUpdateInAddress :: HasCallStack => TestParams -> IO () +testRelayProfileUpdateInAddress ps = + withNewTestChat ps "alice" aliceProfile $ \alice -> + withNewTestChatOpts ps relayTestOpts "bob" bobProfile $ \bob -> do + bob ##> "/ad" + (bobSLink, _cLink) <- getContactLinks bob True + + alice ##> ("/relay test " <> bobSLink) + alice <## "relay test passed, profile: bob (Bob)" + + bob ##> "/p bob2 Bob relay" + bob <## "user profile is changed to bob2 (Bob relay) (your 0 contacts are notified)" + + threadDelay 100000 + + alice ##> ("/relay test " <> bobSLink) + alice <## "relay test passed, profile: bob2 (Bob relay)" + -- Create a public group with relay=1, wait for relay to join createChannelWithRelay :: HasCallStack => String -> TestCC -> TestCC -> IO () createChannelWithRelay gName owner relay = do diff --git a/tests/OperatorTests.hs b/tests/OperatorTests.hs index 044ee06023..ea125d1c0b 100644 --- a/tests/OperatorTests.hs +++ b/tests/OperatorTests.hs @@ -20,7 +20,7 @@ import Simplex.Chat import Simplex.Chat.Controller (ChatConfig (..), PresetServers (..)) import Simplex.Chat.Operators import Simplex.Chat.Operators.Presets -import Simplex.Chat.Protocol (RelayProfile (..)) +import Simplex.Chat.Protocol (RelayProfile (..), relayProfileFromName) import Simplex.Chat.Types import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..), allRoles) @@ -52,13 +52,8 @@ validateServersTest = describe "validate user servers" $ do ) it "should warn without chat relays" $ validateUserServers [invalidNoChatRelays] [] `shouldBe` ([], [USWNoChatRelays Nothing]) - it "should fail with duplicate chat relay name" $ do - validateUserServers [invalidDuplicateChatRelayName] [] - `shouldBe` ( [ USEDuplicateChatRelayName "chat_relay_1", - USEDuplicateChatRelayName "chat_relay_1" - ], - [] - ) + it "should allow duplicate chat relay name" $ + validateUserServers [duplicateChatRelayName] [] `shouldBe` ([], []) it "should fail with duplicate chat relay address" $ do validateUserServers [invalidDuplicateChatRelayAddress] [] `shouldBe` ( [ USEDuplicateChatRelayAddress "chat_relay_1" duplicateAddr, @@ -98,7 +93,7 @@ updatedServersTest = describe "validate user servers" $ do ( ops'', saveSrvs $ take 3 simplexChatSMPServers <> [newUserServer "smp://abcd@smp.example.im"], saveSrvs $ map (presetServer True) $ L.take 3 defaultXFTPServers, - saveRelays $ take 2 simplexChatRelays <> [newChatRelay "custom_relay" ["example.im"] customRelayAddr] + saveRelays $ take 2 simplexChatRelays <> [newChatRelay (relayProfileFromName "custom_relay") ["example.im"] customRelayAddr] ) [op1, op2, op3] <- pure $ map updatedUserServers uss [p1, p2] <- pure operators -- presets @@ -123,7 +118,7 @@ updatedServersTest = describe "validate user servers" $ do map chatRelayAddress presetRelays `shouldBe` map relayAddr' (chatRelays' op) srvHost' (AUS _ s) = srvHost s relayAddr' (AUCR _ r) = chatRelayAddress r - relayName' (AUCR _ UserChatRelay {relayProfile = RelayProfile {name}}) = name + relayName' (AUCR _ UserChatRelay {relayProfile = RelayProfile {displayName}}) = displayName PresetServers {operators} = presetServers defaultChatConfig customRelayAddr = either error id $ strDecode "https://relay.example.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y" @@ -172,16 +167,16 @@ invalidDuplicateSrv = invalidNoChatRelays :: UpdatedUserOperatorServers invalidNoChatRelays = (valid :: UpdatedUserOperatorServers) {chatRelays = []} -invalidDuplicateChatRelayName :: UpdatedUserOperatorServers -invalidDuplicateChatRelayName = +duplicateChatRelayName :: UpdatedUserOperatorServers +duplicateChatRelayName = (valid :: UpdatedUserOperatorServers) - { chatRelays = map (AUCR SDBNew) $ simplexChatRelays <> [presetChatRelay True "chat_relay_1" ["simplex.im"] (either error id $ strDecode "https://smp444.simplex.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y")] + { chatRelays = map (AUCR SDBNew) $ simplexChatRelays <> [presetChatRelay True (relayProfileFromName "chat_relay_1") ["simplex.im"] (either error id $ strDecode "https://smp444.simplex.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y")] } invalidDuplicateChatRelayAddress :: UpdatedUserOperatorServers invalidDuplicateChatRelayAddress = (valid :: UpdatedUserOperatorServers) - { chatRelays = map (AUCR SDBNew) $ simplexChatRelays <> [presetChatRelay True "chat_relay_4" ["simplex.im"] duplicateAddr] + { chatRelays = map (AUCR SDBNew) $ simplexChatRelays <> [presetChatRelay True (relayProfileFromName "chat_relay_4") ["simplex.im"] duplicateAddr] } duplicateAddr :: ShortLinkContact