2025-01-10 15:27:29 +04:00
{- # LANGUAGE CPP # -}
2024-11-14 17:43:34 +00:00
{- # LANGUAGE DataKinds # -}
2023-10-05 21:49:20 +03:00
{- # LANGUAGE DeriveAnyClass # -}
2023-06-18 10:20:11 +01:00
{- # LANGUAGE DuplicateRecordFields # -}
2024-11-14 17:43:34 +00:00
{- # LANGUAGE GADTs # -}
{- # LANGUAGE LambdaCase # -}
2023-06-18 10:20:11 +01:00
{- # LANGUAGE NamedFieldPuns # -}
{- # LANGUAGE OverloadedStrings # -}
{- # LANGUAGE QuasiQuotes # -}
{- # LANGUAGE ScopedTypeVariables # -}
2023-10-26 15:44:50 +01:00
{- # LANGUAGE TemplateHaskell # -}
2023-11-07 17:45:59 +04:00
{- # LANGUAGE TupleSections # -}
2023-06-18 10:20:11 +01:00
{- # LANGUAGE TypeApplications # -}
{- # LANGUAGE TypeOperators # -}
2023-08-25 04:56:37 +08:00
{- # OPTIONS_GHC - fno - warn - ambiguous - fields # -}
2023-06-18 10:20:11 +01:00
module Simplex.Chat.Store.Profiles
( AutoAccept ( .. ) ,
2023-07-13 23:48:25 +01:00
UserMsgReceiptSettings ( .. ) ,
2023-06-18 10:20:11 +01:00
UserContactLink ( .. ) ,
2025-03-03 18:57:29 +00:00
GroupLinkInfo ( .. ) ,
2023-06-18 10:20:11 +01:00
createUserRecord ,
createUserRecordAt ,
getUsersInfo ,
getUsers ,
setActiveUser ,
getUser ,
getUserIdByName ,
getUserByAConnId ,
getUserByASndFileId ,
getUserByARcvFileId ,
getUserByContactId ,
getUserByGroupId ,
2024-01-11 19:01:44 +02:00
getUserByNoteFolderId ,
2023-06-18 10:20:11 +01:00
getUserByFileId ,
getUserFileInfo ,
deleteUserRecord ,
updateUserPrivacy ,
2023-07-13 23:48:25 +01:00
updateAllContactReceipts ,
updateUserContactReceipts ,
2023-07-26 14:49:35 +04:00
updateUserGroupReceipts ,
2023-06-18 10:20:11 +01:00
updateUserProfile ,
setUserProfileContactLink ,
getUserContactProfiles ,
createUserContactLink ,
getUserAddressConnections ,
getUserContactLinks ,
deleteUserAddress ,
getUserAddress ,
getUserContactLinkById ,
2025-03-03 18:57:29 +00:00
getGroupLinkInfo ,
2023-10-10 21:19:04 +04:00
getUserContactLinkByConnReq ,
2025-04-14 21:25:32 +01:00
getUserContactLinkViaShortLink ,
2023-11-07 17:45:59 +04:00
getContactWithoutConnViaAddress ,
2023-06-18 10:20:11 +01:00
updateUserAddressAutoAccept ,
getProtocolServers ,
2024-11-14 17:43:34 +00:00
insertProtocolServer ,
getUpdateServerOperators ,
2024-11-04 13:28:57 +00:00
getServerOperators ,
2024-11-14 17:43:34 +00:00
getUserServers ,
2024-11-05 14:15:20 +04:00
setServerOperators ,
2024-11-04 21:11:03 +04:00
getCurrentUsageConditions ,
2024-11-05 14:15:20 +04:00
getLatestAcceptedConditions ,
2024-11-05 21:40:33 +04:00
setConditionsNotified ,
acceptConditions ,
setUserServers ,
2024-11-18 18:44:28 +00:00
setUserServers' ,
2023-06-18 10:20:11 +01:00
createCall ,
deleteCalls ,
getCalls ,
createCommand ,
setCommandConnId ,
deleteCommand ,
updateCommandStatus ,
getCommandDataByCorrId ,
2024-05-08 15:36:20 +01:00
setUserUIThemes ,
2023-06-18 10:20:11 +01:00
)
where
2023-08-25 04:56:37 +08:00
import Control.Monad
2023-06-18 10:20:11 +01:00
import Control.Monad.Except
2023-08-25 04:56:37 +08:00
import Control.Monad.IO.Class
2023-10-26 15:44:50 +01:00
import qualified Data.Aeson.TH as J
2023-06-18 10:20:11 +01:00
import Data.Functor ( ( $> ) )
import Data.Int ( Int64 )
2023-11-26 18:16:37 +00:00
import Data.List.NonEmpty ( NonEmpty )
2023-06-18 10:20:11 +01:00
import qualified Data.List.NonEmpty as L
2024-11-18 18:44:28 +00:00
import Data.Maybe ( catMaybes , fromMaybe )
2024-11-14 17:43:34 +00:00
import Data.Text ( Text )
import qualified Data.Text as T
2023-06-18 10:20:11 +01:00
import Data.Text.Encoding ( decodeLatin1 , encodeUtf8 )
2024-11-05 21:40:33 +04:00
import Data.Time.Clock ( UTCTime ( .. ) , getCurrentTime )
2023-06-18 10:20:11 +01:00
import Simplex.Chat.Call
import Simplex.Chat.Messages
2024-11-04 13:28:57 +00:00
import Simplex.Chat.Operators
2023-06-18 10:20:11 +01:00
import Simplex.Chat.Protocol
import Simplex.Chat.Store.Direct
import Simplex.Chat.Store.Shared
import Simplex.Chat.Types
2023-07-21 21:32:28 +01:00
import Simplex.Chat.Types.Preferences
2024-04-04 20:41:56 +01:00
import Simplex.Chat.Types.Shared
2024-05-08 15:36:20 +01:00
import Simplex.Chat.Types.UITheme
2024-11-14 17:43:34 +00:00
import Simplex.Messaging.Agent.Env.SQLite ( ServerRoles ( .. ) )
2025-04-14 21:25:32 +01:00
import Simplex.Messaging.Agent.Protocol ( ACorrId , ConnId , ConnectionLink ( .. ) , CreatedConnLink ( .. ) , UserId )
2024-12-28 12:35:34 +00:00
import Simplex.Messaging.Agent.Store.AgentStore ( firstRow , maybeFirstRow )
2025-01-10 15:27:29 +04:00
import Simplex.Messaging.Agent.Store.DB ( BoolInt ( .. ) )
import qualified Simplex.Messaging.Agent.Store.DB as DB
2023-06-18 10:20:11 +01:00
import qualified Simplex.Messaging.Crypto as C
2024-03-07 17:39:09 +04:00
import qualified Simplex.Messaging.Crypto.Ratchet as CR
2023-06-18 10:20:11 +01:00
import Simplex.Messaging.Encoding.String
2023-10-26 15:44:50 +01:00
import Simplex.Messaging.Parsers ( defaultJSON )
2024-11-18 18:44:28 +00:00
import Simplex.Messaging.Protocol ( BasicAuth ( .. ) , ProtoServerWithAuth ( .. ) , ProtocolServer ( .. ) , ProtocolType ( .. ) , ProtocolTypeI ( .. ) , SProtocolType ( .. ) , SubscriptionMode )
2023-06-18 10:20:11 +01:00
import Simplex.Messaging.Transport.Client ( TransportHost )
2023-11-26 18:16:37 +00:00
import Simplex.Messaging.Util ( eitherToMaybe , safeDecodeUtf8 )
2025-01-10 15:27:29 +04:00
# if defined ( dbPostgres )
import Database.PostgreSQL.Simple ( Only ( .. ) , Query , ( :. ) ( .. ) )
import Database.PostgreSQL.Simple.SqlQQ ( sql )
# else
import Database.SQLite.Simple ( Only ( .. ) , Query , ( :. ) ( .. ) )
import Database.SQLite.Simple.QQ ( sql )
# endif
2023-06-18 10:20:11 +01:00
createUserRecord :: DB . Connection -> AgentUserId -> Profile -> Bool -> ExceptT StoreError IO User
createUserRecord db auId p activeUser = createUserRecordAt db auId p activeUser =<< liftIO getCurrentTime
createUserRecordAt :: DB . Connection -> AgentUserId -> Profile -> Bool -> UTCTime -> ExceptT StoreError IO User
createUserRecordAt db ( AgentUserId auId ) Profile { displayName , fullName , image , preferences = userPreferences } activeUser currentTs =
checkConstraint SEDuplicateName . liftIO $ do
when activeUser $ DB . execute_ db " UPDATE users SET active_user = 0 "
2023-07-13 23:48:25 +01:00
let showNtfs = True
sendRcptsContacts = True
2023-07-26 14:49:35 +04:00
sendRcptsSmallGroups = True
2024-09-21 13:07:27 +01:00
order <- getNextActiveOrder db
2023-06-18 10:20:11 +01:00
DB . execute
db
2024-09-21 13:07:27 +01:00
" INSERT INTO users (agent_user_id, local_display_name, active_user, active_order, contact_id, show_ntfs, send_rcpts_contacts, send_rcpts_small_groups, created_at, updated_at) VALUES (?,?,?,?,0,?,?,?,?,?) "
2025-01-10 15:27:29 +04:00
( auId , displayName , BI activeUser , order , BI showNtfs , BI sendRcptsContacts , BI sendRcptsSmallGroups , currentTs , currentTs )
2023-06-18 10:20:11 +01:00
userId <- insertedRowId db
DB . execute
db
" INSERT INTO display_names (local_display_name, ldn_base, user_id, created_at, updated_at) VALUES (?,?,?,?,?) "
( displayName , displayName , userId , currentTs , currentTs )
DB . execute
db
" INSERT INTO contact_profiles (display_name, full_name, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?) "
( displayName , fullName , image , userId , userPreferences , currentTs , currentTs )
profileId <- insertedRowId db
DB . execute
db
2023-12-11 15:50:32 +02:00
" INSERT INTO contacts (contact_profile_id, local_display_name, user_id, is_user, created_at, updated_at, chat_ts) VALUES (?,?,?,?,?,?,?) "
2025-01-10 15:27:29 +04:00
( profileId , displayName , userId , BI True , currentTs , currentTs , currentTs )
2023-06-18 10:20:11 +01:00
contactId <- insertedRowId db
DB . execute db " UPDATE users SET contact_id = ? WHERE user_id = ? " ( contactId , userId )
2025-01-10 15:27:29 +04:00
pure $ toUser $ ( userId , auId , contactId , profileId , BI activeUser , order , displayName , fullName , image , Nothing , userPreferences ) :. ( BI showNtfs , BI sendRcptsContacts , BI sendRcptsSmallGroups , Nothing , Nothing , Nothing , Nothing )
2023-06-18 10:20:11 +01:00
2025-02-03 20:47:32 +00:00
-- TODO [mentions]
2023-06-18 10:20:11 +01:00
getUsersInfo :: DB . Connection -> IO [ UserInfo ]
getUsersInfo db = getUsers db >>= mapM getUserInfo
where
getUserInfo :: User -> IO UserInfo
getUserInfo user @ User { userId } = do
ctCount <-
maybeFirstRow fromOnly $
DB . query
db
[ sql |
SELECT COUNT ( 1 )
FROM chat_items i
JOIN contacts ct USING ( contact_id )
WHERE i . user_id = ? AND i . item_status = ? AND ( ct . enable_ntfs = 1 OR ct . enable_ntfs IS NULL ) AND ct . deleted = 0
| ]
( userId , CISRcvNew )
gCount <-
maybeFirstRow fromOnly $
DB . query
db
[ sql |
SELECT COUNT ( 1 )
FROM chat_items i
JOIN groups g USING ( group_id )
2025-02-02 23:30:52 +00:00
WHERE i . user_id = ? AND i . item_status = ?
AND ( g . enable_ntfs = 1 OR g . enable_ntfs IS NULL OR ( g . enable_ntfs = 2 AND i . user_mention = 1 ) )
2023-06-18 10:20:11 +01:00
| ]
( userId , CISRcvNew )
pure UserInfo { user , unreadCount = fromMaybe 0 ctCount + fromMaybe 0 gCount }
getUsers :: DB . Connection -> IO [ User ]
getUsers db =
map toUser <$> DB . query_ db userQuery
2024-09-21 13:07:27 +01:00
setActiveUser :: DB . Connection -> User -> IO User
setActiveUser db user @ User { userId } = do
2023-06-18 10:20:11 +01:00
DB . execute_ db " UPDATE users SET active_user = 0 "
2024-09-21 13:07:27 +01:00
activeOrder <- getNextActiveOrder db
DB . execute db " UPDATE users SET active_user = 1, active_order = ? WHERE user_id = ? " ( activeOrder , userId )
pure user { activeUser = True , activeOrder }
2023-06-18 10:20:11 +01:00
2024-09-21 13:07:27 +01:00
getNextActiveOrder :: DB . Connection -> IO Int64
getNextActiveOrder db = do
order <- fromMaybe 0 . join <$> maybeFirstRow fromOnly ( DB . query_ db " SELECT max(active_order) FROM users " )
if order == maxBound
then 0 <$ DB . execute db " UPDATE users SET active_order = active_order - ? " ( Only ( maxBound :: Int64 ) )
else pure $ order + 1
2023-06-18 10:20:11 +01:00
getUser :: DB . Connection -> UserId -> ExceptT StoreError IO User
getUser db userId =
ExceptT . firstRow toUser ( SEUserNotFound userId ) $
DB . query db ( userQuery <> " WHERE u.user_id = ? " ) ( Only userId )
getUserIdByName :: DB . Connection -> UserName -> ExceptT StoreError IO Int64
getUserIdByName db uName =
ExceptT . firstRow fromOnly ( SEUserNotFoundByName uName ) $
DB . query db " SELECT user_id FROM users WHERE local_display_name = ? " ( Only uName )
getUserByAConnId :: DB . Connection -> AgentConnId -> IO ( Maybe User )
getUserByAConnId db agentConnId =
maybeFirstRow toUser $
DB . query db ( userQuery <> " JOIN connections c ON c.user_id = u.user_id WHERE c.agent_conn_id = ? " ) ( Only agentConnId )
getUserByASndFileId :: DB . Connection -> AgentSndFileId -> IO ( Maybe User )
getUserByASndFileId db aSndFileId =
maybeFirstRow toUser $
DB . query db ( userQuery <> " JOIN files f ON f.user_id = u.user_id WHERE f.agent_snd_file_id = ? " ) ( Only aSndFileId )
getUserByARcvFileId :: DB . Connection -> AgentRcvFileId -> IO ( Maybe User )
getUserByARcvFileId db aRcvFileId =
maybeFirstRow toUser $
DB . query db ( userQuery <> " JOIN files f ON f.user_id = u.user_id JOIN rcv_files r ON r.file_id = f.file_id WHERE r.agent_rcv_file_id = ? " ) ( Only aRcvFileId )
getUserByContactId :: DB . Connection -> ContactId -> ExceptT StoreError IO User
getUserByContactId db contactId =
ExceptT . firstRow toUser ( SEUserNotFoundByContactId contactId ) $
DB . query db ( userQuery <> " JOIN contacts ct ON ct.user_id = u.user_id WHERE ct.contact_id = ? AND ct.deleted = 0 " ) ( Only contactId )
getUserByGroupId :: DB . Connection -> GroupId -> ExceptT StoreError IO User
getUserByGroupId db groupId =
ExceptT . firstRow toUser ( SEUserNotFoundByGroupId groupId ) $
DB . query db ( userQuery <> " JOIN groups g ON g.user_id = u.user_id WHERE g.group_id = ? " ) ( Only groupId )
2024-01-11 19:01:44 +02:00
getUserByNoteFolderId :: DB . Connection -> NoteFolderId -> ExceptT StoreError IO User
getUserByNoteFolderId db contactId =
ExceptT . firstRow toUser ( SEUserNotFoundByContactId contactId ) $
DB . query db ( userQuery <> " JOIN note_folders nf ON nf.user_id = u.user_id WHERE nf.note_folder_id = ? " ) ( Only contactId )
2023-06-18 10:20:11 +01:00
getUserByFileId :: DB . Connection -> FileTransferId -> ExceptT StoreError IO User
getUserByFileId db fileId =
ExceptT . firstRow toUser ( SEUserNotFoundByFileId fileId ) $
DB . query db ( userQuery <> " JOIN files f ON f.user_id = u.user_id WHERE f.file_id = ? " ) ( Only fileId )
getUserFileInfo :: DB . Connection -> User -> IO [ CIFileInfo ]
getUserFileInfo db User { userId } =
map toFileInfo
<$> DB . query db ( fileInfoQuery <> " WHERE i.user_id = ? " ) ( Only userId )
deleteUserRecord :: DB . Connection -> User -> IO ()
deleteUserRecord db User { userId } =
DB . execute db " DELETE FROM users WHERE user_id = ? " ( Only userId )
updateUserPrivacy :: DB . Connection -> User -> IO ()
updateUserPrivacy db User { userId , showNtfs , viewPwdHash } =
DB . execute
db
[ sql |
UPDATE users
SET view_pwd_hash = ? , view_pwd_salt = ? , show_ntfs = ?
WHERE user_id = ?
| ]
2025-01-10 15:27:29 +04:00
( hashSalt viewPwdHash :. ( BI showNtfs , userId ) )
2023-06-18 10:20:11 +01:00
where
hashSalt = L . unzip . fmap ( \ UserPwdHash { hash , salt } -> ( hash , salt ) )
2023-07-13 23:48:25 +01:00
updateAllContactReceipts :: DB . Connection -> Bool -> IO ()
updateAllContactReceipts db onOff =
2023-07-26 14:49:35 +04:00
DB . execute
db
" UPDATE users SET send_rcpts_contacts = ?, send_rcpts_small_groups = ? WHERE view_pwd_hash IS NULL "
2025-01-10 15:27:29 +04:00
( BI onOff , BI onOff )
2023-07-13 23:48:25 +01:00
updateUserContactReceipts :: DB . Connection -> User -> UserMsgReceiptSettings -> IO ()
updateUserContactReceipts db User { userId } UserMsgReceiptSettings { enable , clearOverrides } = do
2025-01-10 15:27:29 +04:00
DB . execute db " UPDATE users SET send_rcpts_contacts = ? WHERE user_id = ? " ( BI enable , userId )
2023-07-13 23:48:25 +01:00
when clearOverrides $ DB . execute_ db " UPDATE contacts SET send_rcpts = NULL "
2023-07-26 14:49:35 +04:00
updateUserGroupReceipts :: DB . Connection -> User -> UserMsgReceiptSettings -> IO ()
updateUserGroupReceipts db User { userId } UserMsgReceiptSettings { enable , clearOverrides } = do
2025-01-10 15:27:29 +04:00
DB . execute db " UPDATE users SET send_rcpts_small_groups = ? WHERE user_id = ? " ( BI enable , userId )
2023-07-26 14:49:35 +04:00
when clearOverrides $ DB . execute_ db " UPDATE groups SET send_rcpts = NULL "
2023-06-18 10:20:11 +01:00
updateUserProfile :: DB . Connection -> User -> Profile -> ExceptT StoreError IO User
updateUserProfile db user p'
2024-01-15 19:56:11 +04:00
| displayName == newName = liftIO $ do
updateContactProfile_ db userId profileId p'
currentTs <- getCurrentTime
userMemberProfileUpdatedAt' <- updateUserMemberProfileUpdatedAt_ currentTs
pure user { profile , fullPreferences , userMemberProfileUpdatedAt = userMemberProfileUpdatedAt' }
2023-06-18 10:20:11 +01:00
| otherwise =
2023-11-26 18:16:37 +00:00
checkConstraint SEDuplicateName . liftIO $ do
currentTs <- getCurrentTime
DB . execute db " UPDATE users SET local_display_name = ?, updated_at = ? WHERE user_id = ? " ( newName , currentTs , userId )
2024-01-15 19:56:11 +04:00
userMemberProfileUpdatedAt' <- updateUserMemberProfileUpdatedAt_ currentTs
2023-11-26 18:16:37 +00:00
DB . execute
db
" INSERT INTO display_names (local_display_name, ldn_base, user_id, created_at, updated_at) VALUES (?,?,?,?,?) "
( newName , newName , userId , currentTs , currentTs )
updateContactProfile_' db userId profileId p' currentTs
2024-02-21 18:24:24 +04:00
updateContactLDN_ db user userContactId localDisplayName newName currentTs
2024-01-15 19:56:11 +04:00
pure user { localDisplayName = newName , profile , fullPreferences , userMemberProfileUpdatedAt = userMemberProfileUpdatedAt' }
2023-06-18 10:20:11 +01:00
where
2024-01-15 19:56:11 +04:00
updateUserMemberProfileUpdatedAt_ currentTs
| userMemberProfileChanged = do
2024-05-08 15:36:20 +01:00
DB . execute db " UPDATE users SET user_member_profile_updated_at = ? WHERE user_id = ? " ( currentTs , userId )
pure $ Just currentTs
2024-01-15 19:56:11 +04:00
| otherwise = pure userMemberProfileUpdatedAt
userMemberProfileChanged = newName /= displayName || newFullName /= fullName || newImage /= image
User { userId , userContactId , localDisplayName , profile = LocalProfile { profileId , displayName , fullName , image , localAlias } , userMemberProfileUpdatedAt } = user
Profile { displayName = newName , fullName = newFullName , image = newImage , preferences } = p'
2023-06-18 10:20:11 +01:00
profile = toLocalProfile profileId p' localAlias
fullPreferences = mergePreferences Nothing preferences
setUserProfileContactLink :: DB . Connection -> User -> Maybe UserContactLink -> IO User
setUserProfileContactLink db user @ User { userId , profile = p @ LocalProfile { profileId } } ucl_ = do
ts <- getCurrentTime
DB . execute
db
[ sql |
UPDATE contact_profiles
SET contact_link = ? , updated_at = ?
WHERE user_id = ? AND contact_profile_id = ?
| ]
2025-04-14 21:25:32 +01:00
( contactLink , ts , userId , profileId )
pure ( user :: User ) { profile = p { contactLink } }
2023-06-18 10:20:11 +01:00
where
2025-04-14 21:25:32 +01:00
-- TODO [short links] this should be replaced with short links once they are supported by all clients.
-- Or, maybe, we want to allow both, when both are optional.
contactLink = case ucl_ of
Just UserContactLink { connLinkContact = CCLink cReq _ } -> Just $ CLFull cReq
2023-06-18 10:20:11 +01:00
_ -> Nothing
-- only used in tests
getUserContactProfiles :: DB . Connection -> User -> IO [ Profile ]
getUserContactProfiles db User { userId } =
map toContactProfile
<$> DB . query
db
[ sql |
SELECT display_name , full_name , image , contact_link , preferences
FROM contact_profiles
WHERE user_id = ?
| ]
( Only userId )
where
2025-04-14 21:25:32 +01:00
toContactProfile :: ( ContactName , Text , Maybe ImageData , Maybe ConnLinkContact , Maybe Preferences ) -> Profile
2023-06-18 10:20:11 +01:00
toContactProfile ( displayName , fullName , image , contactLink , preferences ) = Profile { displayName , fullName , image , contactLink , preferences }
2025-04-14 21:25:32 +01:00
createUserContactLink :: DB . Connection -> User -> ConnId -> CreatedLinkContact -> SubscriptionMode -> ExceptT StoreError IO ()
createUserContactLink db User { userId } agentConnId ( CCLink cReq shortLink ) subMode =
2023-06-18 10:20:11 +01:00
checkConstraint SEDuplicateContactLink . liftIO $ do
currentTs <- getCurrentTime
DB . execute
db
2025-04-14 21:25:32 +01:00
" INSERT INTO user_contact_links (user_id, conn_req_contact, short_link_contact, created_at, updated_at) VALUES (?,?,?,?,?) "
( userId , cReq , shortLink , currentTs , currentTs )
2023-06-18 10:20:11 +01:00
userContactLinkId <- insertedRowId db
2024-07-18 20:33:51 +04:00
void $ createConnection_ db userId ConnUserContact ( Just userContactLinkId ) agentConnId ConnNew initialChatVersion chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode CR . PQSupportOff
2023-06-18 10:20:11 +01:00
2024-04-22 20:46:48 +04:00
getUserAddressConnections :: DB . Connection -> VersionRangeChat -> User -> ExceptT StoreError IO [ Connection ]
2024-03-10 20:52:29 +00:00
getUserAddressConnections db vr User { userId } = do
2023-06-18 10:20:11 +01:00
cs <- liftIO getUserAddressConnections_
if null cs then throwError SEUserContactLinkNotFound else pure cs
where
getUserAddressConnections_ :: IO [ Connection ]
getUserAddressConnections_ =
2024-03-10 20:52:29 +00:00
map ( toConnection vr )
2023-06-18 10:20:11 +01:00
<$> DB . query
db
[ sql |
SELECT c . connection_id , c . agent_conn_id , c . conn_level , c . via_contact , c . via_user_contact_link , c . via_group_link , c . group_link_id , c . custom_user_profile_id ,
2024-03-03 17:51:42 +04:00
c . conn_status , c . conn_type , c . contact_conn_initiated , c . local_alias , c . contact_id , c . group_member_id , c . snd_file_id , c . rcv_file_id , c . user_contact_link_id ,
2024-05-28 16:42:07 +04:00
c . created_at , c . security_code , c . security_code_verified_at , c . pq_support , c . pq_encryption , c . pq_snd_enabled , c . pq_rcv_enabled , c . auth_err_counter , c . quota_err_counter ,
2024-03-10 11:31:14 +00:00
c . conn_chat_version , c . peer_chat_min_version , c . peer_chat_max_version
2023-06-18 10:20:11 +01:00
FROM connections c
JOIN user_contact_links uc ON c . user_contact_link_id = uc . user_contact_link_id
WHERE c . user_id = ? AND uc . user_id = ? AND uc . local_display_name = ' ' AND uc . group_id IS NULL
| ]
( userId , userId )
2024-04-22 20:46:48 +04:00
getUserContactLinks :: DB . Connection -> VersionRangeChat -> User -> IO [ ( Connection , UserContact ) ]
2024-03-10 20:52:29 +00:00
getUserContactLinks db vr User { userId } =
2023-06-18 10:20:11 +01:00
map toUserContactConnection
<$> DB . query
db
[ sql |
SELECT c . connection_id , c . agent_conn_id , c . conn_level , c . via_contact , c . via_user_contact_link , c . via_group_link , c . group_link_id , c . custom_user_profile_id ,
2024-03-03 17:51:42 +04:00
c . conn_status , c . conn_type , c . contact_conn_initiated , c . local_alias , c . contact_id , c . group_member_id , c . snd_file_id , c . rcv_file_id , c . user_contact_link_id ,
2024-05-28 16:42:07 +04:00
c . created_at , c . security_code , c . security_code_verified_at , c . pq_support , c . pq_encryption , c . pq_snd_enabled , c . pq_rcv_enabled , c . auth_err_counter , c . quota_err_counter ,
2024-03-10 11:31:14 +00:00
c . conn_chat_version , c . peer_chat_min_version , c . peer_chat_max_version ,
2023-06-18 10:20:11 +01:00
uc . user_contact_link_id , uc . conn_req_contact , uc . group_id
FROM connections c
JOIN user_contact_links uc ON c . user_contact_link_id = uc . user_contact_link_id
WHERE c . user_id = ? AND uc . user_id = ?
| ]
( userId , userId )
where
toUserContactConnection :: ( ConnectionRow :. ( Int64 , ConnReqContact , Maybe GroupId ) ) -> ( Connection , UserContact )
2024-03-10 20:52:29 +00:00
toUserContactConnection ( connRow :. ( userContactLinkId , connReqContact , groupId ) ) = ( toConnection vr connRow , UserContact { userContactLinkId , connReqContact , groupId } )
2023-06-18 10:20:11 +01:00
deleteUserAddress :: DB . Connection -> User -> IO ()
deleteUserAddress db user @ User { userId } = do
DB . execute
db
[ sql |
DELETE FROM connections WHERE connection_id IN (
SELECT connection_id
FROM connections c
JOIN user_contact_links uc USING ( user_contact_link_id )
WHERE uc . user_id = ? AND uc . local_display_name = ' ' AND uc . group_id IS NULL
)
| ]
( Only userId )
2025-01-10 15:27:29 +04:00
DB . execute
2023-06-18 10:20:11 +01:00
db
[ sql |
DELETE FROM display_names
2025-01-10 15:27:29 +04:00
WHERE user_id = ?
2023-06-18 10:20:11 +01:00
AND local_display_name in (
SELECT cr . local_display_name
FROM contact_requests cr
JOIN user_contact_links uc USING ( user_contact_link_id )
2025-01-10 15:27:29 +04:00
WHERE uc . user_id = ? AND uc . local_display_name = ' ' AND uc . group_id IS NULL
2023-06-18 10:20:11 +01:00
)
2025-01-10 15:27:29 +04:00
AND local_display_name NOT IN ( SELECT local_display_name FROM users WHERE user_id = ? )
2023-06-18 10:20:11 +01:00
| ]
2025-01-10 15:27:29 +04:00
( userId , userId , userId )
DB . execute
2023-06-18 10:20:11 +01:00
db
[ sql |
DELETE FROM contact_profiles
WHERE contact_profile_id in (
SELECT cr . contact_profile_id
FROM contact_requests cr
JOIN user_contact_links uc USING ( user_contact_link_id )
2025-01-10 15:27:29 +04:00
WHERE uc . user_id = ? AND uc . local_display_name = ' ' AND uc . group_id IS NULL
2023-06-18 10:20:11 +01:00
)
| ]
2025-01-10 15:27:29 +04:00
( Only userId )
2023-06-18 10:20:11 +01:00
void $ setUserProfileContactLink db user Nothing
DB . execute db " DELETE FROM user_contact_links WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL " ( Only userId )
2023-07-13 23:48:25 +01:00
data UserMsgReceiptSettings = UserMsgReceiptSettings
{ enable :: Bool ,
clearOverrides :: Bool
}
deriving ( Show )
2023-06-18 10:20:11 +01:00
data UserContactLink = UserContactLink
2025-04-14 21:25:32 +01:00
{ connLinkContact :: CreatedLinkContact ,
2023-06-18 10:20:11 +01:00
autoAccept :: Maybe AutoAccept
}
2023-10-26 15:44:50 +01:00
deriving ( Show )
2023-06-18 10:20:11 +01:00
2025-03-03 18:57:29 +00:00
data GroupLinkInfo = GroupLinkInfo
{ groupId :: GroupId ,
memberRole :: GroupMemberRole
}
deriving ( Show )
2023-06-18 10:20:11 +01:00
data AutoAccept = AutoAccept
2024-12-02 14:01:23 +00:00
{ businessAddress :: Bool , -- possibly, it can be wrapped together with acceptIncognito, or AutoAccept made sum type
acceptIncognito :: IncognitoEnabled ,
2023-06-18 10:20:11 +01:00
autoReply :: Maybe MsgContent
}
2023-10-26 15:44:50 +01:00
deriving ( Show )
$ ( J . deriveJSON defaultJSON ''AutoAccept )
2023-06-18 10:20:11 +01:00
2023-10-26 15:44:50 +01:00
$ ( J . deriveJSON defaultJSON ''UserContactLink )
2023-06-18 10:20:11 +01:00
2025-04-14 21:25:32 +01:00
toUserContactLink :: ( ConnReqContact , Maybe ShortLinkContact , BoolInt , BoolInt , BoolInt , Maybe MsgContent ) -> UserContactLink
toUserContactLink ( connReq , shortLink , BI autoAccept , BI businessAddress , BI acceptIncognito , autoReply ) =
UserContactLink ( CCLink connReq shortLink ) $
2024-12-02 14:01:23 +00:00
if autoAccept then Just AutoAccept { businessAddress , acceptIncognito , autoReply } else Nothing
2023-06-18 10:20:11 +01:00
getUserAddress :: DB . Connection -> User -> ExceptT StoreError IO UserContactLink
getUserAddress db User { userId } =
ExceptT . firstRow toUserContactLink SEUserContactLinkNotFound $
2025-04-14 21:25:32 +01:00
DB . query db ( userContactLinkQuery <> " WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL " ) ( Only userId )
2023-06-18 10:20:11 +01:00
2025-03-03 18:57:29 +00:00
getUserContactLinkById :: DB . Connection -> UserId -> Int64 -> ExceptT StoreError IO ( UserContactLink , Maybe GroupLinkInfo )
2023-06-18 10:20:11 +01:00
getUserContactLinkById db userId userContactLinkId =
2025-03-03 18:57:29 +00:00
ExceptT . firstRow ( \ ( ucl :. gli ) -> ( toUserContactLink ucl , toGroupLinkInfo gli ) ) SEUserContactLinkNotFound $
2025-03-28 18:48:54 +00:00
DB . query
db
[ sql |
2025-04-14 21:25:32 +01:00
SELECT conn_req_contact , short_link_contact , auto_accept , business_address , auto_accept_incognito , auto_reply_msg_content , group_id , group_link_member_role
2025-03-28 18:48:54 +00:00
FROM user_contact_links
WHERE user_id = ? AND user_contact_link_id = ?
| ]
( userId , userContactLinkId )
2025-03-03 18:57:29 +00:00
toGroupLinkInfo :: ( Maybe GroupId , Maybe GroupMemberRole ) -> Maybe GroupLinkInfo
toGroupLinkInfo ( groupId_ , mRole_ ) =
( \ groupId -> GroupLinkInfo { groupId , memberRole = fromMaybe GRMember mRole_ } )
<$> groupId_
getGroupLinkInfo :: DB . Connection -> UserId -> GroupId -> IO ( Maybe GroupLinkInfo )
getGroupLinkInfo db userId groupId =
fmap join $ maybeFirstRow toGroupLinkInfo $
2025-03-28 18:48:54 +00:00
DB . query
db
[ sql |
SELECT group_id , group_link_member_role
FROM user_contact_links
WHERE user_id = ? AND group_id = ?
| ]
( userId , groupId )
2023-06-18 10:20:11 +01:00
2023-10-21 19:13:32 +04:00
getUserContactLinkByConnReq :: DB . Connection -> User -> ( ConnReqContact , ConnReqContact ) -> IO ( Maybe UserContactLink )
getUserContactLinkByConnReq db User { userId } ( cReqSchema1 , cReqSchema2 ) =
2023-10-10 21:19:04 +04:00
maybeFirstRow toUserContactLink $
2025-04-14 21:25:32 +01:00
DB . query db ( userContactLinkQuery <> " WHERE user_id = ? AND conn_req_contact IN (?,?) " ) ( userId , cReqSchema1 , cReqSchema2 )
getUserContactLinkViaShortLink :: DB . Connection -> User -> ShortLinkContact -> IO ( Maybe UserContactLink )
getUserContactLinkViaShortLink db User { userId } shortLink =
maybeFirstRow toUserContactLink $
DB . query db ( userContactLinkQuery <> " WHERE user_id = ? AND short_link_contact = ? " ) ( userId , shortLink )
userContactLinkQuery :: Query
userContactLinkQuery =
[ sql |
SELECT conn_req_contact , short_link_contact , auto_accept , business_address , auto_accept_incognito , auto_reply_msg_content
FROM user_contact_links
| ]
2023-10-10 21:19:04 +04:00
2024-04-22 20:46:48 +04:00
getContactWithoutConnViaAddress :: DB . Connection -> VersionRangeChat -> User -> ( ConnReqContact , ConnReqContact ) -> IO ( Maybe Contact )
2024-03-10 20:52:29 +00:00
getContactWithoutConnViaAddress db vr user @ User { userId } ( cReqSchema1 , cReqSchema2 ) = do
2023-11-26 18:16:37 +00:00
ctId_ <-
maybeFirstRow fromOnly $
DB . query
db
[ sql |
SELECT ct . contact_id
FROM contacts ct
JOIN contact_profiles cp ON cp . contact_profile_id = ct . contact_profile_id
LEFT JOIN connections c ON c . contact_id = ct . contact_id
WHERE cp . user_id = ? AND cp . contact_link IN ( ? , ? ) AND c . connection_id IS NULL
| ]
( userId , cReqSchema1 , cReqSchema2 )
2024-03-10 20:52:29 +00:00
maybe ( pure Nothing ) ( fmap eitherToMaybe . runExceptT . getContact db vr user ) ctId_
2023-11-07 17:45:59 +04:00
2023-06-18 10:20:11 +01:00
updateUserAddressAutoAccept :: DB . Connection -> User -> Maybe AutoAccept -> ExceptT StoreError IO UserContactLink
updateUserAddressAutoAccept db user @ User { userId } autoAccept = do
link <- getUserAddress db user
liftIO updateUserAddressAutoAccept_ $> link { autoAccept }
where
updateUserAddressAutoAccept_ =
DB . execute
db
[ sql |
UPDATE user_contact_links
2024-12-02 14:01:23 +00:00
SET auto_accept = ? , business_address = ? , auto_accept_incognito = ? , auto_reply_msg_content = ?
2023-06-18 10:20:11 +01:00
WHERE user_id = ? AND local_display_name = ' ' AND group_id IS NULL
| ]
( ucl :. Only userId )
ucl = case autoAccept of
2025-01-10 15:27:29 +04:00
Just AutoAccept { businessAddress , acceptIncognito , autoReply } -> ( BI True , BI businessAddress , BI acceptIncognito , autoReply )
_ -> ( BI False , BI False , BI False , Nothing )
2023-06-18 10:20:11 +01:00
2024-11-14 17:43:34 +00:00
getProtocolServers :: forall p . ProtocolTypeI p => DB . Connection -> SProtocolType p -> User -> IO [ UserServer p ]
getProtocolServers db p User { userId } =
map toUserServer
2023-06-18 10:20:11 +01:00
<$> DB . query
db
[ sql |
2024-11-14 17:43:34 +00:00
SELECT smp_server_id , host , port , key_hash , basic_auth , preset , tested , enabled
FROM protocol_servers
WHERE user_id = ? AND protocol = ?
2023-06-18 10:20:11 +01:00
| ]
2024-11-14 17:43:34 +00:00
( userId , decodeLatin1 $ strEncode p )
2023-06-18 10:20:11 +01:00
where
2025-01-10 15:27:29 +04:00
toUserServer :: ( DBEntityId , NonEmpty TransportHost , String , C . KeyHash , Maybe Text , BoolInt , Maybe BoolInt , BoolInt ) -> UserServer p
toUserServer ( serverId , host , port , keyHash , auth_ , BI preset , tested , BI enabled ) =
2024-11-14 17:43:34 +00:00
let server = ProtoServerWithAuth ( ProtocolServer p host port keyHash ) ( BasicAuth . encodeUtf8 <$> auth_ )
2025-01-10 15:27:29 +04:00
in UserServer { serverId , server , preset , tested = unBI <$> tested , enabled , deleted = False }
2023-06-18 10:20:11 +01:00
2024-11-14 17:43:34 +00:00
insertProtocolServer :: forall p . ProtocolTypeI p => DB . Connection -> SProtocolType p -> User -> UTCTime -> NewUserServer p -> IO ( UserServer p )
insertProtocolServer db p User { userId } ts srv @ UserServer { server , preset , tested , enabled } = do
DB . execute
db
[ sql |
INSERT INTO protocol_servers
( protocol , host , port , key_hash , basic_auth , preset , tested , enabled , user_id , created_at , updated_at )
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )
| ]
2025-01-10 15:27:29 +04:00
( serverColumns p server :. ( BI preset , BI <$> tested , BI enabled , userId , ts , ts ) )
2024-11-14 17:43:34 +00:00
sId <- insertedRowId db
pure ( srv :: NewUserServer p ) { serverId = DBEntityId sId }
updateProtocolServer :: ProtocolTypeI p => DB . Connection -> SProtocolType p -> UTCTime -> UserServer p -> IO ()
updateProtocolServer db p ts UserServer { serverId , server , preset , tested , enabled } =
DB . execute
db
[ sql |
UPDATE protocol_servers
SET protocol = ? , host = ? , port = ? , key_hash = ? , basic_auth = ? ,
preset = ? , tested = ? , enabled = ? , updated_at = ?
WHERE smp_server_id = ?
| ]
2025-01-10 15:27:29 +04:00
( serverColumns p server :. ( BI preset , BI <$> tested , BI enabled , ts , serverId ) )
2024-11-14 17:43:34 +00:00
serverColumns :: ProtocolTypeI p => SProtocolType p -> ProtoServerWithAuth p -> ( Text , NonEmpty TransportHost , String , C . KeyHash , Maybe Text )
serverColumns p ( ProtoServerWithAuth ProtocolServer { host , port , keyHash } auth_ ) =
let protocol = decodeLatin1 $ strEncode p
auth = safeDecodeUtf8 . unBasicAuth <$> auth_
in ( protocol , host , port , keyHash , auth )
2023-06-18 10:20:11 +01:00
2024-11-15 12:08:15 +00:00
getServerOperators :: DB . Connection -> ExceptT StoreError IO ServerOperatorConditions
2024-11-04 21:11:03 +04:00
getServerOperators db = do
2024-11-15 12:08:15 +00:00
currentConditions <- getCurrentUsageConditions db
2024-11-14 17:43:34 +00:00
liftIO $ do
now <- getCurrentTime
latestAcceptedConds_ <- getLatestAcceptedConditions db
2024-11-15 12:08:15 +00:00
let getConds op = ( \ ca -> op { conditionsAcceptance = ca } ) <$> getOperatorConditions_ db op currentConditions latestAcceptedConds_ now
ops <- mapM getConds =<< getServerOperators_ db
let conditionsAction = usageConditionsAction ops currentConditions now
pure ServerOperatorConditions { serverOperators = ops , currentConditions , conditionsAction }
2024-11-14 17:43:34 +00:00
2024-12-02 14:01:23 +00:00
getUserServers :: DB . Connection -> User -> ExceptT StoreError IO ( [ Maybe ServerOperator ] , [ UserServer 'PSMP ] , [ UserServer 'PXFTP ] )
2024-11-14 17:43:34 +00:00
getUserServers db user =
( , , )
2024-11-18 18:44:28 +00:00
<$> ( map Just . serverOperators <$> getServerOperators db )
2024-11-14 17:43:34 +00:00
<*> liftIO ( getProtocolServers db SPSMP user )
<*> liftIO ( getProtocolServers db SPXFTP user )
setServerOperators :: DB . Connection -> NonEmpty ServerOperator -> IO ()
setServerOperators db ops = do
currentTs <- getCurrentTime
mapM_ ( updateServerOperator db currentTs ) ops
updateServerOperator :: DB . Connection -> UTCTime -> ServerOperator -> IO ()
2024-11-15 12:08:15 +00:00
updateServerOperator db currentTs ServerOperator { operatorId , enabled , smpRoles , xftpRoles } =
2024-11-14 17:43:34 +00:00
DB . execute
db
[ sql |
UPDATE server_operators
2024-11-15 12:08:15 +00:00
SET enabled = ? , smp_role_storage = ? , smp_role_proxy = ? , xftp_role_storage = ? , xftp_role_proxy = ? , updated_at = ?
2024-11-14 17:43:34 +00:00
WHERE server_operator_id = ?
| ]
2025-01-10 15:27:29 +04:00
( BI enabled , BI ( storage smpRoles ) , BI ( proxy smpRoles ) , BI ( storage xftpRoles ) , BI ( proxy xftpRoles ) , currentTs , operatorId )
2024-11-14 17:43:34 +00:00
2024-11-18 18:44:28 +00:00
getUpdateServerOperators :: DB . Connection -> NonEmpty PresetOperator -> Bool -> IO [ ( Maybe PresetOperator , Maybe ServerOperator ) ]
2024-11-14 17:43:34 +00:00
getUpdateServerOperators db presetOps newUser = do
conds <- map toUsageConditions <$> DB . query_ db usageCondsQuery
now <- getCurrentTime
2025-01-04 18:33:27 +00:00
let ( currentConds , condsToAdd ) = usageConditionsToAdd newUser now conds
2024-11-14 17:43:34 +00:00
mapM_ insertConditions condsToAdd
latestAcceptedConds_ <- getLatestAcceptedConditions db
ops <- updatedServerOperators presetOps <$> getServerOperators_ db
2024-12-02 14:01:23 +00:00
forM ops $ traverse $ mapM $ \ ( ASO _ op ) ->
-- traverse for tuple, mapM for Maybe
2024-11-14 17:43:34 +00:00
case operatorId op of
2025-01-04 18:33:27 +00:00
DBNewEntity -> insertOperator op
2024-11-14 17:43:34 +00:00
DBEntityId _ -> do
updateOperator op
getOperatorConditions_ db op currentConds latestAcceptedConds_ now >>= \ case
2024-12-24 14:13:47 +00:00
CARequired ( Just ts ) | ts < now -> autoAcceptConditions op currentConds now
2024-11-14 17:43:34 +00:00
ca -> pure op { conditionsAcceptance = ca }
2024-11-04 21:11:03 +04:00
where
2024-11-14 17:43:34 +00:00
insertConditions UsageConditions { conditionsId , conditionsCommit , notifiedAt , createdAt } =
DB . execute
db
[ sql |
INSERT INTO usage_conditions
( usage_conditions_id , conditions_commit , notified_at , created_at )
VALUES ( ? , ? , ? , ? )
| ]
( conditionsId , conditionsCommit , notifiedAt , createdAt )
updateOperator :: ServerOperator -> IO ()
2024-11-15 12:08:15 +00:00
updateOperator ServerOperator { operatorId , tradeName , legalName , serverDomains , enabled , smpRoles , xftpRoles } =
2024-11-14 17:43:34 +00:00
DB . execute
db
[ sql |
UPDATE server_operators
2024-11-15 12:08:15 +00:00
SET trade_name = ? , legal_name = ? , server_domains = ? , enabled = ? , smp_role_storage = ? , smp_role_proxy = ? , xftp_role_storage = ? , xftp_role_proxy = ?
2024-11-14 17:43:34 +00:00
WHERE server_operator_id = ?
| ]
2025-01-10 15:27:29 +04:00
( tradeName , legalName , T . intercalate " , " serverDomains , BI enabled , BI ( storage smpRoles ) , BI ( proxy smpRoles ) , BI ( storage xftpRoles ) , BI ( proxy xftpRoles ) , operatorId )
2024-11-14 17:43:34 +00:00
insertOperator :: NewServerOperator -> IO ServerOperator
2024-11-15 12:08:15 +00:00
insertOperator op @ ServerOperator { operatorTag , tradeName , legalName , serverDomains , enabled , smpRoles , xftpRoles } = do
2024-11-14 17:43:34 +00:00
DB . execute
db
[ sql |
INSERT INTO server_operators
2024-11-15 12:08:15 +00:00
( server_operator_tag , trade_name , legal_name , server_domains , enabled , smp_role_storage , smp_role_proxy , xftp_role_storage , xftp_role_proxy )
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? )
2024-11-14 17:43:34 +00:00
| ]
2025-01-10 15:27:29 +04:00
( operatorTag , tradeName , legalName , T . intercalate " , " serverDomains , BI enabled , BI ( storage smpRoles ) , BI ( proxy smpRoles ) , BI ( storage xftpRoles ) , BI ( proxy xftpRoles ) )
2024-11-14 17:43:34 +00:00
opId <- insertedRowId db
pure op { operatorId = DBEntityId opId }
2024-12-24 14:13:47 +00:00
autoAcceptConditions op UsageConditions { conditionsCommit } now =
acceptConditions_ db op conditionsCommit now True
$> op { conditionsAcceptance = CAAccepted ( Just now ) True }
2024-11-14 17:43:34 +00:00
serverOperatorQuery :: Query
serverOperatorQuery =
[ sql |
SELECT server_operator_id , server_operator_tag , trade_name , legal_name ,
2024-11-15 12:08:15 +00:00
server_domains , enabled , smp_role_storage , smp_role_proxy , xftp_role_storage , xftp_role_proxy
2024-11-14 17:43:34 +00:00
FROM server_operators
| ]
getServerOperators_ :: DB . Connection -> IO [ ServerOperator ]
getServerOperators_ db = map toServerOperator <$> DB . query_ db serverOperatorQuery
2025-01-10 15:27:29 +04:00
toServerOperator :: ( DBEntityId , Maybe OperatorTag , Text , Maybe Text , Text , BoolInt ) :. ( BoolInt , BoolInt ) :. ( BoolInt , BoolInt ) -> ServerOperator
toServerOperator ( ( operatorId , operatorTag , tradeName , legalName , domains , BI enabled ) :. smpRoles' :. xftpRoles' ) =
2024-11-14 17:43:34 +00:00
ServerOperator
{ operatorId ,
operatorTag ,
tradeName ,
legalName ,
serverDomains = T . splitOn " , " domains ,
conditionsAcceptance = CARequired Nothing ,
enabled ,
2024-11-15 12:08:15 +00:00
smpRoles = serverRoles smpRoles' ,
xftpRoles = serverRoles xftpRoles'
2024-11-14 17:43:34 +00:00
}
2024-11-15 12:08:15 +00:00
where
2025-01-10 15:27:29 +04:00
serverRoles ( BI storage , BI proxy ) = ServerRoles { storage , proxy }
2024-11-14 17:43:34 +00:00
getOperatorConditions_ :: DB . Connection -> ServerOperator -> UsageConditions -> Maybe UsageConditions -> UTCTime -> IO ConditionsAcceptance
getOperatorConditions_ db ServerOperator { operatorId } UsageConditions { conditionsCommit = currentCommit , createdAt , notifiedAt } latestAcceptedConds_ now = do
case latestAcceptedConds_ of
Nothing -> pure $ CARequired Nothing -- no conditions accepted by any operator
Just UsageConditions { conditionsCommit = latestAcceptedCommit } -> do
operatorAcceptedConds_ <-
maybeFirstRow id $
DB . query
db
[ sql |
2024-12-24 14:13:47 +00:00
SELECT conditions_commit , accepted_at , auto_accepted
2024-11-14 17:43:34 +00:00
FROM operator_usage_conditions
WHERE server_operator_id = ?
ORDER BY operator_usage_conditions_id DESC
LIMIT 1
| ]
( Only operatorId )
pure $ case operatorAcceptedConds_ of
2025-01-10 15:27:29 +04:00
Just ( operatorCommit , acceptedAt_ , BI autoAccept )
2024-11-14 17:43:34 +00:00
| operatorCommit /= latestAcceptedCommit -> CARequired Nothing -- TODO should we consider this operator disabled?
| currentCommit /= latestAcceptedCommit -> CARequired $ conditionsRequiredOrDeadline createdAt ( fromMaybe now notifiedAt )
2024-12-24 14:13:47 +00:00
| otherwise -> CAAccepted acceptedAt_ autoAccept
2024-11-14 17:43:34 +00:00
_ -> CARequired Nothing -- no conditions were accepted for this operator
2024-11-04 21:11:03 +04:00
getCurrentUsageConditions :: DB . Connection -> ExceptT StoreError IO UsageConditions
getCurrentUsageConditions db =
ExceptT . firstRow toUsageConditions SEUsageConditionsNotFound $
2024-11-14 17:43:34 +00:00
DB . query_ db ( usageCondsQuery <> " DESC LIMIT 1 " )
usageCondsQuery :: Query
usageCondsQuery =
[ sql |
SELECT usage_conditions_id , conditions_commit , notified_at , created_at
FROM usage_conditions
ORDER BY usage_conditions_id
| ]
2024-11-04 21:11:03 +04:00
toUsageConditions :: ( Int64 , Text , Maybe UTCTime , UTCTime ) -> UsageConditions
toUsageConditions ( conditionsId , conditionsCommit , notifiedAt , createdAt ) =
UsageConditions { conditionsId , conditionsCommit , notifiedAt , createdAt }
2024-11-04 13:28:57 +00:00
2024-11-14 17:43:34 +00:00
getLatestAcceptedConditions :: DB . Connection -> IO ( Maybe UsageConditions )
getLatestAcceptedConditions db =
maybeFirstRow toUsageConditions $
DB . query_
db
[ sql |
SELECT usage_conditions_id , conditions_commit , notified_at , created_at
FROM usage_conditions
WHERE conditions_commit = (
2024-11-05 14:15:20 +04:00
SELECT conditions_commit
FROM operator_usage_conditions
ORDER BY accepted_at DESC
LIMIT 1
2024-11-14 17:43:34 +00:00
)
| ]
2024-11-05 14:15:20 +04:00
2024-11-05 21:40:33 +04:00
setConditionsNotified :: DB . Connection -> Int64 -> UTCTime -> IO ()
2024-11-14 17:43:34 +00:00
setConditionsNotified db condId notifiedAt =
DB . execute db " UPDATE usage_conditions SET notified_at = ? WHERE usage_conditions_id = ? " ( notifiedAt , condId )
acceptConditions :: DB . Connection -> Int64 -> NonEmpty Int64 -> UTCTime -> ExceptT StoreError IO ()
acceptConditions db condId opIds acceptedAt = do
UsageConditions { conditionsCommit } <- getUsageConditionsById_ db condId
operators <- mapM getServerOperator_ opIds
2024-12-24 14:13:47 +00:00
liftIO $ forM_ operators $ \ op -> acceptConditions_ db op conditionsCommit acceptedAt False
2024-11-14 17:43:34 +00:00
where
getServerOperator_ opId =
2024-12-02 14:01:23 +00:00
ExceptT $
firstRow toServerOperator ( SEOperatorNotFound opId ) $
DB . query db ( serverOperatorQuery <> " WHERE server_operator_id = ? " ) ( Only opId )
2024-11-05 21:40:33 +04:00
2024-12-24 14:13:47 +00:00
acceptConditions_ :: DB . Connection -> ServerOperator -> Text -> UTCTime -> Bool -> IO ()
acceptConditions_ db ServerOperator { operatorId , operatorTag } conditionsCommit acceptedAt autoAccepted = do
2025-01-10 15:27:29 +04:00
acceptedAt_ :: Maybe ( Maybe UTCTime ) <- maybeFirstRow fromOnly $ DB . query db " SELECT accepted_at FROM operator_usage_conditions WHERE server_operator_id = ? AND conditions_commit = ? " ( operatorId , conditionsCommit )
2024-12-24 14:13:47 +00:00
case acceptedAt_ of
2025-01-10 15:27:29 +04:00
Just Nothing ->
DB . execute
db
( q <> " ON CONFLICT (server_operator_id, conditions_commit) DO UPDATE SET accepted_at = ?, auto_accepted = ? " )
( operatorId , operatorTag , conditionsCommit , acceptedAt , BI autoAccepted , acceptedAt , BI autoAccepted )
Just ( Just _ ) ->
DB . execute
db
( q <> " ON CONFLICT (server_operator_id, conditions_commit) DO NOTHING " )
( operatorId , operatorTag , conditionsCommit , acceptedAt , BI autoAccepted )
Nothing ->
DB . execute
db
q
( operatorId , operatorTag , conditionsCommit , acceptedAt , BI autoAccepted )
2024-12-24 14:13:47 +00:00
where
q =
[ sql |
INSERT INTO operator_usage_conditions
( server_operator_id , server_operator_tag , conditions_commit , accepted_at , auto_accepted )
VALUES ( ? , ? , ? , ? , ? )
| ]
2024-11-05 21:40:33 +04:00
getUsageConditionsById_ :: DB . Connection -> Int64 -> ExceptT StoreError IO UsageConditions
getUsageConditionsById_ db conditionsId =
ExceptT . firstRow toUsageConditions SEUsageConditionsNotFound $
DB . query
db
[ sql |
SELECT usage_conditions_id , conditions_commit , notified_at , created_at
FROM usage_conditions
WHERE usage_conditions_id = ?
| ]
( Only conditionsId )
2024-11-18 18:44:28 +00:00
setUserServers :: DB . Connection -> User -> UTCTime -> UpdatedUserOperatorServers -> ExceptT StoreError IO UserOperatorServers
setUserServers db user ts = checkConstraint SEUniqueID . liftIO . setUserServers' db user ts
setUserServers' :: DB . Connection -> User -> UTCTime -> UpdatedUserOperatorServers -> IO UserOperatorServers
setUserServers' db user @ User { userId } ts UpdatedUserOperatorServers { operator , smpServers , xftpServers } = do
mapM_ ( updateServerOperator db ts ) operator
smpSrvs' <- catMaybes <$> mapM ( upsertOrDelete SPSMP ) smpServers
xftpSrvs' <- catMaybes <$> mapM ( upsertOrDelete SPXFTP ) xftpServers
pure UserOperatorServers { operator , smpServers = smpSrvs' , xftpServers = xftpSrvs' }
2024-11-05 21:40:33 +04:00
where
2024-11-18 18:44:28 +00:00
upsertOrDelete :: ProtocolTypeI p => SProtocolType p -> AUserServer p -> IO ( Maybe ( UserServer p ) )
upsertOrDelete p ( AUS _ s @ UserServer { serverId , deleted } ) = case serverId of
2024-11-14 17:43:34 +00:00
DBNewEntity
2024-11-18 18:44:28 +00:00
| deleted -> pure Nothing
| otherwise -> Just <$> insertProtocolServer db p user ts s
2024-11-14 17:43:34 +00:00
DBEntityId srvId
2025-01-10 15:27:29 +04:00
| deleted -> Nothing <$ DB . execute db " DELETE FROM protocol_servers WHERE user_id = ? AND smp_server_id = ? AND preset = ? " ( userId , srvId , BI False )
2024-11-18 18:44:28 +00:00
| otherwise -> Just s <$ updateProtocolServer db p ts s
2024-11-04 13:28:57 +00:00
2023-06-18 10:20:11 +01:00
createCall :: DB . Connection -> User -> Call -> UTCTime -> IO ()
2024-08-28 14:31:02 +00:00
createCall db user @ User { userId } Call { contactId , callId , callUUID , chatItemId , callState } callTs = do
2023-06-18 10:20:11 +01:00
currentTs <- getCurrentTime
deleteCalls db user contactId
DB . execute
db
[ sql |
INSERT INTO calls
2024-08-28 14:31:02 +00:00
( contact_id , shared_call_id , call_uuid , chat_item_id , call_state , call_ts , user_id , created_at , updated_at )
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? )
2023-06-18 10:20:11 +01:00
| ]
2024-08-28 14:31:02 +00:00
( contactId , callId , callUUID , chatItemId , callState , callTs , userId , currentTs , currentTs )
2023-06-18 10:20:11 +01:00
deleteCalls :: DB . Connection -> User -> ContactId -> IO ()
deleteCalls db User { userId } contactId = do
DB . execute db " DELETE FROM calls WHERE user_id = ? AND contact_id = ? " ( userId , contactId )
getCalls :: DB . Connection -> IO [ Call ]
getCalls db =
map toCall
<$> DB . query_
db
[ sql |
SELECT
2024-08-28 14:31:02 +00:00
contact_id , shared_call_id , call_uuid , chat_item_id , call_state , call_ts
2023-06-18 10:20:11 +01:00
FROM calls
ORDER BY call_ts ASC
| ]
where
2024-08-28 14:31:02 +00:00
toCall :: ( ContactId , CallId , Text , ChatItemId , CallState , UTCTime ) -> Call
toCall ( contactId , callId , callUUID , chatItemId , callState , callTs ) = Call { contactId , callId , callUUID , chatItemId , callState , callTs }
2023-06-18 10:20:11 +01:00
createCommand :: DB . Connection -> User -> Maybe Int64 -> CommandFunction -> IO CommandId
createCommand db User { userId } connId commandFunction = do
currentTs <- getCurrentTime
DB . execute
db
[ sql |
INSERT INTO commands ( connection_id , command_function , command_status , user_id , created_at , updated_at )
VALUES ( ? , ? , ? , ? , ? , ? )
| ]
( connId , commandFunction , CSCreated , userId , currentTs , currentTs )
insertedRowId db
deleteCommand :: DB . Connection -> User -> CommandId -> IO ()
deleteCommand db User { userId } cmdId =
DB . execute db " DELETE FROM commands WHERE user_id = ? AND command_id = ? " ( userId , cmdId )
updateCommandStatus :: DB . Connection -> User -> CommandId -> CommandStatus -> IO ()
updateCommandStatus db User { userId } cmdId status = do
updatedAt <- getCurrentTime
DB . execute
db
[ sql |
UPDATE commands
SET command_status = ? , updated_at = ?
WHERE user_id = ? AND command_id = ?
| ]
( status , updatedAt , userId , cmdId )
getCommandDataByCorrId :: DB . Connection -> User -> ACorrId -> IO ( Maybe CommandData )
getCommandDataByCorrId db User { userId } corrId =
maybeFirstRow toCommandData $
DB . query
db
[ sql |
SELECT command_id , connection_id , command_function , command_status
FROM commands
WHERE user_id = ? AND command_id = ?
| ]
( userId , commandId corrId )
where
toCommandData :: ( CommandId , Maybe Int64 , CommandFunction , CommandStatus ) -> CommandData
toCommandData ( cmdId , cmdConnId , cmdFunction , cmdStatus ) = CommandData { cmdId , cmdConnId , cmdFunction , cmdStatus }
2024-05-08 15:36:20 +01:00
setUserUIThemes :: DB . Connection -> User -> Maybe UIThemeEntityOverrides -> IO ()
setUserUIThemes db User { userId } uiThemes = do
updatedAt <- getCurrentTime
DB . execute db " UPDATE users SET ui_themes = ?, updated_at = ? WHERE user_id = ? " ( uiThemes , updatedAt , userId )