2023-06-18 10:20:11 +01:00
{- # LANGUAGE DataKinds # -}
{- # LANGUAGE DuplicateRecordFields # -}
{- # LANGUAGE GADTs # -}
2023-12-11 15:50:32 +02:00
{- # LANGUAGE KindSignatures # -}
2023-06-18 10:20:11 +01:00
{- # LANGUAGE LambdaCase # -}
{- # LANGUAGE NamedFieldPuns # -}
{- # LANGUAGE OverloadedStrings # -}
2023-10-11 19:10:38 +01:00
{- # LANGUAGE PatternSynonyms # -}
2023-06-18 10:20:11 +01:00
{- # LANGUAGE QuasiQuotes # -}
{- # LANGUAGE ScopedTypeVariables # -}
{- # LANGUAGE TupleSections # -}
{- # 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.Messages
( getContactConnIds_ ,
2023-06-22 20:38:09 +04:00
2023-06-18 10:20:11 +01:00
-- * Message and chat item functions
deleteContactCIs ,
getGroupFileInfo ,
deleteGroupCIs ,
createNewSndMessage ,
createSndMsgDelivery ,
createNewMessageAndRcvMsgDelivery ,
2023-11-18 21:52:01 +04:00
createNewRcvMessage ,
2023-12-23 17:07:23 +04:00
updateSndMsgDeliveryStatus ,
updateRcvMsgDeliveryStatus ,
2023-06-18 10:20:11 +01:00
createPendingGroupMessage ,
getPendingGroupMessages ,
deletePendingGroupMessage ,
deleteOldMessages ,
updateChatTs ,
createNewSndChatItem ,
createNewRcvChatItem ,
createNewChatItemNoMsg ,
getChatPreviews ,
getDirectChat ,
getGroupChat ,
getDirectChatItemsLast ,
getAllChatItems ,
getAChatItem ,
updateDirectChatItem ,
updateDirectChatItem' ,
addInitialAndNewCIVersions ,
createChatItemVersion ,
deleteDirectChatItem ,
markDirectChatItemDeleted ,
2023-07-26 14:49:35 +04:00
updateGroupChatItemStatus ,
2023-06-18 10:20:11 +01:00
updateGroupChatItem ,
deleteGroupChatItem ,
updateGroupChatItemModerated ,
markGroupChatItemDeleted ,
2023-10-11 19:10:38 +01:00
markGroupChatItemBlocked ,
2023-06-18 10:20:11 +01:00
updateDirectChatItemsRead ,
getDirectUnreadTimedItems ,
setDirectChatItemDeleteAt ,
updateGroupChatItemsRead ,
getGroupUnreadTimedItems ,
setGroupChatItemDeleteAt ,
getChatRefViaItemId ,
getChatItemVersions ,
getDirectCIReactions ,
getDirectReactions ,
setDirectReaction ,
getGroupCIReactions ,
getGroupReactions ,
setGroupReaction ,
getChatItemIdByAgentMsgId ,
getDirectChatItem ,
2023-10-18 10:19:24 +01:00
getDirectCIWithReactions ,
2023-06-18 10:20:11 +01:00
getDirectChatItemBySharedMsgId ,
getDirectChatItemByAgentMsgId ,
getGroupChatItem ,
2023-10-18 10:19:24 +01:00
getGroupCIWithReactions ,
2023-06-18 10:20:11 +01:00
getGroupChatItemBySharedMsgId ,
getGroupMemberCIBySharedMsgId ,
2023-07-26 14:49:35 +04:00
getGroupChatItemByAgentMsgId ,
2023-06-18 10:20:11 +01:00
getGroupMemberChatItemLast ,
getDirectChatItemIdByText ,
getDirectChatItemIdByText' ,
getGroupChatItemIdByText ,
getGroupChatItemIdByText' ,
getChatItemByFileId ,
getChatItemByGroupId ,
updateDirectChatItemStatus ,
getTimedItems ,
getChatItemTTL ,
setChatItemTTL ,
getContactExpiredFileInfo ,
deleteContactExpiredCIs ,
getGroupExpiredFileInfo ,
deleteGroupExpiredCIs ,
2023-06-22 20:38:09 +04:00
createCIModeration ,
getCIModeration ,
deleteCIModeration ,
2023-07-26 14:49:35 +04:00
createGroupSndStatus ,
getGroupSndStatus ,
updateGroupSndStatus ,
getGroupSndStatuses ,
getGroupSndStatusCounts ,
2023-12-23 17:07:23 +04:00
getGroupHistoryItems ,
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-06-18 10:20:11 +01:00
import Crypto.Random ( ChaChaDRG )
import Data.Bifunctor ( first )
import Data.ByteString.Char8 ( ByteString )
import Data.Either ( fromRight , rights )
import Data.Int ( Int64 )
2023-12-11 15:50:32 +02:00
import Data.List ( sortBy )
2023-06-18 10:20:11 +01:00
import Data.Maybe ( fromMaybe , isJust , mapMaybe )
2023-12-11 15:50:32 +02:00
import Data.Ord ( Down ( .. ) , comparing )
2023-06-18 10:20:11 +01:00
import Data.Text ( Text )
import Data.Time ( addUTCTime )
import Data.Time.Clock ( UTCTime ( .. ) , getCurrentTime )
2023-12-11 15:50:32 +02:00
import Database.SQLite.Simple ( NamedParam ( .. ) , Only ( .. ) , Query , ( :. ) ( .. ) )
2023-06-18 10:20:11 +01:00
import Database.SQLite.Simple.QQ ( sql )
2023-12-11 15:50:32 +02:00
import Simplex.Chat.Controller ( ChatListQuery ( .. ) , ChatPagination ( .. ) , PaginationByTime ( .. ) )
2023-06-18 10:20:11 +01:00
import Simplex.Chat.Markdown
import Simplex.Chat.Messages
import Simplex.Chat.Messages.CIContent
import Simplex.Chat.Protocol
import Simplex.Chat.Store.Direct
import Simplex.Chat.Store.Groups
import Simplex.Chat.Store.Shared
import Simplex.Chat.Types
import Simplex.Messaging.Agent.Protocol ( AgentMsgId , ConnId , MsgMeta ( .. ) , UserId )
import Simplex.Messaging.Agent.Store.SQLite ( firstRow , firstRow' , maybeFirstRow )
2023-08-12 18:27:10 +01:00
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
2023-09-01 19:43:27 +01:00
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.File ( CryptoFile ( .. ) , CryptoFileArgs ( .. ) )
2023-06-18 10:20:11 +01:00
import Simplex.Messaging.Util ( eitherToMaybe )
import UnliftIO.STM
deleteContactCIs :: DB . Connection -> User -> Contact -> IO ()
deleteContactCIs db user @ User { userId } ct @ Contact { contactId } = do
connIds <- getContactConnIds_ db user ct
forM_ connIds $ \ connId ->
DB . execute db " DELETE FROM messages WHERE connection_id = ? " ( Only connId )
DB . execute db " DELETE FROM chat_item_reactions WHERE contact_id = ? " ( Only contactId )
DB . execute db " DELETE FROM chat_items WHERE user_id = ? AND contact_id = ? " ( userId , contactId )
getContactConnIds_ :: DB . Connection -> User -> Contact -> IO [ Int64 ]
getContactConnIds_ db User { userId } Contact { contactId } =
map fromOnly
<$> DB . query db " SELECT connection_id FROM connections WHERE user_id = ? AND contact_id = ? " ( userId , contactId )
getGroupFileInfo :: DB . Connection -> User -> GroupInfo -> IO [ CIFileInfo ]
getGroupFileInfo db User { userId } GroupInfo { groupId } =
map toFileInfo
<$> DB . query db ( fileInfoQuery <> " WHERE i.user_id = ? AND i.group_id = ? " ) ( userId , groupId )
deleteGroupCIs :: DB . Connection -> User -> GroupInfo -> IO ()
deleteGroupCIs db User { userId } GroupInfo { groupId } = do
DB . execute db " DELETE FROM messages WHERE group_id = ? " ( Only groupId )
DB . execute db " DELETE FROM chat_item_reactions WHERE group_id = ? " ( Only groupId )
DB . execute db " DELETE FROM chat_items WHERE user_id = ? AND group_id = ? " ( userId , groupId )
2023-12-23 17:07:23 +04:00
createNewSndMessage :: MsgEncodingI e => DB . Connection -> TVar ChaChaDRG -> ConnOrGroupId -> ChatMsgEvent e -> ( SharedMsgId -> EncodedChatMessage ) -> ExceptT StoreError IO SndMessage
createNewSndMessage db gVar connOrGroupId chatMsgEvent encodeMessage =
createWithRandomId' gVar $ \ sharedMsgId ->
case encodeMessage ( SharedMsgId sharedMsgId ) of
ECMLarge -> pure $ Left SELargeMsg
ECMEncoded msgBody -> do
createdAt <- getCurrentTime
DB . execute
db
[ sql |
INSERT INTO messages (
msg_sent , chat_msg_event , msg_body , connection_id , group_id ,
shared_msg_id , shared_msg_id_user , created_at , updated_at
) VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? )
| ]
( MDSnd , toCMEventTag chatMsgEvent , msgBody , connId_ , groupId_ , sharedMsgId , Just True , createdAt , createdAt )
msgId <- insertedRowId db
pure $ Right SndMessage { msgId , sharedMsgId = SharedMsgId sharedMsgId , msgBody }
2023-06-18 10:20:11 +01:00
where
( connId_ , groupId_ ) = case connOrGroupId of
ConnectionId connId -> ( Just connId , Nothing )
GroupId groupId -> ( Nothing , Just groupId )
createSndMsgDelivery :: DB . Connection -> SndMsgDelivery -> MessageId -> IO Int64
2023-12-23 17:07:23 +04:00
createSndMsgDelivery db SndMsgDelivery { connId , agentMsgId } messageId = do
2023-06-18 10:20:11 +01:00
currentTs <- getCurrentTime
2023-12-23 17:07:23 +04:00
DB . execute
db
[ sql |
INSERT INTO msg_deliveries
( message_id , connection_id , agent_msg_id , chat_ts , created_at , updated_at , delivery_status )
VALUES ( ? , ? , ? , ? , ? , ? , ? )
| ]
( messageId , connId , agentMsgId , currentTs , currentTs , currentTs , MDSSndAgent )
insertedRowId db
2023-06-18 10:20:11 +01:00
2023-12-23 17:07:23 +04:00
createNewMessageAndRcvMsgDelivery :: forall e . MsgEncodingI e => DB . Connection -> ConnOrGroupId -> NewRcvMessage e -> Maybe SharedMsgId -> RcvMsgDelivery -> Maybe GroupMemberId -> ExceptT StoreError IO RcvMessage
2023-11-18 21:52:01 +04:00
createNewMessageAndRcvMsgDelivery db connOrGroupId newMessage sharedMsgId_ RcvMsgDelivery { connId , agentMsgId , agentMsgMeta , agentAckCmdId } authorGroupMemberId_ = do
msg @ RcvMessage { msgId } <- createNewRcvMessage db connOrGroupId newMessage sharedMsgId_ authorGroupMemberId_ Nothing
liftIO $ do
currentTs <- getCurrentTime
DB . execute
db
2023-12-23 17:07:23 +04:00
[ sql |
INSERT INTO msg_deliveries
( message_id , connection_id , agent_msg_id , agent_msg_meta , agent_ack_cmd_id , chat_ts , created_at , updated_at , delivery_status )
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? )
| ]
( msgId , connId , agentMsgId , msgMetaJson agentMsgMeta , agentAckCmdId , snd $ broker agentMsgMeta , currentTs , currentTs , MDSRcvAgent )
2023-11-18 21:52:01 +04:00
pure msg
2023-12-23 17:07:23 +04:00
createNewRcvMessage :: forall e . MsgEncodingI e => DB . Connection -> ConnOrGroupId -> NewRcvMessage e -> Maybe SharedMsgId -> Maybe GroupMemberId -> Maybe GroupMemberId -> ExceptT StoreError IO RcvMessage
createNewRcvMessage db connOrGroupId NewRcvMessage { chatMsgEvent , msgBody } sharedMsgId_ authorMember forwardedByMember =
2023-11-18 21:52:01 +04:00
case connOrGroupId of
ConnectionId connId -> liftIO $ insertRcvMsg ( Just connId ) Nothing
GroupId groupId -> case sharedMsgId_ of
2023-11-26 18:16:37 +00:00
Just sharedMsgId ->
liftIO ( duplicateGroupMsgMemberIds groupId sharedMsgId ) >>= \ case
Just ( duplAuthorId , duplFwdMemberId ) ->
throwError $ SEDuplicateGroupMessage groupId sharedMsgId duplAuthorId duplFwdMemberId
Nothing -> liftIO $ insertRcvMsg Nothing $ Just groupId
2023-11-18 21:52:01 +04:00
Nothing -> liftIO $ insertRcvMsg Nothing $ Just groupId
2023-11-26 18:16:37 +00:00
where
duplicateGroupMsgMemberIds :: Int64 -> SharedMsgId -> IO ( Maybe ( Maybe GroupMemberId , Maybe GroupMemberId ) )
duplicateGroupMsgMemberIds groupId sharedMsgId =
maybeFirstRow id $
DB . query
db
[ sql |
SELECT author_group_member_id , forwarded_by_group_member_id
FROM messages
WHERE group_id = ? AND shared_msg_id = ? LIMIT 1
| ]
( groupId , sharedMsgId )
insertRcvMsg connId_ groupId_ = do
currentTs <- getCurrentTime
DB . execute
2023-11-18 21:52:01 +04:00
db
[ sql |
2023-11-26 18:16:37 +00:00
INSERT INTO messages
( msg_sent , chat_msg_event , msg_body , created_at , updated_at , connection_id , group_id , shared_msg_id , author_group_member_id , forwarded_by_group_member_id )
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )
2023-11-18 21:52:01 +04:00
| ]
2023-11-26 18:16:37 +00:00
( MDRcv , toCMEventTag chatMsgEvent , msgBody , currentTs , currentTs , connId_ , groupId_ , sharedMsgId_ , authorMember , forwardedByMember )
msgId <- insertedRowId db
pure RcvMessage { msgId , chatMsgEvent = ACME ( encoding @ e ) chatMsgEvent , sharedMsgId_ , msgBody , authorMember , forwardedByMember }
2023-06-18 10:20:11 +01:00
2023-12-23 17:07:23 +04:00
updateSndMsgDeliveryStatus :: DB . Connection -> Int64 -> AgentMsgId -> MsgDeliveryStatus 'MDSnd -> IO ()
updateSndMsgDeliveryStatus db connId agentMsgId sndMsgDeliveryStatus = do
currentTs <- getCurrentTime
2023-06-18 10:20:11 +01:00
DB . execute
db
[ sql |
2023-12-23 17:07:23 +04:00
UPDATE msg_deliveries
SET delivery_status = ? , updated_at = ?
WHERE connection_id = ? AND agent_msg_id = ?
2023-06-18 10:20:11 +01:00
| ]
2023-12-23 17:07:23 +04:00
( sndMsgDeliveryStatus , currentTs , connId , agentMsgId )
2023-06-18 10:20:11 +01:00
2023-12-23 17:07:23 +04:00
updateRcvMsgDeliveryStatus :: DB . Connection -> Int64 -> CommandId -> MsgDeliveryStatus 'MDRcv -> IO ()
updateRcvMsgDeliveryStatus db connId cmdId rcvMsgDeliveryStatus = do
currentTs <- getCurrentTime
2023-06-18 10:20:11 +01:00
DB . execute
db
[ sql |
2023-12-23 17:07:23 +04:00
UPDATE msg_deliveries
SET delivery_status = ? , updated_at = ?
WHERE connection_id = ? AND agent_ack_cmd_id = ?
2023-06-18 10:20:11 +01:00
| ]
2023-12-23 17:07:23 +04:00
( rcvMsgDeliveryStatus , currentTs , connId , cmdId )
2023-06-18 10:20:11 +01:00
createPendingGroupMessage :: DB . Connection -> Int64 -> MessageId -> Maybe Int64 -> IO ()
createPendingGroupMessage db groupMemberId messageId introId_ = do
currentTs <- getCurrentTime
DB . execute
db
[ sql |
INSERT INTO pending_group_messages
( group_member_id , message_id , group_member_intro_id , created_at , updated_at ) VALUES ( ? , ? , ? , ? , ? )
| ]
( groupMemberId , messageId , introId_ , currentTs , currentTs )
getPendingGroupMessages :: DB . Connection -> Int64 -> IO [ PendingGroupMessage ]
getPendingGroupMessages db groupMemberId =
map pendingGroupMessage
<$> DB . query
db
[ sql |
SELECT pgm . message_id , m . chat_msg_event , m . msg_body , pgm . group_member_intro_id
FROM pending_group_messages pgm
JOIN messages m USING ( message_id )
WHERE pgm . group_member_id = ?
ORDER BY pgm . message_id ASC
| ]
( Only groupMemberId )
where
pendingGroupMessage ( msgId , cmEventTag , msgBody , introId_ ) =
PendingGroupMessage { msgId , cmEventTag , msgBody , introId_ }
deletePendingGroupMessage :: DB . Connection -> Int64 -> MessageId -> IO ()
deletePendingGroupMessage db groupMemberId messageId =
DB . execute db " DELETE FROM pending_group_messages WHERE group_member_id = ? AND message_id = ? " ( groupMemberId , messageId )
deleteOldMessages :: DB . Connection -> UTCTime -> IO ()
deleteOldMessages db createdAtCutoff = do
DB . execute db " DELETE FROM messages WHERE created_at <= ? " ( Only createdAtCutoff )
type NewQuoteRow = ( Maybe SharedMsgId , Maybe UTCTime , Maybe MsgContent , Maybe Bool , Maybe MemberId )
updateChatTs :: DB . Connection -> User -> ChatDirection c d -> UTCTime -> IO ()
updateChatTs db User { userId } chatDirection chatTs = case toChatInfo chatDirection of
DirectChat Contact { contactId } ->
DB . execute
db
" UPDATE contacts SET chat_ts = ? WHERE user_id = ? AND contact_id = ? "
( chatTs , userId , contactId )
GroupChat GroupInfo { groupId } ->
DB . execute
db
" UPDATE groups SET chat_ts = ? WHERE user_id = ? AND group_id = ? "
( chatTs , userId , groupId )
_ -> pure ()
createNewSndChatItem :: DB . Connection -> User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> Maybe ( CIQuote c ) -> Maybe CITimed -> Bool -> UTCTime -> IO ChatItemId
createNewSndChatItem db user chatDirection SndMessage { msgId , sharedMsgId } ciContent quotedItem timed live createdAt =
2023-11-18 21:52:01 +04:00
createNewChatItem_ db user chatDirection createdByMsgId ( Just sharedMsgId ) ciContent quoteRow timed live createdAt Nothing createdAt
2023-06-18 10:20:11 +01:00
where
createdByMsgId = if msgId == 0 then Nothing else Just msgId
quoteRow :: NewQuoteRow
quoteRow = case quotedItem of
Nothing -> ( Nothing , Nothing , Nothing , Nothing , Nothing )
Just CIQuote { chatDir , sharedMsgId = quotedSharedMsgId , sentAt , content } ->
uncurry ( quotedSharedMsgId , Just sentAt , Just content , , ) $ case chatDir of
CIQDirectSnd -> ( Just True , Nothing )
CIQDirectRcv -> ( Just False , Nothing )
CIQGroupSnd -> ( Just True , Nothing )
CIQGroupRcv ( Just GroupMember { memberId } ) -> ( Just False , Just memberId )
CIQGroupRcv Nothing -> ( Just False , Nothing )
createNewRcvChatItem :: DB . Connection -> User -> ChatDirection c 'MDRcv -> RcvMessage -> Maybe SharedMsgId -> CIContent 'MDRcv -> Maybe CITimed -> Bool -> UTCTime -> UTCTime -> IO ( ChatItemId , Maybe ( CIQuote c ) )
2023-11-20 14:00:55 +04:00
createNewRcvChatItem db user chatDirection RcvMessage { msgId , chatMsgEvent , forwardedByMember } sharedMsgId_ ciContent timed live itemTs createdAt = do
ciId <- createNewChatItem_ db user chatDirection ( Just msgId ) sharedMsgId_ ciContent quoteRow timed live itemTs forwardedByMember createdAt
2023-06-18 10:20:11 +01:00
quotedItem <- mapM ( getChatItemQuote_ db user chatDirection ) quotedMsg
pure ( ciId , quotedItem )
where
quotedMsg = cmToQuotedMsg chatMsgEvent
quoteRow :: NewQuoteRow
quoteRow = case quotedMsg of
Nothing -> ( Nothing , Nothing , Nothing , Nothing , Nothing )
Just QuotedMsg { msgRef = MsgRef { msgId = sharedMsgId , sentAt , sent , memberId } , content } ->
uncurry ( sharedMsgId , Just sentAt , Just content , , ) $ case chatDirection of
CDDirectRcv _ -> ( Just $ not sent , Nothing )
CDGroupRcv GroupInfo { membership = GroupMember { memberId = userMemberId } } _ ->
( Just $ Just userMemberId == memberId , memberId )
createNewChatItemNoMsg :: forall c d . MsgDirectionI d => DB . Connection -> User -> ChatDirection c d -> CIContent d -> UTCTime -> UTCTime -> IO ChatItemId
2023-11-18 21:52:01 +04:00
createNewChatItemNoMsg db user chatDirection ciContent itemTs =
createNewChatItem_ db user chatDirection Nothing Nothing ciContent quoteRow Nothing False itemTs Nothing
2023-06-18 10:20:11 +01:00
where
quoteRow :: NewQuoteRow
quoteRow = ( Nothing , Nothing , Nothing , Nothing , Nothing )
2023-11-18 21:52:01 +04:00
createNewChatItem_ :: forall c d . MsgDirectionI d => DB . Connection -> User -> ChatDirection c d -> Maybe MessageId -> Maybe SharedMsgId -> CIContent d -> NewQuoteRow -> Maybe CITimed -> Bool -> UTCTime -> Maybe GroupMemberId -> UTCTime -> IO ChatItemId
2023-11-20 14:00:55 +04:00
createNewChatItem_ db User { userId } chatDirection msgId_ sharedMsgId ciContent quoteRow timed live itemTs forwardedByMember createdAt = do
2023-06-18 10:20:11 +01:00
DB . execute
db
[ sql |
INSERT INTO chat_items (
-- user and IDs
user_id , created_by_msg_id , contact_id , group_id , group_member_id ,
-- meta
2023-12-14 17:08:40 +04:00
item_sent , item_ts , item_content , item_content_tag , item_text , item_status , shared_msg_id ,
forwarded_by_group_member_id , created_at , updated_at , item_live , timed_ttl , timed_delete_at ,
2023-06-18 10:20:11 +01:00
-- quote
quoted_shared_msg_id , quoted_sent_at , quoted_content , quoted_sent , quoted_member_id
2023-12-14 17:08:40 +04:00
) VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )
2023-06-18 10:20:11 +01:00
| ]
( ( userId , msgId_ ) :. idsRow :. itemRow :. quoteRow )
ciId <- insertedRowId db
forM_ msgId_ $ \ msgId -> insertChatItemMessage_ db ciId msgId createdAt
pure ciId
where
2023-12-14 17:08:40 +04:00
itemRow :: ( SMsgDirection d , UTCTime , CIContent d , Text , Text , CIStatus d , Maybe SharedMsgId , Maybe GroupMemberId ) :. ( UTCTime , UTCTime , Maybe Bool ) :. ( Maybe Int , Maybe UTCTime )
itemRow = ( msgDirection @ d , itemTs , ciContent , toCIContentTag ciContent , ciContentToText ciContent , ciCreateStatus ciContent , sharedMsgId , forwardedByMember ) :. ( createdAt , createdAt , justTrue live ) :. ciTimedRow timed
2023-06-18 10:20:11 +01:00
idsRow :: ( Maybe Int64 , Maybe Int64 , Maybe Int64 )
idsRow = case chatDirection of
CDDirectRcv Contact { contactId } -> ( Just contactId , Nothing , Nothing )
CDDirectSnd Contact { contactId } -> ( Just contactId , Nothing , Nothing )
CDGroupRcv GroupInfo { groupId } GroupMember { groupMemberId } -> ( Nothing , Just groupId , Just groupMemberId )
CDGroupSnd GroupInfo { groupId } -> ( Nothing , Just groupId , Nothing )
ciTimedRow :: Maybe CITimed -> ( Maybe Int , Maybe UTCTime )
ciTimedRow ( Just CITimed { ttl , deleteAt } ) = ( Just ttl , deleteAt )
ciTimedRow _ = ( Nothing , Nothing )
insertChatItemMessage_ :: DB . Connection -> ChatItemId -> MessageId -> UTCTime -> IO ()
insertChatItemMessage_ db ciId msgId ts = DB . execute db " INSERT INTO chat_item_messages (chat_item_id, message_id, created_at, updated_at) VALUES (?,?,?,?) " ( ciId , msgId , ts , ts )
getChatItemQuote_ :: DB . Connection -> User -> ChatDirection c 'MDRcv -> QuotedMsg -> IO ( CIQuote c )
getChatItemQuote_ db User { userId , userContactId } chatDirection QuotedMsg { msgRef = MsgRef { msgId , sentAt , sent , memberId } , content } =
case chatDirection of
CDDirectRcv Contact { contactId } -> getDirectChatItemQuote_ contactId ( not sent )
CDGroupRcv GroupInfo { groupId , membership = GroupMember { memberId = userMemberId } } sender @ GroupMember { memberId = senderMemberId } ->
case memberId of
Just mId
| mId == userMemberId -> ( ` ciQuote ` CIQGroupSnd ) <$> getUserGroupChatItemId_ groupId
| mId == senderMemberId -> ( ` ciQuote ` CIQGroupRcv ( Just sender ) ) <$> getGroupChatItemId_ groupId mId
| otherwise -> getGroupChatItemQuote_ groupId mId
_ -> pure . ciQuote Nothing $ CIQGroupRcv Nothing
where
ciQuote :: Maybe ChatItemId -> CIQDirection c -> CIQuote c
ciQuote itemId dir = CIQuote dir itemId msgId sentAt content . parseMaybeMarkdownList $ msgContentText content
getDirectChatItemQuote_ :: Int64 -> Bool -> IO ( CIQuote 'CTDirect )
getDirectChatItemQuote_ contactId userSent = do
fmap ciQuoteDirect . maybeFirstRow fromOnly $
DB . query
db
" SELECT chat_item_id FROM chat_items WHERE user_id = ? AND contact_id = ? AND shared_msg_id = ? AND item_sent = ? "
( userId , contactId , msgId , userSent )
where
ciQuoteDirect :: Maybe ChatItemId -> CIQuote 'CTDirect
ciQuoteDirect = ( ` ciQuote ` if userSent then CIQDirectSnd else CIQDirectRcv )
getUserGroupChatItemId_ :: Int64 -> IO ( Maybe ChatItemId )
getUserGroupChatItemId_ groupId =
maybeFirstRow fromOnly $
DB . query
db
" SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND shared_msg_id = ? AND item_sent = ? AND group_member_id IS NULL "
( userId , groupId , msgId , MDSnd )
getGroupChatItemId_ :: Int64 -> MemberId -> IO ( Maybe ChatItemId )
getGroupChatItemId_ groupId mId =
maybeFirstRow fromOnly $
DB . query
db
" SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND shared_msg_id = ? AND item_sent = ? AND group_member_id = ? "
( userId , groupId , msgId , MDRcv , mId )
getGroupChatItemQuote_ :: Int64 -> MemberId -> IO ( CIQuote 'CTGroup )
getGroupChatItemQuote_ groupId mId = do
ciQuoteGroup
<$> DB . queryNamed
db
[ sql |
2023-12-11 15:50:32 +02:00
SELECT i . chat_item_id ,
2023-06-18 10:20:11 +01:00
-- GroupMember
2023-11-18 21:52:01 +04:00
m . group_member_id , m . group_id , m . member_id , m . peer_chat_min_version , m . peer_chat_max_version , m . member_role , m . member_category ,
m . member_status , m . show_messages , m . invited_by , m . invited_by_group_member_id , m . local_display_name , m . contact_id , m . contact_profile_id , p . contact_profile_id ,
2023-06-18 10:20:11 +01:00
p . display_name , p . full_name , p . image , p . contact_link , p . local_alias , p . preferences
FROM group_members m
JOIN contact_profiles p ON p . contact_profile_id = COALESCE ( m . member_profile_id , m . contact_profile_id )
LEFT JOIN contacts c ON m . contact_id = c . contact_id
LEFT JOIN chat_items i ON i . group_id = m . group_id
AND m . group_member_id = i . group_member_id
AND i . shared_msg_id = : msg_id
WHERE m . user_id = : user_id AND m . group_id = : group_id AND m . member_id = : member_id
| ]
[ " :user_id " := userId , " :group_id " := groupId , " :member_id " := mId , " :msg_id " := msgId ]
where
ciQuoteGroup :: [ Only ( Maybe ChatItemId ) :. GroupMemberRow ] -> CIQuote 'CTGroup
ciQuoteGroup [] = ciQuote Nothing $ CIQGroupRcv Nothing
ciQuoteGroup ( ( Only itemId :. memberRow ) : _ ) = ciQuote itemId . CIQGroupRcv . Just $ toGroupMember userContactId memberRow
2023-12-11 15:50:32 +02:00
getChatPreviews :: DB . Connection -> User -> Bool -> PaginationByTime -> ChatListQuery -> IO [ Either StoreError AChat ]
getChatPreviews db user withPCC pagination query = do
directChats <- findDirectChatPreviews_ db user pagination query
groupChats <- findGroupChatPreviews_ db user pagination query
cReqChats <- getContactRequestChatPreviews_ db user pagination query
connChats <- if withPCC then getContactConnectionChatPreviews_ db user pagination query else pure []
let refs = sortTake $ concat [ directChats , groupChats , cReqChats , connChats ]
mapM ( runExceptT <$> getChatPreview ) refs
2023-06-18 10:20:11 +01:00
where
2023-12-11 15:50:32 +02:00
ts :: AChatPreviewData -> UTCTime
ts ( ACPD _ cpd ) = case cpd of
2023-12-13 15:32:23 +04:00
( DirectChatPD t _ _ _ ) -> t
( GroupChatPD t _ _ _ ) -> t
2023-12-11 15:50:32 +02:00
( ContactRequestPD t _ ) -> t
( ContactConnectionPD t _ ) -> t
sortTake = case pagination of
PTLast count -> take count . sortBy ( comparing $ Down . ts )
PTAfter _ count -> reverse . take count . sortBy ( comparing ts )
PTBefore _ count -> take count . sortBy ( comparing $ Down . ts )
getChatPreview :: AChatPreviewData -> ExceptT StoreError IO AChat
getChatPreview ( ACPD cType cpd ) = case cType of
SCTDirect -> getDirectChatPreview_ db user cpd
SCTGroup -> getGroupChatPreview_ db user cpd
SCTContactRequest -> let ( ContactRequestPD _ chat ) = cpd in pure chat
SCTContactConnection -> let ( ContactConnectionPD _ chat ) = cpd in pure chat
data ChatPreviewData ( c :: ChatType ) where
2023-12-13 15:32:23 +04:00
DirectChatPD :: UTCTime -> ContactId -> Maybe ChatItemId -> ChatStats -> ChatPreviewData 'CTDirect
GroupChatPD :: UTCTime -> GroupId -> Maybe ChatItemId -> ChatStats -> ChatPreviewData 'CTGroup
2023-12-11 15:50:32 +02:00
ContactRequestPD :: UTCTime -> AChat -> ChatPreviewData 'CTContactRequest
ContactConnectionPD :: UTCTime -> AChat -> ChatPreviewData 'CTContactConnection
data AChatPreviewData = forall c . ChatTypeI c => ACPD ( SChatType c ) ( ChatPreviewData c )
paginationByTimeFilter :: PaginationByTime -> ( Query , [ NamedParam ] )
paginationByTimeFilter = \ case
PTLast count -> ( " \ n ORDER BY ts DESC LIMIT :count " , [ " :count " := count ] )
PTAfter ts count -> ( " \ n AND ts > :ts ORDER BY ts ASC LIMIT :count " , [ " :ts " := ts , " :count " := count ] )
PTBefore ts count -> ( " \ n AND ts < :ts ORDER BY ts DESC LIMIT :count " , [ " :ts " := ts , " :count " := count ] )
2023-12-13 15:32:23 +04:00
type ChatStatsRow = ( Int , ChatItemId , Bool )
2023-12-11 15:50:32 +02:00
2023-12-13 15:32:23 +04:00
toChatStats :: ChatStatsRow -> ChatStats
toChatStats ( unreadCount , minUnreadItemId , unreadChat ) = ChatStats { unreadCount , minUnreadItemId , unreadChat }
2023-12-11 15:50:32 +02:00
findDirectChatPreviews_ :: DB . Connection -> User -> PaginationByTime -> ChatListQuery -> IO [ AChatPreviewData ]
findDirectChatPreviews_ db User { userId } pagination clq =
map toPreview <$> getPreviews
where
2023-12-13 15:32:23 +04:00
toPreview :: ( ContactId , UTCTime , Maybe ChatItemId ) :. ChatStatsRow -> AChatPreviewData
toPreview ( ( contactId , ts , lastItemId_ ) :. statsRow ) =
ACPD SCTDirect $ DirectChatPD ts contactId lastItemId_ ( toChatStats statsRow )
baseQuery =
[ sql |
SELECT ct . contact_id , ct . chat_ts as ts , LastItems . chat_item_id , COALESCE ( ChatStats . UnreadCount , 0 ) , COALESCE ( ChatStats . MinUnread , 0 ) , ct . unread_chat
FROM contacts ct
LEFT JOIN (
SELECT contact_id , chat_item_id , MAX ( created_at )
FROM chat_items
GROUP BY contact_id
) LastItems ON LastItems . contact_id = ct . contact_id
LEFT JOIN (
SELECT contact_id , COUNT ( 1 ) AS UnreadCount , MIN ( chat_item_id ) AS MinUnread
FROM chat_items
WHERE item_status = : rcv_new
GROUP BY contact_id
) ChatStats ON ChatStats . contact_id = ct . contact_id
| ]
2023-12-11 15:50:32 +02:00
( pagQuery , pagParams ) = paginationByTimeFilter pagination
getPreviews = case clq of
CLQFilters { favorite = False , unread = False } ->
DB . queryNamed
db
2023-12-13 15:32:23 +04:00
( baseQuery
<> [ sql |
WHERE ct . user_id = : user_id AND ct . is_user = 0 AND ct . deleted = 0 AND ct . contact_used
| ]
2023-12-11 15:50:32 +02:00
<> pagQuery
)
2023-12-13 15:32:23 +04:00
( [ " :user_id " := userId , " :rcv_new " := CISRcvNew ] <> pagParams )
2023-12-11 15:50:32 +02:00
CLQFilters { favorite = True , unread = False } ->
DB . queryNamed
db
2023-12-13 15:32:23 +04:00
( baseQuery
<> [ sql |
WHERE ct . user_id = : user_id AND ct . is_user = 0 AND ct . deleted = 0 AND ct . contact_used
AND ct . favorite = 1
| ]
2023-12-11 15:50:32 +02:00
<> pagQuery
)
2023-12-13 15:32:23 +04:00
( [ " :user_id " := userId , " :rcv_new " := CISRcvNew ] <> pagParams )
2023-12-11 15:50:32 +02:00
CLQFilters { favorite = False , unread = True } ->
DB . queryNamed
db
2023-12-13 15:32:23 +04:00
( baseQuery
<> [ sql |
WHERE ct . user_id = : user_id AND ct . is_user = 0 AND ct . deleted = 0 AND ct . contact_used
AND ( ct . unread_chat = 1 OR ChatStats . UnreadCount > 0 )
| ]
2023-12-11 15:50:32 +02:00
<> pagQuery
)
( [ " :user_id " := userId , " :rcv_new " := CISRcvNew ] <> pagParams )
CLQFilters { favorite = True , unread = True } ->
DB . queryNamed
db
2023-12-13 15:32:23 +04:00
( baseQuery
<> [ sql |
WHERE ct . user_id = : user_id AND ct . is_user = 0 AND ct . deleted = 0 AND ct . contact_used
AND ( ct . favorite = 1
OR ct . unread_chat = 1 OR ChatStats . UnreadCount > 0 )
| ]
2023-12-11 15:50:32 +02:00
<> pagQuery
)
( [ " :user_id " := userId , " :rcv_new " := CISRcvNew ] <> pagParams )
CLQSearch { search } ->
DB . queryNamed
db
2023-12-13 15:32:23 +04:00
( baseQuery
<> [ sql |
JOIN contact_profiles cp ON ct . contact_profile_id = cp . contact_profile_id
WHERE ct . user_id = : user_id AND ct . is_user = 0 AND ct . deleted = 0 AND ct . contact_used
AND (
ct . local_display_name LIKE '%' || : search || '%'
OR cp . display_name LIKE '%' || : search || '%'
OR cp . full_name LIKE '%' || : search || '%'
OR cp . local_alias LIKE '%' || : search || '%'
)
| ]
2023-12-11 15:50:32 +02:00
<> pagQuery
2023-06-18 10:20:11 +01:00
)
2023-12-13 15:32:23 +04:00
( [ " :user_id " := userId , " :rcv_new " := CISRcvNew , " :search " := search ] <> pagParams )
2023-12-11 15:50:32 +02:00
getDirectChatPreview_ :: DB . Connection -> User -> ChatPreviewData 'CTDirect -> ExceptT StoreError IO AChat
2023-12-13 15:32:23 +04:00
getDirectChatPreview_ db user ( DirectChatPD _ contactId lastItemId_ stats ) = do
2023-12-11 15:50:32 +02:00
contact <- getContact db user contactId
2023-12-13 15:32:23 +04:00
lastItem <- case lastItemId_ of
Just lastItemId -> ( : [] ) <$> getDirectChatItem db user contactId lastItemId
Nothing -> pure []
2023-12-11 15:50:32 +02:00
pure $ AChat SCTDirect ( Chat ( DirectChat contact ) lastItem stats )
findGroupChatPreviews_ :: DB . Connection -> User -> PaginationByTime -> ChatListQuery -> IO [ AChatPreviewData ]
findGroupChatPreviews_ db User { userId } pagination clq =
map toPreview <$> getPreviews
2023-06-18 10:20:11 +01:00
where
2023-12-13 15:32:23 +04:00
toPreview :: ( GroupId , UTCTime , Maybe ChatItemId ) :. ChatStatsRow -> AChatPreviewData
toPreview ( ( groupId , ts , lastItemId_ ) :. statsRow ) =
ACPD SCTGroup $ GroupChatPD ts groupId lastItemId_ ( toChatStats statsRow )
baseQuery =
[ sql |
SELECT g . group_id , g . chat_ts as ts , LastItems . chat_item_id , COALESCE ( ChatStats . UnreadCount , 0 ) , COALESCE ( ChatStats . MinUnread , 0 ) , g . unread_chat
FROM groups g
LEFT JOIN (
SELECT group_id , chat_item_id , MAX ( item_ts )
FROM chat_items
GROUP BY group_id
) LastItems ON LastItems . group_id = g . group_id
LEFT JOIN (
SELECT group_id , COUNT ( 1 ) AS UnreadCount , MIN ( chat_item_id ) AS MinUnread
FROM chat_items
WHERE item_status = : rcv_new
GROUP BY group_id
) ChatStats ON ChatStats . group_id = g . group_id
| ]
2023-12-11 15:50:32 +02:00
( pagQuery , pagParams ) = paginationByTimeFilter pagination
getPreviews = case clq of
CLQFilters { favorite = False , unread = False } ->
DB . queryNamed
db
2023-12-13 15:32:23 +04:00
( baseQuery
<> [ sql |
WHERE g . user_id = : user_id
| ]
2023-12-11 15:50:32 +02:00
<> pagQuery
)
2023-12-13 15:32:23 +04:00
( [ " :user_id " := userId , " :rcv_new " := CISRcvNew ] <> pagParams )
2023-12-11 15:50:32 +02:00
CLQFilters { favorite = True , unread = False } ->
DB . queryNamed
db
2023-12-13 15:32:23 +04:00
( baseQuery
<> [ sql |
WHERE g . user_id = : user_id
AND g . favorite = 1
| ]
2023-12-11 15:50:32 +02:00
<> pagQuery
)
2023-12-13 15:32:23 +04:00
( [ " :user_id " := userId , " :rcv_new " := CISRcvNew ] <> pagParams )
2023-12-11 15:50:32 +02:00
CLQFilters { favorite = False , unread = True } ->
DB . queryNamed
db
2023-12-13 15:32:23 +04:00
( baseQuery
<> [ sql |
WHERE g . user_id = : user_id
AND ( g . unread_chat = 1 OR ChatStats . UnreadCount > 0 )
| ]
2023-12-11 15:50:32 +02:00
<> pagQuery
)
( [ " :user_id " := userId , " :rcv_new " := CISRcvNew ] <> pagParams )
CLQFilters { favorite = True , unread = True } ->
DB . queryNamed
db
2023-12-13 15:32:23 +04:00
( baseQuery
<> [ sql |
WHERE g . user_id = : user_id
AND ( g . favorite = 1
OR g . unread_chat = 1 OR ChatStats . UnreadCount > 0 )
| ]
2023-12-11 15:50:32 +02:00
<> pagQuery
)
( [ " :user_id " := userId , " :rcv_new " := CISRcvNew ] <> pagParams )
CLQSearch { search } ->
DB . queryNamed
db
2023-12-13 15:32:23 +04:00
( baseQuery
<> [ sql |
JOIN group_profiles gp ON gp . group_profile_id = g . group_profile_id
WHERE g . user_id = : user_id
AND (
g . local_display_name LIKE '%' || : search || '%'
OR gp . display_name LIKE '%' || : search || '%'
OR gp . full_name LIKE '%' || : search || '%'
OR gp . description LIKE '%' || : search || '%'
)
| ]
2023-12-11 15:50:32 +02:00
<> pagQuery
)
2023-12-13 15:32:23 +04:00
( [ " :user_id " := userId , " :rcv_new " := CISRcvNew , " :search " := search ] <> pagParams )
2023-12-11 15:50:32 +02:00
getGroupChatPreview_ :: DB . Connection -> User -> ChatPreviewData 'CTGroup -> ExceptT StoreError IO AChat
2023-12-13 15:32:23 +04:00
getGroupChatPreview_ db user ( GroupChatPD _ groupId lastItemId_ stats ) = do
2023-12-11 15:50:32 +02:00
groupInfo <- getGroupInfo db user groupId
2023-12-13 15:32:23 +04:00
lastItem <- case lastItemId_ of
Just lastItemId -> ( : [] ) <$> getGroupChatItem db user groupId lastItemId
Nothing -> pure []
2023-12-11 15:50:32 +02:00
pure $ AChat SCTGroup ( Chat ( GroupChat groupInfo ) lastItem stats )
getContactRequestChatPreviews_ :: DB . Connection -> User -> PaginationByTime -> ChatListQuery -> IO [ AChatPreviewData ]
getContactRequestChatPreviews_ db User { userId } pagination clq = case clq of
CLQFilters { favorite = False , unread = False } -> query " "
CLQFilters { favorite = True , unread = False } -> pure []
CLQFilters { favorite = False , unread = True } -> query " "
CLQFilters { favorite = True , unread = True } -> query " "
CLQSearch { search } -> query search
2023-06-18 10:20:11 +01:00
where
2023-12-11 15:50:32 +02:00
( pagQuery , pagParams ) = paginationByTimeFilter pagination
query search =
map toPreview
<$> DB . queryNamed
db
( [ sql |
SELECT
cr . contact_request_id , cr . local_display_name , cr . agent_invitation_id , cr . user_contact_link_id ,
c . agent_conn_id , cr . contact_profile_id , p . display_name , p . full_name , p . image , p . contact_link , cr . xcontact_id , p . preferences ,
cr . created_at , cr . updated_at as ts ,
cr . peer_chat_min_version , cr . peer_chat_max_version
FROM contact_requests cr
JOIN connections c ON c . user_contact_link_id = cr . user_contact_link_id
JOIN contact_profiles p ON p . contact_profile_id = cr . contact_profile_id
JOIN user_contact_links uc ON uc . user_contact_link_id = cr . user_contact_link_id
WHERE cr . user_id = : user_id
AND uc . user_id = : user_id
AND uc . local_display_name = ' '
AND uc . group_id IS NULL
AND (
cr . local_display_name LIKE '%' || : search || '%'
OR p . display_name LIKE '%' || : search || '%'
OR p . full_name LIKE '%' || : search || '%'
)
| ]
<> pagQuery
)
( [ " :user_id " := userId , " :search " := search ] <> pagParams )
toPreview :: ContactRequestRow -> AChatPreviewData
toPreview cReqRow =
let cReq @ UserContactRequest { updatedAt } = toContactRequest cReqRow
2023-06-18 10:20:11 +01:00
stats = ChatStats { unreadCount = 0 , minUnreadItemId = 0 , unreadChat = False }
2023-12-11 15:50:32 +02:00
aChat = AChat SCTContactRequest $ Chat ( ContactRequest cReq ) [] stats
in ACPD SCTContactRequest $ ContactRequestPD updatedAt aChat
getContactConnectionChatPreviews_ :: DB . Connection -> User -> PaginationByTime -> ChatListQuery -> IO [ AChatPreviewData ]
getContactConnectionChatPreviews_ db User { userId } pagination clq = case clq of
CLQFilters { favorite = False , unread = False } -> query " "
CLQFilters { favorite = True , unread = False } -> pure []
CLQFilters { favorite = False , unread = True } -> pure []
CLQFilters { favorite = True , unread = True } -> pure []
CLQSearch { search } -> query search
2023-06-18 10:20:11 +01:00
where
2023-12-11 15:50:32 +02:00
( pagQuery , pagParams ) = paginationByTimeFilter pagination
query search =
map toPreview
<$> DB . queryNamed
db
( [ sql |
SELECT
connection_id , agent_conn_id , conn_status , via_contact_uri_hash , via_user_contact_link , group_link_id ,
custom_user_profile_id , conn_req_inv , local_alias , created_at , updated_at as ts
FROM connections
WHERE user_id = : user_id
AND conn_type = : conn_contact
AND contact_id IS NULL
AND conn_level = 0
AND via_contact IS NULL
AND ( via_group_link = 0 || ( via_group_link = 1 AND group_link_id IS NOT NULL ) )
AND local_alias LIKE '%' || : search || '%'
| ]
<> pagQuery
)
( [ " :user_id " := userId , " :conn_contact " := ConnContact , " :search " := search ] <> pagParams )
toPreview :: ( Int64 , ConnId , ConnStatus , Maybe ByteString , Maybe Int64 , Maybe GroupLinkId , Maybe Int64 , Maybe ConnReqInvitation , LocalAlias , UTCTime , UTCTime ) -> AChatPreviewData
toPreview connRow =
let conn @ PendingContactConnection { updatedAt } = toPendingContactConnection connRow
2023-06-18 10:20:11 +01:00
stats = ChatStats { unreadCount = 0 , minUnreadItemId = 0 , unreadChat = False }
2023-12-11 15:50:32 +02:00
aChat = AChat SCTContactConnection $ Chat ( ContactConnection conn ) [] stats
in ACPD SCTContactConnection $ ContactConnectionPD updatedAt aChat
2023-06-18 10:20:11 +01:00
getDirectChat :: DB . Connection -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO ( Chat 'CTDirect )
getDirectChat db user contactId pagination search_ = do
let search = fromMaybe " " search_
ct <- getContact db user contactId
liftIO . getDirectChatReactions_ db ct =<< case pagination of
CPLast count -> getDirectChatLast_ db user ct count search
CPAfter afterId count -> getDirectChatAfter_ db user ct afterId count search
CPBefore beforeId count -> getDirectChatBefore_ db user ct beforeId count search
getDirectChatLast_ :: DB . Connection -> User -> Contact -> Int -> String -> ExceptT StoreError IO ( Chat 'CTDirect )
getDirectChatLast_ db user ct @ Contact { contactId } count search = do
let stats = ChatStats { unreadCount = 0 , minUnreadItemId = 0 , unreadChat = False }
chatItems <- getDirectChatItemsLast db user contactId count search
pure $ Chat ( DirectChat ct ) ( reverse chatItems ) stats
-- the last items in reverse order (the last item in the conversation is the first in the returned list)
getDirectChatItemsLast :: DB . Connection -> User -> ContactId -> Int -> String -> ExceptT StoreError IO [ CChatItem 'CTDirect ]
getDirectChatItemsLast db User { userId } contactId count search = ExceptT $ do
currentTs <- getCurrentTime
mapM ( toDirectChatItem currentTs )
<$> DB . query
db
[ sql |
SELECT
-- ChatItem
i . chat_item_id , i . item_ts , i . item_sent , i . item_content , i . item_text , i . item_status , i . shared_msg_id , i . item_deleted , i . item_deleted_ts , i . item_edited , i . created_at , i . updated_at , i . timed_ttl , i . timed_delete_at , i . item_live ,
-- CIFile
2023-09-01 19:43:27 +01:00
f . file_id , f . file_name , f . file_size , f . file_path , f . file_crypto_key , f . file_crypto_nonce , f . ci_file_status , f . protocol ,
2023-06-18 10:20:11 +01:00
-- DirectQuote
ri . chat_item_id , i . quoted_shared_msg_id , i . quoted_sent_at , i . quoted_content , i . quoted_sent
FROM chat_items i
LEFT JOIN files f ON f . chat_item_id = i . chat_item_id
LEFT JOIN chat_items ri ON ri . user_id = i . user_id AND ri . contact_id = i . contact_id AND ri . shared_msg_id = i . quoted_shared_msg_id
WHERE i . user_id = ? AND i . contact_id = ? AND i . item_text LIKE '%' || ? || '%'
2023-11-27 17:14:12 +04:00
ORDER BY i . created_at DESC , i . chat_item_id DESC
2023-06-18 10:20:11 +01:00
LIMIT ?
| ]
( userId , contactId , search , count )
getDirectChatAfter_ :: DB . Connection -> User -> Contact -> ChatItemId -> Int -> String -> ExceptT StoreError IO ( Chat 'CTDirect )
getDirectChatAfter_ db User { userId } ct @ Contact { contactId } afterChatItemId count search = do
let stats = ChatStats { unreadCount = 0 , minUnreadItemId = 0 , unreadChat = False }
chatItems <- ExceptT getDirectChatItemsAfter_
pure $ Chat ( DirectChat ct ) chatItems stats
where
getDirectChatItemsAfter_ :: IO ( Either StoreError [ CChatItem 'CTDirect ] )
getDirectChatItemsAfter_ = do
currentTs <- getCurrentTime
mapM ( toDirectChatItem currentTs )
<$> DB . query
db
[ sql |
SELECT
-- ChatItem
i . chat_item_id , i . item_ts , i . item_sent , i . item_content , i . item_text , i . item_status , i . shared_msg_id , i . item_deleted , i . item_deleted_ts , i . item_edited , i . created_at , i . updated_at , i . timed_ttl , i . timed_delete_at , i . item_live ,
-- CIFile
2023-09-01 19:43:27 +01:00
f . file_id , f . file_name , f . file_size , f . file_path , f . file_crypto_key , f . file_crypto_nonce , f . ci_file_status , f . protocol ,
2023-06-18 10:20:11 +01:00
-- DirectQuote
ri . chat_item_id , i . quoted_shared_msg_id , i . quoted_sent_at , i . quoted_content , i . quoted_sent
FROM chat_items i
LEFT JOIN files f ON f . chat_item_id = i . chat_item_id
LEFT JOIN chat_items ri ON ri . user_id = i . user_id AND ri . contact_id = i . contact_id AND ri . shared_msg_id = i . quoted_shared_msg_id
WHERE i . user_id = ? AND i . contact_id = ? AND i . item_text LIKE '%' || ? || '%'
AND i . chat_item_id > ?
2023-11-27 17:14:12 +04:00
ORDER BY i . created_at ASC , i . chat_item_id ASC
2023-06-18 10:20:11 +01:00
LIMIT ?
| ]
( userId , contactId , search , afterChatItemId , count )
getDirectChatBefore_ :: DB . Connection -> User -> Contact -> ChatItemId -> Int -> String -> ExceptT StoreError IO ( Chat 'CTDirect )
getDirectChatBefore_ db User { userId } ct @ Contact { contactId } beforeChatItemId count search = do
let stats = ChatStats { unreadCount = 0 , minUnreadItemId = 0 , unreadChat = False }
chatItems <- ExceptT getDirectChatItemsBefore_
pure $ Chat ( DirectChat ct ) ( reverse chatItems ) stats
where
getDirectChatItemsBefore_ :: IO ( Either StoreError [ CChatItem 'CTDirect ] )
getDirectChatItemsBefore_ = do
currentTs <- getCurrentTime
mapM ( toDirectChatItem currentTs )
<$> DB . query
db
[ sql |
SELECT
-- ChatItem
i . chat_item_id , i . item_ts , i . item_sent , i . item_content , i . item_text , i . item_status , i . shared_msg_id , i . item_deleted , i . item_deleted_ts , i . item_edited , i . created_at , i . updated_at , i . timed_ttl , i . timed_delete_at , i . item_live ,
-- CIFile
2023-09-01 19:43:27 +01:00
f . file_id , f . file_name , f . file_size , f . file_path , f . file_crypto_key , f . file_crypto_nonce , f . ci_file_status , f . protocol ,
2023-06-18 10:20:11 +01:00
-- DirectQuote
ri . chat_item_id , i . quoted_shared_msg_id , i . quoted_sent_at , i . quoted_content , i . quoted_sent
FROM chat_items i
LEFT JOIN files f ON f . chat_item_id = i . chat_item_id
LEFT JOIN chat_items ri ON ri . user_id = i . user_id AND ri . contact_id = i . contact_id AND ri . shared_msg_id = i . quoted_shared_msg_id
WHERE i . user_id = ? AND i . contact_id = ? AND i . item_text LIKE '%' || ? || '%'
AND i . chat_item_id < ?
2023-11-27 17:14:12 +04:00
ORDER BY i . created_at DESC , i . chat_item_id DESC
2023-06-18 10:20:11 +01:00
LIMIT ?
| ]
( userId , contactId , search , beforeChatItemId , count )
getGroupChat :: DB . Connection -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO ( Chat 'CTGroup )
getGroupChat db user groupId pagination search_ = do
let search = fromMaybe " " search_
g <- getGroupInfo db user groupId
2023-10-18 10:19:24 +01:00
case pagination of
2023-06-18 10:20:11 +01:00
CPLast count -> getGroupChatLast_ db user g count search
CPAfter afterId count -> getGroupChatAfter_ db user g afterId count search
CPBefore beforeId count -> getGroupChatBefore_ db user g beforeId count search
getGroupChatLast_ :: DB . Connection -> User -> GroupInfo -> Int -> String -> ExceptT StoreError IO ( Chat 'CTGroup )
getGroupChatLast_ db user @ User { userId } g @ GroupInfo { groupId } count search = do
let stats = ChatStats { unreadCount = 0 , minUnreadItemId = 0 , unreadChat = False }
chatItemIds <- liftIO getGroupChatItemIdsLast_
2023-10-18 10:19:24 +01:00
chatItems <- mapM ( getGroupCIWithReactions db user g ) chatItemIds
2023-06-18 10:20:11 +01:00
pure $ Chat ( GroupChat g ) ( reverse chatItems ) stats
where
getGroupChatItemIdsLast_ :: IO [ ChatItemId ]
getGroupChatItemIdsLast_ =
map fromOnly
<$> DB . query
db
[ sql |
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%'
ORDER BY item_ts DESC , chat_item_id DESC
LIMIT ?
| ]
( userId , groupId , search , count )
getGroupMemberChatItemLast :: DB . Connection -> User -> GroupId -> GroupMemberId -> ExceptT StoreError IO ( CChatItem 'CTGroup )
getGroupMemberChatItemLast db user @ User { userId } groupId groupMemberId = do
chatItemId <-
ExceptT . firstRow fromOnly ( SEChatItemNotFoundByGroupId groupId ) $
DB . query
db
[ sql |
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND group_id = ? AND group_member_id = ?
ORDER BY item_ts DESC , chat_item_id DESC
LIMIT 1
| ]
( userId , groupId , groupMemberId )
getGroupChatItem db user groupId chatItemId
getGroupChatAfter_ :: DB . Connection -> User -> GroupInfo -> ChatItemId -> Int -> String -> ExceptT StoreError IO ( Chat 'CTGroup )
getGroupChatAfter_ db user @ User { userId } g @ GroupInfo { groupId } afterChatItemId count search = do
let stats = ChatStats { unreadCount = 0 , minUnreadItemId = 0 , unreadChat = False }
afterChatItem <- getGroupChatItem db user groupId afterChatItemId
chatItemIds <- liftIO $ getGroupChatItemIdsAfter_ ( chatItemTs afterChatItem )
2023-10-18 10:19:24 +01:00
chatItems <- mapM ( getGroupCIWithReactions db user g ) chatItemIds
2023-06-18 10:20:11 +01:00
pure $ Chat ( GroupChat g ) chatItems stats
where
getGroupChatItemIdsAfter_ :: UTCTime -> IO [ ChatItemId ]
getGroupChatItemIdsAfter_ afterChatItemTs =
map fromOnly
<$> DB . query
db
[ sql |
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%'
AND ( item_ts > ? OR ( item_ts = ? AND chat_item_id > ? ) )
ORDER BY item_ts ASC , chat_item_id ASC
LIMIT ?
| ]
( userId , groupId , search , afterChatItemTs , afterChatItemTs , afterChatItemId , count )
getGroupChatBefore_ :: DB . Connection -> User -> GroupInfo -> ChatItemId -> Int -> String -> ExceptT StoreError IO ( Chat 'CTGroup )
getGroupChatBefore_ db user @ User { userId } g @ GroupInfo { groupId } beforeChatItemId count search = do
let stats = ChatStats { unreadCount = 0 , minUnreadItemId = 0 , unreadChat = False }
beforeChatItem <- getGroupChatItem db user groupId beforeChatItemId
chatItemIds <- liftIO $ getGroupChatItemIdsBefore_ ( chatItemTs beforeChatItem )
2023-10-18 10:19:24 +01:00
chatItems <- mapM ( getGroupCIWithReactions db user g ) chatItemIds
2023-06-18 10:20:11 +01:00
pure $ Chat ( GroupChat g ) ( reverse chatItems ) stats
where
getGroupChatItemIdsBefore_ :: UTCTime -> IO [ ChatItemId ]
getGroupChatItemIdsBefore_ beforeChatItemTs =
map fromOnly
<$> DB . query
db
[ sql |
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%'
AND ( item_ts < ? OR ( item_ts = ? AND chat_item_id < ? ) )
ORDER BY item_ts DESC , chat_item_id DESC
LIMIT ?
| ]
( userId , groupId , search , beforeChatItemTs , beforeChatItemTs , beforeChatItemId , count )
toChatItemRef :: ( ChatItemId , Maybe Int64 , Maybe Int64 ) -> Either StoreError ( ChatRef , ChatItemId )
toChatItemRef = \ case
( itemId , Just contactId , Nothing ) -> Right ( ChatRef CTDirect contactId , itemId )
( itemId , Nothing , Just groupId ) -> Right ( ChatRef CTGroup groupId , itemId )
( itemId , _ , _ ) -> Left $ SEBadChatItem itemId
updateDirectChatItemsRead :: DB . Connection -> User -> ContactId -> Maybe ( ChatItemId , ChatItemId ) -> IO ()
updateDirectChatItemsRead db User { userId } contactId itemsRange_ = do
currentTs <- getCurrentTime
case itemsRange_ of
Just ( fromItemId , toItemId ) ->
DB . execute
db
[ sql |
UPDATE chat_items SET item_status = ? , updated_at = ?
WHERE user_id = ? AND contact_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ?
| ]
( CISRcvRead , currentTs , userId , contactId , fromItemId , toItemId , CISRcvNew )
_ ->
DB . execute
db
[ sql |
UPDATE chat_items SET item_status = ? , updated_at = ?
WHERE user_id = ? AND contact_id = ? AND item_status = ?
| ]
( CISRcvRead , currentTs , userId , contactId , CISRcvNew )
getDirectUnreadTimedItems :: DB . Connection -> User -> ContactId -> Maybe ( ChatItemId , ChatItemId ) -> IO [ ( ChatItemId , Int ) ]
getDirectUnreadTimedItems db User { userId } contactId itemsRange_ = case itemsRange_ of
Just ( fromItemId , toItemId ) ->
DB . query
db
[ sql |
SELECT chat_item_id , timed_ttl
FROM chat_items
WHERE user_id = ? AND contact_id = ?
AND chat_item_id >= ? AND chat_item_id <= ?
AND item_status = ?
AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL
AND ( item_live IS NULL OR item_live = ? )
| ]
( userId , contactId , fromItemId , toItemId , CISRcvNew , False )
_ ->
DB . query
db
[ sql |
SELECT chat_item_id , timed_ttl
FROM chat_items
WHERE user_id = ? AND contact_id = ? AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL
| ]
( userId , contactId , CISRcvNew )
setDirectChatItemDeleteAt :: DB . Connection -> User -> ContactId -> ChatItemId -> UTCTime -> IO ()
setDirectChatItemDeleteAt db User { userId } contactId chatItemId deleteAt =
DB . execute
db
" UPDATE chat_items SET timed_delete_at = ? WHERE user_id = ? AND contact_id = ? AND chat_item_id = ? "
( deleteAt , userId , contactId , chatItemId )
updateGroupChatItemsRead :: DB . Connection -> UserId -> GroupId -> Maybe ( ChatItemId , ChatItemId ) -> IO ()
updateGroupChatItemsRead db userId groupId itemsRange_ = do
currentTs <- getCurrentTime
case itemsRange_ of
Just ( fromItemId , toItemId ) ->
DB . execute
db
[ sql |
UPDATE chat_items SET item_status = ? , updated_at = ?
WHERE user_id = ? AND group_id = ? AND chat_item_id >= ? AND chat_item_id <= ? AND item_status = ?
| ]
( CISRcvRead , currentTs , userId , groupId , fromItemId , toItemId , CISRcvNew )
_ ->
DB . execute
db
[ sql |
UPDATE chat_items SET item_status = ? , updated_at = ?
WHERE user_id = ? AND group_id = ? AND item_status = ?
| ]
( CISRcvRead , currentTs , userId , groupId , CISRcvNew )
getGroupUnreadTimedItems :: DB . Connection -> User -> GroupId -> Maybe ( ChatItemId , ChatItemId ) -> IO [ ( ChatItemId , Int ) ]
getGroupUnreadTimedItems db User { userId } groupId itemsRange_ = case itemsRange_ of
Just ( fromItemId , toItemId ) ->
DB . query
db
[ sql |
SELECT chat_item_id , timed_ttl
FROM chat_items
WHERE user_id = ? AND group_id = ?
AND chat_item_id >= ? AND chat_item_id <= ?
AND item_status = ?
AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL
AND ( item_live IS NULL OR item_live = ? )
| ]
( userId , groupId , fromItemId , toItemId , CISRcvNew , False )
_ ->
DB . query
db
[ sql |
SELECT chat_item_id , timed_ttl
FROM chat_items
WHERE user_id = ? AND group_id = ? AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL
| ]
( userId , groupId , CISRcvNew )
setGroupChatItemDeleteAt :: DB . Connection -> User -> GroupId -> ChatItemId -> UTCTime -> IO ()
setGroupChatItemDeleteAt db User { userId } groupId chatItemId deleteAt =
DB . execute
db
" UPDATE chat_items SET timed_delete_at = ? WHERE user_id = ? AND group_id = ? AND chat_item_id = ? "
( deleteAt , userId , groupId , chatItemId )
2023-09-01 19:43:27 +01:00
type MaybeCIFIleRow = ( Maybe Int64 , Maybe String , Maybe Integer , Maybe FilePath , Maybe C . SbKey , Maybe C . CbNonce , Maybe ACIFileStatus , Maybe FileProtocol )
2023-06-18 10:20:11 +01:00
type ChatItemModeRow = ( Maybe Int , Maybe UTCTime , Maybe Bool )
2023-10-11 19:10:38 +01:00
type ChatItemRow = ( Int64 , ChatItemTs , AMsgDirection , Text , Text , ACIStatus , Maybe SharedMsgId ) :. ( Int , Maybe UTCTime , Maybe Bool , UTCTime , UTCTime ) :. ChatItemModeRow :. MaybeCIFIleRow
2023-06-18 10:20:11 +01:00
type QuoteRow = ( Maybe ChatItemId , Maybe SharedMsgId , Maybe UTCTime , Maybe MsgContent , Maybe Bool )
toDirectQuote :: QuoteRow -> Maybe ( CIQuote 'CTDirect )
toDirectQuote qr @ ( _ , _ , _ , _ , quotedSent ) = toQuote qr $ direction <$> quotedSent
where
direction sent = if sent then CIQDirectSnd else CIQDirectRcv
toQuote :: QuoteRow -> Maybe ( CIQDirection c ) -> Maybe ( CIQuote c )
toQuote ( quotedItemId , quotedSharedMsgId , quotedSentAt , quotedMsgContent , _ ) dir =
CIQuote <$> dir <*> pure quotedItemId <*> pure quotedSharedMsgId <*> quotedSentAt <*> quotedMsgContent <*> ( parseMaybeMarkdownList . msgContentText <$> quotedMsgContent )
-- this function can be changed so it never fails, not only avoid failure on invalid json
toDirectChatItem :: UTCTime -> ChatItemRow :. QuoteRow -> Either StoreError ( CChatItem 'CTDirect )
2023-09-01 19:43:27 +01:00
toDirectChatItem currentTs ( ( ( itemId , itemTs , AMsgDirection msgDir , itemContentText , itemText , itemStatus , sharedMsgId ) :. ( itemDeleted , deletedTs , itemEdited , createdAt , updatedAt ) :. ( timedTTL , timedDeleteAt , itemLive ) :. ( fileId_ , fileName_ , fileSize_ , filePath , fileKey , fileNonce , fileStatus_ , fileProtocol_ ) ) :. quoteRow ) =
2023-06-18 10:20:11 +01:00
chatItem $ fromRight invalid $ dbParseACIContent itemContentText
where
invalid = ACIContent msgDir $ CIInvalidJSON itemContentText
chatItem itemContent = case ( itemContent , itemStatus , fileStatus_ ) of
( ACIContent SMDSnd ciContent , ACIStatus SMDSnd ciStatus , Just ( AFS SMDSnd fileStatus ) ) ->
Right $ cItem SMDSnd CIDirectSnd ciStatus ciContent ( maybeCIFile fileStatus )
( ACIContent SMDSnd ciContent , ACIStatus SMDSnd ciStatus , Nothing ) ->
Right $ cItem SMDSnd CIDirectSnd ciStatus ciContent Nothing
( ACIContent SMDRcv ciContent , ACIStatus SMDRcv ciStatus , Just ( AFS SMDRcv fileStatus ) ) ->
Right $ cItem SMDRcv CIDirectRcv ciStatus ciContent ( maybeCIFile fileStatus )
( ACIContent SMDRcv ciContent , ACIStatus SMDRcv ciStatus , Nothing ) ->
Right $ cItem SMDRcv CIDirectRcv ciStatus ciContent Nothing
_ -> badItem
maybeCIFile :: CIFileStatus d -> Maybe ( CIFile d )
maybeCIFile fileStatus =
case ( fileId_ , fileName_ , fileSize_ , fileProtocol_ ) of
2023-09-01 19:43:27 +01:00
( Just fileId , Just fileName , Just fileSize , Just fileProtocol ) ->
let cfArgs = CFArgs <$> fileKey <*> fileNonce
fileSource = ( ` CryptoFile ` cfArgs ) <$> filePath
in Just CIFile { fileId , fileName , fileSize , fileSource , fileStatus , fileProtocol }
2023-06-18 10:20:11 +01:00
_ -> Nothing
cItem :: MsgDirectionI d => SMsgDirection d -> CIDirection 'CTDirect d -> CIStatus d -> CIContent d -> Maybe ( CIFile d ) -> CChatItem 'CTDirect
cItem d chatDir ciStatus content file =
CChatItem d ChatItem { chatDir , meta = ciMeta content ciStatus , content , formattedText = parseMaybeMarkdownList itemText , quotedItem = toDirectQuote quoteRow , reactions = [] , file }
badItem = Left $ SEBadChatItem itemId
ciMeta :: CIContent d -> CIStatus d -> CIMeta 'CTDirect d
ciMeta content status =
2023-10-11 19:10:38 +01:00
let itemDeleted' = case itemDeleted of
DBCINotDeleted -> Nothing
_ -> Just ( CIDeleted @ 'CTDirect deletedTs )
2023-06-18 10:20:11 +01:00
itemEdited' = fromMaybe False itemEdited
2023-11-18 21:52:01 +04:00
in mkCIMeta itemId content itemText status sharedMsgId itemDeleted' itemEdited' ciTimed itemLive currentTs itemTs Nothing createdAt updatedAt
2023-06-18 10:20:11 +01:00
ciTimed :: Maybe CITimed
ciTimed = timedTTL >>= \ ttl -> Just CITimed { ttl , deleteAt = timedDeleteAt }
type GroupQuoteRow = QuoteRow :. MaybeGroupMemberRow
toGroupQuote :: QuoteRow -> Maybe GroupMember -> Maybe ( CIQuote 'CTGroup )
toGroupQuote qr @ ( _ , _ , _ , _ , quotedSent ) quotedMember_ = toQuote qr $ direction quotedSent quotedMember_
where
direction ( Just True ) _ = Just CIQGroupSnd
direction ( Just False ) ( Just member ) = Just . CIQGroupRcv $ Just member
direction ( Just False ) Nothing = Just $ CIQGroupRcv Nothing
direction _ _ = Nothing
-- this function can be changed so it never fails, not only avoid failure on invalid json
2023-11-18 21:52:01 +04:00
toGroupChatItem :: UTCTime -> Int64 -> ChatItemRow :. Only ( Maybe GroupMemberId ) :. MaybeGroupMemberRow :. GroupQuoteRow :. MaybeGroupMemberRow -> Either StoreError ( CChatItem 'CTGroup )
2023-11-20 14:00:55 +04:00
toGroupChatItem currentTs userContactId ( ( ( itemId , itemTs , AMsgDirection msgDir , itemContentText , itemText , itemStatus , sharedMsgId ) :. ( itemDeleted , deletedTs , itemEdited , createdAt , updatedAt ) :. ( timedTTL , timedDeleteAt , itemLive ) :. ( fileId_ , fileName_ , fileSize_ , filePath , fileKey , fileNonce , fileStatus_ , fileProtocol_ ) ) :. Only forwardedByMember :. memberRow_ :. ( quoteRow :. quotedMemberRow_ ) :. deletedByGroupMemberRow_ ) = do
2023-06-18 10:20:11 +01:00
chatItem $ fromRight invalid $ dbParseACIContent itemContentText
where
member_ = toMaybeGroupMember userContactId memberRow_
quotedMember_ = toMaybeGroupMember userContactId quotedMemberRow_
deletedByGroupMember_ = toMaybeGroupMember userContactId deletedByGroupMemberRow_
invalid = ACIContent msgDir $ CIInvalidJSON itemContentText
chatItem itemContent = case ( itemContent , itemStatus , member_ , fileStatus_ ) of
( ACIContent SMDSnd ciContent , ACIStatus SMDSnd ciStatus , _ , Just ( AFS SMDSnd fileStatus ) ) ->
Right $ cItem SMDSnd CIGroupSnd ciStatus ciContent ( maybeCIFile fileStatus )
( ACIContent SMDSnd ciContent , ACIStatus SMDSnd ciStatus , _ , Nothing ) ->
Right $ cItem SMDSnd CIGroupSnd ciStatus ciContent Nothing
( ACIContent SMDRcv ciContent , ACIStatus SMDRcv ciStatus , Just member , Just ( AFS SMDRcv fileStatus ) ) ->
Right $ cItem SMDRcv ( CIGroupRcv member ) ciStatus ciContent ( maybeCIFile fileStatus )
( ACIContent SMDRcv ciContent , ACIStatus SMDRcv ciStatus , Just member , Nothing ) ->
Right $ cItem SMDRcv ( CIGroupRcv member ) ciStatus ciContent Nothing
_ -> badItem
maybeCIFile :: CIFileStatus d -> Maybe ( CIFile d )
maybeCIFile fileStatus =
case ( fileId_ , fileName_ , fileSize_ , fileProtocol_ ) of
2023-09-01 19:43:27 +01:00
( Just fileId , Just fileName , Just fileSize , Just fileProtocol ) ->
let cfArgs = CFArgs <$> fileKey <*> fileNonce
fileSource = ( ` CryptoFile ` cfArgs ) <$> filePath
in Just CIFile { fileId , fileName , fileSize , fileSource , fileStatus , fileProtocol }
2023-06-18 10:20:11 +01:00
_ -> Nothing
cItem :: MsgDirectionI d => SMsgDirection d -> CIDirection 'CTGroup d -> CIStatus d -> CIContent d -> Maybe ( CIFile d ) -> CChatItem 'CTGroup
cItem d chatDir ciStatus content file =
CChatItem d ChatItem { chatDir , meta = ciMeta content ciStatus , content , formattedText = parseMaybeMarkdownList itemText , quotedItem = toGroupQuote quoteRow quotedMember_ , reactions = [] , file }
badItem = Left $ SEBadChatItem itemId
ciMeta :: CIContent d -> CIStatus d -> CIMeta 'CTGroup d
ciMeta content status =
2023-10-11 19:10:38 +01:00
let itemDeleted' = case itemDeleted of
DBCINotDeleted -> Nothing
2023-10-12 10:43:59 +01:00
DBCIBlocked -> Just ( CIBlocked deletedTs )
2023-10-11 19:10:38 +01:00
_ -> Just ( maybe ( CIDeleted @ 'CTGroup deletedTs ) ( CIModerated deletedTs ) deletedByGroupMember_ )
2023-06-18 10:20:11 +01:00
itemEdited' = fromMaybe False itemEdited
2023-11-20 14:00:55 +04:00
in mkCIMeta itemId content itemText status sharedMsgId itemDeleted' itemEdited' ciTimed itemLive currentTs itemTs forwardedByMember createdAt updatedAt
2023-06-18 10:20:11 +01:00
ciTimed :: Maybe CITimed
ciTimed = timedTTL >>= \ ttl -> Just CITimed { ttl , deleteAt = timedDeleteAt }
getAllChatItems :: DB . Connection -> User -> ChatPagination -> Maybe String -> ExceptT StoreError IO [ AChatItem ]
getAllChatItems db user @ User { userId } pagination search_ = do
itemRefs <-
rights . map toChatItemRef <$> case pagination of
CPLast count -> liftIO $ getAllChatItemsLast_ count
CPAfter afterId count -> liftIO . getAllChatItemsAfter_ afterId count . aChatItemTs =<< getAChatItem_ afterId
CPBefore beforeId count -> liftIO . getAllChatItemsBefore_ beforeId count . aChatItemTs =<< getAChatItem_ beforeId
mapM ( uncurry ( getAChatItem db user ) >=> liftIO . getACIReactions db ) itemRefs
where
search = fromMaybe " " search_
getAChatItem_ itemId = do
chatRef <- getChatRefViaItemId db user itemId
getAChatItem db user chatRef itemId
getAllChatItemsLast_ count =
reverse
<$> DB . query
db
[ sql |
SELECT chat_item_id , contact_id , group_id
FROM chat_items
WHERE user_id = ? AND item_text LIKE '%' || ? || '%'
ORDER BY item_ts DESC , chat_item_id DESC
LIMIT ?
| ]
( userId , search , count )
getAllChatItemsAfter_ afterId count afterTs =
DB . query
db
[ sql |
SELECT chat_item_id , contact_id , group_id
FROM chat_items
WHERE user_id = ? AND item_text LIKE '%' || ? || '%'
AND ( item_ts > ? OR ( item_ts = ? AND chat_item_id > ? ) )
ORDER BY item_ts ASC , chat_item_id ASC
LIMIT ?
| ]
( userId , search , afterTs , afterTs , afterId , count )
getAllChatItemsBefore_ beforeId count beforeTs =
reverse
<$> DB . query
db
[ sql |
SELECT chat_item_id , contact_id , group_id
FROM chat_items
WHERE user_id = ? AND item_text LIKE '%' || ? || '%'
AND ( item_ts < ? OR ( item_ts = ? AND chat_item_id < ? ) )
ORDER BY item_ts DESC , chat_item_id DESC
LIMIT ?
| ]
( userId , search , beforeTs , beforeTs , beforeId , count )
getChatItemIdByAgentMsgId :: DB . Connection -> Int64 -> AgentMsgId -> IO ( Maybe ChatItemId )
getChatItemIdByAgentMsgId db connId msgId =
fmap join . maybeFirstRow fromOnly $
DB . query
db
[ sql |
SELECT chat_item_id
FROM chat_item_messages
WHERE message_id = (
SELECT message_id
FROM msg_deliveries
WHERE connection_id = ? AND agent_msg_id = ?
LIMIT 1
)
| ]
( connId , msgId )
2023-10-18 10:19:24 +01:00
updateDirectChatItemStatus :: forall d . MsgDirectionI d => DB . Connection -> User -> Contact -> ChatItemId -> CIStatus d -> ExceptT StoreError IO ( ChatItem 'CTDirect d )
updateDirectChatItemStatus db user @ User { userId } ct @ Contact { contactId } itemId itemStatus = do
ci <- liftEither . correctDir =<< getDirectCIWithReactions db user ct itemId
2023-06-18 10:20:11 +01:00
currentTs <- liftIO getCurrentTime
liftIO $ DB . execute db " UPDATE chat_items SET item_status = ?, updated_at = ? WHERE user_id = ? AND contact_id = ? AND chat_item_id = ? " ( itemStatus , currentTs , userId , contactId , itemId )
pure ci { meta = ( meta ci ) { itemStatus } }
2023-10-18 10:19:24 +01:00
updateDirectChatItem :: MsgDirectionI d => DB . Connection -> User -> Contact -> ChatItemId -> CIContent d -> Bool -> Maybe MessageId -> ExceptT StoreError IO ( ChatItem 'CTDirect d )
updateDirectChatItem db user ct @ Contact { contactId } itemId newContent live msgId_ = do
ci <- liftEither . correctDir =<< getDirectCIWithReactions db user ct itemId
2023-06-18 10:20:11 +01:00
liftIO $ updateDirectChatItem' db user contactId ci newContent live msgId_
2023-10-18 10:19:24 +01:00
getDirectCIWithReactions :: DB . Connection -> User -> Contact -> ChatItemId -> ExceptT StoreError IO ( CChatItem 'CTDirect )
getDirectCIWithReactions db user ct @ Contact { contactId } itemId =
liftIO . directCIWithReactions db ct =<< getDirectChatItem db user contactId itemId
correctDir :: MsgDirectionI d => CChatItem c -> Either StoreError ( ChatItem c d )
correctDir ( CChatItem _ ci ) = first SEInternalError $ checkDirection ci
2023-06-18 10:20:11 +01:00
2023-09-01 19:43:27 +01:00
updateDirectChatItem' :: forall d . MsgDirectionI d => DB . Connection -> User -> Int64 -> ChatItem 'CTDirect d -> CIContent d -> Bool -> Maybe MessageId -> IO ( ChatItem 'CTDirect d )
2023-06-18 10:20:11 +01:00
updateDirectChatItem' db User { userId } contactId ci newContent live msgId_ = do
currentTs <- liftIO getCurrentTime
let ci' = updatedChatItem ci newContent live currentTs
liftIO $ updateDirectChatItem_ db userId contactId ci' msgId_
pure ci'
updatedChatItem :: ChatItem c d -> CIContent d -> Bool -> UTCTime -> ChatItem c d
updatedChatItem ci @ ChatItem { meta = meta @ CIMeta { itemStatus , itemEdited , itemTimed , itemLive } } newContent live currentTs =
let newText = ciContentToText newContent
edited' = itemEdited || ( itemLive /= Just True )
live' = ( live && ) <$> itemLive
timed' = case ( itemStatus , itemTimed , itemLive , live ) of
( CISRcvNew , _ , _ , _ ) -> itemTimed
( _ , Just CITimed { ttl , deleteAt = Nothing } , Just True , False ) ->
-- timed item, sent or read, not set for deletion, was live, now not live
let deleteAt' = addUTCTime ( realToFrac ttl ) currentTs
in Just CITimed { ttl , deleteAt = Just deleteAt' }
_ -> itemTimed
in ci { content = newContent , meta = meta { itemText = newText , itemEdited = edited' , itemTimed = timed' , itemLive = live' } , formattedText = parseMaybeMarkdownList newText }
-- this function assumes that direct item with correct chat direction already exists,
-- it should be checked before calling it
updateDirectChatItem_ :: forall d . MsgDirectionI d => DB . Connection -> UserId -> Int64 -> ChatItem 'CTDirect d -> Maybe MessageId -> IO ()
updateDirectChatItem_ db userId contactId ChatItem { meta , content } msgId_ = do
let CIMeta { itemId , itemText , itemStatus , itemDeleted , itemEdited , itemTimed , itemLive , updatedAt } = meta
itemDeleted' = isJust itemDeleted
itemDeletedTs' = itemDeletedTs =<< itemDeleted
DB . execute
db
[ sql |
UPDATE chat_items
SET item_content = ? , item_text = ? , item_status = ? , item_deleted = ? , item_deleted_ts = ? , item_edited = ? , item_live = ? , updated_at = ? , timed_ttl = ? , timed_delete_at = ?
WHERE user_id = ? AND contact_id = ? AND chat_item_id = ?
| ]
( ( content , itemText , itemStatus , itemDeleted' , itemDeletedTs' , itemEdited , itemLive , updatedAt ) :. ciTimedRow itemTimed :. ( userId , contactId , itemId ) )
forM_ msgId_ $ \ msgId -> liftIO $ insertChatItemMessage_ db itemId msgId updatedAt
addInitialAndNewCIVersions :: DB . Connection -> ChatItemId -> ( UTCTime , MsgContent ) -> ( UTCTime , MsgContent ) -> IO ()
addInitialAndNewCIVersions db itemId ( initialTs , initialMC ) ( newTs , newMC ) = do
versionsCount <- getChatItemVersionsCount db itemId
when ( versionsCount == 0 ) $
createChatItemVersion db itemId initialTs initialMC
createChatItemVersion db itemId newTs newMC
getChatItemVersionsCount :: DB . Connection -> ChatItemId -> IO Int
getChatItemVersionsCount db itemId = do
count <-
maybeFirstRow fromOnly $
DB . query db " SELECT COUNT(1) FROM chat_item_versions WHERE chat_item_id = ? " ( Only itemId )
pure $ fromMaybe 0 count
createChatItemVersion :: DB . Connection -> ChatItemId -> UTCTime -> MsgContent -> IO ()
createChatItemVersion db itemId itemVersionTs msgContent =
DB . execute
db
[ sql |
INSERT INTO chat_item_versions ( chat_item_id , msg_content , item_version_ts )
VALUES ( ? , ? , ? )
| ]
( itemId , toMCText msgContent , itemVersionTs )
2023-10-11 19:10:38 +01:00
deleteDirectChatItem :: DB . Connection -> User -> Contact -> ChatItem 'CTDirect d -> IO ()
deleteDirectChatItem db User { userId } Contact { contactId } ci = do
2023-06-18 10:20:11 +01:00
let itemId = chatItemId' ci
deleteChatItemMessages_ db itemId
deleteChatItemVersions_ db itemId
deleteDirectCIReactions_ db contactId ci
DB . execute
db
[ sql |
DELETE FROM chat_items
WHERE user_id = ? AND contact_id = ? AND chat_item_id = ?
| ]
( userId , contactId , itemId )
deleteChatItemMessages_ :: DB . Connection -> ChatItemId -> IO ()
deleteChatItemMessages_ db itemId =
DB . execute
db
[ sql |
DELETE FROM messages
WHERE message_id IN (
SELECT message_id
FROM chat_item_messages
WHERE chat_item_id = ?
)
| ]
( Only itemId )
deleteChatItemVersions_ :: DB . Connection -> ChatItemId -> IO ()
deleteChatItemVersions_ db itemId =
DB . execute db " DELETE FROM chat_item_versions WHERE chat_item_id = ? " ( Only itemId )
2023-10-11 19:10:38 +01:00
markDirectChatItemDeleted :: DB . Connection -> User -> Contact -> ChatItem 'CTDirect d -> MessageId -> UTCTime -> IO ( ChatItem 'CTDirect d )
markDirectChatItemDeleted db User { userId } Contact { contactId } ci @ ChatItem { meta } msgId deletedTs = do
2023-06-18 10:20:11 +01:00
currentTs <- liftIO getCurrentTime
let itemId = chatItemId' ci
insertChatItemMessage_ db itemId msgId currentTs
DB . execute
db
[ sql |
UPDATE chat_items
2023-10-11 19:10:38 +01:00
SET item_deleted = ? , item_deleted_ts = ? , updated_at = ?
2023-06-18 10:20:11 +01:00
WHERE user_id = ? AND contact_id = ? AND chat_item_id = ?
| ]
2023-10-11 19:10:38 +01:00
( DBCIDeleted , deletedTs , currentTs , userId , contactId , itemId )
pure ci { meta = meta { itemDeleted = Just $ CIDeleted $ Just deletedTs } }
2023-06-18 10:20:11 +01:00
getDirectChatItemBySharedMsgId :: DB . Connection -> User -> ContactId -> SharedMsgId -> ExceptT StoreError IO ( CChatItem 'CTDirect )
getDirectChatItemBySharedMsgId db user @ User { userId } contactId sharedMsgId = do
itemId <- getDirectChatItemIdBySharedMsgId_ db userId contactId sharedMsgId
getDirectChatItem db user contactId itemId
getDirectChatItemByAgentMsgId :: DB . Connection -> User -> ContactId -> Int64 -> AgentMsgId -> IO ( Maybe ( CChatItem 'CTDirect ) )
getDirectChatItemByAgentMsgId db user contactId connId msgId = do
itemId_ <- getChatItemIdByAgentMsgId db connId msgId
maybe ( pure Nothing ) ( fmap eitherToMaybe . runExceptT . getDirectChatItem db user contactId ) itemId_
getDirectChatItemIdBySharedMsgId_ :: DB . Connection -> UserId -> Int64 -> SharedMsgId -> ExceptT StoreError IO Int64
getDirectChatItemIdBySharedMsgId_ db userId contactId sharedMsgId =
ExceptT . firstRow fromOnly ( SEChatItemSharedMsgIdNotFound sharedMsgId ) $
DB . query
db
[ sql |
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND contact_id = ? AND shared_msg_id = ?
ORDER BY chat_item_id DESC
LIMIT 1
| ]
( userId , contactId , sharedMsgId )
getDirectChatItem :: DB . Connection -> User -> Int64 -> ChatItemId -> ExceptT StoreError IO ( CChatItem 'CTDirect )
getDirectChatItem db User { userId } contactId itemId = ExceptT $ do
currentTs <- getCurrentTime
2023-10-18 10:19:24 +01:00
firstRow' ( toDirectChatItem currentTs ) ( SEChatItemNotFound itemId ) getItem
2023-06-18 10:20:11 +01:00
where
getItem =
DB . query
db
[ sql |
SELECT
-- ChatItem
i . chat_item_id , i . item_ts , i . item_sent , i . item_content , i . item_text , i . item_status , i . shared_msg_id , i . item_deleted , i . item_deleted_ts , i . item_edited , i . created_at , i . updated_at , i . timed_ttl , i . timed_delete_at , i . item_live ,
-- CIFile
2023-09-01 19:43:27 +01:00
f . file_id , f . file_name , f . file_size , f . file_path , f . file_crypto_key , f . file_crypto_nonce , f . ci_file_status , f . protocol ,
2023-06-18 10:20:11 +01:00
-- DirectQuote
ri . chat_item_id , i . quoted_shared_msg_id , i . quoted_sent_at , i . quoted_content , i . quoted_sent
FROM chat_items i
LEFT JOIN files f ON f . chat_item_id = i . chat_item_id
LEFT JOIN chat_items ri ON ri . user_id = i . user_id AND ri . contact_id = i . contact_id AND ri . shared_msg_id = i . quoted_shared_msg_id
WHERE i . user_id = ? AND i . contact_id = ? AND i . chat_item_id = ?
| ]
( userId , contactId , itemId )
getDirectChatItemIdByText :: DB . Connection -> UserId -> Int64 -> SMsgDirection d -> Text -> ExceptT StoreError IO ChatItemId
getDirectChatItemIdByText db userId contactId msgDir quotedMsg =
ExceptT . firstRow fromOnly ( SEChatItemNotFoundByText quotedMsg ) $
DB . query
db
[ sql |
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND contact_id = ? AND item_sent = ? AND item_text LIKE ?
ORDER BY chat_item_id DESC
LIMIT 1
| ]
( userId , contactId , msgDir , quotedMsg <> " % " )
getDirectChatItemIdByText' :: DB . Connection -> User -> ContactId -> Text -> ExceptT StoreError IO ChatItemId
getDirectChatItemIdByText' db User { userId } contactId msg =
ExceptT . firstRow fromOnly ( SEChatItemNotFoundByText msg ) $
DB . query
db
[ sql |
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND contact_id = ? AND item_text LIKE ?
ORDER BY chat_item_id DESC
LIMIT 1
| ]
( userId , contactId , msg <> " % " )
2023-10-18 10:19:24 +01:00
updateGroupChatItemStatus :: MsgDirectionI d => DB . Connection -> User -> GroupInfo -> ChatItemId -> CIStatus d -> ExceptT StoreError IO ( ChatItem 'CTGroup d )
updateGroupChatItemStatus db user @ User { userId } g @ GroupInfo { groupId } itemId itemStatus = do
ci <- liftEither . correctDir =<< getGroupCIWithReactions db user g itemId
2023-07-26 14:49:35 +04:00
currentTs <- liftIO getCurrentTime
liftIO $ DB . execute db " UPDATE chat_items SET item_status = ?, updated_at = ? WHERE user_id = ? AND group_id = ? AND chat_item_id = ? " ( itemStatus , currentTs , userId , groupId , itemId )
pure ci { meta = ( meta ci ) { itemStatus } }
2023-10-18 10:19:24 +01:00
getGroupCIWithReactions :: DB . Connection -> User -> GroupInfo -> ChatItemId -> ExceptT StoreError IO ( CChatItem 'CTGroup )
getGroupCIWithReactions db user g @ GroupInfo { groupId } itemId = do
liftIO . groupCIWithReactions db g =<< getGroupChatItem db user groupId itemId
groupCIWithReactions :: DB . Connection -> GroupInfo -> CChatItem 'CTGroup -> IO ( CChatItem 'CTGroup )
groupCIWithReactions db g cci @ ( CChatItem md ci @ ChatItem { meta = CIMeta { itemSharedMsgId } } ) = case itemSharedMsgId of
Just sharedMsgId -> do
let GroupMember { memberId } = chatItemMember g ci
reactions <- getGroupCIReactions db g memberId sharedMsgId
pure $ CChatItem md ci { reactions }
Nothing -> pure cci
updateGroupChatItem :: MsgDirectionI d => DB . Connection -> User -> Int64 -> ChatItem 'CTGroup d -> CIContent d -> Bool -> Maybe MessageId -> IO ( ChatItem 'CTGroup d )
2023-06-18 10:20:11 +01:00
updateGroupChatItem db user groupId ci newContent live msgId_ = do
currentTs <- liftIO getCurrentTime
let ci' = updatedChatItem ci newContent live currentTs
liftIO $ updateGroupChatItem_ db user groupId ci' msgId_
pure ci'
-- this function assumes that the group item with correct chat direction already exists,
-- it should be checked before calling it
2023-10-18 10:19:24 +01:00
updateGroupChatItem_ :: MsgDirectionI d => DB . Connection -> User -> Int64 -> ChatItem 'CTGroup d -> Maybe MessageId -> IO ()
2023-06-18 10:20:11 +01:00
updateGroupChatItem_ db User { userId } groupId ChatItem { content , meta } msgId_ = do
let CIMeta { itemId , itemText , itemStatus , itemDeleted , itemEdited , itemTimed , itemLive , updatedAt } = meta
itemDeleted' = isJust itemDeleted
itemDeletedTs' = itemDeletedTs =<< itemDeleted
DB . execute
db
[ sql |
UPDATE chat_items
SET item_content = ? , item_text = ? , item_status = ? , item_deleted = ? , item_deleted_ts = ? , item_edited = ? , item_live = ? , updated_at = ? , timed_ttl = ? , timed_delete_at = ?
WHERE user_id = ? AND group_id = ? AND chat_item_id = ?
| ]
( ( content , itemText , itemStatus , itemDeleted' , itemDeletedTs' , itemEdited , itemLive , updatedAt ) :. ciTimedRow itemTimed :. ( userId , groupId , itemId ) )
forM_ msgId_ $ \ msgId -> insertChatItemMessage_ db itemId msgId updatedAt
2023-10-11 19:10:38 +01:00
deleteGroupChatItem :: DB . Connection -> User -> GroupInfo -> ChatItem 'CTGroup d -> IO ()
deleteGroupChatItem db User { userId } g @ GroupInfo { groupId } ci = do
2023-06-18 10:20:11 +01:00
let itemId = chatItemId' ci
deleteChatItemMessages_ db itemId
deleteChatItemVersions_ db itemId
deleteGroupCIReactions_ db g ci
DB . execute
db
[ sql |
DELETE FROM chat_items
WHERE user_id = ? AND group_id = ? AND chat_item_id = ?
| ]
( userId , groupId , itemId )
2023-10-11 19:10:38 +01:00
updateGroupChatItemModerated :: forall d . MsgDirectionI d => DB . Connection -> User -> GroupInfo -> ChatItem 'CTGroup d -> GroupMember -> UTCTime -> IO ( ChatItem 'CTGroup d )
updateGroupChatItemModerated db User { userId } GroupInfo { groupId } ci m @ GroupMember { groupMemberId } deletedTs = do
2023-06-18 10:20:11 +01:00
currentTs <- getCurrentTime
2023-10-11 19:10:38 +01:00
let toContent = msgDirToModeratedContent_ $ msgDirection @ d
2023-06-18 10:20:11 +01:00
toText = ciModeratedText
itemId = chatItemId' ci
deleteChatItemMessages_ db itemId
deleteChatItemVersions_ db itemId
liftIO $
DB . execute
db
[ sql |
UPDATE chat_items
SET item_deleted = 1 , item_deleted_ts = ? , item_deleted_by_group_member_id = ? , item_content = ? , item_text = ? , updated_at = ?
WHERE user_id = ? AND group_id = ? AND chat_item_id = ?
| ]
( deletedTs , groupMemberId , toContent , toText , currentTs , userId , groupId , itemId )
2023-10-11 19:10:38 +01:00
pure $ ci { content = toContent , meta = ( meta ci ) { itemText = toText , itemDeleted = Just ( CIModerated ( Just currentTs ) m ) , editable = False } , formattedText = Nothing }
2023-06-18 10:20:11 +01:00
2023-10-11 19:10:38 +01:00
pattern DBCINotDeleted :: Int
pattern DBCINotDeleted = 0
pattern DBCIDeleted :: Int
pattern DBCIDeleted = 1
pattern DBCIBlocked :: Int
pattern DBCIBlocked = 2
markGroupChatItemDeleted :: DB . Connection -> User -> GroupInfo -> ChatItem 'CTGroup d -> MessageId -> Maybe GroupMember -> UTCTime -> IO ( ChatItem 'CTGroup d )
markGroupChatItemDeleted db User { userId } GroupInfo { groupId } ci @ ChatItem { meta } msgId byGroupMember_ deletedTs = do
2023-06-18 10:20:11 +01:00
currentTs <- liftIO getCurrentTime
let itemId = chatItemId' ci
2023-10-11 19:10:38 +01:00
( deletedByGroupMemberId , itemDeleted ) = case byGroupMember_ of
Just m @ GroupMember { groupMemberId } -> ( Just groupMemberId , Just $ CIModerated ( Just deletedTs ) m )
_ -> ( Nothing , Just $ CIDeleted @ 'CTGroup ( Just deletedTs ) )
2023-06-18 10:20:11 +01:00
insertChatItemMessage_ db itemId msgId currentTs
DB . execute
db
[ sql |
UPDATE chat_items
2023-10-11 19:10:38 +01:00
SET item_deleted = ? , item_deleted_ts = ? , item_deleted_by_group_member_id = ? , updated_at = ?
WHERE user_id = ? AND group_id = ? AND chat_item_id = ?
| ]
( DBCIDeleted , deletedTs , deletedByGroupMemberId , currentTs , userId , groupId , itemId )
pure ci { meta = meta { itemDeleted } }
markGroupChatItemBlocked :: DB . Connection -> User -> GroupInfo -> ChatItem 'CTGroup 'MDRcv -> IO ( ChatItem 'CTGroup 'MDRcv )
markGroupChatItemBlocked db User { userId } GroupInfo { groupId } ci @ ChatItem { meta } = do
deletedTs <- getCurrentTime
DB . execute
db
[ sql |
UPDATE chat_items
SET item_deleted = ? , item_deleted_ts = ? , updated_at = ?
2023-06-18 10:20:11 +01:00
WHERE user_id = ? AND group_id = ? AND chat_item_id = ?
| ]
2023-10-11 19:10:38 +01:00
( DBCIBlocked , deletedTs , deletedTs , userId , groupId , chatItemId' ci )
pure ci { meta = meta { itemDeleted = Just $ CIBlocked $ Just deletedTs } }
2023-06-18 10:20:11 +01:00
getGroupChatItemBySharedMsgId :: DB . Connection -> User -> GroupId -> GroupMemberId -> SharedMsgId -> ExceptT StoreError IO ( CChatItem 'CTGroup )
getGroupChatItemBySharedMsgId db user @ User { userId } groupId groupMemberId sharedMsgId = do
itemId <-
ExceptT . firstRow fromOnly ( SEChatItemSharedMsgIdNotFound sharedMsgId ) $
DB . query
db
[ sql |
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND group_id = ? AND group_member_id = ? AND shared_msg_id = ?
ORDER BY chat_item_id DESC
LIMIT 1
| ]
( userId , groupId , groupMemberId , sharedMsgId )
getGroupChatItem db user groupId itemId
getGroupMemberCIBySharedMsgId :: DB . Connection -> User -> GroupId -> MemberId -> SharedMsgId -> ExceptT StoreError IO ( CChatItem 'CTGroup )
getGroupMemberCIBySharedMsgId db user @ User { userId } groupId memberId sharedMsgId = do
itemId <-
ExceptT . firstRow fromOnly ( SEChatItemSharedMsgIdNotFound sharedMsgId ) $
DB . query
db
[ sql |
SELECT i . chat_item_id
FROM chat_items i
JOIN group_members m ON m . group_id = i . group_id
AND ( ( i . group_member_id IS NULL AND m . member_category = ? )
OR i . group_member_id = m . group_member_id )
WHERE i . user_id = ? AND i . group_id = ? AND m . member_id = ? AND i . shared_msg_id = ?
ORDER BY i . chat_item_id DESC
LIMIT 1
| ]
( GCUserMember , userId , groupId , memberId , sharedMsgId )
getGroupChatItem db user groupId itemId
2023-07-26 14:49:35 +04:00
getGroupChatItemByAgentMsgId :: DB . Connection -> User -> GroupId -> Int64 -> AgentMsgId -> IO ( Maybe ( CChatItem 'CTGroup ) )
getGroupChatItemByAgentMsgId db user groupId connId msgId = do
itemId_ <- getChatItemIdByAgentMsgId db connId msgId
maybe ( pure Nothing ) ( fmap eitherToMaybe . runExceptT . getGroupChatItem db user groupId ) itemId_
2023-06-18 10:20:11 +01:00
getGroupChatItem :: DB . Connection -> User -> Int64 -> ChatItemId -> ExceptT StoreError IO ( CChatItem 'CTGroup )
getGroupChatItem db User { userId , userContactId } groupId itemId = ExceptT $ do
currentTs <- getCurrentTime
2023-10-18 10:19:24 +01:00
firstRow' ( toGroupChatItem currentTs userContactId ) ( SEChatItemNotFound itemId ) getItem
2023-06-18 10:20:11 +01:00
where
getItem =
DB . query
db
[ sql |
SELECT
-- ChatItem
i . chat_item_id , i . item_ts , i . item_sent , i . item_content , i . item_text , i . item_status , i . shared_msg_id , i . item_deleted , i . item_deleted_ts , i . item_edited , i . created_at , i . updated_at , i . timed_ttl , i . timed_delete_at , i . item_live ,
-- CIFile
2023-09-01 19:43:27 +01:00
f . file_id , f . file_name , f . file_size , f . file_path , f . file_crypto_key , f . file_crypto_nonce , f . ci_file_status , f . protocol ,
2023-11-20 14:00:55 +04:00
-- CIMeta forwardedByMember
2023-11-18 21:52:01 +04:00
i . forwarded_by_group_member_id ,
2023-06-18 10:20:11 +01:00
-- GroupMember
2023-11-18 21:52:01 +04:00
m . group_member_id , m . group_id , m . member_id , m . peer_chat_min_version , m . peer_chat_max_version , m . member_role , m . member_category ,
m . member_status , m . show_messages , m . invited_by , m . invited_by_group_member_id , m . local_display_name , m . contact_id , m . contact_profile_id , p . contact_profile_id ,
2023-06-18 10:20:11 +01:00
p . display_name , p . full_name , p . image , p . contact_link , p . local_alias , p . preferences ,
-- quoted ChatItem
ri . chat_item_id , i . quoted_shared_msg_id , i . quoted_sent_at , i . quoted_content , i . quoted_sent ,
-- quoted GroupMember
2023-11-18 21:52:01 +04:00
rm . group_member_id , rm . group_id , 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 . invited_by , rm . invited_by_group_member_id , rm . local_display_name , rm . contact_id , rm . contact_profile_id , rp . contact_profile_id ,
2023-06-18 10:20:11 +01:00
rp . display_name , rp . full_name , rp . image , rp . contact_link , rp . local_alias , rp . preferences ,
-- deleted by GroupMember
2023-11-18 21:52:01 +04:00
dbm . group_member_id , dbm . group_id , dbm . member_id , dbm . peer_chat_min_version , dbm . peer_chat_max_version , dbm . member_role , dbm . member_category ,
dbm . member_status , dbm . show_messages , dbm . invited_by , dbm . invited_by_group_member_id , dbm . local_display_name , dbm . contact_id , dbm . contact_profile_id , dbp . contact_profile_id ,
2023-06-18 10:20:11 +01:00
dbp . display_name , dbp . full_name , dbp . image , dbp . contact_link , dbp . local_alias , dbp . preferences
FROM chat_items i
LEFT JOIN files f ON f . chat_item_id = i . chat_item_id
LEFT JOIN group_members m ON m . group_member_id = i . group_member_id
LEFT JOIN contact_profiles p ON p . contact_profile_id = COALESCE ( m . member_profile_id , m . contact_profile_id )
LEFT JOIN chat_items ri ON ri . shared_msg_id = i . quoted_shared_msg_id AND ri . group_id = i . group_id
LEFT JOIN group_members rm ON rm . group_member_id = ri . group_member_id
LEFT JOIN contact_profiles rp ON rp . contact_profile_id = COALESCE ( rm . member_profile_id , rm . contact_profile_id )
LEFT JOIN group_members dbm ON dbm . group_member_id = i . item_deleted_by_group_member_id
LEFT JOIN contact_profiles dbp ON dbp . contact_profile_id = COALESCE ( dbm . member_profile_id , dbm . contact_profile_id )
WHERE i . user_id = ? AND i . group_id = ? AND i . chat_item_id = ?
| ]
( userId , groupId , itemId )
getGroupChatItemIdByText :: DB . Connection -> User -> GroupId -> Maybe ContactName -> Text -> ExceptT StoreError IO ChatItemId
getGroupChatItemIdByText db User { userId , localDisplayName = userName } groupId contactName_ quotedMsg =
ExceptT . firstRow fromOnly ( SEChatItemNotFoundByText quotedMsg ) $ case contactName_ of
Nothing -> anyMemberChatItem_
Just cName
| userName == cName -> userChatItem_
| otherwise -> memberChatItem_ cName
where
anyMemberChatItem_ =
DB . query
db
[ sql |
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND group_id = ? AND item_text like ?
ORDER BY chat_item_id DESC
LIMIT 1
| ]
( userId , groupId , quotedMsg <> " % " )
userChatItem_ =
DB . query
db
[ sql |
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND group_id = ? AND group_member_id IS NULL AND item_text like ?
ORDER BY chat_item_id DESC
LIMIT 1
| ]
( userId , groupId , quotedMsg <> " % " )
memberChatItem_ cName =
DB . query
db
[ sql |
SELECT i . chat_item_id
FROM chat_items i
JOIN group_members m ON m . group_member_id = i . group_member_id
JOIN contacts c ON c . contact_id = m . contact_id
WHERE i . user_id = ? AND i . group_id = ? AND c . local_display_name = ? AND i . item_text like ?
ORDER BY i . chat_item_id DESC
LIMIT 1
| ]
( userId , groupId , cName , quotedMsg <> " % " )
getGroupChatItemIdByText' :: DB . Connection -> User -> GroupId -> Text -> ExceptT StoreError IO ChatItemId
getGroupChatItemIdByText' db User { userId } groupId msg =
ExceptT . firstRow fromOnly ( SEChatItemNotFoundByText msg ) $
DB . query
db
[ sql |
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND group_id = ? AND item_text like ?
ORDER BY chat_item_id DESC
LIMIT 1
| ]
( userId , groupId , msg <> " % " )
getChatItemByFileId :: DB . Connection -> User -> Int64 -> ExceptT StoreError IO AChatItem
getChatItemByFileId db user @ User { userId } fileId = do
( chatRef , itemId ) <-
ExceptT . firstRow' toChatItemRef ( SEChatItemNotFoundByFileId fileId ) $
DB . query
db
[ sql |
SELECT i . chat_item_id , i . contact_id , i . group_id
FROM chat_items i
JOIN files f ON f . chat_item_id = i . chat_item_id
WHERE f . user_id = ? AND f . file_id = ?
LIMIT 1
| ]
( userId , fileId )
getAChatItem db user chatRef itemId
getChatItemByGroupId :: DB . Connection -> User -> GroupId -> ExceptT StoreError IO AChatItem
getChatItemByGroupId db user @ User { userId } groupId = do
( chatRef , itemId ) <-
ExceptT . firstRow' toChatItemRef ( SEChatItemNotFoundByGroupId groupId ) $
DB . query
db
[ sql |
SELECT i . chat_item_id , i . contact_id , i . group_id
FROM chat_items i
JOIN groups g ON g . chat_item_id = i . chat_item_id
WHERE g . user_id = ? AND g . group_id = ?
LIMIT 1
| ]
( userId , groupId )
getAChatItem db user chatRef itemId
getChatRefViaItemId :: DB . Connection -> User -> ChatItemId -> ExceptT StoreError IO ChatRef
getChatRefViaItemId db User { userId } itemId = do
ExceptT . firstRow' toChatRef ( SEChatItemNotFound itemId ) $
DB . query db " SELECT contact_id, group_id FROM chat_items WHERE user_id = ? AND chat_item_id = ? " ( userId , itemId )
where
toChatRef = \ case
( Just contactId , Nothing ) -> Right $ ChatRef CTDirect contactId
( Nothing , Just groupId ) -> Right $ ChatRef CTGroup groupId
( _ , _ ) -> Left $ SEBadChatItem itemId
getAChatItem :: DB . Connection -> User -> ChatRef -> ChatItemId -> ExceptT StoreError IO AChatItem
getAChatItem db user chatRef itemId = case chatRef of
ChatRef CTDirect contactId -> do
ct <- getContact db user contactId
( CChatItem msgDir ci ) <- getDirectChatItem db user contactId itemId
pure $ AChatItem SCTDirect msgDir ( DirectChat ct ) ci
ChatRef CTGroup groupId -> do
gInfo <- getGroupInfo db user groupId
( CChatItem msgDir ci ) <- getGroupChatItem db user groupId itemId
pure $ AChatItem SCTGroup msgDir ( GroupChat gInfo ) ci
_ -> throwError $ SEChatItemNotFound itemId
getChatItemVersions :: DB . Connection -> ChatItemId -> IO [ ChatItemVersion ]
getChatItemVersions db itemId = do
map toChatItemVersion
<$> DB . query
db
[ sql |
SELECT chat_item_version_id , msg_content , item_version_ts , created_at
FROM chat_item_versions
WHERE chat_item_id = ?
ORDER BY chat_item_version_id DESC
| ]
( Only itemId )
where
toChatItemVersion :: ( Int64 , MsgContent , UTCTime , UTCTime ) -> ChatItemVersion
toChatItemVersion ( chatItemVersionId , msgContent , itemVersionTs , createdAt ) =
let formattedText = parseMaybeMarkdownList $ msgContentText msgContent
in ChatItemVersion { chatItemVersionId , msgContent , formattedText , itemVersionTs , createdAt }
getDirectChatReactions_ :: DB . Connection -> Contact -> Chat 'CTDirect -> IO ( Chat 'CTDirect )
getDirectChatReactions_ db ct c @ Chat { chatItems } = do
2023-10-18 10:19:24 +01:00
chatItems' <- mapM ( directCIWithReactions db ct ) chatItems
2023-06-18 10:20:11 +01:00
pure c { chatItems = chatItems' }
2023-10-18 10:19:24 +01:00
directCIWithReactions :: DB . Connection -> Contact -> CChatItem 'CTDirect -> IO ( CChatItem 'CTDirect )
directCIWithReactions db ct cci @ ( CChatItem md ci @ ChatItem { meta = CIMeta { itemSharedMsgId } } ) = case itemSharedMsgId of
Just sharedMsgId -> do
reactions <- getDirectCIReactions db ct sharedMsgId
2023-06-18 10:20:11 +01:00
pure $ CChatItem md ci { reactions }
2023-10-18 10:19:24 +01:00
Nothing -> pure cci
2023-06-18 10:20:11 +01:00
getDirectCIReactions :: DB . Connection -> Contact -> SharedMsgId -> IO [ CIReactionCount ]
getDirectCIReactions db Contact { contactId } itemSharedMsgId =
map toCIReaction
<$> DB . query
db
[ sql |
SELECT reaction , MAX ( reaction_sent ) , COUNT ( chat_item_reaction_id )
FROM chat_item_reactions
WHERE contact_id = ? AND shared_msg_id = ?
GROUP BY reaction
| ]
( contactId , itemSharedMsgId )
getGroupCIReactions :: DB . Connection -> GroupInfo -> MemberId -> SharedMsgId -> IO [ CIReactionCount ]
getGroupCIReactions db GroupInfo { groupId } itemMemberId itemSharedMsgId =
map toCIReaction
<$> DB . query
db
[ sql |
SELECT reaction , MAX ( reaction_sent ) , COUNT ( chat_item_reaction_id )
FROM chat_item_reactions
WHERE group_id = ? AND item_member_id = ? AND shared_msg_id = ?
GROUP BY reaction
| ]
( groupId , itemMemberId , itemSharedMsgId )
getACIReactions :: DB . Connection -> AChatItem -> IO AChatItem
getACIReactions db aci @ ( AChatItem _ md chat ci @ ChatItem { meta = CIMeta { itemSharedMsgId } } ) = case itemSharedMsgId of
Just itemSharedMId -> case chat of
DirectChat ct -> do
reactions <- getDirectCIReactions db ct itemSharedMId
pure $ AChatItem SCTDirect md chat ci { reactions }
GroupChat g -> do
let GroupMember { memberId } = chatItemMember g ci
reactions <- getGroupCIReactions db g memberId itemSharedMId
pure $ AChatItem SCTGroup md chat ci { reactions }
_ -> pure aci
_ -> pure aci
deleteDirectCIReactions_ :: DB . Connection -> ContactId -> ChatItem 'CTDirect d -> IO ()
deleteDirectCIReactions_ db contactId ChatItem { meta = CIMeta { itemSharedMsgId } } =
forM_ itemSharedMsgId $ \ itemSharedMId ->
DB . execute db " DELETE FROM chat_item_reactions WHERE contact_id = ? AND shared_msg_id = ? " ( contactId , itemSharedMId )
deleteGroupCIReactions_ :: DB . Connection -> GroupInfo -> ChatItem 'CTGroup d -> IO ()
deleteGroupCIReactions_ db g @ GroupInfo { groupId } ci @ ChatItem { meta = CIMeta { itemSharedMsgId } } =
forM_ itemSharedMsgId $ \ itemSharedMId -> do
let GroupMember { memberId } = chatItemMember g ci
DB . execute
db
" DELETE FROM chat_item_reactions WHERE group_id = ? AND shared_msg_id = ? AND item_member_id = ? "
( groupId , itemSharedMId , memberId )
toCIReaction :: ( MsgReaction , Bool , Int ) -> CIReactionCount
toCIReaction ( reaction , userReacted , totalReacted ) = CIReactionCount { reaction , userReacted , totalReacted }
getDirectReactions :: DB . Connection -> Contact -> SharedMsgId -> Bool -> IO [ MsgReaction ]
getDirectReactions db ct itemSharedMId sent =
map fromOnly
<$> DB . query
db
[ sql |
SELECT reaction
FROM chat_item_reactions
WHERE contact_id = ? AND shared_msg_id = ? AND reaction_sent = ?
| ]
( contactId' ct , itemSharedMId , sent )
setDirectReaction :: DB . Connection -> Contact -> SharedMsgId -> Bool -> MsgReaction -> Bool -> MessageId -> UTCTime -> IO ()
setDirectReaction db ct itemSharedMId sent reaction add msgId reactionTs
| add =
2023-11-26 18:16:37 +00:00
DB . execute
db
[ sql |
INSERT INTO chat_item_reactions
( contact_id , shared_msg_id , reaction_sent , reaction , created_by_msg_id , reaction_ts )
VALUES ( ? , ? , ? , ? , ? , ? )
| ]
( contactId' ct , itemSharedMId , sent , reaction , msgId , reactionTs )
2023-06-18 10:20:11 +01:00
| otherwise =
2023-11-26 18:16:37 +00:00
DB . execute
db
[ sql |
DELETE FROM chat_item_reactions
WHERE contact_id = ? AND shared_msg_id = ? AND reaction_sent = ? AND reaction = ?
| ]
( contactId' ct , itemSharedMId , sent , reaction )
2023-06-18 10:20:11 +01:00
getGroupReactions :: DB . Connection -> GroupInfo -> GroupMember -> MemberId -> SharedMsgId -> Bool -> IO [ MsgReaction ]
getGroupReactions db GroupInfo { groupId } m itemMemberId itemSharedMId sent =
map fromOnly
<$> DB . query
db
[ sql |
SELECT reaction
FROM chat_item_reactions
WHERE group_id = ? AND group_member_id = ? AND item_member_id = ? AND shared_msg_id = ? AND reaction_sent = ?
| ]
( groupId , groupMemberId' m , itemMemberId , itemSharedMId , sent )
setGroupReaction :: DB . Connection -> GroupInfo -> GroupMember -> MemberId -> SharedMsgId -> Bool -> MsgReaction -> Bool -> MessageId -> UTCTime -> IO ()
setGroupReaction db GroupInfo { groupId } m itemMemberId itemSharedMId sent reaction add msgId reactionTs
| add =
2023-11-26 18:16:37 +00:00
DB . execute
db
[ sql |
INSERT INTO chat_item_reactions
( group_id , group_member_id , item_member_id , shared_msg_id , reaction_sent , reaction , created_by_msg_id , reaction_ts )
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? )
| ]
( groupId , groupMemberId' m , itemMemberId , itemSharedMId , sent , reaction , msgId , reactionTs )
2023-06-18 10:20:11 +01:00
| otherwise =
2023-11-26 18:16:37 +00:00
DB . execute
db
[ sql |
DELETE FROM chat_item_reactions
WHERE group_id = ? AND group_member_id = ? AND shared_msg_id = ? AND item_member_id = ? AND reaction_sent = ? AND reaction = ?
| ]
( groupId , groupMemberId' m , itemSharedMId , itemMemberId , sent , reaction )
2023-06-18 10:20:11 +01:00
getTimedItems :: DB . Connection -> User -> UTCTime -> IO [ ( ( ChatRef , ChatItemId ) , UTCTime ) ]
getTimedItems db User { userId } startTimedThreadCutoff =
mapMaybe toCIRefDeleteAt
<$> DB . query
db
[ sql |
SELECT chat_item_id , contact_id , group_id , timed_delete_at
FROM chat_items
WHERE user_id = ? AND timed_delete_at IS NOT NULL AND timed_delete_at <= ?
| ]
( userId , startTimedThreadCutoff )
where
toCIRefDeleteAt :: ( ChatItemId , Maybe ContactId , Maybe GroupId , UTCTime ) -> Maybe ( ( ChatRef , ChatItemId ) , UTCTime )
toCIRefDeleteAt = \ case
( itemId , Just contactId , Nothing , deleteAt ) -> Just ( ( ChatRef CTDirect contactId , itemId ) , deleteAt )
( itemId , Nothing , Just groupId , deleteAt ) -> Just ( ( ChatRef CTGroup groupId , itemId ) , deleteAt )
_ -> Nothing
getChatItemTTL :: DB . Connection -> User -> IO ( Maybe Int64 )
getChatItemTTL db User { userId } =
fmap join . maybeFirstRow fromOnly $ DB . query db " SELECT chat_item_ttl FROM settings WHERE user_id = ? LIMIT 1 " ( Only userId )
setChatItemTTL :: DB . Connection -> User -> Maybe Int64 -> IO ()
setChatItemTTL db User { userId } chatItemTTL = do
currentTs <- getCurrentTime
r :: ( Maybe Int64 ) <- maybeFirstRow fromOnly $ DB . query db " SELECT 1 FROM settings WHERE user_id = ? LIMIT 1 " ( Only userId )
case r of
Just _ -> do
DB . execute
db
" UPDATE settings SET chat_item_ttl = ?, updated_at = ? WHERE user_id = ? "
( chatItemTTL , currentTs , userId )
Nothing -> do
DB . execute
db
" INSERT INTO settings (user_id, chat_item_ttl, created_at, updated_at) VALUES (?,?,?,?) "
( userId , chatItemTTL , currentTs , currentTs )
getContactExpiredFileInfo :: DB . Connection -> User -> Contact -> UTCTime -> IO [ CIFileInfo ]
getContactExpiredFileInfo db User { userId } Contact { contactId } expirationDate =
map toFileInfo
<$> DB . query
db
( fileInfoQuery <> " WHERE i.user_id = ? AND i.contact_id = ? AND i.created_at <= ? " )
( userId , contactId , expirationDate )
deleteContactExpiredCIs :: DB . Connection -> User -> Contact -> UTCTime -> IO ()
deleteContactExpiredCIs db user @ User { userId } ct @ Contact { contactId } expirationDate = do
connIds <- getContactConnIds_ db user ct
forM_ connIds $ \ connId ->
DB . execute db " DELETE FROM messages WHERE connection_id = ? AND created_at <= ? " ( connId , expirationDate )
DB . execute db " DELETE FROM chat_item_reactions WHERE contact_id = ? AND created_at <= ? " ( contactId , expirationDate )
DB . execute db " DELETE FROM chat_items WHERE user_id = ? AND contact_id = ? AND created_at <= ? " ( userId , contactId , expirationDate )
getGroupExpiredFileInfo :: DB . Connection -> User -> GroupInfo -> UTCTime -> UTCTime -> IO [ CIFileInfo ]
getGroupExpiredFileInfo db User { userId } GroupInfo { groupId } expirationDate createdAtCutoff =
map toFileInfo
<$> DB . query
db
( fileInfoQuery <> " WHERE i.user_id = ? AND i.group_id = ? AND i.item_ts <= ? AND i.created_at <= ? " )
( userId , groupId , expirationDate , createdAtCutoff )
deleteGroupExpiredCIs :: DB . Connection -> User -> GroupInfo -> UTCTime -> UTCTime -> IO ()
deleteGroupExpiredCIs db User { userId } GroupInfo { groupId } expirationDate createdAtCutoff = do
DB . execute db " DELETE FROM messages WHERE group_id = ? AND created_at <= ? " ( groupId , min expirationDate createdAtCutoff )
DB . execute db " DELETE FROM chat_item_reactions WHERE group_id = ? AND reaction_ts <= ? AND created_at <= ? " ( groupId , expirationDate , createdAtCutoff )
DB . execute db " DELETE FROM chat_items WHERE user_id = ? AND group_id = ? AND item_ts <= ? AND created_at <= ? " ( userId , groupId , expirationDate , createdAtCutoff )
2023-06-22 20:38:09 +04:00
createCIModeration :: DB . Connection -> GroupInfo -> GroupMember -> MemberId -> SharedMsgId -> MessageId -> UTCTime -> IO ()
createCIModeration db GroupInfo { groupId } moderatorMember itemMemberId itemSharedMId msgId moderatedAtTs =
DB . execute
db
[ sql |
INSERT INTO chat_item_moderations
( group_id , moderator_member_id , item_member_id , shared_msg_id , created_by_msg_id , moderated_at )
VALUES ( ? , ? , ? , ? , ? , ? )
| ]
( groupId , groupMemberId' moderatorMember , itemMemberId , itemSharedMId , msgId , moderatedAtTs )
getCIModeration :: DB . Connection -> User -> GroupInfo -> MemberId -> Maybe SharedMsgId -> IO ( Maybe CIModeration )
getCIModeration _ _ _ _ Nothing = pure Nothing
getCIModeration db user GroupInfo { groupId } itemMemberId ( Just sharedMsgId ) = do
r_ <-
maybeFirstRow id $
DB . query
db
[ sql |
SELECT chat_item_moderation_id , moderator_member_id , created_by_msg_id , moderated_at
FROM chat_item_moderations
WHERE group_id = ? AND item_member_id = ? AND shared_msg_id = ?
LIMIT 1
| ]
( groupId , itemMemberId , sharedMsgId )
case r_ of
Just ( moderationId , moderatorId , createdByMsgId , moderatedAt ) -> do
runExceptT ( getGroupMember db user groupId moderatorId ) >>= \ case
Right moderatorMember -> pure ( Just CIModeration { moderationId , moderatorMember , createdByMsgId , moderatedAt } )
_ -> pure Nothing
_ -> pure Nothing
deleteCIModeration :: DB . Connection -> GroupInfo -> MemberId -> Maybe SharedMsgId -> IO ()
deleteCIModeration _ _ _ Nothing = pure ()
deleteCIModeration db GroupInfo { groupId } itemMemberId ( Just sharedMsgId ) =
DB . execute
db
" DELETE FROM chat_item_moderations WHERE group_id = ? AND item_member_id = ? AND shared_msg_id = ? "
( groupId , itemMemberId , sharedMsgId )
2023-07-26 14:49:35 +04:00
createGroupSndStatus :: DB . Connection -> ChatItemId -> GroupMemberId -> CIStatus 'MDSnd -> IO ()
createGroupSndStatus db itemId memberId status =
DB . execute
db
" INSERT INTO group_snd_item_statuses (chat_item_id, group_member_id, group_snd_item_status) VALUES (?,?,?) "
( itemId , memberId , status )
getGroupSndStatus :: DB . Connection -> ChatItemId -> GroupMemberId -> ExceptT StoreError IO ( CIStatus 'MDSnd )
getGroupSndStatus db itemId memberId =
ExceptT . firstRow fromOnly ( SENoGroupSndStatus itemId memberId ) $
DB . query
db
[ sql |
SELECT group_snd_item_status
FROM group_snd_item_statuses
WHERE chat_item_id = ? AND group_member_id = ?
LIMIT 1
| ]
( itemId , memberId )
updateGroupSndStatus :: DB . Connection -> ChatItemId -> GroupMemberId -> CIStatus 'MDSnd -> IO ()
updateGroupSndStatus db itemId memberId status = do
currentTs <- liftIO getCurrentTime
DB . execute
db
[ sql |
UPDATE group_snd_item_statuses
SET group_snd_item_status = ? , updated_at = ?
WHERE chat_item_id = ? AND group_member_id = ?
| ]
( status , currentTs , itemId , memberId )
getGroupSndStatuses :: DB . Connection -> ChatItemId -> IO [ ( GroupMemberId , CIStatus 'MDSnd ) ]
getGroupSndStatuses db itemId =
DB . query
db
[ sql |
SELECT group_member_id , group_snd_item_status
FROM group_snd_item_statuses
WHERE chat_item_id = ?
| ]
( Only itemId )
getGroupSndStatusCounts :: DB . Connection -> ChatItemId -> IO [ ( CIStatus 'MDSnd , Int ) ]
getGroupSndStatusCounts db itemId =
DB . query
db
[ sql |
SELECT group_snd_item_status , COUNT ( 1 )
FROM group_snd_item_statuses
WHERE chat_item_id = ?
GROUP BY group_snd_item_status
| ]
( Only itemId )
2023-12-23 17:07:23 +04:00
getGroupHistoryItems :: DB . Connection -> User -> GroupInfo -> Int -> IO [ Either StoreError ( CChatItem 'CTGroup ) ]
getGroupHistoryItems db user @ User { userId } GroupInfo { groupId } count = do
chatItemIds <- getLastItemIds_
-- use getGroupCIWithReactions to read reactions data
reverse <$> mapM ( runExceptT . getGroupChatItem db user groupId ) chatItemIds
where
getLastItemIds_ :: IO [ ChatItemId ]
getLastItemIds_ =
map fromOnly
<$> DB . query
db
[ sql |
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND group_id = ?
AND item_content_tag IN ( ? , ? )
AND item_deleted = 0
ORDER BY item_ts DESC , chat_item_id DESC
LIMIT ?
| ]
( userId , groupId , rcvMsgContentTag , sndMsgContentTag , count )