core: agent users (#1727)

This commit is contained in:
JRoberts 2023-01-13 13:54:07 +04:00 committed by GitHub
parent 7323bb4333
commit 424328b9d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 132 additions and 61 deletions

View file

@ -481,7 +481,8 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
}
suspend fun testSMPServer(smpServer: String): SMPTestFailure? {
val r = sendCmd(CC.TestSMPServer(smpServer))
val userId = chatModel.currentUser.value?.userId ?: run { throw Exception("testSMPServer: no current user") }
val r = sendCmd(CC.TestSMPServer(userId, smpServer))
return when (r) {
is CR.SmpTestResult -> r.smpTestFailure
else -> {
@ -1615,7 +1616,7 @@ sealed class CC {
class APIGetGroupLink(val groupId: Long): CC()
class APIGetUserSMPServers(val userId: Long): CC()
class APISetUserSMPServers(val userId: Long, val smpServers: List<ServerCfg>): CC()
class TestSMPServer(val smpServer: String): CC()
class TestSMPServer(val userId: Long, val smpServer: String): CC()
class APISetChatItemTTL(val userId: Long, val seconds: Long?): CC()
class APIGetChatItemTTL(val userId: Long): CC()
class APISetNetworkConfig(val networkConfig: NetCfg): CC()
@ -1686,7 +1687,7 @@ sealed class CC {
is APIGetGroupLink -> "/_get link #$groupId"
is APIGetUserSMPServers -> "/_smp $userId"
is APISetUserSMPServers -> "/_smp $userId ${smpServersStr(smpServers)}"
is TestSMPServer -> "/smp test $smpServer"
is TestSMPServer -> "/smp test $userId $smpServer"
is APISetChatItemTTL -> "/_ttl $userId ${chatItemTTLStr(seconds)}"
is APIGetChatItemTTL -> "/_ttl $userId"
is APISetNetworkConfig -> "/_network ${json.encodeToString(networkConfig)}"

View file

@ -323,7 +323,8 @@ func setUserSMPServers(smpServers: [ServerCfg]) async throws {
}
func testSMPServer(smpServer: String) async throws -> Result<(), SMPTestFailure> {
let r = await chatSendCmd(.testSMPServer(smpServer: smpServer))
guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("testSMPServer: no current user") }
let r = await chatSendCmd(.testSMPServer(userId: userId, smpServer: smpServer))
if case let .smpTestResult(testFailure) = r {
if let t = testFailure {
return .failure(t)

View file

@ -48,7 +48,7 @@ public enum ChatCommand {
case apiGetGroupLink(groupId: Int64)
case apiGetUserSMPServers(userId: Int64)
case apiSetUserSMPServers(userId: Int64, smpServers: [ServerCfg])
case testSMPServer(smpServer: String)
case testSMPServer(userId: Int64, smpServer: String)
case apiSetChatItemTTL(userId: Int64, seconds: Int64?)
case apiGetChatItemTTL(userId: Int64)
case apiSetNetworkConfig(networkConfig: NetCfg)
@ -132,7 +132,7 @@ public enum ChatCommand {
case let .apiGetGroupLink(groupId): return "/_get link #\(groupId)"
case let .apiGetUserSMPServers(userId): return "/_smp \(userId)"
case let .apiSetUserSMPServers(userId, smpServers): return "/_smp \(userId) \(smpServersStr(smpServers: smpServers))"
case let .testSMPServer(smpServer): return "/smp test \(smpServer)"
case let .testSMPServer(userId, smpServer): return "/smp test \(userId) \(smpServer)"
case let .apiSetChatItemTTL(userId, seconds): return "/_ttl \(userId) \(chatItemTTLStr(seconds: seconds))"
case let .apiGetChatItemTTL(userId): return "/_ttl \(userId)"
case let .apiSetNetworkConfig(networkConfig): return "/_network \(encodeJSON(networkConfig))"

View file

@ -7,7 +7,12 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package
type: git
location: https://github.com/simplex-chat/simplexmq.git
tag: 058e3ac55e8577280267f9341ccd7d3e971bc51a
tag: 8e024590bc2b4428e64e625a9c2392908fc5912e
source-repository-package
type: git
location: https://github.com/simplex-chat/hs-socks.git
tag: a30cc7a79a08d8108316094f8f2f82a0c5e1ac51
source-repository-package
type: git

View file

@ -1,5 +1,6 @@
{
"https://github.com/simplex-chat/simplexmq.git"."058e3ac55e8577280267f9341ccd7d3e971bc51a" = "1rw0j3d5higdrq5klsgnj8b8zfh08g5zv72hqcm7wkw1mmllpfrk";
"https://github.com/simplex-chat/simplexmq.git"."8e024590bc2b4428e64e625a9c2392908fc5912e" = "0rgsf1jz2dpqbdpdfpajsi8gry47jl8jqgw13dfxr3ll9v7pr4sf";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
"https://github.com/simplex-chat/direct-sqlcipher.git"."34309410eb2069b029b8fc1872deb1e0db123294" = "0kwkmhyfsn2lixdlgl15smgr1h5gjk7fky6abzh8rng2h5ymnffd";
"https://github.com/simplex-chat/sqlcipher-simple.git"."5e154a2aeccc33ead6c243ec07195ab673137221" = "1d1gc5wax4vqg0801ajsmx1sbwvd9y7p7b8mmskvqsmpbwgbh0m0";
"https://github.com/simplex-chat/aeson.git"."3eb66f9a68f103b5f1489382aad89f5712a64db7" = "0kilkx59fl6c3qy3kjczqvm8c3f4n3p0bdk9biyflf51ljnzp4yp";

View file

@ -74,6 +74,7 @@ library
Simplex.Chat.Migrations.M20221223_idx_chat_items_item_status
Simplex.Chat.Migrations.M20221230_idxs
Simplex.Chat.Migrations.M20230107_connections_auth_err_counter
Simplex.Chat.Migrations.M20230111_users_agent_user_id
Simplex.Chat.Mobile
Simplex.Chat.Options
Simplex.Chat.ProfileGenerator

View file

@ -94,7 +94,7 @@ defaultChatConfig =
},
yesToMigrations = False,
defaultServers =
InitialAgentServers
DefaultAgentServers
{ smp = _defaultSMPServers,
ntf = _defaultNtfServers,
netCfg = defaultNetworkConfig
@ -162,19 +162,25 @@ newChatController ChatDatabase {chatStore, agentStore} user cfg@ChatConfig {agen
showLiveItems <- newTVarIO False
pure ChatController {activeTo, firstTime, currentUser, smpAgent, agentAsync, chatStore, chatStoreChanged, idsDrg, inputQ, outputQ, notifyQ, chatLock, sndFiles, rcvFiles, currentCalls, config, sendNotification, incognitoMode, filesFolder, expireCIsAsync, expireCIs, cleanupManagerAsync, timedItemThreads, showLiveItems}
where
configServers :: InitialAgentServers
configServers :: DefaultAgentServers
configServers =
let smp' = fromMaybe (smp defaultServers) (nonEmpty smpServers)
let smp' = fromMaybe (smp (defaultServers :: DefaultAgentServers)) (nonEmpty smpServers)
in defaultServers {smp = smp', netCfg = networkConfig}
agentServers :: ChatConfig -> IO InitialAgentServers
agentServers config@ChatConfig {defaultServers = ss@InitialAgentServers {smp}} = do
smp' <- maybe (pure smp) userServers user
pure ss {smp = smp'}
agentServers config@ChatConfig {defaultServers = DefaultAgentServers {smp, ntf, netCfg}} = do
users <- withTransaction chatStore getUsers
smp' <- case users of
[] -> pure $ M.fromList [(1, smp)]
_ -> M.fromList <$> initialServers users
pure InitialAgentServers {smp = smp', ntf, netCfg}
where
initialServers :: [User] -> IO [(UserId, NonEmpty SMPServerWithAuth)]
initialServers = mapM (\u -> (aUserId u,) <$> userServers u)
userServers :: User -> IO (NonEmpty SMPServerWithAuth)
userServers user' = activeAgentServers config <$> withTransaction chatStore (`getSMPServers` user')
activeAgentServers :: ChatConfig -> [ServerCfg] -> NonEmpty SMPServerWithAuth
activeAgentServers ChatConfig {defaultServers = InitialAgentServers {smp}} =
activeAgentServers ChatConfig {defaultServers = DefaultAgentServers {smp}} =
fromMaybe smp
. nonEmpty
. map (\ServerCfg {server} -> server)
@ -264,11 +270,17 @@ processChatCommand = \case
ShowActiveUser -> withUser' $ pure . CRActiveUser
CreateActiveUser p -> do
u <- asks currentUser
user <- withStore $ \db -> createUser db p True
-- TODO option to choose current user servers
DefaultAgentServers {smp} <- asks $ defaultServers . config
auId <-
withStore' getUsers >>= \case
[] -> pure 1
_ -> withAgent (`createUser` smp)
user <- withStore $ \db -> createUserRecord db (AgentUserId auId) p True
atomically . writeTVar u $ Just user
pure $ CRActiveUser user
ListUsers -> do
users <- withStore' $ \db -> getUsers db
users <- withStore' getUsers
pure $ CRUsersList users
APISetActiveUser userId -> do
u <- asks currentUser
@ -359,7 +371,7 @@ processChatCommand = \case
(agentConnId_, fileConnReq) <-
if isJust fileInline
then pure (Nothing, Nothing)
else bimap Just Just <$> withAgent (\a -> createConnection a True SCMInvitation Nothing)
else bimap Just Just <$> withAgent (\a -> createConnection a (aUserId user) True SCMInvitation Nothing)
let fileName = takeFileName file
fileInvitation = FileInvitation {fileName, fileSize, fileConnReq, fileInline}
withStore' $ \db -> do
@ -773,7 +785,7 @@ processChatCommand = \case
pure CRNtfMessages {user, connEntity, msgTs = msgTs', ntfMessages}
APIGetUserSMPServers cmdUserId -> withUser $ \user -> do
checkCorrectCmdUser cmdUserId user
ChatConfig {defaultServers = InitialAgentServers {smp = defaultSMPServers}} <- asks config
ChatConfig {defaultServers = DefaultAgentServers {smp = defaultSMPServers}} <- asks config
smpServers <- withStore' (`getSMPServers` user)
let smpServers' = fromMaybe (L.map toServerCfg defaultSMPServers) $ nonEmpty smpServers
pure $ CRUserSMPServers user smpServers' defaultSMPServers
@ -785,11 +797,13 @@ processChatCommand = \case
checkCorrectCmdUser cmdUserId user
withStore $ \db -> overwriteSMPServers db user smpServers
cfg <- asks config
withAgent $ \a -> setSMPServers a $ activeAgentServers cfg smpServers
withAgent $ \a -> setSMPServers a (aUserId user) $ activeAgentServers cfg smpServers
pure $ CRCmdOk (Just user)
SetUserSMPServers smpServersConfig -> withUser $ \User {userId} ->
processChatCommand $ APISetUserSMPServers userId smpServersConfig
TestSMPServer smpServer -> CRSmpTestResult <$> withAgent (`testSMPServerConnection` smpServer)
TestSMPServer cmdUserId smpServer -> withUser $ \user -> do
checkCorrectCmdUser cmdUserId user
CRSmpTestResult <$> (withAgent $ \a -> testSMPServerConnection a (aUserId user) smpServer)
APISetChatItemTTL cmdUserId newTTL_ -> withUser' $ \user -> do
checkCorrectCmdUser cmdUserId user
checkStoreNotChanged $
@ -921,7 +935,7 @@ processChatCommand = \case
-- [incognito] generate profile for connection
incognito <- readTVarIO =<< asks incognitoMode
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
(connId, cReq) <- withAgent $ \a -> createConnection a True SCMInvitation Nothing
(connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing
conn <- withStore' $ \db -> createDirectConnection db userId connId cReq ConnNew incognitoProfile
toView $ CRNewContactConnection user conn
pure $ CRInvitation user cReq
@ -933,7 +947,7 @@ processChatCommand = \case
incognito <- readTVarIO =<< asks incognitoMode
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
let profileToSend = userProfileToSend user incognitoProfile Nothing
connId <- withAgent $ \a -> joinConnection a True cReq . directMessage $ XInfo profileToSend
connId <- withAgent $ \a -> joinConnection a (aUserId user) True cReq . directMessage $ XInfo profileToSend
conn <- withStore' $ \db -> createDirectConnection db userId connId cReq ConnJoined $ incognitoProfile $> profileToSend
toView $ CRNewContactConnection user conn
pure $ CRSentConfirmation user
@ -957,7 +971,7 @@ processChatCommand = \case
processChatCommand $ APIListContacts userId
APICreateMyAddress cmdUserId -> withUser $ \user@User {userId} -> withChatLock "createMyAddress" . procCmd $ do
checkCorrectCmdUser cmdUserId user
(connId, cReq) <- withAgent $ \a -> createConnection a True SCMContact Nothing
(connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMContact Nothing
withStore $ \db -> createUserContactLink db userId connId cReq
pure $ CRUserContactLinkCreated user cReq
CreateMyAddress -> withUser $ \User {userId} ->
@ -1047,7 +1061,7 @@ processChatCommand = \case
case contactMember contact members of
Nothing -> do
gVar <- asks idsDrg
(agentConnId, cReq) <- withAgent $ \a -> createConnection a True SCMInvitation Nothing
(agentConnId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing
member <- withStore $ \db -> createNewContactMember db gVar user groupId contact memRole agentConnId cReq
sendInvitation member cReq
pure $ CRSentGroupInvitation user gInfo contact member
@ -1063,7 +1077,7 @@ processChatCommand = \case
APIJoinGroup groupId -> withUser $ \user@User {userId} -> do
ReceivedGroupInvitation {fromMember, connRequest, groupInfo = g@GroupInfo {membership}} <- withStore $ \db -> getGroupInvitation db user groupId
withChatLock "joinGroup" . procCmd $ do
agentConnId <- withAgent $ \a -> joinConnection a True connRequest . directMessage $ XGrpAcpt (memberId (membership :: GroupMember))
agentConnId <- withAgent $ \a -> joinConnection a (aUserId user) True connRequest . directMessage $ XGrpAcpt (memberId (membership :: GroupMember))
withStore' $ \db -> do
createMemberConnection db userId fromMember agentConnId
updateGroupMemberStatus db userId fromMember GSMemAccepted
@ -1180,7 +1194,7 @@ processChatCommand = \case
unless (memberActive membership) $ throwChatError CEGroupMemberNotActive
groupLinkId <- GroupLinkId <$> (asks idsDrg >>= liftIO . (`randomBytes` 16))
let crClientData = encodeJSON $ CRDataGroup groupLinkId
(connId, cReq) <- withAgent $ \a -> createConnection a True SCMContact $ Just crClientData
(connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMContact $ Just crClientData
withStore $ \db -> createGroupLink db user gInfo connId cReq groupLinkId
pure $ CRGroupLinkCreated user gInfo cReq
APIDeleteGroupLink groupId -> withUser $ \user -> withChatLock "deleteGroupLink" $ do
@ -1388,7 +1402,7 @@ processChatCommand = \case
incognito <- readTVarIO =<< asks incognitoMode
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
let profileToSend = userProfileToSend user incognitoProfile Nothing
connId <- withAgent $ \a -> joinConnection a True cReq $ directMessage (XContact profileToSend $ Just xContactId)
connId <- withAgent $ \a -> joinConnection a (aUserId user) True cReq $ directMessage (XContact profileToSend $ Just xContactId)
let groupLinkId = crClientData >>= decodeJSON >>= \(CRDataGroup gli) -> Just gli
conn <- withStore' $ \db -> createConnReqConnection db userId connId cReqHash xContactId incognitoProfile groupLinkId
toView $ CRNewContactConnection user conn
@ -3563,13 +3577,13 @@ markGroupCIDeleted user gInfo ci@(CChatItem msgDir deletedItem) msgId byUser = d
createAgentConnectionAsync :: forall m c. (ChatMonad m, ConnectionModeI c) => User -> CommandFunction -> Bool -> SConnectionMode c -> m (CommandId, ConnId)
createAgentConnectionAsync user cmdFunction enableNtfs cMode = do
cmdId <- withStore' $ \db -> createCommand db user Nothing cmdFunction
connId <- withAgent $ \a -> createConnectionAsync a (aCorrId cmdId) enableNtfs cMode
connId <- withAgent $ \a -> createConnectionAsync a (aUserId user) (aCorrId cmdId) enableNtfs cMode
pure (cmdId, connId)
joinAgentConnectionAsync :: ChatMonad m => User -> Bool -> ConnectionRequestUri c -> ConnInfo -> m (CommandId, ConnId)
joinAgentConnectionAsync user enableNtfs cReqUri cInfo = do
cmdId <- withStore' $ \db -> createCommand db user Nothing CFJoinConn
connId <- withAgent $ \a -> joinConnectionAsync a (aCorrId cmdId) enableNtfs cReqUri cInfo
connId <- withAgent $ \a -> joinConnectionAsync a (aUserId user) (aCorrId cmdId) enableNtfs cReqUri cInfo
pure (cmdId, connId)
allowAgentConnectionAsync :: (MsgEncodingI e, ChatMonad m) => User -> Connection -> ConfirmationId -> ChatMsgEvent e -> m ()
@ -3684,7 +3698,7 @@ getCreateActiveUser st = do
loop = do
displayName <- getContactName
fullName <- T.pack <$> getWithPrompt "full name (optional)"
withTransaction st (\db -> runExceptT $ createUser db Profile {displayName, fullName, image = Nothing, preferences = Nothing} True) >>= \case
withTransaction st (\db -> runExceptT $ createUserRecord db (AgentUserId 1) Profile {displayName, fullName, image = Nothing, preferences = Nothing} True) >>= \case
Left SEDuplicateName -> do
putStrLn "chosen display name is already used by another profile on this device, choose another one"
loop
@ -3848,7 +3862,7 @@ chatCommandP =
"/smp_servers " *> (SetUserSMPServers . SMPServersConfig . map toServerCfg <$> smpServersP),
"/smp_servers" $> GetUserSMPServers,
"/smp default" $> SetUserSMPServers (SMPServersConfig []),
"/smp test " *> (TestSMPServer <$> strP),
"/smp test " *> (TestSMPServer <$> A.decimal <* A.space <*> strP),
"/_smp " *> (APISetUserSMPServers <$> A.decimal <* A.space <*> jsonP),
"/smp " *> (SetUserSMPServers . SMPServersConfig . map toServerCfg <$> smpServersP),
"/_smp " *> (APIGetUserSMPServers <$> A.decimal),

View file

@ -43,7 +43,7 @@ import Simplex.Chat.Store (AutoAccept, StoreError, UserContactLink)
import Simplex.Chat.Types
import Simplex.Messaging.Agent (AgentClient)
import Simplex.Messaging.Agent.Client (AgentLocks, SMPTestFailure)
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, InitialAgentServers, NetworkConfig)
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig)
import Simplex.Messaging.Agent.Lock
import Simplex.Messaging.Agent.Protocol
import Simplex.Messaging.Agent.Store.SQLite (SQLiteStore)
@ -51,7 +51,7 @@ import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), NtfTknStatus)
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, parseAll, parseString, sumTypeJSON)
import Simplex.Messaging.Protocol (AProtocolType, CorrId, MsgFlags)
import Simplex.Messaging.Protocol (AProtocolType, CorrId, MsgFlags, NtfServer)
import Simplex.Messaging.TMap (TMap)
import Simplex.Messaging.Transport.Client (TransportHost)
import System.IO (Handle)
@ -70,7 +70,7 @@ updateStr = "To update run: curl -o- https://raw.githubusercontent.com/simplex-c
data ChatConfig = ChatConfig
{ agentConfig :: AgentConfig,
yesToMigrations :: Bool,
defaultServers :: InitialAgentServers,
defaultServers :: DefaultAgentServers,
tbqSize :: Natural,
fileChunkSize :: Integer,
inlineFiles :: InlineFilesConfig,
@ -80,6 +80,12 @@ data ChatConfig = ChatConfig
testView :: Bool
}
data DefaultAgentServers = DefaultAgentServers
{ smp :: NonEmpty SMPServerWithAuth,
ntf :: [NtfServer],
netCfg :: NetworkConfig
}
data InlineFilesConfig = InlineFilesConfig
{ offerChunks :: Integer,
sendChunks :: Integer,
@ -203,7 +209,7 @@ data ChatCommand
| GetUserSMPServers
| APISetUserSMPServers UserId SMPServersConfig
| SetUserSMPServers SMPServersConfig
| TestSMPServer SMPServerWithAuth
| TestSMPServer UserId SMPServerWithAuth
| APISetChatItemTTL UserId (Maybe Int64)
| SetChatItemTTL (Maybe Int64)
| APIGetChatItemTTL UserId

View file

@ -0,0 +1,17 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20230111_users_agent_user_id where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
m20230111_users_agent_user_id :: Query
m20230111_users_agent_user_id =
[sql|
PRAGMA ignore_check_constraints=ON;
ALTER TABLE users ADD COLUMN agent_user_id INTEGER CHECK (agent_user_id NOT NULL);
UPDATE users SET agent_user_id = 1;
PRAGMA ignore_check_constraints=OFF;
|]

View file

@ -29,7 +29,8 @@ CREATE TABLE users(
local_display_name TEXT NOT NULL UNIQUE,
active_user INTEGER NOT NULL DEFAULT 0,
created_at TEXT CHECK(created_at NOT NULL),
updated_at TEXT CHECK(updated_at NOT NULL), -- 1 for active user
updated_at TEXT CHECK(updated_at NOT NULL),
agent_user_id INTEGER CHECK(agent_user_id NOT NULL), -- 1 for active user
FOREIGN KEY(user_id, local_display_name)
REFERENCES display_names(user_id, local_display_name)
ON DELETE CASCADE

View file

@ -25,7 +25,7 @@ module Simplex.Chat.Store
createChatStore,
chatStoreFile,
agentStoreFile,
createUser,
createUserRecord,
getUsers,
setActiveUser,
getSetActiveUser,
@ -330,6 +330,7 @@ import Simplex.Chat.Migrations.M20221222_chat_ts
import Simplex.Chat.Migrations.M20221223_idx_chat_items_item_status
import Simplex.Chat.Migrations.M20221230_idxs
import Simplex.Chat.Migrations.M20230107_connections_auth_err_counter
import Simplex.Chat.Migrations.M20230111_users_agent_user_id
import Simplex.Chat.Protocol
import Simplex.Chat.Types
import Simplex.Chat.Util (week)
@ -390,7 +391,8 @@ schemaMigrations =
("20221222_chat_ts", m20221222_chat_ts),
("20221223_idx_chat_items_item_status", m20221223_idx_chat_items_item_status),
("20221230_idxs", m20221230_idxs),
("20230107_connections_auth_err_counter", m20230107_connections_auth_err_counter)
("20230107_connections_auth_err_counter", m20230107_connections_auth_err_counter),
("20230111_users_agent_user_id", m20230111_users_agent_user_id)
]
-- | The list of migrations in ascending order by date
@ -419,15 +421,15 @@ handleSQLError err e
insertedRowId :: DB.Connection -> IO Int64
insertedRowId db = fromOnly . head <$> DB.query_ db "SELECT last_insert_rowid()"
createUser :: DB.Connection -> Profile -> Bool -> ExceptT StoreError IO User
createUser db Profile {displayName, fullName, image, preferences = userPreferences} activeUser =
createUserRecord :: DB.Connection -> AgentUserId -> Profile -> Bool -> ExceptT StoreError IO User
createUserRecord db (AgentUserId auId) Profile {displayName, fullName, image, preferences = userPreferences} activeUser =
checkConstraint SEDuplicateName . liftIO $ do
currentTs <- getCurrentTime
when activeUser $ DB.execute_ db "UPDATE users SET active_user = 0"
DB.execute
db
"INSERT INTO users (local_display_name, active_user, contact_id, created_at, updated_at) VALUES (?,?,0,?,?)"
(displayName, activeUser, currentTs, currentTs)
"INSERT INTO users (agent_user_id, local_display_name, active_user, contact_id, created_at, updated_at) VALUES (?,?,?,0,?,?)"
(auId, displayName, activeUser, currentTs, currentTs)
userId <- insertedRowId db
DB.execute
db
@ -444,7 +446,7 @@ createUser db Profile {displayName, fullName, image, preferences = userPreferenc
(profileId, displayName, userId, True, currentTs, currentTs)
contactId <- insertedRowId db
DB.execute db "UPDATE users SET contact_id = ? WHERE user_id = ?" (contactId, userId)
pure $ toUser (userId, contactId, profileId, activeUser, displayName, fullName, image, userPreferences)
pure $ toUser (userId, auId, contactId, profileId, activeUser, displayName, fullName, image, userPreferences)
getUsers :: DB.Connection -> IO [User]
getUsers db =
@ -453,16 +455,16 @@ getUsers db =
userQuery :: Query
userQuery =
[sql|
SELECT u.user_id, u.contact_id, cp.contact_profile_id, u.active_user, u.local_display_name, cp.full_name, cp.image, cp.preferences
SELECT u.user_id, u.agent_user_id, u.contact_id, cp.contact_profile_id, u.active_user, u.local_display_name, cp.full_name, cp.image, cp.preferences
FROM users u
JOIN contacts ct ON ct.contact_id = u.contact_id
JOIN contact_profiles cp ON cp.contact_profile_id = ct.contact_profile_id
|]
toUser :: (UserId, ContactId, ProfileId, Bool, ContactName, Text, Maybe ImageData, Maybe Preferences) -> User
toUser (userId, userContactId, profileId, activeUser, displayName, fullName, image, userPreferences) =
toUser :: (UserId, UserId, ContactId, ProfileId, Bool, ContactName, Text, Maybe ImageData, Maybe Preferences) -> User
toUser (userId, auId, userContactId, profileId, activeUser, displayName, fullName, image, userPreferences) =
let profile = LocalProfile {profileId, displayName, fullName, image, preferences = userPreferences, localAlias = ""}
in User {userId, userContactId, localDisplayName = displayName, profile, activeUser, fullPreferences = mergePreferences Nothing userPreferences}
in User {userId, agentUserId = AgentUserId auId, userContactId, localDisplayName = displayName, profile, activeUser, fullPreferences = mergePreferences Nothing userPreferences}
setActiveUser :: DB.Connection -> UserId -> IO ()
setActiveUser db userId = do

View file

@ -17,7 +17,6 @@ import Simplex.Chat.Options
import Simplex.Chat.Terminal.Input
import Simplex.Chat.Terminal.Notification
import Simplex.Chat.Terminal.Output
import Simplex.Messaging.Agent.Env.SQLite (InitialAgentServers (..))
import Simplex.Messaging.Client (defaultNetworkConfig)
import Simplex.Messaging.Util (raceAny_)
import System.Exit (exitFailure)
@ -26,7 +25,7 @@ terminalChatConfig :: ChatConfig
terminalChatConfig =
defaultChatConfig
{ defaultServers =
InitialAgentServers
DefaultAgentServers
{ smp =
L.fromList
[ "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im,o5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion",

View file

@ -80,8 +80,31 @@ instance IsContact Contact where
preferences' Contact {profile = LocalProfile {preferences}} = preferences
{-# INLINE preferences' #-}
newtype AgentUserId = AgentUserId UserId
deriving (Eq, Show)
instance StrEncoding AgentUserId where
strEncode (AgentUserId uId) = strEncode uId
strDecode s = AgentUserId <$> strDecode s
strP = AgentUserId <$> strP
instance FromJSON AgentUserId where
parseJSON = strParseJSON "AgentUserId"
instance ToJSON AgentUserId where
toJSON = strToJSON
toEncoding = strToJEncoding
instance FromField AgentUserId where fromField f = AgentUserId <$> fromField f
instance ToField AgentUserId where toField (AgentUserId uId) = toField uId
aUserId :: User -> UserId
aUserId User {agentUserId = AgentUserId uId} = uId
data User = User
{ userId :: UserId,
agentUserId :: AgentUserId,
userContactId :: ContactId,
localDisplayName :: ContactName,
profile :: LocalProfile,
@ -92,7 +115,7 @@ data User = User
instance ToJSON User where toEncoding = J.genericToEncoding J.defaultOptions
type UserId = ContactId
type UserId = Int64
type ContactId = Int64

View file

@ -49,7 +49,7 @@ extra-deps:
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
# - ../simplexmq
- github: simplex-chat/simplexmq
commit: 058e3ac55e8577280267f9341ccd7d3e971bc51a
commit: 8e024590bc2b4428e64e625a9c2392908fc5912e
# - ../direct-sqlcipher
- github: simplex-chat/direct-sqlcipher
commit: 34309410eb2069b029b8fc1872deb1e0db123294

View file

@ -25,7 +25,7 @@ import Simplex.Chat.Options
import Simplex.Chat.Store
import Simplex.Chat.Terminal
import Simplex.Chat.Terminal.Output (newChatTerminal)
import Simplex.Chat.Types (Profile, User (..))
import Simplex.Chat.Types (AgentUserId (..), Profile, User (..))
import Simplex.Messaging.Agent.Env.SQLite
import Simplex.Messaging.Agent.RetryInterval
import Simplex.Messaging.Client (ProtocolClientConfig (..), defaultNetworkConfig)
@ -108,7 +108,7 @@ testCfgV1 = testCfg {agentConfig = testAgentCfgV1}
createTestChat :: ChatConfig -> ChatOpts -> String -> Profile -> IO TestCC
createTestChat cfg opts@ChatOpts {dbKey} dbPrefix profile = do
db@ChatDatabase {chatStore} <- createChatDatabase (testDBPrefix <> dbPrefix) dbKey False
Right user <- withTransaction chatStore $ \db' -> runExceptT $ createUser db' profile True
Right user <- withTransaction chatStore $ \db' -> runExceptT $ createUserRecord db' (AgentUserId 1) profile True
startTestChat_ db cfg opts user
startTestChat :: ChatConfig -> ChatOpts -> String -> IO TestCC

View file

@ -3800,14 +3800,14 @@ testTestSMPServerConnection :: IO ()
testTestSMPServerConnection =
testChat2 aliceProfile bobProfile $
\alice _ -> do
alice ##> "/smp test smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=@localhost:5001"
alice ##> "/smp test 1 smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=@localhost:5001"
alice <## "SMP server test passed"
-- to test with password:
-- alice <## "SMP server test failed at CreateQueue, error: SMP AUTH"
-- alice <## "Server requires authorization to create queues, check password"
alice ##> "/smp test smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:5001"
alice ##> "/smp test 1 smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:5001"
alice <## "SMP server test passed"
alice ##> "/smp test smp://LcJU@localhost:5001"
alice ##> "/smp test 1 smp://LcJU@localhost:5001"
alice <## "SMP server test failed at Connect, error: BROKER smp://LcJU@localhost:5001 NETWORK"
alice <## "Possibly, certificate fingerprint in server address is incorrect"

View file

@ -7,7 +7,7 @@ import ChatTests
import Control.Monad.Except
import Simplex.Chat.Mobile
import Simplex.Chat.Store
import Simplex.Chat.Types (Profile (..))
import Simplex.Chat.Types (AgentUserId (..), Profile (..))
import Test.Hspec
mobileTests :: Spec
@ -32,9 +32,9 @@ activeUserExists = "{\"resp\":{\"type\":\"chatCmdError\",\"chatError\":{\"type\"
activeUser :: String
#if defined(darwin_HOST_OS) && defined(swiftJSON)
activeUser = "{\"resp\":{\"activeUser\":{\"user\":{\"userId\":1,\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"no\"},\"fullDelete\":{\"allow\":\"no\"},\"voice\":{\"allow\":\"yes\"}},\"activeUser\":true}}}"
activeUser = "{\"resp\":{\"activeUser\":{\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"no\"},\"fullDelete\":{\"allow\":\"no\"},\"voice\":{\"allow\":\"yes\"}},\"activeUser\":true}}}"
#else
activeUser = "{\"resp\":{\"type\":\"activeUser\",\"user\":{\"userId\":1,\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"no\"},\"fullDelete\":{\"allow\":\"no\"},\"voice\":{\"allow\":\"yes\"}},\"activeUser\":true}}}"
activeUser = "{\"resp\":{\"type\":\"activeUser\",\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"no\"},\"fullDelete\":{\"allow\":\"no\"},\"voice\":{\"allow\":\"yes\"}},\"activeUser\":true}}}"
#endif
chatStarted :: String
@ -93,7 +93,7 @@ testChatApi = withTmpFiles $ do
let dbPrefix = testDBPrefix <> "1"
f = chatStoreFile dbPrefix
st <- createChatStore f "myKey" True
Right _ <- withTransaction st $ \db -> runExceptT $ createUser db aliceProfile {preferences = Nothing} True
Right _ <- withTransaction st $ \db -> runExceptT $ createUserRecord db (AgentUserId 1) aliceProfile {preferences = Nothing} True
Right cc <- chatMigrateInit dbPrefix "myKey"
Left (DBMErrorNotADatabase _) <- chatMigrateInit dbPrefix ""
Left (DBMErrorNotADatabase _) <- chatMigrateInit dbPrefix "anotherKey"