diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 84275caf64..cf520a65c7 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2500,7 +2500,7 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable { } public var isContactCard: Bool { - (activeConn == nil || activeConn?.connStatus == .prepared) && profile.contactLink != nil && active && preparedContact == nil && contactRequestId == nil + (activeConn == nil || activeConn?.connStatus == .prepared) && profile.contactLink != nil && active && preparedContact == nil && contactRequestId == nil && groupDirectInv == nil } @inline(__always) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 05e9975daf..7e98be9604 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1931,7 +1931,7 @@ data class Contact( } val isContactCard: Boolean get() = - (activeConn == null || activeConn.connStatus == ConnStatus.Prepared) && profile.contactLink != null && active && preparedContact == null && contactRequestId == null + (activeConn == null || activeConn.connStatus == ConnStatus.Prepared) && profile.contactLink != null && active && preparedContact == null && contactRequestId == null && groupDirectInv == null val isBot: Boolean get() = profile.peerType == ChatPeerType.Bot diff --git a/bots/src/API/Docs/Types.hs b/bots/src/API/Docs/Types.hs index 0b1fa073b0..de38683d98 100644 --- a/bots/src/API/Docs/Types.hs +++ b/bots/src/API/Docs/Types.hs @@ -20,7 +20,7 @@ import Data.Char (isUpper, toLower, toUpper) import Data.List (find, mapAccumL, sortOn) import qualified Data.List.NonEmpty as L import qualified Data.Map.Strict as M -import Data.Maybe (fromMaybe) +import Data.Maybe (fromMaybe, mapMaybe) import qualified Data.Set as S import Data.Text (Text) import GHC.Generics @@ -177,11 +177,15 @@ normalizeConsName pfx consName -- making chatDir optional because clients use CIDirection? instead of CIQDirection (the type is replaced in Types.hs) ciQuoteType :: SumTypeInfo -ciQuoteType = - let st@(STI _ records) = sti @(CIQuote 'CTDirect) - optChatDir f@(FieldInfo n t) = if n == "chatDir" then FieldInfo n (TIOptional t) else f - updateRecord (RecordTypeInfo name fields) = RecordTypeInfo name $ map optChatDir fields - in st {recordTypes = map updateRecord records} -- need to map even though there is one constructor in this type +ciQuoteType = updateFields mkOptional $ sti @(CIQuote 'CTDirect) + where + mkOptional = map (\f@(FieldInfo n t) -> if n == "chatDir" then FieldInfo n (TIOptional t) else f) + +removeField :: String -> SumTypeInfo -> SumTypeInfo +removeField n = updateFields $ mapMaybe (\f@(FieldInfo n' _) -> if n == n' then Nothing else Just f) + +updateFields :: ([FieldInfo] -> [FieldInfo]) -> SumTypeInfo -> SumTypeInfo +updateFields f st@(STI _ records) = st {recordTypes = map (\(RecordTypeInfo name fields) -> RecordTypeInfo name $ f fields) records} -- JSON field names drop the "nr" prefix, as NameRecord's deriveJSON does nameRecordType :: SumTypeInfo @@ -310,7 +314,7 @@ chatTypesDocsData = (sti @GroupMemberSettings, STRecord, "", [], "", ""), (sti @GroupMemberStatus, STEnum' ((\case "group_deleted" -> "deleted"; "intro_invited" -> "intro-inv"; s -> s) . consSep "GSMem" '_'), "", [], "", ""), (sti @GroupPreference, STRecord, "", [], "", ""), - (sti @GroupPreferences, STRecord, "", [], "", ""), + (removeField "_json" $ sti @GroupPreferences, STRecord, "", [], "", ""), (sti @GroupProfile, STRecord, "", [], "", ""), (sti @GroupRelay, STRecord, "", [], "", ""), (sti @GroupShortLinkData, STRecord, "", [], "", ""), @@ -350,7 +354,7 @@ chatTypesDocsData = (sti @PendingContactConnection, STRecord, "", [], "", ""), (sti @PlanResolveMode, STEnum, "PRM", [], "", ""), (sti @PrefEnabled, STRecord, "", [], "", ""), - (sti @Preferences, STRecord, "", [], "", ""), + (removeField "_json" $ sti @Preferences, STRecord, "", [], "", ""), (sti @PreparedContact, STRecord, "", [], "", ""), (sti @GroupDirectInvitation, STRecord, "", [], "", ""), (sti @PreparedGroup, STRecord, "", [], "", ""), diff --git a/cabal.project b/cabal.project index 5e00d0764b..b8c73b14cf 100644 --- a/cabal.project +++ b/cabal.project @@ -21,7 +21,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 900c45ffaee5eb7eef8ec9402bc4971e4eb7ef33 + tag: 5294b7d8b7285a887b849b738e29bec207e5dde8 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 926d6e8860..a77e1d0c8c 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."900c45ffaee5eb7eef8ec9402bc4971e4eb7ef33" = "1iygb9hkc86ky553jg5apc8vfbjj0lghjaywikc0m7h1yk7zibxg"; + "https://github.com/simplex-chat/simplexmq.git"."5294b7d8b7285a887b849b738e29bec207e5dde8" = "0adjvg6mcyk936qzyl3a4daq68kslzpqhpdh369cf8wwcfqkiqg4"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 5497c9c9dc..a71ed7f748 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -167,6 +167,7 @@ library Simplex.Chat.Store.Postgres.Migrations.M20260904_file_badges Simplex.Chat.Store.Postgres.Migrations.M20260915_user_badges Simplex.Chat.Store.Postgres.Migrations.M20260918_badge_issue_errors + Simplex.Chat.Store.Postgres.Migrations.M20260923_preferences_json Simplex.Chat.Store.Postgres.Migrations.M20260924_simplex_name_resolved else exposed-modules: @@ -344,6 +345,7 @@ library Simplex.Chat.Store.SQLite.Migrations.M20260904_file_badges Simplex.Chat.Store.SQLite.Migrations.M20260915_user_badges Simplex.Chat.Store.SQLite.Migrations.M20260918_badge_issue_errors + Simplex.Chat.Store.SQLite.Migrations.M20260923_preferences_json Simplex.Chat.Store.SQLite.Migrations.M20260924_simplex_name_resolved other-modules: Paths_simplex_chat diff --git a/src/Simplex/Chat/Core.hs b/src/Simplex/Chat/Core.hs index f5f7f581df..433e2605fd 100644 --- a/src/Simplex/Chat/Core.hs +++ b/src/Simplex/Chat/Core.hs @@ -42,6 +42,7 @@ import System.Exit (exitFailure) import System.IO (hFlush, stdout) import Text.Read (readMaybe) import UnliftIO.Async +import UnliftIO.Exception (finally) simplexChatCore :: ChatConfig -> ChatOpts -> (User -> ChatController -> IO ()) -> IO () simplexChatCore cfg@ChatConfig {confirmMigrations, testView, chatHooks} opts@ChatOpts {coreOptions = coreOptions@CoreChatOpts {dbOptions, logAgent, yesToUpMigrations, migrationBackupPath, maintenance}, createBot, userDisplayName, userImageFile} chat = @@ -89,7 +90,7 @@ simplexChatCore cfg@ChatConfig {confirmMigrations, testView, chatHooks} opts@Cha runSimplexChat :: ChatConfig -> ChatOpts -> User -> ChatController -> (User -> ChatController -> IO ()) -> IO () runSimplexChat ChatConfig {testView} ChatOpts {coreOptions = CoreChatOpts {chatRelay, chatRelayServer, headless, maintenance}} u cc@ChatController {config = ChatConfig {chatHooks}} chat | maintenance = wait =<< async (chat u cc) - | otherwise = do + | otherwise = flip finally (stopChatController cc) $ do a1 <- runReaderT (startChatController True True False) cc when (chatRelay && not testView) $ askCreateRelayAddress cc u chatRelayServer headless forM_ (postStartHook chatHooks) ($ cc) diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 15f03c8b84..35d81fc905 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1722,7 +1722,7 @@ processChatCommand cxt nm = \case Just _ -> do let chatV = initialChatVersion subMode <- chatReadVar subscriptionMode - connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq PQSupportOff + (connId, _) <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq PQSupportOff conn@Connection {connId = testCId} <- withFastStore $ \db -> createRelayTestConnection db cxt user connId ConnPrepared chatV subMode challenge <- drgRandomBytes 32 @@ -2427,7 +2427,8 @@ processChatCommand cxt nm = \case g' <- withFastStore' $ \db -> setGroupDomainVerified db user g verified expiresAt pure $ CRGroupDomainVerified user g' reason APIConnectContactViaAddress userId incognito contactId -> withUserId userId $ \user -> do - ct@Contact {profile = LocalProfile {contactLink}} <- withFastStore $ \db -> getContact db cxt user contactId + ct@Contact {profile = LocalProfile {contactLink}, groupDirectInv} <- withFastStore $ \db -> getContact db cxt user contactId + when (isJust groupDirectInv) $ throwCmdError "contact is a member contact request" ccLink <- case contactLink of Just (CLFull cReq) -> pure $ CCLink cReq Nothing Just (CLShort sLnk) -> do @@ -2845,7 +2846,7 @@ processChatCommand cxt nm = \case dm <- encodeConnInfo $ XGrpAcpt membershipMemId (Just $ groupMemberKey gks) agentConnId <- case memberConn fromMember of Nothing -> do - agentConnId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True connRequest PQSupportOff + (agentConnId, _) <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True connRequest PQSupportOff let chatV = vr cxt `peerConnChatVersion` peerChatVRange void $ withFastStore' $ \db -> createMemberConnection db userId fromMember agentConnId chatV peerChatVRange subMode pure agentConnId @@ -3399,7 +3400,7 @@ processChatCommand cxt nm = \case -- possible improvement: use agent connRequestAgentVersion to determine pqSupport here; -- for joinPreparedConn below - same + encodeConnInfoPQ; -- same for auto-accept on xGrpDirectInv - acId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq PQSupportOff + (acId, _) <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq PQSupportOff conn <- withStore $ \db -> do connId <- liftIO $ createMemberContactConn db user acId Nothing gInfo mConn ConnPrepared contactId subMode getConnectionById db cxt user connId @@ -3758,7 +3759,7 @@ processChatCommand cxt nm = \case withMemberName gName mName cmd = withUser $ \user -> getGroupAndMemberId user gName mName >>= processChatCommand cxt nm . uncurry cmd getConnectionCode :: ConnId -> CM Text - getConnectionCode connId = verificationCode <$> withAgent (`getConnectionRatchetAdHash` connId) + getConnectionCode connId = verificationCode . codeAD <$> withAgent (`getConnectionVerifyCodes` connId) getChannelMemberCode :: GroupInfo -> GroupMember -> CM Text getChannelMemberCode GroupInfo {membership} m = case (memberPubKey membership, memberPubKey m) of @@ -3819,7 +3820,7 @@ processChatCommand cxt nm = \case joinNewConn chatV = do -- [incognito] generate profile to send incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing - connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup' + (connId, _) <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup' let ccLink = CCLink cReq $ serverShortLink <$> sLnk_ conn <- withFastStore' $ \db -> createDirectConnection' db userId connId ccLink contactId_ ConnPrepared incognitoProfile subMode chatV pqSup' joinPreparedConn conn incognitoProfile @@ -3874,13 +3875,13 @@ processChatCommand cxt nm = \case relayMemberId_ = case preparedEntity_ of Just (PCEGroup (GIK gInfo _) m) | useRelays' gInfo -> Just (memberId' m) _ -> Nothing - joinPreparedConn' xContactId_ conn@Connection {customUserProfileId} gInfo_ = do + joinPreparedConn' xContactId_ conn@Connection {connId, customUserProfileId} gInfo_ = do when (incognito /= isJust customUserProfileId) $ throwCmdError "incognito mode is different from prepared connection" -- TODO [relays] member: refactor joinContact and up avoiding parallel ifs, xContactId is not used xContactId <- mkXContactId xContactId_ - localIncognitoProfile <- forM customUserProfileId $ \pId -> withFastStore $ \db -> getProfileById db userId pId + (cReq', localIncognitoProfile) <- withFastStore $ \db -> (,) <$> getConnReqContact db connId <*> forM customUserProfileId (getProfileById db userId) let incognitoProfile = fromLocalProfile <$> localIncognitoProfile - conn' <- joinContact user conn cReq incognitoProfile xContactId welcomeSharedMsgId msg_ gInfo_ relayMemberId_ PQSupportOn + conn' <- joinContact user conn cReq' incognitoProfile xContactId welcomeSharedMsgId msg_ gInfo_ relayMemberId_ PQSupportOn pure $ CVRSentInvitation conn' incognitoProfile connect' groupLinkId xContactId_ gInfo_ = do let inGroup = isJust groupLinkId @@ -3913,13 +3914,13 @@ processChatCommand cxt nm = \case void $ joinContact user conn cReq incognitoProfile newXContactId Nothing Nothing Nothing Nothing pqSup ct' <- withStore $ \db -> getContact db cxt user contactId pure $ CRSentInvitationToContact user ct' incognitoProfile - Just conn@Connection {connStatus, xContactId = xContactId_, customUserProfileId} -> case connStatus of + Just conn@Connection {connId, connStatus, xContactId = xContactId_, customUserProfileId} -> case connStatus of ConnPrepared -> do when (incognito /= isJust customUserProfileId) $ throwCmdError "incognito mode is different from prepared connection" xContactId <- mkXContactId xContactId_ - localIncognitoProfile <- forM customUserProfileId $ \pId -> withFastStore $ \db -> getProfileById db userId pId + (cReq', localIncognitoProfile) <- withFastStore $ \db -> (,) <$> getConnReqContact db connId <*> forM customUserProfileId (getProfileById db userId) let incognitoProfile = fromLocalProfile <$> localIncognitoProfile - void $ joinContact user conn cReq incognitoProfile xContactId Nothing Nothing Nothing Nothing PQSupportOn + void $ joinContact user conn cReq' incognitoProfile xContactId Nothing Nothing Nothing Nothing PQSupportOn ct' <- withStore $ \db -> getContact db cxt user contactId pure $ CRSentInvitationToContact user ct' incognitoProfile _ -> throwCmdError "contact already has connection" @@ -3966,7 +3967,7 @@ processChatCommand cxt nm = \case Nothing -> throwChatError CEInvalidConnReq Just _ -> do let chatV = initialChatVersion - connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup + (connId, _) <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup pure (connId, chatV) mkXContactId :: Maybe XContactId -> CM XContactId mkXContactId = maybe (XContactId <$> drgRandomBytes 16) pure @@ -4305,7 +4306,7 @@ processChatCommand cxt nm = \case let chatV = initialChatVersion gVar <- asks random subMode <- chatReadVar subscriptionMode - connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq PQSupportOff + (connId, _) <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq PQSupportOff (relayMember, conn, groupRelay) <- withFastStore $ \db -> do relayMember <- createRelayForOwner db cxt gVar user gInfo relay groupRelay <- createGroupRelayRecord db gInfo relayMember relay @@ -4761,7 +4762,7 @@ processChatCommand cxt nm = \case SRDirect contactId -> do ct <- withFastStore $ \db -> getContact db cxt u contactId forM (contactConn ct) $ \conn -> - (CBDirect,) <$> withAgent (`getConnectionRatchetAdHash` aConnId conn) + (CBDirect,) . codeAD <$> withAgent (`getConnectionVerifyCodes` aConnId conn) SRGroup toGroupId _ asGroup -> do GroupInfo {groupProfile = GroupProfile {publicGroup}, membership = m} <- withFastStore $ \db -> getGroupInfo db cxt u toGroupId pure $ mkBinding m <$> publicGroup diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index 76e8687739..186e8e58d2 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -977,7 +977,7 @@ acceptContactRequest nm user@User {userId} UserContactRequest {agentInvitationId (ct, conn, incognitoProfile) <- case contactId_ of Nothing -> do incognitoProfile <- if incognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing - connId <- withAgent $ \a -> prepareConnectionToAccept a (aUserId user) True invId pqSup' + (connId, _) <- withAgent $ \a -> prepareConnectionToAccept a (aUserId user) True invId pqSup' (ct, conn) <- withStore' $ \db -> createContactFromRequest db user userContactLinkId_ connId chatV cReqChatVRange cName profileId cp xContactId incognitoProfile subMode pqSup' False pure (ct, conn, incognitoProfile) @@ -986,7 +986,7 @@ acceptContactRequest nm user@User {userId} UserContactRequest {agentInvitationId case contactConn ct of Nothing -> do incognitoProfile <- if incognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing - connId <- withAgent $ \a -> prepareConnectionToAccept a (aUserId user) True invId pqSup' + (connId, _) <- withAgent $ \a -> prepareConnectionToAccept a (aUserId user) True invId pqSup' currentTs <- liftIO getCurrentTime conn <- withStore' $ \db -> do forM_ xContactId $ \xcId -> setContactAcceptedXContactId db ct xcId @@ -2328,7 +2328,7 @@ type HistoryFile = (FileInvitation, RcvFileDescrText, Maybe UTCTime, Maybe Badge directChatBinding :: Contact -> CM (Maybe ByteString) directChatBinding ct = forM (contactConn ct) $ \conn -> - encodeChatBinding CBDirect <$> withAgent (`getConnectionRatchetAdHash` aConnId conn) + encodeChatBinding CBDirect . codeAD <$> withAgent (`getConnectionVerifyCodes` aConnId conn) rcvGroupChatBinding :: GroupInfo -> Maybe GroupMember -> ShowGroupAsSender -> Maybe BadgeProof -> Maybe ByteString rcvGroupChatBinding gInfo m_ asGroup badge_ = @@ -3028,7 +3028,7 @@ prepareAgentJoin user conn_ enableNtfs cReqUri = do cmdId <- withStore' $ \db -> createCommand db user (dbConnId <$> conn_) CFJoinConn connId <- case conn_ of Just conn -> pure $ aConnId conn - Nothing -> withAgent $ \a -> prepareConnectionToJoin a (aUserId user) enableNtfs cReqUri PQSupportOff + Nothing -> fst <$> withAgent (\a -> prepareConnectionToJoin a (aUserId user) enableNtfs cReqUri PQSupportOff) pure (cmdId, connId) joinAgentConnectionAsync :: CommandId -> Bool -> ConnId -> Bool -> ConnectionRequestUri c -> ConnInfo -> SubscriptionMode -> CM () @@ -3054,7 +3054,7 @@ allowAgentConnectionInfo user conn@Connection {connId} confId dm = do prepareAgentAccept :: User -> Bool -> InvitationId -> PQSupport -> CM (CommandId, ConnId) prepareAgentAccept user enableNtfs invId pqSup = do cmdId <- withStore' $ \db -> createCommand db user Nothing CFAcceptContact - connId <- withAgent $ \a -> prepareConnectionToAccept a (aUserId user) enableNtfs invId pqSup + (connId, _) <- withAgent $ \a -> prepareConnectionToAccept a (aUserId user) enableNtfs invId pqSup pure (cmdId, connId) agentAcceptContactAsync :: MsgEncodingI e => CommandId -> ConnId -> Bool -> InvitationId -> ChatMsgEvent e -> PQSupport -> SubscriptionMode -> CM () diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 2288763791..1dbf1adcb9 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -1390,7 +1390,7 @@ processAgentMessageConn cxt user@User {userId} entity gks_ corrId agentConnId ag processContactConnMessage :: AEvent e -> ConnectionEntity -> Connection -> UserContact -> CM () processContactConnMessage agentMsg connEntity conn UserContact {userContactLinkId = uclId, groupId = ucGroupId_} = case agentMsg of - REQ invId pqSupport _ connInfo rejectionSupported -> do + REQ invId pqSupport _ connInfo _ rejectionSupported -> do (signedMsg_, ChatMessage {chatVRange, chatMsgEvent}) <- parseChatMessage' conn connInfo case chatMsgEvent of XContact p memberKey_ xContactId_ welcomeMsgId_ requestMsg_ -> profileContactRequest invId chatVRange p memberKey_ xContactId_ welcomeMsgId_ requestMsg_ pqSupport rejectionSupported @@ -1908,7 +1908,7 @@ processAgentMessageConn cxt user@User {userId} entity gks_ corrId agentConnId ag keepSig <- case contactConn ct of Nothing -> pure False Just conn -> do - adHash <- withAgent (`getConnectionRatchetAdHash` aConnId conn) + adHash <- codeAD <$> withAgent (`getConnectionVerifyCodes` aConnId conn) pure $ encodeChatBinding CBDirect adHash == binding pure $ if keepSig then c else MCChat {text, chatLink, ownerSig = Nothing} _ -> pure c @@ -3877,7 +3877,7 @@ processAgentMessageConn cxt user@User {userId} entity gks_ corrId agentConnId ag securityCodeChanged mCt' createItems mCt' m | otherwise = do - acId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True connReq PQSupportOff + (acId, _) <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True connReq PQSupportOff mCt' <- withStore $ \db -> do updateMemberContactInvited db user mCt groupDirectInv void $ liftIO $ createMemberContactConn db user acId Nothing g mConn ConnPrepared mContactId subMode @@ -3898,7 +3898,7 @@ processAgentMessageConn cxt user@User {userId} entity gks_ corrId agentConnId ag createInternalChatItem user (CDDirectSnd mCt) CIChatBanner (Just epochStart) createItems mCt m' | otherwise = do - acId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True connReq PQSupportOff + (acId, _) <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True connReq PQSupportOff (mCt, m') <- withStore $ \db -> do (mContactId, m') <- liftIO $ createMemberContactInvited db user g m groupDirectInv void $ liftIO $ createMemberContactConn db user acId Nothing g mConn ConnPrepared mContactId subMode diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs index 1057a71a02..0867118cb6 100644 --- a/src/Simplex/Chat/Store/Connections.hs +++ b/src/Simplex/Chat/Store/Connections.hs @@ -37,6 +37,7 @@ import Simplex.Chat.Store.Direct import Simplex.Chat.Store.Groups import Simplex.Chat.Store.Shared import Simplex.Chat.Types +import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Protocol (ConnId) import Simplex.Messaging.Agent.Store.AgentStore (firstRow, firstRow', fromOnlyBI, maybeFirstRow) import Simplex.Messaging.Agent.Store.DB (BoolInt (..)) @@ -119,7 +120,7 @@ getConnectionEntityKeys db cxt user@User {userId, userContactId} agentConnId = d [sql| SELECT c.contact_profile_id, c.local_display_name, p.display_name, p.full_name, p.short_descr, p.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, c.contact_used, c.contact_status, c.enable_ntfs, c.send_rcpts, c.favorite, - p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.conn_full_link_to_connect, c.conn_short_link_to_connect, c.welcome_shared_msg_id, c.request_shared_msg_id, c.contact_request_id, cr2.rejection_supported, + p.preferences, p.preferences_json, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.conn_full_link_to_connect, c.conn_short_link_to_connect, c.welcome_shared_msg_id, c.request_shared_msg_id, c.contact_request_id, cr2.rejection_supported, c.contact_group_member_id, c.contact_grp_inv_sent, c.grp_direct_inv_link, c.grp_direct_inv_from_group_id, c.grp_direct_inv_from_group_member_id, c.grp_direct_inv_from_member_conn_id, c.grp_direct_inv_started_connection, c.ui_themes, c.chat_deleted, c.custom_data, c.chat_item_ttl, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, @@ -131,8 +132,9 @@ getConnectionEntityKeys db cxt user@User {userId, userContactId} agentConnId = d |] (userId, contactId, CSActive) toContact' :: UTCTime -> Int64 -> Connection -> [ChatTagId] -> ContactRow' -> Contact - toContact' currentTs contactId conn chatTags ((profileId, localDisplayName, displayName, fullName, shortDescr, description, image, contactLink, peerType, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. preparedContactRow :. (contactRequestId, rejectionSupported_, contactGroupMemberId, BI contactGrpInvSent) :. groupDirectInvRow :. (uiThemes, BI chatDeleted, customData, chatItemTTL) :. badgeRow :. domainRow) = - let profile = LocalProfile {profileId, displayName, fullName, shortDescr, description, image, contactLink, contactDomain = rowToContactDomain domainRow, contactDomainVerified = rowToDomainVerified domainRow, peerType, localBadge = rowToBadge currentTs badgeRow, preferences, localAlias} + toContact' currentTs contactId conn chatTags ((profileId, localDisplayName, displayName, fullName, shortDescr, description, image, contactLink, peerType, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, encodedPrefs, receivedPrefs, userPreferences, createdAt, updatedAt, chatTs) :. preparedContactRow :. (contactRequestId, rejectionSupported_, contactGroupMemberId, BI contactGrpInvSent) :. groupDirectInvRow :. (uiThemes, BI chatDeleted, customData, chatItemTTL) :. badgeRow :. domainRow) = + let preferences = chatPrefsFromRow encodedPrefs receivedPrefs + profile = LocalProfile {profileId, displayName, fullName, shortDescr, description, image, contactLink, contactDomain = rowToContactDomain domainRow, contactDomainVerified = rowToDomainVerified domainRow, peerType, localBadge = rowToBadge currentTs badgeRow, preferences, localAlias} chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite} mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn activeConn = Just conn @@ -153,7 +155,7 @@ getConnectionEntityKeys db cxt user@User {userId, userContactId} agentConnId = d -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_type, gp.group_link, gp.public_group_id, gp.group_web_page, gp.group_domain, gp.domain_web_page, gp.allow_embedding, gp.group_domain_proof, - g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, + g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.preferences_json, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, @@ -164,13 +166,13 @@ getConnectionEntityKeys db cxt user@User {userId, userContactId} agentConnId = d mu.group_member_id, mu.group_id, mu.index_in_group, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, -- GroupInfo {membership = GroupMember {memberProfile}} - pu.display_name, pu.full_name, pu.short_descr, pu.description, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.short_descr, pu.description, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences, pu.preferences_json, pu.badge_proof, pu.badge_pres_header, pu.badge_expiry, pu.badge_type, pu.badge_verified, pu.badge_extra, pu.badge_master_key, pu.badge_signature, pu.badge_key_idx, pu.contact_domain, pu.contact_domain_proof, pu.contact_domain_verified, 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.member_pub_key, mu.relay_link, mu.member_security_code, mu.member_security_code_verified_at, -- from GroupMember m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at diff --git a/src/Simplex/Chat/Store/ContactRequest.hs b/src/Simplex/Chat/Store/ContactRequest.hs index f87a1e242a..5ef9cbe723 100644 --- a/src/Simplex/Chat/Store/ContactRequest.hs +++ b/src/Simplex/Chat/Store/ContactRequest.hs @@ -116,7 +116,7 @@ createOrUpdateContactRequest SELECT -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, cp.display_name, cp.full_name, cp.short_descr, cp.description, cp.image, cp.contact_link, cp.chat_peer_type, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, - cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, cr2.rejection_supported, + cp.preferences, cp.preferences_json, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, cr2.rejection_supported, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.grp_direct_inv_link, ct.grp_direct_inv_from_group_id, ct.grp_direct_inv_from_group_member_id, ct.grp_direct_inv_from_member_conn_id, ct.grp_direct_inv_started_connection, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, cp.badge_proof, cp.badge_pres_header, cp.badge_expiry, cp.badge_type, cp.badge_verified, cp.badge_extra, cp.badge_master_key, cp.badge_signature, cp.badge_key_idx, cp.contact_domain, cp.contact_domain_proof, cp.contact_domain_verified, @@ -153,7 +153,7 @@ createOrUpdateContactRequest cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.business_group_id, cr.user_contact_link_id, cr.rejection_supported, cr.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, cr.xcontact_id, - cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, + cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, p.preferences_json, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified @@ -172,8 +172,8 @@ createOrUpdateContactRequest liftIO $ DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, short_descr, description, image, contact_link, user_id, local_alias, preferences, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified, badge_extra, badge_master_key, badge_signature, badge_key_idx) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" - ((displayName, fullName, shortDescr, description, image, contactLink, userId) :. ("" :: LocalAlias, preferences, currentTs, currentTs) :. badgeToRow badge badgeVerified) + "INSERT INTO contact_profiles (display_name, full_name, short_descr, description, image, contact_link, user_id, local_alias, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified, badge_extra, badge_master_key, badge_signature, badge_key_idx, preferences, preferences_json) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + ((displayName, fullName, shortDescr, description, image, contactLink, userId) :. ("" :: LocalAlias, currentTs, currentTs) :. badgeToRow badge badgeVerified :. prefsToRow preferences) profileId <- liftIO $ insertedRowId db liftIO $ DB.execute diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index 2368107f2d..145eeeed07 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -323,7 +323,7 @@ getContactByConnReqHash db cxt user@User {userId} cReqHash1 cReqHash2 = do SELECT -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, cp.display_name, cp.full_name, cp.short_descr, cp.description, cp.image, cp.contact_link, cp.chat_peer_type, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, - cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, cr2.rejection_supported, + cp.preferences, cp.preferences_json, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, cr2.rejection_supported, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.grp_direct_inv_link, ct.grp_direct_inv_from_group_id, ct.grp_direct_inv_from_group_member_id, ct.grp_direct_inv_from_member_conn_id, ct.grp_direct_inv_started_connection, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, cp.badge_proof, cp.badge_pres_header, cp.badge_expiry, cp.badge_type, cp.badge_verified, cp.badge_extra, cp.badge_master_key, cp.badge_signature, cp.badge_key_idx, @@ -747,12 +747,13 @@ updateContactProfile_' db userId profileId Profile {displayName, fullName, short db [sql| UPDATE contact_profiles - SET display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, contact_link = ?, preferences = ?, chat_peer_type = ?, updated_at = ?, + SET preferences = ?, preferences_json = ?, + display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, contact_link = ?, chat_peer_type = ?, updated_at = ?, badge_proof = ?, badge_pres_header = ?, badge_expiry = ?, badge_type = ?, badge_verified = ?, badge_extra = ?, badge_master_key = ?, badge_signature = ?, badge_key_idx = ?, contact_domain = ?, contact_domain_proof = ? WHERE user_id = ? AND contact_profile_id = ? |] - ((displayName, fullName, shortDescr, description, image, contactLink, preferences, peerType, updatedAt) :. badgeToRow badge badgeVerified :. contactDomainToRow contactDomain :. (userId, profileId)) + (prefsToRow preferences :. (displayName, fullName, shortDescr, description, image, contactLink, peerType, updatedAt) :. badgeToRow badge badgeVerified :. contactDomainToRow contactDomain :. (userId, profileId)) -- update only member profile fields (when member doesn't have associated contact - we can reset contactLink and prefs) updateMemberContactProfileReset_ :: DB.Connection -> UserId -> ProfileId -> Profile -> Maybe Bool -> IO () @@ -766,7 +767,7 @@ updateMemberContactProfileReset_' db userId profileId Profile {displayName, full db [sql| UPDATE contact_profiles - SET display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, contact_link = NULL, preferences = NULL, updated_at = ?, + SET display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, contact_link = NULL, preferences = NULL, preferences_json = NULL, updated_at = ?, badge_proof = ?, badge_pres_header = ?, badge_expiry = ?, badge_type = ?, badge_verified = ?, badge_extra = ?, badge_master_key = ?, badge_signature = ?, badge_key_idx = ?, contact_domain = ?, contact_domain_proof = ? WHERE user_id = ? AND contact_profile_id = ? @@ -861,7 +862,7 @@ contactRequestQuery = cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.business_group_id, cr.user_contact_link_id, cr.rejection_supported, cr.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, cr.xcontact_id, - cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, + cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, p.preferences_json, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, @@ -983,7 +984,7 @@ getContact_ db cxt user@User {userId} contactId deleted = do SELECT -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, cp.display_name, cp.full_name, cp.short_descr, cp.description, cp.image, cp.contact_link, cp.chat_peer_type, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, - cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, cr2.rejection_supported, + cp.preferences, cp.preferences_json, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, cr2.rejection_supported, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.grp_direct_inv_link, ct.grp_direct_inv_from_group_id, ct.grp_direct_inv_from_group_member_id, ct.grp_direct_inv_from_member_conn_id, ct.grp_direct_inv_started_connection, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, cp.badge_proof, cp.badge_pres_header, cp.badge_expiry, cp.badge_type, cp.badge_verified, cp.badge_extra, cp.badge_master_key, cp.badge_signature, cp.badge_key_idx, diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 9c252ececf..a44b4c9480 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -217,7 +217,7 @@ import Control.Monad import Control.Monad.Except import Control.Monad.IO.Class import Crypto.Random (ChaChaDRG) -import Data.Bifunctor (first, second) +import Data.Bifunctor (first) import Data.ByteString (ByteString) import qualified Data.ByteString as B import Data.Char (toLower) @@ -264,11 +264,11 @@ import Database.SQLite.Simple (Only (..), Query, (:.) (..)) import Database.SQLite.Simple.QQ (sql) #endif -type MaybeGroupMemberRow = (Maybe GroupMemberId, Maybe GroupId, 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 Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, Maybe LocalAlias, Maybe Preferences) :. BadgeRow :. ContactDomainRow) :. (Maybe UTCTime, Maybe UTCTime) :. (Maybe UTCTime, Maybe Int64, Maybe Int64, Maybe Int64, Maybe UTCTime, Maybe C.PublicKeyEd25519, Maybe ShortLinkContact, Maybe Text, Maybe UTCTime) +type MaybeGroupMemberRow = (Maybe GroupMemberId, Maybe GroupId, 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 Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, Maybe LocalAlias, Maybe Text, Maybe Text) :. BadgeRow :. ContactDomainRow) :. (Maybe UTCTime, Maybe UTCTime) :. (Maybe UTCTime, Maybe Int64, Maybe Int64, Maybe Int64, Maybe UTCTime, Maybe C.PublicKeyEd25519, Maybe ShortLinkContact, Maybe Text, Maybe UTCTime) toMaybeGroupMember :: UTCTime -> Int64 -> MaybeGroupMemberRow -> Maybe GroupMember -toMaybeGroupMember now userContactId ((Just groupMemberId, Just groupId, Just indexInGroup, 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, description, image, contactLink, peerType, Just localAlias, contactPreferences) :. badgeRow :. domainRow) :. (Just createdAt, Just updatedAt) :. (supportChatTs, Just supportChatUnread, Just supportChatUnanswered, Just supportChatMentions, supportChatLastMsgFromMemberTs, memberPubKey, relayLink, memberCode_, memberCodeVerifiedAt_)) = - Just $ toGroupMember now userContactId ((groupMemberId, groupId, indexInGroup, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberBlocked') :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId) :. ((profileId, displayName, fullName, shortDescr, description, image, contactLink, peerType, localAlias, contactPreferences) :. badgeRow :. domainRow) :. (createdAt, updatedAt) :. (supportChatTs, supportChatUnread, supportChatUnanswered, supportChatMentions, supportChatLastMsgFromMemberTs, memberPubKey, relayLink, memberCode_, memberCodeVerifiedAt_)) +toMaybeGroupMember now userContactId ((Just groupMemberId, Just groupId, Just indexInGroup, 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, description, image, contactLink, peerType, Just localAlias, encodedPrefs, receivedPrefs) :. badgeRow :. domainRow) :. (Just createdAt, Just updatedAt) :. (supportChatTs, Just supportChatUnread, Just supportChatUnanswered, Just supportChatMentions, supportChatLastMsgFromMemberTs, memberPubKey, relayLink, memberCode_, memberCodeVerifiedAt_)) = + Just $ toGroupMember now userContactId ((groupMemberId, groupId, indexInGroup, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberBlocked') :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId) :. ((profileId, displayName, fullName, shortDescr, description, image, contactLink, peerType, localAlias, encodedPrefs, receivedPrefs) :. badgeRow :. domainRow) :. (createdAt, updatedAt) :. (supportChatTs, supportChatUnread, supportChatUnanswered, supportChatMentions, supportChatLastMsgFromMemberTs, memberPubKey, relayLink, memberCode_, memberCodeVerifiedAt_)) toMaybeGroupMember _ _ _ = Nothing createGroupLink :: DB.Connection -> TVar ChaChaDRG -> User -> GroupInfo -> ConnId -> CreatedLinkContact -> GroupLinkId -> GroupMemberRole -> SubscriptionMode -> ExceptT StoreError IO GroupLink @@ -404,11 +404,11 @@ createNewGroup db cxt user@User {userId} groupProfile incognitoProfile memberId (display_name, full_name, short_descr, description, image, group_type, group_link, public_group_id, group_web_page, group_domain, domain_web_page, allow_embedding, group_domain_proof, - user_id, preferences, member_admission, created_at, updated_at) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) + user_id, member_admission, created_at, updated_at, preferences, preferences_json) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] ((displayName, fullName, shortDescr, description, image, groupType_, groupLink_, publicGroupId_) :. publicGroupAccessRow publicGroup - :. (userId, groupPreferences, memberAdmission, currentTs, currentTs)) + :. (userId, memberAdmission, currentTs, currentTs) :. prefsToRow groupPreferences) profileId <- insertedRowId db DB.execute db @@ -487,8 +487,8 @@ createGroupInvitation db cxt user@User {userId} contact@Contact {contactId, acti groupId <- liftIO $ do DB.execute db - "INSERT INTO group_profiles (display_name, full_name, short_descr, description, image, user_id, preferences, member_admission, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?)" - (displayName, fullName, shortDescr, description, image, userId, groupPreferences, memberAdmission, currentTs, currentTs) + "INSERT INTO group_profiles (display_name, full_name, short_descr, description, image, user_id, member_admission, created_at, updated_at, preferences, preferences_json) VALUES (?,?,?,?,?,?,?,?,?,?,?)" + ((displayName, fullName, shortDescr, description, image, userId, memberAdmission, currentTs, currentTs) :. prefsToRow groupPreferences) profileId <- insertedRowId db DB.execute db @@ -918,11 +918,11 @@ createGroup_ db userId groupProfile prepared business useRelays relayOwnStatus p (display_name, full_name, short_descr, description, image, group_type, group_link, public_group_id, group_web_page, group_domain, domain_web_page, allow_embedding, group_domain_proof, - user_id, preferences, member_admission, created_at, updated_at) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) + user_id, member_admission, created_at, updated_at, preferences, preferences_json) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] ((displayName, fullName, shortDescr, description, image, groupType_, groupLink_, publicGroupId_) :. publicGroupAccessRow publicGroup - :. (userId, groupPreferences, memberAdmission, currentTs, currentTs)) + :. (userId, memberAdmission, currentTs, currentTs) :. prefsToRow groupPreferences) profileId <- insertedRowId db DB.execute db @@ -1072,11 +1072,11 @@ getBaseGroupDetails db cxt User {userId, userContactId} _contactId_ search_ = do getContactGroupPreferences :: DB.Connection -> User -> Contact -> IO [(GroupMemberRole, FullGroupPreferences)] getContactGroupPreferences db User {userId} Contact {contactId} = do - map (second mergeGroupPreferences) + map (\(role, encodedPrefs, receivedPrefs) -> (role, mergeGroupPreferences $ groupPrefsFromRow encodedPrefs receivedPrefs)) <$> DB.query db [sql| - SELECT m.member_role, gp.preferences + SELECT m.member_role, gp.preferences, gp.preferences_json FROM groups g JOIN group_profiles gp USING (group_profile_id) JOIN group_members m USING (group_id) @@ -2088,8 +2088,8 @@ createJoiningMember liftIO $ DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, short_descr, description, image, contact_link, user_id, preferences, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified, badge_extra, badge_master_key, badge_signature, badge_key_idx) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" - ((displayName, fullName, shortDescr, description, image, contactLink, userId, preferences, currentTs, currentTs) :. badgeToRow badge badgeVerified) + "INSERT INTO contact_profiles (display_name, full_name, short_descr, description, image, contact_link, user_id, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified, badge_extra, badge_master_key, badge_signature, badge_key_idx, preferences, preferences_json) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + ((displayName, fullName, shortDescr, description, image, contactLink, userId, currentTs, currentTs) :. badgeToRow badge badgeVerified :. prefsToRow preferences) profileId <- liftIO $ insertedRowId db case cReqMemberId_ of Just memberId -> do @@ -2170,8 +2170,8 @@ createBusinessRequestGroup liftIO $ DB.execute db - "INSERT INTO group_profiles (display_name, full_name, short_descr, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)" - (displayName, fullName, shortDescr, image, userId, groupPreferences, currentTs, currentTs) + "INSERT INTO group_profiles (display_name, full_name, short_descr, image, user_id, created_at, updated_at, preferences, preferences_json) VALUES (?,?,?,?,?,?,?,?,?)" + ((displayName, fullName, shortDescr, image, userId, currentTs, currentTs) :. prefsToRow (Just groupPreferences)) groupProfileId <- liftIO $ insertedRowId db liftIO $ DB.execute @@ -2441,8 +2441,8 @@ createNewMemberProfile_ db cxt User {userId} Profile {displayName, fullName, sho badgeVerified <- verifyBadge_ (badgeKeys cxt) badge DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, short_descr, description, image, contact_link, user_id, preferences, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified, badge_extra, badge_master_key, badge_signature, badge_key_idx) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" - ((displayName, fullName, shortDescr, description, image, contactLink, userId, preferences, createdAt, createdAt) :. badgeToRow badge badgeVerified) + "INSERT INTO contact_profiles (display_name, full_name, short_descr, description, image, contact_link, user_id, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified, badge_extra, badge_master_key, badge_signature, badge_key_idx, preferences, preferences_json) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + ((displayName, fullName, shortDescr, description, image, contactLink, userId, createdAt, createdAt) :. badgeToRow badge badgeVerified :. prefsToRow preferences) profileId <- insertedRowId db pure $ Right (ldn, profileId, badgeVerified) @@ -2720,19 +2720,21 @@ updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName, db [sql| UPDATE group_profiles - SET display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, + SET preferences = ?, preferences_json = ?, + display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, group_type = ?, group_link = ?, group_web_page = ?, group_domain = CASE WHEN ? THEN ? ELSE group_domain END, domain_web_page = ?, allow_embedding = ?, group_domain_proof = ?, - preferences = ?, member_admission = ?, updated_at = ? + member_admission = ?, updated_at = ? WHERE group_profile_id IN ( SELECT group_profile_id FROM groups WHERE user_id = ? AND group_id = ? ) |] - ( (newName, fullName, shortDescr, description, image, groupType_, groupLink_) + ( prefsToRow groupPreferences + :. (newName, fullName, shortDescr, description, image, groupType_, groupLink_) :. (groupWebPage_, isJust publicGroup, groupDomain_, domainWebPage_, allowEmbedding_, groupDomainProof_) - :. (groupPreferences, memberAdmission, currentTs, userId, groupId) + :. (memberAdmission, currentTs, userId, groupId) ) updateGroup_ ldn currentTs = do DB.execute @@ -2778,14 +2780,14 @@ updateGroupPreferences db User {userId} g@GroupInfo {groupId, groupProfile = p} db [sql| UPDATE group_profiles - SET preferences = ?, updated_at = ? + SET preferences = ?, preferences_json = ?, updated_at = ? WHERE group_profile_id IN ( SELECT group_profile_id FROM groups WHERE user_id = ? AND group_id = ? ) |] - (ps, currentTs, userId, groupId) + (prefsToRow (Just ps) :. (currentTs, userId, groupId)) pure (g :: GroupInfo) {groupProfile = p {groupPreferences = Just ps}, fullGroupPreferences = mergeGroupPreferences $ Just ps} updateGroupProfileFromMember :: DB.Connection -> User -> GroupInfo -> Profile -> ExceptT StoreError IO GroupInfo @@ -2805,15 +2807,16 @@ getGroupProfileById db groupId = SELECT gp.display_name, gp.full_name, gp.short_descr, gp.description, gp.image, gp.group_type, gp.group_link, gp.public_group_id, gp.group_web_page, gp.group_domain, gp.domain_web_page, gp.allow_embedding, gp.group_domain_proof, - gp.preferences, gp.member_admission + gp.preferences, gp.preferences_json, gp.member_admission FROM group_profiles gp JOIN groups g ON gp.group_profile_id = g.group_profile_id WHERE g.group_id = ? |] (Only groupId) where - toGroupProfile ((displayName, fullName, shortDescr, description, image, groupType_, groupLink_, publicGroupId_) :. accessRow :. (groupPreferences, memberAdmission)) = - let publicGroupAccess = toPublicGroupAccess accessRow + toGroupProfile ((displayName, fullName, shortDescr, description, image, groupType_, groupLink_, publicGroupId_) :. accessRow :. (encodedPrefs, receivedPrefs, memberAdmission)) = + let groupPreferences = groupPrefsFromRow encodedPrefs receivedPrefs + publicGroupAccess = toPublicGroupAccess accessRow in GroupProfile {displayName, fullName, shortDescr, description, image, publicGroup = toPublicGroupProfile groupType_ groupLink_ publicGroupId_ publicGroupAccess, groupPreferences, memberAdmission} getGroupInfoByUserContactLinkConnReq :: DB.Connection -> StoreCxt -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe GroupInfo) diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 3998e1f249..b8909da1b3 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -725,7 +725,7 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe -- GroupMember m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + p.display_name, p.full_name, p.short_descr, p.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at @@ -1148,7 +1148,7 @@ getContactRequestChatPreviews_ db User {userId} pagination clq = do cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.business_group_id, cr.user_contact_link_id, cr.rejection_supported, cr.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, cr.xcontact_id, - cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, + cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, p.preferences_json, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified @@ -3110,7 +3110,7 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do -- GroupMember m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + p.display_name, p.full_name, p.short_descr, p.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at, @@ -3119,14 +3119,14 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do -- quoted GroupMember rm.group_member_id, rm.group_id, rm.index_in_group, rm.member_id, rm.peer_chat_min_version, rm.peer_chat_max_version, rm.member_role, rm.member_category, rm.member_status, rm.show_messages, rm.member_restriction, rm.invited_by, rm.invited_by_group_member_id, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id, - rp.display_name, rp.full_name, rp.short_descr, rp.description, rp.image, rp.contact_link, rp.chat_peer_type, rp.local_alias, rp.preferences, + rp.display_name, rp.full_name, rp.short_descr, rp.description, rp.image, rp.contact_link, rp.chat_peer_type, rp.local_alias, rp.preferences, rp.preferences_json, rp.badge_proof, rp.badge_pres_header, rp.badge_expiry, rp.badge_type, rp.badge_verified, rp.badge_extra, rp.badge_master_key, rp.badge_signature, rp.badge_key_idx, rp.contact_domain, rp.contact_domain_proof, rp.contact_domain_verified, 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.member_pub_key, rm.relay_link, rm.member_security_code, rm.member_security_code_verified_at, -- deleted by GroupMember dbm.group_member_id, dbm.group_id, dbm.index_in_group, 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.description, dbp.image, dbp.contact_link, dbp.chat_peer_type, dbp.local_alias, dbp.preferences, + dbp.display_name, dbp.full_name, dbp.short_descr, dbp.description, dbp.image, dbp.contact_link, dbp.chat_peer_type, dbp.local_alias, dbp.preferences, dbp.preferences_json, dbp.badge_proof, dbp.badge_pres_header, dbp.badge_expiry, dbp.badge_type, dbp.badge_verified, dbp.badge_extra, dbp.badge_master_key, dbp.badge_signature, dbp.badge_key_idx, dbp.contact_domain, dbp.contact_domain_proof, dbp.contact_domain_verified, 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.member_pub_key, dbm.relay_link, dbm.member_security_code, dbm.member_security_code_verified_at diff --git a/src/Simplex/Chat/Store/Postgres/Migrations.hs b/src/Simplex/Chat/Store/Postgres/Migrations.hs index 2d351f4bec..5aa1f3321d 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations.hs @@ -52,6 +52,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20260828_file_expiry import Simplex.Chat.Store.Postgres.Migrations.M20260904_file_badges import Simplex.Chat.Store.Postgres.Migrations.M20260915_user_badges import Simplex.Chat.Store.Postgres.Migrations.M20260918_badge_issue_errors +import Simplex.Chat.Store.Postgres.Migrations.M20260923_preferences_json import Simplex.Chat.Store.Postgres.Migrations.M20260924_simplex_name_resolved import Simplex.Messaging.Agent.Store.Shared (Migration (..)) @@ -105,6 +106,7 @@ schemaMigrations = ("20260904_file_badges", m20260904_file_badges, Just down_m20260904_file_badges), ("20260915_user_badges", m20260915_user_badges, Just down_m20260915_user_badges), ("20260918_badge_issue_errors", m20260918_badge_issue_errors, Just down_m20260918_badge_issue_errors), + ("20260923_preferences_json", m20260923_preferences_json, Just down_m20260923_preferences_json), ("20260924_simplex_name_resolved", m20260924_simplex_name_resolved, Just down_m20260924_simplex_name_resolved) ] diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20260923_preferences_json.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20260923_preferences_json.hs new file mode 100644 index 0000000000..81039139e8 --- /dev/null +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20260923_preferences_json.hs @@ -0,0 +1,21 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.Postgres.Migrations.M20260923_preferences_json where + +import Data.Text (Text) +import Text.RawString.QQ (r) + +m20260923_preferences_json :: Text +m20260923_preferences_json = + [r| +ALTER TABLE contact_profiles ADD COLUMN preferences_json TEXT; +ALTER TABLE group_profiles ADD COLUMN preferences_json TEXT; +|] + +down_m20260923_preferences_json :: Text +down_m20260923_preferences_json = + [r| +ALTER TABLE group_profiles DROP COLUMN preferences_json; +ALTER TABLE contact_profiles DROP COLUMN preferences_json; +|] diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql index 823ea602de..1928f7de90 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql @@ -681,6 +681,7 @@ CREATE TABLE test_chat_schema.contact_profiles ( contact_domain_proof text, contact_domain_verified smallint, description text, + preferences_json text, contact_domain_resolved_at timestamp with time zone, contact_domain_expires_at timestamp with time zone ); @@ -1044,7 +1045,8 @@ CREATE TABLE test_chat_schema.group_profiles ( group_domain text, domain_web_page bigint, allow_embedding bigint, - group_domain_proof text + group_domain_proof text, + preferences_json text ); diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index 20e05009a5..6498279f77 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -375,10 +375,11 @@ updateUserProfileFields_' db userId profileId Profile {displayName, fullName, sh db [sql| UPDATE contact_profiles - SET display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, contact_link = ?, preferences = ?, chat_peer_type = ?, updated_at = ? + SET preferences = ?, preferences_json = ?, + display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, contact_link = ?, chat_peer_type = ?, updated_at = ? WHERE user_id = ? AND contact_profile_id = ? |] - ((displayName, fullName, shortDescr, description, image, contactLink, preferences, peerType, updatedAt) :. (userId, profileId)) + (prefsToRow preferences :. (displayName, fullName, shortDescr, description, image, contactLink, peerType, updatedAt) :. (userId, profileId)) -- store the user's own badge credential; touches only the badge columns. -- bumps user_member_profile_updated_at so groups receive the updated profile (with the badge) on the next message. @@ -429,14 +430,14 @@ getUserContactProfiles db User {userId} = <$> DB.query db [sql| - SELECT display_name, full_name, short_descr, description, image, contact_link, chat_peer_type, contact_domain, preferences + SELECT display_name, full_name, short_descr, description, image, contact_link, chat_peer_type, contact_domain, preferences, preferences_json FROM contact_profiles WHERE user_id = ? |] (Only userId) where - toContactProfile :: (ContactName, Text, Maybe Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, Maybe SimplexDomain, Maybe Preferences) -> Profile - toContactProfile (displayName, fullName, shortDescr, description, image, contactLink, peerType, domain_, preferences) = Profile {displayName, fullName, shortDescr, description, image, contactLink, contactDomain = mkDomainClaim <$> domain_, peerType, preferences, badge = Nothing} + toContactProfile :: (ContactName, Text, Maybe Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, Maybe SimplexDomain, Maybe Text, Maybe Text) -> Profile + toContactProfile (displayName, fullName, shortDescr, description, image, contactLink, peerType, domain_, encodedPrefs, receivedPrefs) = Profile {displayName, fullName, shortDescr, description, image, contactLink, contactDomain = mkDomainClaim <$> domain_, peerType, preferences = chatPrefsFromRow encodedPrefs receivedPrefs, badge = Nothing} createUserContactLink :: DB.Connection -> User -> ConnId -> CreatedLinkContact -> SubscriptionMode -> C.PrivateKeyEd25519 -> ExceptT StoreError IO () createUserContactLink db User {userId} agentConnId (CCLink cReq shortLink) subMode linkPrivSigKey = diff --git a/src/Simplex/Chat/Store/SQLite/Migrations.hs b/src/Simplex/Chat/Store/SQLite/Migrations.hs index 55b1b7c3bb..d42177feac 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations.hs @@ -175,6 +175,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20260828_file_expiry import Simplex.Chat.Store.SQLite.Migrations.M20260904_file_badges import Simplex.Chat.Store.SQLite.Migrations.M20260915_user_badges import Simplex.Chat.Store.SQLite.Migrations.M20260918_badge_issue_errors +import Simplex.Chat.Store.SQLite.Migrations.M20260923_preferences_json import Simplex.Chat.Store.SQLite.Migrations.M20260924_simplex_name_resolved import Simplex.Messaging.Agent.Store.Shared (Migration (..)) @@ -351,6 +352,7 @@ schemaMigrations = ("20260904_file_badges", m20260904_file_badges, Just down_m20260904_file_badges), ("20260915_user_badges", m20260915_user_badges, Just down_m20260915_user_badges), ("20260918_badge_issue_errors", m20260918_badge_issue_errors, Just down_m20260918_badge_issue_errors), + ("20260923_preferences_json", m20260923_preferences_json, Just down_m20260923_preferences_json), ("20260924_simplex_name_resolved", m20260924_simplex_name_resolved, Just down_m20260924_simplex_name_resolved) ] diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20260923_preferences_json.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20260923_preferences_json.hs new file mode 100644 index 0000000000..4ee4dbc1e2 --- /dev/null +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20260923_preferences_json.hs @@ -0,0 +1,20 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.SQLite.Migrations.M20260923_preferences_json where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20260923_preferences_json :: Query +m20260923_preferences_json = + [sql| +ALTER TABLE contact_profiles ADD COLUMN preferences_json TEXT; +ALTER TABLE group_profiles ADD COLUMN preferences_json TEXT; +|] + +down_m20260923_preferences_json :: Query +down_m20260923_preferences_json = + [sql| +ALTER TABLE group_profiles DROP COLUMN preferences_json; +ALTER TABLE contact_profiles DROP COLUMN preferences_json; +|] diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt index 9886f1d5cb..9654256a5e 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt @@ -704,9 +704,11 @@ SEARCH snd_message_deliveries USING COVERING INDEX idx_snd_message_deliveries_co Query: INSERT INTO ratchets - (conn_id, ratchet_state, x3dh_pub_key_1, x3dh_pub_key_2, pq_pub_kem) VALUES (?, ?, ?, ?, ?) + (conn_id, ratchet_state, rc_verify_code_ad, rc_verify_code_pq, x3dh_pub_key_1, x3dh_pub_key_2, pq_pub_kem) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT (conn_id) DO UPDATE SET ratchet_state = EXCLUDED.ratchet_state, + rc_verify_code_ad = EXCLUDED.rc_verify_code_ad, + rc_verify_code_pq = EXCLUDED.rc_verify_code_pq, x3dh_priv_key_1 = NULL, x3dh_priv_key_2 = NULL, x3dh_pub_key_1 = EXCLUDED.x3dh_pub_key_1, @@ -717,10 +719,12 @@ Query: Plan: Query: - INSERT INTO ratchets (conn_id, ratchet_state) - VALUES (?, ?) + INSERT INTO ratchets (conn_id, ratchet_state, rc_verify_code_ad, rc_verify_code_pq) + VALUES (?, ?, ?, ?) ON CONFLICT (conn_id) DO UPDATE SET - ratchet_state = ?, + ratchet_state = EXCLUDED.ratchet_state, + rc_verify_code_ad = EXCLUDED.rc_verify_code_ad, + rc_verify_code_pq = EXCLUDED.rc_verify_code_pq, x3dh_priv_key_1 = NULL, x3dh_priv_key_2 = NULL, x3dh_pub_key_1 = NULL, @@ -1361,6 +1365,10 @@ Query: SELECT conn_id FROM connections WHERE user_id = ? Plan: SEARCH connections USING COVERING INDEX idx_connections_user (user_id=?) +Query: SELECT conn_id, rc_verify_code_ad, rc_verify_code_pq, CASE WHEN rc_verify_code_ad IS NULL THEN ratchet_state END FROM ratchets WHERE conn_id = ? +Plan: +SEARCH ratchets USING PRIMARY KEY (conn_id=?) + Query: SELECT count(1) FROM connections Plan: SCAN connections USING COVERING INDEX idx_connections_deleted diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 940b48eae8..e86a33275b 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -124,7 +124,7 @@ Query: SELECT -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, cp.display_name, cp.full_name, cp.short_descr, cp.description, cp.image, cp.contact_link, cp.chat_peer_type, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, - cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, cr2.rejection_supported, + cp.preferences, cp.preferences_json, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, cr2.rejection_supported, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.grp_direct_inv_link, ct.grp_direct_inv_from_group_id, ct.grp_direct_inv_from_group_member_id, ct.grp_direct_inv_from_member_conn_id, ct.grp_direct_inv_started_connection, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, cp.badge_proof, cp.badge_pres_header, cp.badge_expiry, cp.badge_type, cp.badge_verified, cp.badge_extra, cp.badge_master_key, cp.badge_signature, cp.badge_key_idx, cp.contact_domain, cp.contact_domain_proof, cp.contact_domain_verified, @@ -149,7 +149,7 @@ Query: -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_type, gp.group_link, gp.public_group_id, gp.group_web_page, gp.group_domain, gp.domain_web_page, gp.allow_embedding, gp.group_domain_proof, - g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, + g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.preferences_json, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, @@ -160,13 +160,13 @@ Query: mu.group_member_id, mu.group_id, mu.index_in_group, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, -- GroupInfo {membership = GroupMember {memberProfile}} - pu.display_name, pu.full_name, pu.short_descr, pu.description, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.short_descr, pu.description, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences, pu.preferences_json, pu.badge_proof, pu.badge_pres_header, pu.badge_expiry, pu.badge_type, pu.badge_verified, pu.badge_extra, pu.badge_master_key, pu.badge_signature, pu.badge_key_idx, pu.contact_domain, pu.contact_domain_proof, pu.contact_domain_verified, 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.member_pub_key, mu.relay_link, mu.member_security_code, mu.member_security_code_verified_at, -- from GroupMember m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at @@ -405,7 +405,7 @@ Query: cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.business_group_id, cr.user_contact_link_id, cr.rejection_supported, cr.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, cr.xcontact_id, - cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, + cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, p.preferences_json, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified @@ -528,10 +528,11 @@ SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?) Query: UPDATE group_profiles - SET display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, + SET preferences = ?, preferences_json = ?, + display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, group_type = ?, group_link = ?, group_web_page = ?, group_domain = CASE WHEN ? THEN ? ELSE group_domain END, domain_web_page = ?, allow_embedding = ?, group_domain_proof = ?, - preferences = ?, member_admission = ?, updated_at = ? + member_admission = ?, updated_at = ? WHERE group_profile_id IN ( SELECT group_profile_id FROM groups @@ -733,7 +734,7 @@ Plan: Query: SELECT c.contact_profile_id, c.local_display_name, p.display_name, p.full_name, p.short_descr, p.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, c.contact_used, c.contact_status, c.enable_ntfs, c.send_rcpts, c.favorite, - p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.conn_full_link_to_connect, c.conn_short_link_to_connect, c.welcome_shared_msg_id, c.request_shared_msg_id, c.contact_request_id, cr2.rejection_supported, + p.preferences, p.preferences_json, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.conn_full_link_to_connect, c.conn_short_link_to_connect, c.welcome_shared_msg_id, c.request_shared_msg_id, c.contact_request_id, cr2.rejection_supported, c.contact_group_member_id, c.contact_grp_inv_sent, c.grp_direct_inv_link, c.grp_direct_inv_from_group_id, c.grp_direct_inv_from_group_member_id, c.grp_direct_inv_from_member_conn_id, c.grp_direct_inv_started_connection, c.ui_themes, c.chat_deleted, c.custom_data, c.chat_item_ttl, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, @@ -1043,7 +1044,7 @@ Query: -- GroupMember m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + p.display_name, p.full_name, p.short_descr, p.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at @@ -1317,8 +1318,8 @@ Query: (display_name, full_name, short_descr, description, image, group_type, group_link, public_group_id, group_web_page, group_domain, domain_web_page, allow_embedding, group_domain_proof, - user_id, preferences, member_admission, created_at, updated_at) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) + user_id, member_admission, created_at, updated_at, preferences, preferences_json) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) Plan: @@ -1395,7 +1396,7 @@ Query: -- GroupMember m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + p.display_name, p.full_name, p.short_descr, p.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at, @@ -1404,14 +1405,14 @@ Query: -- quoted GroupMember rm.group_member_id, rm.group_id, rm.index_in_group, rm.member_id, rm.peer_chat_min_version, rm.peer_chat_max_version, rm.member_role, rm.member_category, rm.member_status, rm.show_messages, rm.member_restriction, rm.invited_by, rm.invited_by_group_member_id, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id, - rp.display_name, rp.full_name, rp.short_descr, rp.description, rp.image, rp.contact_link, rp.chat_peer_type, rp.local_alias, rp.preferences, + rp.display_name, rp.full_name, rp.short_descr, rp.description, rp.image, rp.contact_link, rp.chat_peer_type, rp.local_alias, rp.preferences, rp.preferences_json, rp.badge_proof, rp.badge_pres_header, rp.badge_expiry, rp.badge_type, rp.badge_verified, rp.badge_extra, rp.badge_master_key, rp.badge_signature, rp.badge_key_idx, rp.contact_domain, rp.contact_domain_proof, rp.contact_domain_verified, 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.member_pub_key, rm.relay_link, rm.member_security_code, rm.member_security_code_verified_at, -- deleted by GroupMember dbm.group_member_id, dbm.group_id, dbm.index_in_group, 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.description, dbp.image, dbp.contact_link, dbp.chat_peer_type, dbp.local_alias, dbp.preferences, + dbp.display_name, dbp.full_name, dbp.short_descr, dbp.description, dbp.image, dbp.contact_link, dbp.chat_peer_type, dbp.local_alias, dbp.preferences, dbp.preferences_json, dbp.badge_proof, dbp.badge_pres_header, dbp.badge_expiry, dbp.badge_type, dbp.badge_verified, dbp.badge_extra, dbp.badge_master_key, dbp.badge_signature, dbp.badge_key_idx, dbp.contact_domain, dbp.contact_domain_proof, dbp.contact_domain_verified, 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.member_pub_key, dbm.relay_link, dbm.member_security_code, dbm.member_security_code_verified_at @@ -1463,7 +1464,7 @@ Query: SELECT -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, cp.display_name, cp.full_name, cp.short_descr, cp.description, cp.image, cp.contact_link, cp.chat_peer_type, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, - cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, cr2.rejection_supported, + cp.preferences, cp.preferences_json, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, cr2.rejection_supported, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.grp_direct_inv_link, ct.grp_direct_inv_from_group_id, ct.grp_direct_inv_from_group_member_id, ct.grp_direct_inv_from_member_conn_id, ct.grp_direct_inv_started_connection, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, cp.badge_proof, cp.badge_pres_header, cp.badge_expiry, cp.badge_type, cp.badge_verified, cp.badge_extra, cp.badge_master_key, cp.badge_signature, cp.badge_key_idx, @@ -2055,7 +2056,7 @@ Query: SELECT -- Contact ct.contact_id, ct.contact_profile_id, ct.local_display_name, cp.display_name, cp.full_name, cp.short_descr, cp.description, cp.image, cp.contact_link, cp.chat_peer_type, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, - cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, cr2.rejection_supported, + cp.preferences, cp.preferences_json, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, cr2.rejection_supported, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.grp_direct_inv_link, ct.grp_direct_inv_from_group_id, ct.grp_direct_inv_from_group_member_id, ct.grp_direct_inv_from_member_conn_id, ct.grp_direct_inv_started_connection, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, cp.badge_proof, cp.badge_pres_header, cp.badge_expiry, cp.badge_type, cp.badge_verified, cp.badge_extra, cp.badge_master_key, cp.badge_signature, cp.badge_key_idx, @@ -2144,7 +2145,7 @@ Query: cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.business_group_id, cr.user_contact_link_id, cr.rejection_supported, cr.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, cr.xcontact_id, - cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, + cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, p.preferences_json, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified @@ -2174,7 +2175,7 @@ Query: cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.business_group_id, cr.user_contact_link_id, cr.rejection_supported, cr.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, cr.xcontact_id, - cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, + cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, p.preferences_json, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified @@ -2204,7 +2205,7 @@ Query: cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.business_group_id, cr.user_contact_link_id, cr.rejection_supported, cr.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, cr.xcontact_id, - cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, + cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, p.preferences_json, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified @@ -3755,7 +3756,7 @@ Plan: SEARCH connections USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT cp.contact_profile_id, cp.display_name, cp.full_name, cp.short_descr, cp.description, cp.image, cp.contact_link, cp.chat_peer_type, cp.local_alias, cp.preferences, -- , ct.user_preferences + SELECT cp.contact_profile_id, cp.display_name, cp.full_name, cp.short_descr, cp.description, cp.image, cp.contact_link, cp.chat_peer_type, cp.local_alias, cp.preferences, cp.preferences_json, -- , ct.user_preferences cp.badge_proof, cp.badge_pres_header, cp.badge_expiry, cp.badge_type, cp.badge_verified, cp.badge_extra, cp.badge_master_key, cp.badge_signature, cp.badge_key_idx, cp.contact_domain, cp.contact_domain_proof, cp.contact_domain_verified FROM contact_profiles cp WHERE cp.user_id = ? AND cp.contact_profile_id = ? @@ -3818,7 +3819,7 @@ SEARCH f USING PRIMARY KEY (file_id=?) SEARCH d USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT display_name, full_name, short_descr, description, image, contact_link, chat_peer_type, contact_domain, preferences + SELECT display_name, full_name, short_descr, description, image, contact_link, chat_peer_type, contact_domain, preferences, preferences_json FROM contact_profiles WHERE user_id = ? @@ -3939,7 +3940,7 @@ Query: SELECT gp.display_name, gp.full_name, gp.short_descr, gp.description, gp.image, gp.group_type, gp.group_link, gp.public_group_id, gp.group_web_page, gp.group_domain, gp.domain_web_page, gp.allow_embedding, gp.group_domain_proof, - gp.preferences, gp.member_admission + gp.preferences, gp.preferences_json, gp.member_admission FROM group_profiles gp JOIN groups g ON gp.group_profile_id = g.group_profile_id WHERE g.group_id = ? @@ -5330,25 +5331,7 @@ SEARCH contact_profiles USING INTEGER PRIMARY KEY (rowid=?) Query: UPDATE contact_profiles - SET display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, contact_link = ?, preferences = ?, chat_peer_type = ?, updated_at = ? - WHERE user_id = ? AND contact_profile_id = ? - -Plan: -SEARCH contact_profiles USING INTEGER PRIMARY KEY (rowid=?) - -Query: - UPDATE contact_profiles - SET display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, contact_link = ?, preferences = ?, chat_peer_type = ?, updated_at = ?, - badge_proof = ?, badge_pres_header = ?, badge_expiry = ?, badge_type = ?, badge_verified = ?, badge_extra = ?, badge_master_key = ?, badge_signature = ?, badge_key_idx = ?, - contact_domain = ?, contact_domain_proof = ? - WHERE user_id = ? AND contact_profile_id = ? - -Plan: -SEARCH contact_profiles USING INTEGER PRIMARY KEY (rowid=?) - -Query: - UPDATE contact_profiles - SET display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, contact_link = NULL, preferences = NULL, updated_at = ?, + SET display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, contact_link = NULL, preferences = NULL, preferences_json = NULL, updated_at = ?, badge_proof = ?, badge_pres_header = ?, badge_expiry = ?, badge_type = ?, badge_verified = ?, badge_extra = ?, badge_master_key = ?, badge_signature = ?, badge_key_idx = ?, contact_domain = ?, contact_domain_proof = ? WHERE user_id = ? AND contact_profile_id = ? @@ -5374,6 +5357,26 @@ Query: Plan: SEARCH contact_profiles USING INTEGER PRIMARY KEY (rowid=?) +Query: + UPDATE contact_profiles + SET preferences = ?, preferences_json = ?, + display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, contact_link = ?, chat_peer_type = ?, updated_at = ? + WHERE user_id = ? AND contact_profile_id = ? + +Plan: +SEARCH contact_profiles USING INTEGER PRIMARY KEY (rowid=?) + +Query: + UPDATE contact_profiles + SET preferences = ?, preferences_json = ?, + display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, contact_link = ?, chat_peer_type = ?, updated_at = ?, + badge_proof = ?, badge_pres_header = ?, badge_expiry = ?, badge_type = ?, badge_verified = ?, badge_extra = ?, badge_master_key = ?, badge_signature = ?, badge_key_idx = ?, + contact_domain = ?, contact_domain_proof = ? + WHERE user_id = ? AND contact_profile_id = ? + +Plan: +SEARCH contact_profiles USING INTEGER PRIMARY KEY (rowid=?) + Query: UPDATE contact_profiles SET contact_domain_verified = ?, contact_domain_resolved_at = ?, contact_domain_expires_at = ? WHERE contact_profile_id IN (SELECT contact_profile_id FROM contacts WHERE user_id = ? AND contact_id = ?) @@ -5486,7 +5489,7 @@ SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?) Query: UPDATE group_profiles - SET preferences = ?, updated_at = ? + SET preferences = ?, preferences_json = ?, updated_at = ? WHERE group_profile_id IN ( SELECT group_profile_id FROM groups @@ -5720,7 +5723,7 @@ Query: -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_type, gp.group_link, gp.public_group_id, gp.group_web_page, gp.group_domain, gp.domain_web_page, gp.allow_embedding, gp.group_domain_proof, - g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, + g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.preferences_json, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, @@ -5730,7 +5733,7 @@ Query: -- GroupMember - membership mu.group_member_id, mu.group_id, mu.index_in_group, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, - pu.display_name, pu.full_name, pu.short_descr, pu.description, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.short_descr, pu.description, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences, pu.preferences_json, pu.badge_proof, pu.badge_pres_header, pu.badge_expiry, pu.badge_type, pu.badge_verified, pu.badge_extra, pu.badge_master_key, pu.badge_signature, pu.badge_key_idx, pu.contact_domain, pu.contact_domain_proof, pu.contact_domain_verified, 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.member_pub_key, mu.relay_link, mu.member_security_code, mu.member_security_code_verified_at @@ -5758,7 +5761,7 @@ Query: -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_type, gp.group_link, gp.public_group_id, gp.group_web_page, gp.group_domain, gp.domain_web_page, gp.allow_embedding, gp.group_domain_proof, - g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, + g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.preferences_json, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, @@ -5768,7 +5771,7 @@ Query: -- GroupMember - membership mu.group_member_id, mu.group_id, mu.index_in_group, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, - pu.display_name, pu.full_name, pu.short_descr, pu.description, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.short_descr, pu.description, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences, pu.preferences_json, pu.badge_proof, pu.badge_pres_header, pu.badge_expiry, pu.badge_type, pu.badge_verified, pu.badge_extra, pu.badge_master_key, pu.badge_signature, pu.badge_key_idx, pu.contact_domain, pu.contact_domain_proof, pu.contact_domain_verified, 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.member_pub_key, mu.relay_link, mu.member_security_code, mu.member_security_code_verified_at @@ -5789,7 +5792,7 @@ Query: -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_type, gp.group_link, gp.public_group_id, gp.group_web_page, gp.group_domain, gp.domain_web_page, gp.allow_embedding, gp.group_domain_proof, - g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, + g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.preferences_json, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, @@ -5799,7 +5802,7 @@ Query: -- GroupMember - membership mu.group_member_id, mu.group_id, mu.index_in_group, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, - pu.display_name, pu.full_name, pu.short_descr, pu.description, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.short_descr, pu.description, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences, pu.preferences_json, pu.badge_proof, pu.badge_pres_header, pu.badge_expiry, pu.badge_type, pu.badge_verified, pu.badge_extra, pu.badge_master_key, pu.badge_signature, pu.badge_key_idx, pu.contact_domain, pu.contact_domain_proof, pu.contact_domain_verified, 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.member_pub_key, mu.relay_link, mu.member_security_code, mu.member_security_code_verified_at @@ -5820,7 +5823,7 @@ Query: cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.business_group_id, cr.user_contact_link_id, cr.rejection_supported, cr.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, cr.xcontact_id, - cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, + cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, p.preferences_json, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, @@ -5837,7 +5840,7 @@ Query: cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.business_group_id, cr.user_contact_link_id, cr.rejection_supported, cr.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, cr.xcontact_id, - cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, + cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, p.preferences_json, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, @@ -5852,7 +5855,7 @@ SEARCH p USING INTEGER PRIMARY KEY (rowid=?) Query: SELECT m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at, @@ -5880,7 +5883,7 @@ SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JO Query: SELECT m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at, @@ -5901,7 +5904,7 @@ SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JO Query: SELECT m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at, @@ -5921,7 +5924,7 @@ SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JO Query: SELECT m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at, @@ -5941,7 +5944,7 @@ SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JO Query: SELECT m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at, @@ -5961,7 +5964,7 @@ SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JO Query: SELECT m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at, @@ -5981,7 +5984,7 @@ SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JO Query: SELECT m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at, @@ -6001,7 +6004,7 @@ SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JO Query: SELECT m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at, @@ -6021,7 +6024,7 @@ SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JO Query: SELECT m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at, @@ -6041,7 +6044,7 @@ SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JO Query: SELECT m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at, @@ -6061,7 +6064,7 @@ SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JO Query: SELECT m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at, @@ -6081,7 +6084,7 @@ SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JO Query: SELECT m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at, @@ -6101,7 +6104,7 @@ SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JO Query: SELECT m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at, @@ -6121,7 +6124,7 @@ SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JO Query: SELECT m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at, @@ -6141,7 +6144,7 @@ SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JO Query: SELECT m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at, @@ -7143,15 +7146,15 @@ Query: INSERT INTO contact_profiles (display_name, full_name, short_descr, descr Plan: SEARCH contact_requests USING COVERING INDEX idx_contact_requests_contact_profile_id (contact_profile_id=?) -Query: INSERT INTO contact_profiles (display_name, full_name, short_descr, description, image, contact_link, chat_peer_type, user_id, local_alias, preferences, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified, badge_extra, badge_master_key, badge_signature, badge_key_idx, contact_domain, contact_domain_proof) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) +Query: INSERT INTO contact_profiles (display_name, full_name, short_descr, description, image, contact_link, chat_peer_type, user_id, local_alias, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified, badge_extra, badge_master_key, badge_signature, badge_key_idx, contact_domain, contact_domain_proof, preferences, preferences_json) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) Plan: SEARCH contact_requests USING COVERING INDEX idx_contact_requests_contact_profile_id (contact_profile_id=?) -Query: INSERT INTO contact_profiles (display_name, full_name, short_descr, description, image, contact_link, user_id, local_alias, preferences, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified, badge_extra, badge_master_key, badge_signature, badge_key_idx) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) +Query: INSERT INTO contact_profiles (display_name, full_name, short_descr, description, image, contact_link, user_id, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified, badge_extra, badge_master_key, badge_signature, badge_key_idx, preferences, preferences_json) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) Plan: SEARCH contact_requests USING COVERING INDEX idx_contact_requests_contact_profile_id (contact_profile_id=?) -Query: INSERT INTO contact_profiles (display_name, full_name, short_descr, description, image, contact_link, user_id, preferences, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified, badge_extra, badge_master_key, badge_signature, badge_key_idx) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) +Query: INSERT INTO contact_profiles (display_name, full_name, short_descr, description, image, contact_link, user_id, local_alias, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified, badge_extra, badge_master_key, badge_signature, badge_key_idx, preferences, preferences_json) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) Plan: SEARCH contact_requests USING COVERING INDEX idx_contact_requests_contact_profile_id (contact_profile_id=?) @@ -7190,10 +7193,10 @@ Plan: Query: INSERT INTO files (user_id, group_id, file_name, file_size, chunk_size, file_inline, ci_file_status, protocol, file_type, shared_msg_id, roster_transfer_id, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) Plan: -Query: INSERT INTO group_profiles (display_name, full_name, short_descr, description, image, user_id, preferences, member_admission, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?) +Query: INSERT INTO group_profiles (display_name, full_name, short_descr, description, image, user_id, member_admission, created_at, updated_at, preferences, preferences_json) VALUES (?,?,?,?,?,?,?,?,?,?,?) Plan: -Query: INSERT INTO group_profiles (display_name, full_name, short_descr, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?) +Query: INSERT INTO group_profiles (display_name, full_name, short_descr, image, user_id, created_at, updated_at, preferences, preferences_json) VALUES (?,?,?,?,?,?,?,?,?) Plan: Query: INSERT INTO group_snd_item_statuses (chat_item_id, group_member_id, group_snd_item_status) VALUES (?,?,?) @@ -7763,6 +7766,10 @@ Query: SELECT user_id FROM users WHERE local_display_name = ? Plan: SEARCH users USING COVERING INDEX sqlite_autoindex_users_2 (local_display_name=?) +Query: SELECT via_contact_uri FROM connections WHERE connection_id = ? +Plan: +SEARCH connections USING INTEGER PRIMARY KEY (rowid=?) + Query: SELECT via_contact_uri, via_contact_uri_hash FROM connections WHERE connection_id = ? Plan: SEARCH connections USING INTEGER PRIMARY KEY (rowid=?) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index 4e2be19828..b1a3c5763f 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -33,6 +33,7 @@ CREATE TABLE contact_profiles( contact_domain_proof TEXT, contact_domain_verified INTEGER, description TEXT, + preferences_json TEXT, contact_domain_resolved_at TEXT, contact_domain_expires_at TEXT ) STRICT; @@ -148,7 +149,8 @@ CREATE TABLE group_profiles( group_domain TEXT, domain_web_page INTEGER, allow_embedding INTEGER, - group_domain_proof TEXT + group_domain_proof TEXT, + preferences_json TEXT ) STRICT; CREATE TABLE groups( group_id INTEGER PRIMARY KEY, -- local group ID diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index 8eeefa328f..c3d42de3d1 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -421,8 +421,8 @@ createContact_ db cxt User {userId} Profile {displayName, fullName, shortDescr, badgeVerified <- verifyBadge_ (badgeKeys cxt) badge DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, short_descr, description, image, contact_link, chat_peer_type, user_id, local_alias, preferences, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified, badge_extra, badge_master_key, badge_signature, badge_key_idx, contact_domain, contact_domain_proof) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" - ((displayName, fullName, shortDescr, description, image, contactLink, peerType) :. (userId, localAlias, preferences, currentTs, currentTs) :. badgeToRow badge badgeVerified :. contactDomainToRow contactDomain) + "INSERT INTO contact_profiles (display_name, full_name, short_descr, description, image, contact_link, chat_peer_type, user_id, local_alias, created_at, updated_at, badge_proof, badge_pres_header, badge_expiry, badge_type, badge_verified, badge_extra, badge_master_key, badge_signature, badge_key_idx, contact_domain, contact_domain_proof, preferences, preferences_json) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + ((displayName, fullName, shortDescr, description, image, contactLink, peerType) :. (userId, localAlias, currentTs, currentTs) :. badgeToRow badge badgeVerified :. contactDomainToRow contactDomain :. prefsToRow preferences) profileId <- insertedRowId db DB.execute db @@ -489,15 +489,16 @@ type PreparedContactRow = (Maybe AConnectionRequestUri, Maybe AConnShortLink, Ma type GroupDirectInvitationRow = (Maybe ConnReqInvitation, Maybe GroupId, Maybe GroupMemberId, Maybe Int64, BoolInt) -type ContactRow' = (ProfileId, ContactName, ContactName, Text, Maybe Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. PreparedContactRow :. (Maybe Int64, Maybe BoolInt, Maybe GroupMemberId, BoolInt) :. GroupDirectInvitationRow :. (Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData, Maybe Int64) :. BadgeRow :. ContactDomainRow +type ContactRow' = (ProfileId, ContactName, ContactName, Text, Maybe Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Text, Maybe Text, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. PreparedContactRow :. (Maybe Int64, Maybe BoolInt, Maybe GroupMemberId, BoolInt) :. GroupDirectInvitationRow :. (Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData, Maybe Int64) :. BadgeRow :. ContactDomainRow type ContactRow = Only ContactId :. ContactRow' type ContactDomainRow = (Maybe SimplexDomain, Maybe SimplexDomainProof, Maybe BoolInt) toContact :: UTCTime -> StoreCxt -> User -> [ChatTagId] -> ContactRow :. MaybeConnectionRow -> Contact -toContact now cxt user chatTags ((Only contactId :. (profileId, localDisplayName, displayName, fullName, shortDescr, description, image, contactLink, peerType, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. preparedContactRow :. (contactRequestId, rejectionSupported_, contactGroupMemberId, BI contactGrpInvSent) :. groupDirectInvRow :. (uiThemes, BI chatDeleted, customData, chatItemTTL) :. badgeRow :. domainRow) :. connRow) = - let profile = LocalProfile {profileId, displayName, fullName, shortDescr, description, image, contactLink, contactDomain = rowToContactDomain domainRow, contactDomainVerified = rowToDomainVerified domainRow, peerType, localBadge = rowToBadge now badgeRow, preferences, localAlias} +toContact now cxt user chatTags ((Only contactId :. (profileId, localDisplayName, displayName, fullName, shortDescr, description, image, contactLink, peerType, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, encodedPrefs, receivedPrefs, userPreferences, createdAt, updatedAt, chatTs) :. preparedContactRow :. (contactRequestId, rejectionSupported_, contactGroupMemberId, BI contactGrpInvSent) :. groupDirectInvRow :. (uiThemes, BI chatDeleted, customData, chatItemTTL) :. badgeRow :. domainRow) :. connRow) = + let preferences = chatPrefsFromRow encodedPrefs receivedPrefs + profile = LocalProfile {profileId, displayName, fullName, shortDescr, description, image, contactLink, contactDomain = rowToContactDomain domainRow, contactDomainVerified = rowToDomainVerified domainRow, peerType, localBadge = rowToBadge now badgeRow, preferences, localAlias} activeConn = toMaybeConnection cxt connRow chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite} incognito = maybe False connIncognito activeConn @@ -539,18 +540,18 @@ getProfileById db userId profileId = do DB.query db [sql| - SELECT cp.contact_profile_id, cp.display_name, cp.full_name, cp.short_descr, cp.description, cp.image, cp.contact_link, cp.chat_peer_type, cp.local_alias, cp.preferences, -- , ct.user_preferences + SELECT cp.contact_profile_id, cp.display_name, cp.full_name, cp.short_descr, cp.description, cp.image, cp.contact_link, cp.chat_peer_type, cp.local_alias, cp.preferences, cp.preferences_json, -- , ct.user_preferences cp.badge_proof, cp.badge_pres_header, cp.badge_expiry, cp.badge_type, cp.badge_verified, cp.badge_extra, cp.badge_master_key, cp.badge_signature, cp.badge_key_idx, cp.contact_domain, cp.contact_domain_proof, cp.contact_domain_verified FROM contact_profiles cp WHERE cp.user_id = ? AND cp.contact_profile_id = ? |] (userId, profileId) -type ContactRequestRow = (Int64, ContactName, AgentInvId, Maybe ContactId, Maybe GroupId, Maybe Int64, BoolInt) :. (Int64, ContactName, Text, Maybe Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias) :. (Maybe XContactId, PQSupport, Maybe SharedMsgId, Maybe SharedMsgId, Maybe Preferences, UTCTime, UTCTime, VersionChat, VersionChat) :. BadgeRow :. ContactDomainRow +type ContactRequestRow = (Int64, ContactName, AgentInvId, Maybe ContactId, Maybe GroupId, Maybe Int64, BoolInt) :. (Int64, ContactName, Text, Maybe Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias) :. (Maybe XContactId, PQSupport, Maybe SharedMsgId, Maybe SharedMsgId, Maybe Text, Maybe Text, UTCTime, UTCTime, VersionChat, VersionChat) :. BadgeRow :. ContactDomainRow toContactRequest :: UTCTime -> ContactRequestRow -> UserContactRequest -toContactRequest now ((contactRequestId, localDisplayName, agentInvitationId, contactId_, businessGroupId_, userContactLinkId_, BI rejectionSupported) :. (profileId, displayName, fullName, shortDescr, description, image, contactLink, peerType, localAlias) :. (xContactId, pqSupport, welcomeSharedMsgId, requestSharedMsgId, preferences, createdAt, updatedAt, minVer, maxVer) :. badgeRow :. domainRow) = do - let profile = LocalProfile {profileId, displayName, fullName, shortDescr, description, image, contactLink, contactDomain = rowToContactDomain domainRow, contactDomainVerified = rowToDomainVerified domainRow, peerType, preferences, localBadge = rowToBadge now badgeRow, localAlias} +toContactRequest now ((contactRequestId, localDisplayName, agentInvitationId, contactId_, businessGroupId_, userContactLinkId_, BI rejectionSupported) :. (profileId, displayName, fullName, shortDescr, description, image, contactLink, peerType, localAlias) :. (xContactId, pqSupport, welcomeSharedMsgId, requestSharedMsgId, encodedPrefs, receivedPrefs, createdAt, updatedAt, minVer, maxVer) :. badgeRow :. domainRow) = do + let profile = LocalProfile {profileId, displayName, fullName, shortDescr, description, image, contactLink, contactDomain = rowToContactDomain domainRow, contactDomainVerified = rowToDomainVerified domainRow, peerType, preferences = chatPrefsFromRow encodedPrefs receivedPrefs, localBadge = rowToBadge now badgeRow, localAlias} cReqChatVRange = fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer in UserContactRequest {contactRequestId, agentInvitationId, contactId_, businessGroupId_, userContactLinkId_, cReqChatVRange, localDisplayName, profileId, profile, xContactId, pqSupport, welcomeSharedMsgId, requestSharedMsgId, createdAt, updatedAt, rejectionSupported} @@ -587,6 +588,14 @@ getConnReqInv db connId = "SELECT conn_req_inv FROM connections WHERE connection_id = ?" (Only connId) +getConnReqContact :: DB.Connection -> Int64 -> ExceptT StoreError IO ConnReqContact +getConnReqContact db connId = + ExceptT . firstRow fromOnly (SEConnectionNotFoundById connId) $ + DB.query + db + "SELECT via_contact_uri FROM connections WHERE connection_id = ?" + (Only connId) + -- | Saves unique local display name based on passed displayName, suffixed with _N if required. -- This function should be called inside transaction. withLocalDisplayName :: forall a. DB.Connection -> UserId -> Text -> (Text -> IO (Either StoreError a)) -> IO (Either StoreError a) @@ -686,18 +695,19 @@ type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe Member type GroupKeysRow = (Maybe C.PrivateKeyEd25519, Maybe C.PublicKeyEd25519, Maybe C.PrivateKeyEd25519) -type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Text, Maybe Text, Maybe ImageData, Maybe GroupType, Maybe ShortLinkContact, Maybe B64UrlByteString) :. PublicGroupAccessRow :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences, Maybe GroupMemberAdmission) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. PreparedGroupRow :. BusinessChatInfoRow :. (BoolInt, Maybe RelayStatus, Maybe UIThemeEntityOverrides, Int64, Maybe Int64, Maybe VersionRoster, Maybe CustomData, Maybe Int64, Int, Maybe ConnReqContact, Maybe BoolInt) :. GroupKeysRow :. GroupMemberRow +type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Text, Maybe Text, Maybe ImageData, Maybe GroupType, Maybe ShortLinkContact, Maybe B64UrlByteString) :. PublicGroupAccessRow :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Text, Maybe Text, Maybe GroupMemberAdmission) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. PreparedGroupRow :. BusinessChatInfoRow :. (BoolInt, Maybe RelayStatus, Maybe UIThemeEntityOverrides, Int64, Maybe Int64, Maybe VersionRoster, Maybe CustomData, Maybe Int64, Int, Maybe ConnReqContact, Maybe BoolInt) :. GroupKeysRow :. GroupMemberRow type PublicGroupAccessRow = (Maybe Text, Maybe SimplexDomain, Maybe BoolInt, Maybe BoolInt, Maybe SimplexDomainProof) type GroupMemberRow = (GroupMemberId, GroupId, 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, Maybe C.PublicKeyEd25519, Maybe ShortLinkContact, Maybe Text, Maybe UTCTime) -type ProfileRow = (ProfileId, ContactName, Text, Maybe Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias, Maybe Preferences) :. BadgeRow :. ContactDomainRow +type ProfileRow = (ProfileId, ContactName, Text, Maybe Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias, Maybe Text, Maybe Text) :. BadgeRow :. ContactDomainRow toGroupInfo :: UTCTime -> StoreCxt -> Int64 -> [ChatTagId] -> GroupInfoRow -> (GroupInfo, GroupKeysRow) -toGroupInfo now cxt userContactId chatTags ((groupId, localDisplayName, displayName, fullName, shortDescr, localAlias, description, image, groupType_, groupLink_, publicGroupId_) :. accessRow :. (enableNtfs_, sendRcpts, BI favorite, groupPreferences, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. preparedGroupRow :. businessRow :. (BI useRelays, relayOwnStatus, uiThemes, currentMembers, publicMemberCount, rosterVersion, customData, chatItemTTL, membersRequireAttention, viaGroupLinkUri, groupDomainVerified) :. groupKeysRow :. userMemberRow) = +toGroupInfo now cxt userContactId chatTags ((groupId, localDisplayName, displayName, fullName, shortDescr, localAlias, description, image, groupType_, groupLink_, publicGroupId_) :. accessRow :. (enableNtfs_, sendRcpts, BI favorite, encodedPrefs, receivedPrefs, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. preparedGroupRow :. businessRow :. (BI useRelays, relayOwnStatus, uiThemes, currentMembers, publicMemberCount, rosterVersion, customData, chatItemTTL, membersRequireAttention, viaGroupLinkUri, groupDomainVerified) :. groupKeysRow :. userMemberRow) = let membership = (toGroupMember now userContactId userMemberRow) {memberChatVRange = vr cxt} chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite} + groupPreferences = groupPrefsFromRow encodedPrefs receivedPrefs fullGroupPreferences = mergeGroupPreferences groupPreferences publicGroup = toPublicGroupProfile groupType_ groupLink_ publicGroupId_ (toPublicGroupAccess accessRow) groupProfile = GroupProfile {displayName, fullName, shortDescr, description, image, publicGroup, groupPreferences, memberAdmission} @@ -793,7 +803,7 @@ groupMemberQuery = [sql| SELECT m.group_member_id, m.group_id, m.index_in_group, 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, + 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.description, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences, p.preferences_json, p.badge_proof, p.badge_pres_header, p.badge_expiry, p.badge_type, p.badge_verified, p.badge_extra, p.badge_master_key, p.badge_signature, p.badge_key_idx, p.contact_domain, p.contact_domain_proof, p.contact_domain_verified, 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.member_pub_key, m.relay_link, m.member_security_code, m.member_security_code_verified_at, @@ -811,8 +821,8 @@ toContactMember now cxt User {userContactId} (memberRow :. connRow) = (toGroupMember now userContactId memberRow) {activeConn = toMaybeConnection cxt connRow} rowToLocalProfile :: UTCTime -> ProfileRow -> LocalProfile -rowToLocalProfile now ((profileId, displayName, fullName, shortDescr, description, image, contactLink, peerType, localAlias, preferences) :. badgeRow :. domainRow) = - LocalProfile {profileId, displayName, fullName, shortDescr, description, image, contactLink, contactDomain = rowToContactDomain domainRow, contactDomainVerified = rowToDomainVerified domainRow, peerType, localBadge = rowToBadge now badgeRow, localAlias, preferences} +rowToLocalProfile now ((profileId, displayName, fullName, shortDescr, description, image, contactLink, peerType, localAlias, encodedPrefs, receivedPrefs) :. badgeRow :. domainRow) = + LocalProfile {profileId, displayName, fullName, shortDescr, description, image, contactLink, contactDomain = rowToContactDomain domainRow, contactDomainVerified = rowToDomainVerified domainRow, peerType, localBadge = rowToBadge now badgeRow, localAlias, preferences = chatPrefsFromRow encodedPrefs receivedPrefs} toBusinessChatInfo :: Maybe SimplexDomainClaim -> BusinessChatInfoRow -> Maybe BusinessChatInfo toBusinessChatInfo businessDomain (Just chatType, Just businessId, Just customerId) = Just BusinessChatInfo {chatType, businessId, customerId, businessDomain} @@ -828,7 +838,7 @@ groupInfoQueryFields = -- GroupInfo g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_type, gp.group_link, gp.public_group_id, gp.group_web_page, gp.group_domain, gp.domain_web_page, gp.allow_embedding, gp.group_domain_proof, - g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, + g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.preferences_json, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, @@ -838,7 +848,7 @@ groupInfoQueryFields = -- GroupMember - membership mu.group_member_id, mu.group_id, mu.index_in_group, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, - pu.display_name, pu.full_name, pu.short_descr, pu.description, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.short_descr, pu.description, pu.image, pu.contact_link, pu.chat_peer_type, pu.local_alias, pu.preferences, pu.preferences_json, pu.badge_proof, pu.badge_pres_header, pu.badge_expiry, pu.badge_type, pu.badge_verified, pu.badge_extra, pu.badge_master_key, pu.badge_signature, pu.badge_key_idx, pu.contact_domain, pu.contact_domain_proof, pu.contact_domain_verified, 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.member_pub_key, mu.relay_link, mu.member_security_code, mu.member_security_code_verified_at diff --git a/src/Simplex/Chat/Terminal/Input.hs b/src/Simplex/Chat/Terminal/Input.hs index fa5efd0354..3f1c434433 100644 --- a/src/Simplex/Chat/Terminal/Input.hs +++ b/src/Simplex/Chat/Terminal/Input.hs @@ -154,7 +154,7 @@ sendUpdatedLiveMessage cc sentMsg LiveMessage {chatName, chatItemId} live = do runTerminalInput :: ChatTerminal -> ChatController -> IO () runTerminalInput ct cc = withChatTerm ct $ do - updateInput ct + withTermLock ct $ updateInput ct receiveFromTTY cc ct receiveFromTTY :: forall m. MonadTerminal m => ChatController -> ChatTerminal -> m () diff --git a/src/Simplex/Chat/Types/Preferences.hs b/src/Simplex/Chat/Types/Preferences.hs index c26f187343..7445a9c2d5 100644 --- a/src/Simplex/Chat/Types/Preferences.hs +++ b/src/Simplex/Chat/Types/Preferences.hs @@ -24,8 +24,10 @@ module Simplex.Chat.Types.Preferences where import Control.Applicative ((<|>)) -import Data.Aeson (FromJSON (..), ToJSON (..)) +import Data.Aeson (FromJSON (..), Object, ToJSON (..), Value (..), decodeStrictText) +import qualified Data.Aeson.Encoding as JE import qualified Data.Aeson.TH as J +import qualified Data.Aeson.Types as JT import qualified Data.Attoparsec.ByteString.Char8 as A import qualified Data.ByteString.Char8 as B import Data.Maybe (fromMaybe, isJust) @@ -37,7 +39,7 @@ import Simplex.Chat.Types.Shared import Simplex.Messaging.Agent.Store.DB (blobFieldDecoder, fromTextField_) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, sumTypeJSON, taggedObjectJSON) -import Simplex.Messaging.Util (decodeJSON, encodeJSON, safeDecodeUtf8, (<$?>)) +import Simplex.Messaging.Util (encodeJSON, safeDecodeUtf8, (<$?>)) data ChatFeature = CFTimedMessages @@ -149,6 +151,34 @@ setPreference_ f pref_ prefs = SCFCalls -> prefs {calls = pref_} SCFSessions -> prefs {sessions = pref_} +newtype PrefsJSON = PrefsJSON {unPrefsJSON :: Maybe Object} + deriving (Eq, Show) + +instance ToJSON PrefsJSON where + toJSON _ = Null + toEncoding _ = JE.null_ + omitField _ = True + +instance FromJSON PrefsJSON where + parseJSON _ = pure $ PrefsJSON Nothing + omittedField = Just $ PrefsJSON Nothing + +keepPrefsJSON :: (ToJSON p, HasField "_json" p PrefsJSON) => Value -> p -> p +keepPrefsJSON v ps = setField @"_json" ps . PrefsJSON $ case v of + Object o | v /= toJSON ps -> Just o + _ -> Nothing + +decodePrefs :: (Value -> JT.Parser p) -> Text -> Maybe p +decodePrefs prefsP t = JT.parseMaybe prefsP =<< decodeStrictText t + +prefsFromRow_ :: (Value -> JT.Parser p) -> Maybe Text -> Maybe Text -> Maybe p +prefsFromRow_ prefsP encodedPrefs receivedPrefs = (decode =<< receivedPrefs) <|> (decode =<< encodedPrefs) + where + decode = decodePrefs prefsP + +prefsToRow :: HasField "_json" p PrefsJSON => Maybe p -> (Maybe p, Maybe Text) +prefsToRow ps = (ps, encodeJSON . Object <$> (unPrefsJSON . getField @"_json" =<< ps)) + -- collection of optional chat preferences for the user and the contact data Preferences = Preferences { timedMessages :: Maybe TimedMessagesPreference, @@ -158,7 +188,8 @@ data Preferences = Preferences files :: Maybe FilesPreference, calls :: Maybe CallsPreference, sessions :: Maybe SessionsPreference, - commands :: Maybe [ChatBotCommand] + commands :: Maybe [ChatBotCommand], + _json :: PrefsJSON } deriving (Eq, Show) @@ -166,6 +197,9 @@ class HasCommands p where commands_ :: p -> Maybe [ChatBotCommand] instance HasCommands Preferences where commands_ Preferences {commands} = commands +instance HasField "_json" Preferences PrefsJSON where + hasField p@Preferences {_json} = (\j -> p {_json = j}, _json) + data GroupFeature = GFTimedMessages | GFDirectMessages @@ -371,12 +405,16 @@ data GroupPreferences = GroupPreferences sessions :: Maybe SessionsGroupPreference, comments :: Maybe CommentsGroupPreference, signMessages :: Maybe SignMessagesGroupPreference, - commands :: Maybe [ChatBotCommand] + commands :: Maybe [ChatBotCommand], + _json :: PrefsJSON } deriving (Eq, Show) instance HasCommands GroupPreferences where commands_ GroupPreferences {commands} = commands +instance HasField "_json" GroupPreferences PrefsJSON where + hasField p@GroupPreferences {_json} = (\j -> p {_json = j}, _json) + data ChatBotCommand = CBCCommand { keyword :: Text, -- "order" @@ -506,7 +544,8 @@ toChatPrefs FullPreferences {timedMessages, fullDelete, reactions, voice, files, files = Just files, calls = Just calls, sessions = Just sessions, - commands = Just cmds + commands = Just cmds, + _json = PrefsJSON Nothing } defaultChatPrefs :: FullPreferences @@ -523,7 +562,7 @@ defaultChatPrefs = } emptyChatPrefs :: Preferences -emptyChatPrefs = Preferences Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing +emptyChatPrefs = Preferences Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing (PrefsJSON Nothing) defaultGroupPrefs :: FullGroupPreferences defaultGroupPrefs = @@ -545,7 +584,7 @@ defaultGroupPrefs = } emptyGroupPrefs :: GroupPreferences -emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing +emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing (PrefsJSON Nothing) businessGroupPrefs :: Preferences -> GroupPreferences businessGroupPrefs Preferences {timedMessages, fullDelete, reactions, voice, files, sessions, commands} = @@ -580,7 +619,8 @@ defaultBusinessGroupPrefs = sessions = Just $ SessionsGroupPreference FEOn Nothing, comments = Just $ CommentsGroupPreference FEOff Nothing, signMessages = Just $ SignMessagesGroupPreference FEOff, - commands = Nothing + commands = Nothing, + _json = PrefsJSON Nothing } data TimedMessagesPreference = TimedMessagesPreference @@ -1092,7 +1132,8 @@ toGroupPreferences groupPreferences@FullGroupPreferences {commands = ListDef cmd sessions = pref SGFSessions, comments = pref SGFComments, signMessages = pref SGFSignMessages, - commands = Just cmds + commands = Just cmds, + _json = PrefsJSON Nothing } where pref :: SGroupFeature f -> Maybe (GroupFeaturePreference f) @@ -1192,13 +1233,22 @@ instance FromJSON SessionsPreference where $(J.deriveJSON (taggedObjectJSON $ dropPrefix "CBC") ''ChatBotCommand) -$(J.deriveJSON defaultJSON ''Preferences) +$(J.deriveToJSON defaultJSON ''Preferences) + +chatPrefsP :: Value -> JT.Parser Preferences +chatPrefsP = $(J.mkParseJSON defaultJSON ''Preferences) + +instance FromJSON Preferences where + parseJSON v = keepPrefsJSON v <$> chatPrefsP v + +chatPrefsFromRow :: Maybe Text -> Maybe Text -> Maybe Preferences +chatPrefsFromRow = prefsFromRow_ chatPrefsP instance ToField Preferences where toField = toField . encodeJSON instance FromField Preferences where - fromField = fromTextField_ decodeJSON + fromField = fromTextField_ $ decodePrefs chatPrefsP $(J.deriveJSON defaultJSON ''GroupPreference) @@ -1240,13 +1290,22 @@ instance FromJSON CommentsGroupPreference where parseJSON v = $(J.mkParseJSON defaultJSON ''CommentsGroupPreference) v omittedField = Just CommentsGroupPreference {enable = FEOff, duration = Nothing} -$(J.deriveJSON defaultJSON ''GroupPreferences) +$(J.deriveToJSON defaultJSON ''GroupPreferences) + +groupPrefsP :: Value -> JT.Parser GroupPreferences +groupPrefsP = $(J.mkParseJSON defaultJSON ''GroupPreferences) + +instance FromJSON GroupPreferences where + parseJSON v = keepPrefsJSON v <$> groupPrefsP v + +groupPrefsFromRow :: Maybe Text -> Maybe Text -> Maybe GroupPreferences +groupPrefsFromRow = prefsFromRow_ groupPrefsP instance ToField GroupPreferences where toField = toField . encodeJSON instance FromField GroupPreferences where - fromField = fromTextField_ decodeJSON + fromField = fromTextField_ $ decodePrefs groupPrefsP $(J.deriveJSON defaultJSON ''FullPreferences) diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 0a7914d366..d321e37b72 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -40,7 +40,7 @@ import Simplex.Chat.Options.DB import Simplex.Chat.Store import Simplex.Chat.Store.Profiles import Simplex.Chat.Terminal -import Simplex.Chat.Terminal.Output (newChatTerminal) +import Simplex.Chat.Terminal.Output (WithTerminal (..), newChatTerminal) import Simplex.Chat.Types import Simplex.Chat.Types.Shared (GroupMemberRole (..)) import Simplex.FileTransfer.Description (kb, mb) @@ -70,7 +70,7 @@ import Simplex.Messaging.Version.Internal import System.Directory (createDirectoryIfMissing, removeDirectoryRecursive) import System.FilePath ((>)) import qualified System.Terminal as C -import System.Terminal.Internal (VirtualTerminal (..), VirtualTerminalSettings (..), withVirtualTerminal) +import System.Terminal.Internal (Command (..), Terminal (..), VirtualTerminal (..), VirtualTerminalSettings (..), withVirtualTerminal) import System.Timeout (timeout) import Test.Hspec (Expectation, HasCallStack, shouldReturn) #if defined(dbPostgres) @@ -197,13 +197,32 @@ termSettings = data TestCC = TestCC { chatController :: ChatController, - virtualTerminal :: VirtualTerminal, chatAsync :: Async (), - termAsync :: Async (), termQ :: TQueue String, printOutput :: Bool } +data TestTerminal = TestTerminal VirtualTerminal (TQueue String) + +instance Terminal TestTerminal where + termType (TestTerminal t _) = termType t + termEvent (TestTerminal t _) = termEvent t + termInterrupt (TestTerminal t _) = termInterrupt t + termCommand (TestTerminal t q) c = do + case c of + PutLn -> atomically $ do + C.Position {row} <- readTVar $ virtualCursor t + rows <- readTVar $ virtualWindow t + writeTQueue q $ dropWhileEnd (== ' ') $ rows !! row + _ -> pure () + termCommand t c + termFlush (TestTerminal t _) = termFlush t + termGetWindowSize (TestTerminal t _) = termGetWindowSize t + termGetCursorPosition (TestTerminal t _) = termGetCursorPosition t + +instance WithTerminal TestTerminal where + withTerm t = ($ t) + aCfg :: AgentConfig aCfg = (agentConfig defaultChatConfig) {tbqSize = 16} @@ -306,29 +325,31 @@ insertUser st = withTransaction st (`DB.execute_` "INSERT INTO users (user_id) V startTestChat_ :: TestParams -> ChatDatabase -> ChatConfig -> ChatOpts -> String -> User -> IO TestCC startTestChat_ TestParams {tmpPath, printOutput} db cfg opts@ChatOpts {coreOptions = CoreChatOpts {maintenance}} dbPrefix user = do - t <- withVirtualTerminal termSettings pure + termQ <- newTQueueIO + t <- withVirtualTerminal termSettings $ pure . (`TestTerminal` termQ) ct <- newChatTerminal t opts Right cc <- newChatController db (Just user) cfg opts False void $ execChatCommand' (SetTempFolder (tmpPath > dbPrefix)) 0 `runReaderT` cc chatAsync <- async $ runSimplexChat cfg opts user cc $ \_u cc' -> runChatTerminal ct cc' opts unless maintenance $ atomically $ readTVar (agentAsync cc) >>= \a -> when (isNothing a) retry - termQ <- newTQueueIO - termAsync <- async $ readTerminalOutput t termQ - pure TestCC {chatController = cc, virtualTerminal = t, chatAsync, termAsync, termQ, printOutput} + pure TestCC {chatController = cc, chatAsync, termQ, printOutput} stopTestChat :: TestParams -> TestCC -> IO () -stopTestChat ps TestCC {chatController = cc@ChatController {smpAgent, chatStore}, chatAsync, termAsync} = do - stopChatController cc - uninterruptibleCancel termAsync - uninterruptibleCancel chatAsync - liftIO $ disposeAgentClient smpAgent +stopTestChat ps TestCC {chatController = cc@ChatController {smpAgent, chatStore}, chatAsync} = do + stopped <- async $ do + stopChatController cc + cancel chatAsync + disposeAgentClient smpAgent + r <- timeout 60000000 $ wait stopped #if !defined(dbPostgres) chatStats <- withConnection chatStore $ readTVarIO . DB.slow atomically $ modifyTVar' (chatQueryStats ps) $ M.unionWith combineStats chatStats agentStats <- withConnection (agentClientStore smpAgent) $ readTVarIO . DB.slow atomically $ modifyTVar' (agentQueryStats ps) $ M.unionWith combineStats agentStats #endif - closeDBStore chatStore + case r of + Just () -> closeDBStore chatStore + Nothing -> putStrLn "stopTestChat: chat did not stop in 60 seconds" threadDelay 200000 #if !defined(dbPostgres) where @@ -381,7 +402,8 @@ withTestChatOpts :: HasCallStack => TestParams -> ChatOpts -> String -> (HasCall withTestChatOpts ps = withTestChatCfgOpts ps testCfg withTestChatCfgOpts :: HasCallStack => TestParams -> ChatConfig -> ChatOpts -> String -> (HasCallStack => TestCC -> IO a) -> IO a -withTestChatCfgOpts ps cfg opts dbPrefix = bracket (startTestChat ps cfg opts dbPrefix) (\cc -> cc / 100000 >> stopTestChat ps cc) +withTestChatCfgOpts ps cfg opts dbPrefix runTest = + bracket (startTestChat ps cfg opts dbPrefix) (stopTestChat ps) (\cc -> runTest cc >>= ((cc / 100000) $>)) -- enable output for specific test. -- usage: withTestOutput $ testChat2 aliceProfile bobProfile $ \alice bob -> do ... @@ -408,29 +430,6 @@ enableNamesRole TestCC {chatController = cc} = do } enableNames srv@UserServer {roles} = (srv :: UserServer 'PSMP) {roles = (roles :: ServerRolesOverride) {names = Just True}} -readTerminalOutput :: VirtualTerminal -> TQueue String -> IO () -readTerminalOutput t termQ = do - let w = virtualWindow t - winVar <- atomically $ newTVar . init =<< readTVar w - forever . atomically $ do - win <- readTVar winVar - win' <- init <$> readTVar w - if win' == win - then retry - else do - let diff = getDiff win' win - forM_ diff $ writeTQueue termQ - writeTVar winVar win' - where - getDiff :: [String] -> [String] -> [String] - getDiff win win' = getDiff_ 1 (length win) win win' - getDiff_ :: Int -> Int -> [String] -> [String] -> [String] - getDiff_ n len win' win = - let diff = drop (len - n) win' - in if drop n win <> diff == win' - then map (dropWhileEnd (== ' ')) diff - else getDiff_ (n + 1) len win' win - withTmpFiles :: IO () -> IO () withTmpFiles = bracket_ @@ -439,16 +438,15 @@ withTmpFiles = testChatN :: HasCallStack => ChatConfig -> ChatOpts -> [Profile] -> (HasCallStack => [TestCC] -> IO ()) -> TestParams -> IO () testChatN cfg opts ps test params = - bracket (getTestCCs $ zip ps [1 ..]) endTests test + bracket (getTestCCs $ zip ps [1 ..]) (mapConcurrently_ $ stopTestChat params) $ \tcs -> do + test tcs + mapConcurrently_ (/ 100000) tcs where useClientServices = False -- useClientServices = True getTestCCs :: [(Profile, Int)] -> IO [TestCC] getTestCCs [] = pure [] getTestCCs ((p, db) : envs') = (:) <$> createTestChat params cfg opts (show db) useClientServices p <*> getTestCCs envs' - endTests tcs = do - mapConcurrently_ (/ 100000) tcs - mapConcurrently_ (stopTestChat params) tcs (/) :: HasCallStack => TestCC -> Int -> Expectation (/) cc t = timeout t (getTermLine cc) `shouldReturn` Nothing @@ -474,7 +472,7 @@ getTermLine' expected cc@TestCC {printOutput} = error $ name <> ": no output for 5 seconds" <> expectedMsg userName :: TestCC -> IO [Char] -userName (TestCC ChatController {currentUser} _ _ _ _ _) = +userName TestCC {chatController = ChatController {currentUser}} = maybe "no current user" (\User {localDisplayName} -> T.unpack localDisplayName) <$> readTVarIO currentUser testChat :: HasCallStack => Profile -> (HasCallStack => TestCC -> IO ()) -> TestParams -> IO () diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 9319f6e711..6a85f6f463 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -21,7 +21,7 @@ import Data.Aeson (ToJSON) import qualified Data.Aeson as J import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as LB -import Data.List (intercalate, stripPrefix) +import Data.List (intercalate, isPrefixOf, stripPrefix) import qualified Data.Map.Strict as M import Data.Maybe (isJust, isNothing) import qualified Data.Text as T @@ -283,7 +283,8 @@ testRetryConnecting ps = testChatCfgOpts2 cfg' opts' aliceProfile bobProfile tes { agentConfig = testAgentCfg { quotaExceededTimeout = 1, - messageRetryInterval = RetryInterval2 {riFast = fastRetryInterval, riSlow = fastRetryInterval} + messageRetryInterval = RetryInterval2 {riFast = fastRetryInterval, riSlow = fastRetryInterval}, + persistErrorInterval = 0 } } opts' = @@ -1350,18 +1351,23 @@ testNegotiateCall = alice ##> "/_call status @2 connected" alice <## "ok" threadDelay 100000 - alice #$> ("/_get chat @2 count=100", chat, chatFeatures <> [(1, "outgoing call: in progress (00:00)")]) + alice #$> ("/_get chat @2 count=100", callChat, chatFeatures <> [(1, "outgoing call: in progress")]) bob ##> "/_call status @2 connected" bob <## "ok" threadDelay 100000 - bob #$> ("/_get chat @2 count=100", chat, chatFeatures <> [(0, "incoming call: in progress (00:00)")]) + bob #$> ("/_get chat @2 count=100", callChat, chatFeatures <> [(0, "incoming call: in progress")]) -- either party can end the call bob ##> "/_call end @2" bob <## "ok" threadDelay 100000 - bob #$> ("/_get chat @2 count=100", chat, chatFeatures <> [(0, "incoming call: ended (00:00)")]) + bob #$> ("/_get chat @2 count=100", callChat, chatFeatures <> [(0, "incoming call: ended")]) alice <## "call with bob ended" - alice #$> ("/_get chat @2 count=100", chat, chatFeatures <> [(1, "outgoing call: ended (00:00)")]) + alice #$> ("/_get chat @2 count=100", callChat, chatFeatures <> [(1, "outgoing call: ended")]) + where + callChat = map (fmap noDuration) . chat + noDuration s = case words s of + ws@(_ : _) | "(0" `isPrefixOf` last ws -> unwords $ init ws + _ -> s testStopStartChat :: HasCallStack => TestParams -> IO () testStopStartChat ps = @@ -3111,13 +3117,15 @@ testMsgDecryptError ps = withTestChat ps "bob" $ \bob -> do bob <## "subscribed 1 connections on server localhost" alice #> "@bob hello again" - bob <# "alice> skipped message ID 9..11" + bob <# "alice> skipped message ID 7..9" bob <# "alice> hello again" bob #> "@alice received!" alice <# "bob> received!" setupDesynchronizedRatchet :: HasCallStack => TestParams -> TestCC -> IO () setupDesynchronizedRatchet ps alice = do + alice ##> "/set receipts all off" + alice <## "ok" copyDb "bob" "bob_old" withTestChat ps "bob" $ \bob -> do bob <## "subscribed 1 connections on server localhost" diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 66deaae225..0cc697b012 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -4149,13 +4149,15 @@ testGroupMsgDecryptError ps = withTestChat ps "bob" $ \bob -> do bob <## "subscribed 2 connections on server localhost" alice #> "#team hello again" - bob <# "#team alice> skipped message ID 8..10" + bob <# "#team alice> skipped message ID 6..8" bob <# "#team alice> hello again" bob #> "#team received!" alice <# "#team bob> received!" setupDesynchronizedRatchet :: HasCallStack => TestParams -> TestCC -> IO () setupDesynchronizedRatchet ps alice = do + alice ##> "/set receipts all off" + alice <## "ok" copyDb "bob" "bob_old" withTestChat ps "bob" $ \bob -> do bob <## "subscribed 2 connections on server localhost" @@ -4167,6 +4169,7 @@ setupDesynchronizedRatchet ps alice = do bob <# "#team alice> 3" bob #> "#team 4" alice <# "#team bob> 4" + threadDelay 500000 withTestChat ps "bob_old" $ \bob -> do bob <## "subscribed 2 connections on server localhost" bob ##> "/sync #team alice" @@ -5234,6 +5237,9 @@ testMemberContactAccept = cath #$> ("/_get chat @3 count=1", chat, [(0, "requested connection from group team")]) + cath ##> "/_connect contact 1 3" + cath <## "bad chat command: contact is a member contact request" + cath ##> "/accept_member_contact @bob" cath <## "contact bob is accepted, starting connection" concurrently_ diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 10cb6e45ff..2a4eeccb74 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -67,6 +67,7 @@ chatProfileTests = do it "rotate address ratchet keys" testRotateAddressRatchetKeys it "create address on specified server" testCreateAddressOnServer it "retry connecting via contact link" testRetryConnectingViaContactLink + it "retry connecting via address in contact profile after address keys rotation" testRetryConnectingContactViaAddress it "add contact link to profile" testProfileLink it "auto accept contact requests" testUserContactLinkAutoAccept it "deduplicate contact requests" testDeduplicateContactRequests @@ -822,6 +823,50 @@ testRetryConnectingViaContactLink ps = testChatCfgOpts2 cfg' opts' aliceProfile } } +testRetryConnectingContactViaAddress :: HasCallStack => TestParams -> IO () +testRetryConnectingContactViaAddress ps = + withNewTestChatOpts ps testOptsNoFullLinks "alice" aliceProfile $ \alice -> + withNewTestChatCfgOpts ps cfg' opts' "bob" bobProfile $ \bob -> do + alice ##> "/ad" + sLink <- getContactLink_ alice True + alice ##> "/pa on" + alice <## "new contact address set" + rotateAddressKeys alice + case A.parseOnly strP (B.pack sLink) of + Left _ -> error "error parsing contact link" + Right shortLink -> do + void $ withCCUser bob $ \user -> withCCTransaction bob $ \db -> runExceptT $ createContact db (storeCxt $ chatController bob) user aliceProfile {contactLink = Just shortLink} + bob ##> "/_connect contact 1 2" + bob <##. "smp agent error: BROKER" + rotateAddressKeys alice + withSmpServer' serverCfg' $ do + bob ##> "/_connect contact 1 2" + bob <## "connection request sent!" + alice <## "bob (Bob) wants to connect to you!" + alice <## "to accept: /ac bob" + alice <## "to reject: /rc bob (the sender will NOT be notified)" + alice ##> "/ac bob" + alice <## "bob (Bob): accepting contact request, you can send messages to contact" + concurrently_ + (bob <## "alice (Alice): contact is connected") + (alice <## "bob (Bob): contact is connected") + alice <##> bob + bob <## "disconnected 1 connections on server localhost" + where + rotateAddressKeys alice = do + alice ##> "/_rotate_address_keys 1" + _ <- getContactLink_ alice False + alice <## "auto_accept off" + serverCfg' = smpServerCfg {transports = [("7003", transport @TLS, False)]} + cfg' = testCfg {agentConfig = testAgentCfg {persistErrorInterval = 0}} + opts' = + testOpts + { coreOptions = + testCoreOpts + { smpServers = ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7003"] + } + } + testProfileLink :: HasCallStack => TestParams -> IO () testProfileLink = testChat3 aliceProfile bobProfile cathProfile $ @@ -1627,7 +1672,7 @@ testPlanAddressContactViaAddress = bob ##> ("/c " <> cLink) connecting alice bob - bob ##> "/delete @alice" + bob ##> "/delete @alice notify=off" bob <## "alice: contact is deleted" alice ##> "/delete @bob" alice <## "bob: contact is deleted" @@ -1689,7 +1734,7 @@ testPlanAddressContactViaShortAddress = bob ##> ("/c " <> sLink) connecting alice bob - bob ##> "/delete @alice" + bob ##> "/delete @alice notify=off" bob <## "alice: contact is deleted" alice ##> "/delete @bob" alice <## "bob: contact is deleted" diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index 305207fef6..9586957281 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -745,7 +745,7 @@ connectUsers_ cc1 cc2 noShortLink = do (cc1 <## (name2 <> ": contact is connected")) showName :: TestCC -> IO String -showName (TestCC ChatController {currentUser} _ _ _ _ _) = do +showName TestCC {chatController = ChatController {currentUser}} = do Just User {localDisplayName, profile = LocalProfile {fullName, shortDescr}} <- readTVarIO currentUser pure . T.unpack $ viewName localDisplayName <> optionalFullName localDisplayName fullName shortDescr diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index 633bbe876c..554398520e 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -1,5 +1,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} @@ -12,6 +13,7 @@ import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as B import Data.List (isInfixOf) import qualified Data.List.NonEmpty as L +import Data.Maybe (fromMaybe) import Data.Time.Clock.System (SystemTime (..), systemToUTCTime) import Simplex.Chat.Library.Internal (decodeLinkUserData, encodeShortLinkData) import Simplex.Chat.Protocol @@ -33,6 +35,41 @@ protocolTests = do decodeChatMessageTest shortLinkDataTests batchLimitTests + preferencesJSONTests + +preferencesJSONTests :: Spec +preferencesJSONTests = describe "preferences JSON" $ do + it "stores no JSON when preferences encode to what was received" $ do + ps <- prefs "{\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"no\"}}" + storedJSON ps `shouldBe` Nothing + it "stores no JSON when group preferences encode to what was received" $ do + ps <- groupPrefs "{\"voice\":{\"enable\":\"on\"},\"reactions\":{\"enable\":\"off\"}}" + storedJSON ps `shouldBe` Nothing + it "stores JSON with a preference that is not defined" $ do + let s = "{\"voice\":{\"enable\":\"on\"},\"polls\":{\"enable\":\"on\"}}" + ps <- groupPrefs s + storedJSON ps `shouldBe` Just (object s) + J.toJSON ps `shouldBe` object "{\"voice\":{\"enable\":\"on\"}}" + it "stores JSON with a field that is not defined in a preference" $ do + let s = "{\"voice\":{\"enable\":\"on\",\"exceptRole\":\"observer\"}}" + ps <- groupPrefs s + storedJSON ps `shouldBe` Just (object s) + it "reads the received preferences from the stored JSON" $ + groupPrefsFromRow (Just "{\"voice\":{\"enable\":\"on\"}}") (Just "{\"voice\":{\"enable\":\"off\"}}") + `shouldBe` groupPrefs_ "{\"voice\":{\"enable\":\"off\"}}" + it "reads the stored preferences when the received JSON does not parse" $ + groupPrefsFromRow (Just "{\"voice\":{\"enable\":\"on\"}}") (Just "{\"voice\":{\"enable\":\"sometimes\"}}") + `shouldBe` groupPrefs_ "{\"voice\":{\"enable\":\"on\"}}" + where + prefs :: ByteString -> IO Preferences + prefs = either fail pure . J.eitherDecodeStrict' + groupPrefs :: ByteString -> IO GroupPreferences + groupPrefs = either fail pure . J.eitherDecodeStrict' + groupPrefs_ :: ByteString -> Maybe GroupPreferences + groupPrefs_ = J.decodeStrict' + storedJSON ps = J.decodeStrictText =<< snd (prefsToRow $ Just ps) :: Maybe J.Value + object :: ByteString -> J.Value + object s = fromMaybe (error $ "not JSON: " <> B.unpack s) $ J.decodeStrict' s batchLimitTests :: Spec batchLimitTests = describe "Chat message batch limits" $ do @@ -133,10 +170,10 @@ s #==# msg = do s ==# msg testChatPreferences :: Maybe Preferences -testChatPreferences = Just Preferences {voice = Just VoicePreference {allow = FAYes}, files = Nothing, fullDelete = Nothing, timedMessages = Nothing, calls = Nothing, reactions = Just ReactionsPreference {allow = FAYes}, sessions = Nothing, commands = Nothing} +testChatPreferences = Just Preferences {voice = Just VoicePreference {allow = FAYes}, files = Nothing, fullDelete = Nothing, timedMessages = Nothing, calls = Nothing, reactions = Just ReactionsPreference {allow = FAYes}, sessions = Nothing, commands = Nothing, _json = PrefsJSON Nothing} testGroupPreferences :: Maybe GroupPreferences -testGroupPreferences = Just GroupPreferences {timedMessages = Nothing, directMessages = Nothing, reactions = Just ReactionsGroupPreference {enable = FEOn}, voice = Just VoiceGroupPreference {enable = FEOn, role = Nothing}, files = Nothing, fullDelete = Nothing, simplexLinks = Nothing, history = Nothing, reports = Nothing, support = Nothing, sessions = Nothing, comments = Nothing, signMessages = Nothing, commands = Nothing} +testGroupPreferences = Just GroupPreferences {timedMessages = Nothing, directMessages = Nothing, reactions = Just ReactionsGroupPreference {enable = FEOn}, voice = Just VoiceGroupPreference {enable = FEOn, role = Nothing}, files = Nothing, fullDelete = Nothing, simplexLinks = Nothing, history = Nothing, reports = Nothing, support = Nothing, sessions = Nothing, comments = Nothing, signMessages = Nothing, commands = Nothing, _json = PrefsJSON Nothing} testProfile :: Profile testProfile = Profile {displayName = "alice", fullName = "Alice", shortDescr = Nothing, description = Nothing, image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="), peerType = Nothing, contactLink = Nothing, preferences = testChatPreferences, badge = Nothing, contactDomain = Nothing} diff --git a/website/src/crowdfunding.html b/website/src/crowdfunding.html index 32fdac1ba5..a4faa67dc8 100644 --- a/website/src/crowdfunding.html +++ b/website/src/crowdfunding.html @@ -220,61 +220,6 @@ templateEngineOverride: njk color: #dfeaff; } - .cf-hero-offer { - position: relative; - margin-top: calc(var(--sec-vhu) * 6.3); - width: calc(var(--sec-vwu) * 56); - } - .cf-hero-offer p { font-family: "Manrope", sans-serif; } - .cf-hero-offer .cf-offer-lead { - font-weight: 600; - font-size: calc(var(--sec-vwu) * 2.1); - line-height: 1.4; - color: #ffffff; - font-variant-numeric: tabular-nums; - } - .cf-hero-offer .cf-offer-lead b { - font-weight: 700; - background: linear-gradient(90deg, #019bfe 0%, #64fdff 58%, #c8feff 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - color: transparent; - } - .cf-hero-offer .cf-offer-lead span { white-space: nowrap; } - .cf-hero-offer .cf-offer-terms { - font-weight: 300; - font-size: calc(var(--sec-vwu) * 1.55); - line-height: 1.55; - color: #dfeaff; - margin-top: calc(var(--sec-vhu) * 0.4); - } - .cf-hero-offer .cf-offer-terms a { position: relative; color: inherit; text-decoration: none; } - .cf-hero-offer .cf-offer-terms abbr { text-decoration: underline dotted; text-underline-offset: 3px; cursor: pointer; } - .cf-hero-offer .cf-offer-terms a::after { - content: attr(data-tip); - position: absolute; - left: calc(100% + 12px); - top: 50%; - transform: translateY(-50%); - white-space: nowrap; - padding: 7px 12px; - border-radius: 8px; - border: 1px solid rgba(255, 255, 255, .18); - background: rgba(5, 9, 32, .96); - color: #dfeaff; - font-family: "Manrope", sans-serif; - font-weight: 400; - font-size: calc(var(--sec-vwu) * 1.1); - line-height: 1.4; - opacity: 0; - visibility: hidden; - transition: opacity .12s ease; - pointer-events: none; - } - .cf-hero-offer .cf-offer-terms a:hover::after, - .cf-hero-offer .cf-offer-terms a:focus-visible::after { opacity: 1; visibility: visible; } - /* ---- close: same left-aligned layout as the hero ---- */ .cf-close .area { position: relative; display: flex; align-items: flex-end; padding: 0 calc(var(--sec-vwu) * 7.5) calc(var(--sec-vhu) * 22) 0; } @@ -655,7 +600,7 @@ templateEngineOverride: njk --cf-phone-h: calc(var(--cf-phone-w) / 0.4914); --cf-phone-part: 0.55; /* bottom of the hero text block, measured from the section top */ - --cf-hero-text-b: calc(var(--cf-top-base) + max(0px, calc(-1 * var(--cf-area-off))) + 92.5vw); + --cf-hero-text-b: calc(var(--cf-top-base) + max(0px, calc(-1 * var(--cf-area-off))) + 75vw); /* the arc crest starts 0.2678 down the cover image, which is 2.349 phone widths tall and sits 0.4034 phone heights above the phone top: the phone may rise no higher than this */ @@ -714,19 +659,6 @@ templateEngineOverride: njk } .cf-close-offer p { font-size: calc(var(--sec-vwu) * 3.6); } .cf-stats { top: 0; gap: calc(var(--sec-vwu) * 8); margin-top: calc(var(--sec-vhu) * 3); } - .cf-hero .cf-hero-offer { - position: relative; - top: 0; - width: auto; - z-index: 4; - margin-top: calc(var(--sec-vhu) * 2); - padding: 10px var(--cf-text-pad) 12px; - margin-left: calc(-1 * var(--cf-gutter)); - margin-right: calc(-1 * var(--cf-gutter)); - background: linear-gradient(to bottom, rgba(2, 7, 29, 0) 0%, rgba(2, 7, 29, var(--cf-offer-veil, 0)) 22%, rgba(2, 7, 29, var(--cf-offer-veil, 0)) 78%, rgba(2, 7, 29, 0) 100%); - } - .cf-hero-offer .cf-offer-lead { font-size: calc(var(--sec-vwu) * 4.2); line-height: 1.3; white-space: nowrap; } - .cf-hero-offer .cf-offer-terms { font-size: calc(var(--sec-vwu) * 4); margin-top: calc(var(--sec-vhu) * 0.8); } .cf-stat img { width: calc(var(--sec-vwu) * 8); } .cf-stat b { font-size: calc(var(--sec-vwu) * 6.5); } .cf-stat span { font-size: calc(var(--sec-vwu) * 3.4); } @@ -826,11 +758,9 @@ templateEngineOverride: njk .cf-tag .n { font-size: 3.54cqh; line-height: 1.3; } } - /* tall phones (modern ratios, with or without the browser bars): the headline takes the - room the screen has; short ones keep the smaller size. */ @media screen and (max-width: 959px) and (orientation: portrait) and (max-aspect-ratio: 62/100) { .cf-hero h1 { font-size: calc(var(--sec-vwu) * 9.6); } - :root { --cf-hero-text-b: calc(var(--cf-top-base) + max(0px, calc(-1 * var(--cf-area-off))) + 91vw); } + :root { --cf-hero-text-b: calc(var(--cf-top-base) + max(0px, calc(-1 * var(--cf-area-off))) + 72.8vw); } } /* 320pt phones: keep the headline at three lines */ @@ -1106,10 +1036,6 @@ templateEngineOverride: njk
SimpleX name for 7 years
-Invest $500+ by the end of September 22, AoE.
-
@@ -1231,13 +1157,13 @@ templateEngineOverride: njk
Invest $500+ by November 22 during early bird terms and get a SimpleX public name for your channel or business for 5 years, ahead of public launch.
+Invest $500+ during early bird terms and get a SimpleX public name for your channel or business for 5 years, ahead of public launch.