chats API for mobile (#230)

Co-authored-by: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
This commit is contained in:
Evgeny Poberezkin
2022-01-28 10:41:09 +00:00
committed by GitHub
parent edc9560d36
commit c3a8ae1eb5
5 changed files with 177 additions and 147 deletions
+27 -17
View File
@@ -36,6 +36,7 @@ import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8)
import Data.Time.Clock (UTCTime, getCurrentTime)
import Data.Time.LocalTime (getCurrentTimeZone)
import Data.Word (Word32)
import Simplex.Chat.Controller
import Simplex.Chat.Messages
@@ -123,6 +124,11 @@ toView event = do
processChatCommand :: forall m. ChatMonad m => User -> ChatCommand -> m ChatResponse
processChatCommand user@User {userId, profile} = \case
APIGetChats -> CRApiChats <$> withStore (`getChatPreviews` user)
APIGetChat cType cId -> case cType of
CTDirect -> CRApiDirectChat <$> withStore (\st -> getDirectChat st user cId)
CTGroup -> pure $ CRChatError ChatErrorNotImplemented
APIGetChatItems _count -> pure $ CRChatError ChatErrorNotImplemented
ChatHelp section -> pure $ CRChatHelp section
Welcome -> pure $ CRWelcome user
AddContact -> procCmd $ do
@@ -179,7 +185,7 @@ processChatCommand user@User {userId, profile} = \case
SendMessage cName msg -> do
contact <- withStore $ \st -> getContact st userId cName
let mc = MCText $ safeDecodeUtf8 msg
ci <- sendDirectChatItem userId contact (XMsgNew mc) (CIMsgContent mc)
ci <- sendDirectChatItem userId contact (XMsgNew mc) (CISndMsgContent mc)
setActive $ ActiveC cName
pure . CRNewChatItem $ AChatItem SCTDirect SMDSnd (DirectChat contact) ci
NewGroup gProfile -> do
@@ -257,7 +263,7 @@ processChatCommand user@User {userId, profile} = \case
group@(Group gInfo@GroupInfo {membership} _) <- withStore $ \st -> getGroup st user gName
unless (memberActive membership) $ throwChatError CEGroupMemberUserRemoved
let mc = MCText $ safeDecodeUtf8 msg
ci <- sendGroupChatItem userId group (XMsgNew mc) (CIMsgContent mc)
ci <- sendGroupChatItem userId group (XMsgNew mc) (CISndMsgContent mc)
setActive $ ActiveG gName
pure . CRNewChatItem $ AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci
SendFile cName f -> do
@@ -288,9 +294,9 @@ processChatCommand user@User {userId, profile} = \case
let ciContent = CISndFileInvitation fileId f
createdAt <- liftIO getCurrentTime
let ci = mkNewChatItem ciContent 0 createdAt createdAt
ciMeta@CIMetaProps {itemId} <- saveChatItem userId (CDSndGroup gInfo) ci
ciMeta@CIMeta {itemId} <- saveChatItem userId (CDGroupSnd gInfo) ci
withStore $ \st -> updateFileTransferChatItemId st fileId itemId
pure . CRNewChatItem $ AChatItem SCTGroup SMDSnd (GroupChat gInfo) $ SndGroupChatItem (CISndMeta ciMeta) ciContent
pure . CRNewChatItem $ AChatItem SCTGroup SMDSnd (GroupChat gInfo) $ ChatItem CIGroupSnd ciMeta ciContent
ReceiveFile fileId filePath_ -> do
ft@RcvFileTransfer {fileInvitation = FileInvitation {fileName, fileConnReq}, fileStatus} <- withStore $ \st -> getRcvFileTransfer st userId fileId
unless (fileStatus == RFSNew) . throwChatError $ CEFileAlreadyReceiving fileName
@@ -804,14 +810,14 @@ processAgentMessage user@User {userId, profile} agentConnId agentMessage = do
newContentMessage :: Contact -> MsgContent -> MessageId -> MsgMeta -> m ()
newContentMessage ct@Contact {localDisplayName = c} mc msgId msgMeta = do
ci <- saveRcvDirectChatItem userId ct msgId msgMeta (CIMsgContent mc)
ci <- saveRcvDirectChatItem userId ct msgId msgMeta (CIRcvMsgContent mc)
toView . CRNewChatItem $ AChatItem SCTDirect SMDRcv (DirectChat ct) ci
showToast (c <> "> ") $ msgContentText mc
setActive $ ActiveC c
newGroupContentMessage :: GroupInfo -> GroupMember -> MsgContent -> MessageId -> MsgMeta -> m ()
newGroupContentMessage gInfo m@GroupMember {localDisplayName = c} mc msgId msgMeta = do
ci <- saveRcvGroupChatItem userId gInfo m msgId msgMeta (CIMsgContent mc)
ci <- saveRcvGroupChatItem userId gInfo m msgId msgMeta (CIRcvMsgContent mc)
toView . CRNewChatItem $ AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci
let g = groupName' gInfo
showToast ("#" <> g <> " " <> c <> "> ") $ msgContentText mc
@@ -1163,32 +1169,33 @@ sendDirectChatItem :: ChatMonad m => UserId -> Contact -> ChatMsgEvent -> CICont
sendDirectChatItem userId contact@Contact {activeConn} chatMsgEvent ciContent = do
msgId <- sendDirectMessage activeConn chatMsgEvent
createdAt <- liftIO getCurrentTime
ciMeta <- saveChatItem userId (CDDirect contact) $ mkNewChatItem ciContent msgId createdAt createdAt
pure $ DirectChatItem (CISndMeta ciMeta) ciContent
ciMeta <- saveChatItem userId (CDDirectSnd contact) $ mkNewChatItem ciContent msgId createdAt createdAt
pure $ ChatItem CIDirectSnd ciMeta ciContent
sendGroupChatItem :: ChatMonad m => UserId -> Group -> ChatMsgEvent -> CIContent 'MDSnd -> m (ChatItem 'CTGroup 'MDSnd)
sendGroupChatItem userId (Group g ms) chatMsgEvent ciContent = do
msgId <- sendGroupMessage ms chatMsgEvent
createdAt <- liftIO getCurrentTime
ciMeta <- saveChatItem userId (CDSndGroup g) $ mkNewChatItem ciContent msgId createdAt createdAt
pure $ SndGroupChatItem (CISndMeta ciMeta) ciContent
ciMeta <- saveChatItem userId (CDGroupSnd g) $ mkNewChatItem ciContent msgId createdAt createdAt
pure $ ChatItem CIGroupSnd ciMeta ciContent
saveRcvDirectChatItem :: ChatMonad m => UserId -> Contact -> MessageId -> MsgMeta -> CIContent 'MDRcv -> m (ChatItem 'CTDirect 'MDRcv)
saveRcvDirectChatItem userId ct msgId MsgMeta {broker = (_, brokerTs)} ciContent = do
createdAt <- liftIO getCurrentTime
ciMeta <- saveChatItem userId (CDDirect ct) $ mkNewChatItem ciContent msgId brokerTs createdAt
pure $ DirectChatItem (CIRcvMeta ciMeta) ciContent
ciMeta <- saveChatItem userId (CDDirectRcv ct) $ mkNewChatItem ciContent msgId brokerTs createdAt
pure $ ChatItem CIDirectRcv ciMeta ciContent
saveRcvGroupChatItem :: ChatMonad m => UserId -> GroupInfo -> GroupMember -> MessageId -> MsgMeta -> CIContent 'MDRcv -> m (ChatItem 'CTGroup 'MDRcv)
saveRcvGroupChatItem userId g m msgId MsgMeta {broker = (_, brokerTs)} ciContent = do
createdAt <- liftIO getCurrentTime
ciMeta <- saveChatItem userId (CDRcvGroup g m) $ mkNewChatItem ciContent msgId brokerTs createdAt
pure $ RcvGroupChatItem m (CIRcvMeta ciMeta) ciContent
ciMeta <- saveChatItem userId (CDGroupRcv g m) $ mkNewChatItem ciContent msgId brokerTs createdAt
pure $ ChatItem (CIGroupRcv m) ciMeta ciContent
saveChatItem :: (MsgDirectionI d, ChatMonad m) => UserId -> ChatDirection c d -> NewChatItem d -> m CIMetaProps
saveChatItem :: ChatMonad m => UserId -> ChatDirection c d -> NewChatItem d -> m CIMeta
saveChatItem userId cd ci@NewChatItem {itemTs, itemText, createdAt} = do
tz <- liftIO getCurrentTimeZone
ciId <- withStore $ \st -> createNewChatItem st userId cd ci
liftIO $ mkCIMetaProps ciId itemTs itemText createdAt
pure $ mkCIMeta ciId itemText tz itemTs createdAt
mkNewChatItem :: forall d. MsgDirectionI d => CIContent d -> MessageId -> UTCTime -> UTCTime -> NewChatItem d
mkNewChatItem itemContent msgId itemTs createdAt =
@@ -1289,7 +1296,10 @@ withStore action =
chatCommandP :: Parser ChatCommand
chatCommandP =
("/help files" <|> "/help file" <|> "/hf") $> ChatHelp HSFiles
"/api/v1/chats" $> APIGetChats
<|> "/api/v1/chat/" *> (APIGetChat <$> ("direct/" $> CTDirect <|> "group/" $> CTGroup) <*> A.decimal)
<|> "/api/v1/chat/items?count=" *> (APIGetChatItems <$> A.decimal)
<|> ("/help files" <|> "/help file" <|> "/hf") $> ChatHelp HSFiles
<|> ("/help groups" <|> "/help group" <|> "/hg") $> ChatHelp HSGroups
<|> ("/help address" <|> "/ha") $> ChatHelp HSMyAddress
<|> ("/help" <|> "/h") $> ChatHelp HSMain
+9 -2
View File
@@ -1,4 +1,5 @@
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
@@ -77,7 +78,10 @@ instance ToJSON HelpSection where
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "HS"
data ChatCommand
= ChatHelp HelpSection
= APIGetChats
| APIGetChat ChatType Int64
| APIGetChatItems Int
| ChatHelp HelpSection
| Welcome
| AddContact
| Connect (Maybe AConnectionRequestUri)
@@ -112,7 +116,9 @@ data ChatCommand
deriving (Show)
data ChatResponse
= CRNewChatItem {chatItem :: AChatItem}
= CRApiChats {chats :: [AChatPreview]}
| CRApiDirectChat {chat :: Chat 'CTDirect}
| CRNewChatItem {chatItem :: AChatItem}
| CRCmdAccepted {corr :: CorrId}
| CRChatHelp {helpSection :: HelpSection}
| CRWelcome {user :: User}
@@ -190,6 +196,7 @@ data ChatError
| ChatErrorMessage {errorMessage :: String}
| ChatErrorAgent {agentError :: AgentErrorType}
| ChatErrorStore {storeError :: StoreError}
| ChatErrorNotImplemented
deriving (Show, Exception, Generic)
instance ToJSON ChatError where
+93 -81
View File
@@ -22,7 +22,7 @@ import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding (decodeLatin1, encodeUtf8)
import Data.Time.Clock (UTCTime)
import Data.Time.LocalTime (ZonedTime, utcToLocalZonedTime)
import Data.Time.LocalTime (TimeZone, ZonedTime, utcToZonedTime)
import Data.Type.Equality
import Data.Typeable (Typeable)
import Database.SQLite.Simple.FromField (FromField (..))
@@ -38,7 +38,11 @@ import Simplex.Messaging.Parsers (dropPrefix)
import Simplex.Messaging.Protocol (MsgBody)
data ChatType = CTDirect | CTGroup
deriving (Show)
deriving (Show, Generic)
instance ToJSON ChatType where
toJSON = J.genericToJSON . enumJSON $ dropPrefix "CT"
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "CT"
data ChatInfo (c :: ChatType) where
DirectChat :: Contact -> ChatInfo 'CTDirect
@@ -64,52 +68,63 @@ jsonChatInfo = \case
DirectChat c -> JCInfoDirect c
GroupChat g -> JCInfoGroup g
type ChatItemData d = (CIMeta d, CIContent d)
data ChatItem (c :: ChatType) (d :: MsgDirection) = ChatItem
{ chatDir :: CIDirection c d,
meta :: CIMeta,
content :: CIContent d
}
deriving (Show, Generic)
data ChatItem (c :: ChatType) (d :: MsgDirection) where
DirectChatItem :: CIMeta d -> CIContent d -> ChatItem 'CTDirect d
SndGroupChatItem :: CIMeta 'MDSnd -> CIContent 'MDSnd -> ChatItem 'CTGroup 'MDSnd
RcvGroupChatItem :: GroupMember -> CIMeta 'MDRcv -> CIContent 'MDRcv -> ChatItem 'CTGroup 'MDRcv
instance ToJSON (ChatItem c d) where
toJSON = J.genericToJSON J.defaultOptions
toEncoding = J.genericToEncoding J.defaultOptions
deriving instance Show (ChatItem c d)
data CIDirection (c :: ChatType) (d :: MsgDirection) where
CIDirectSnd :: CIDirection 'CTDirect 'MDSnd
CIDirectRcv :: CIDirection 'CTDirect 'MDRcv
CIGroupSnd :: CIDirection 'CTGroup 'MDSnd
CIGroupRcv :: GroupMember -> CIDirection 'CTGroup 'MDRcv
data JSONChatItem d
= JCItemDirect {dir :: MsgDirection, meta :: CIMeta d, content :: CIContent d}
| JCItemSndGroup {dir :: MsgDirection, meta :: CIMeta d, content :: CIContent d}
| JCItemRcvGroup {dir :: MsgDirection, member :: GroupMember, meta :: CIMeta d, content :: CIContent d}
deriving instance Show (CIDirection c d)
data JSONCIDirection
= JCIDirectSnd
| JCIDirectRcv
| JCIGroupSnd
| JCIGroupRcv {groupMember :: GroupMember}
deriving (Generic)
instance MsgDirectionI d => ToJSON (JSONChatItem d) where
toJSON = J.genericToJSON . singleFieldJSON $ dropPrefix "JCItem"
toEncoding = J.genericToEncoding . singleFieldJSON $ dropPrefix "JCItem"
instance ToJSON JSONCIDirection where
toJSON = J.genericToJSON . singleFieldJSON $ dropPrefix "JCI"
toEncoding = J.genericToEncoding . singleFieldJSON $ dropPrefix "JCI"
instance MsgDirectionI d => ToJSON (ChatItem c d) where
toJSON = J.toJSON . jsonChatItem
toEncoding = J.toEncoding . jsonChatItem
instance ToJSON (CIDirection c d) where
toJSON = J.toJSON . jsonCIDirection
toEncoding = J.toEncoding . jsonCIDirection
jsonChatItem :: forall c d. MsgDirectionI d => ChatItem c d -> JSONChatItem d
jsonChatItem = \case
DirectChatItem meta cic -> JCItemDirect md meta cic
SndGroupChatItem meta cic -> JCItemSndGroup md meta cic
RcvGroupChatItem m meta cic -> JCItemRcvGroup md m meta cic
where
md = toMsgDirection $ msgDirection @d
jsonCIDirection :: CIDirection c d -> JSONCIDirection
jsonCIDirection = \case
CIDirectSnd -> JCIDirectSnd
CIDirectRcv -> JCIDirectRcv
CIGroupSnd -> JCIGroupSnd
CIGroupRcv m -> JCIGroupRcv m
data CChatItem c = forall d. CChatItem (SMsgDirection d) (ChatItem c d)
deriving instance Show (CChatItem c)
instance ToJSON (CChatItem c) where
toJSON (CChatItem _ ci) = J.toJSON ci
toEncoding (CChatItem _ ci) = J.toEncoding ci
chatItemId :: ChatItem c d -> ChatItemId
chatItemId = \case
DirectChatItem (CISndMeta CIMetaProps {itemId}) _ -> itemId
DirectChatItem (CIRcvMeta CIMetaProps {itemId}) _ -> itemId
SndGroupChatItem (CISndMeta CIMetaProps {itemId}) _ -> itemId
RcvGroupChatItem _ (CIRcvMeta CIMetaProps {itemId}) _ -> itemId
chatItemId ChatItem {meta = CIMeta {itemId}} = itemId
data ChatDirection (c :: ChatType) (d :: MsgDirection) where
CDDirect :: Contact -> ChatDirection 'CTDirect d
CDSndGroup :: GroupInfo -> ChatDirection 'CTGroup 'MDSnd
CDRcvGroup :: GroupInfo -> GroupMember -> ChatDirection 'CTGroup 'MDRcv
CDDirectSnd :: Contact -> ChatDirection 'CTDirect 'MDSnd
CDDirectRcv :: Contact -> ChatDirection 'CTDirect 'MDRcv
CDGroupSnd :: GroupInfo -> ChatDirection 'CTGroup 'MDSnd
CDGroupRcv :: GroupInfo -> GroupMember -> ChatDirection 'CTGroup 'MDRcv
data NewChatItem d = NewChatItem
{ createdByMsgId :: Maybe MessageId,
@@ -122,16 +137,38 @@ data NewChatItem d = NewChatItem
deriving (Show)
-- | type to show one chat with messages
data Chat c = Chat (ChatInfo c) [CChatItem c]
deriving (Show)
data Chat c = Chat {chatInfo :: ChatInfo c, chatItems :: [CChatItem c]}
deriving (Show, Generic)
instance ToJSON (Chat c) where
toJSON = J.genericToJSON J.defaultOptions
toEncoding = J.genericToEncoding J.defaultOptions
data ChatPreview c = ChatPreview {chatInfo :: ChatInfo c, lastChatItem :: Maybe (CChatItem c)}
deriving (Show, Generic)
instance ToJSON (ChatPreview c) where
toJSON = J.genericToJSON J.defaultOptions
toEncoding = J.genericToEncoding J.defaultOptions
-- | type to show the list of chats, with one last message in each
data AChatPreview = forall c. AChatPreview (SChatType c) (ChatInfo c) (Maybe (CChatItem c))
deriving instance Show AChatPreview
instance ToJSON AChatPreview where
toJSON (AChatPreview _ chat ccItem_) = J.toJSON $ JSONAnyChatPreview chat ccItem_
toEncoding (AChatPreview _ chat ccItem_) = J.toEncoding $ J.toJSON $ JSONAnyChatPreview chat ccItem_
data JSONAnyChatPreview c d = JSONAnyChatPreview {chatInfo :: ChatInfo c, chatItem :: Maybe (CChatItem c)}
deriving (Generic)
instance ToJSON (JSONAnyChatPreview c d) where
toJSON = J.genericToJSON J.defaultOptions
toEncoding = J.genericToEncoding J.defaultOptions
-- | type to show a mix of messages from multiple chats
data AChatItem = forall c d. MsgDirectionI d => AChatItem (SChatType c) (SMsgDirection d) (ChatInfo c) (ChatItem c d)
data AChatItem = forall c d. AChatItem (SChatType c) (SMsgDirection d) (ChatInfo c) (ChatItem c d)
deriving instance Show AChatItem
@@ -142,35 +179,11 @@ instance ToJSON AChatItem where
data JSONAnyChatItem c d = JSONAnyChatItem {chatInfo :: ChatInfo c, chatItem :: ChatItem c d}
deriving (Generic)
instance MsgDirectionI d => ToJSON (JSONAnyChatItem c d) where
instance ToJSON (JSONAnyChatItem c d) where
toJSON = J.genericToJSON J.defaultOptions
toEncoding = J.genericToEncoding J.defaultOptions
data CIMeta (d :: MsgDirection) where
CISndMeta :: CIMetaProps -> CIMeta 'MDSnd
CIRcvMeta :: CIMetaProps -> CIMeta 'MDRcv
deriving instance Show (CIMeta d)
instance ToJSON (CIMeta d) where
toJSON = J.toJSON . jsonCIMeta
toEncoding = J.toEncoding . jsonCIMeta
data JSONCIMeta
= JCIMetaSnd {meta :: CIMetaProps}
| JCIMetaRcv {meta :: CIMetaProps}
deriving (Generic)
instance ToJSON JSONCIMeta where
toJSON = J.genericToJSON . singleFieldJSON $ dropPrefix "JCIMeta"
toEncoding = J.genericToEncoding . singleFieldJSON $ dropPrefix "JCIMeta"
jsonCIMeta :: CIMeta d -> JSONCIMeta
jsonCIMeta = \case
CISndMeta meta -> JCIMetaSnd meta
CIRcvMeta meta -> JCIMetaRcv meta
data CIMetaProps = CIMetaProps
data CIMeta = CIMeta
{ itemId :: ChatItemId,
itemTs :: ChatItemTs,
itemText :: Text,
@@ -179,19 +192,20 @@ data CIMetaProps = CIMetaProps
}
deriving (Show, Generic, FromJSON)
mkCIMetaProps :: ChatItemId -> ChatItemTs -> Text -> UTCTime -> IO CIMetaProps
mkCIMetaProps itemId itemTs itemText createdAt = do
localItemTs <- utcToLocalZonedTime itemTs
pure CIMetaProps {itemId, itemTs, itemText, localItemTs, createdAt}
mkCIMeta :: ChatItemId -> Text -> TimeZone -> ChatItemTs -> UTCTime -> CIMeta
mkCIMeta itemId itemText tz itemTs createdAt =
let localItemTs = utcToZonedTime tz itemTs
in CIMeta {itemId, itemTs, itemText, localItemTs, createdAt}
instance ToJSON CIMetaProps where toEncoding = J.genericToEncoding J.defaultOptions
instance ToJSON CIMeta where toEncoding = J.genericToEncoding J.defaultOptions
type ChatItemId = Int64
type ChatItemTs = UTCTime
data CIContent (d :: MsgDirection) where
CIMsgContent :: MsgContent -> CIContent d
CISndMsgContent :: MsgContent -> CIContent 'MDSnd
CIRcvMsgContent :: MsgContent -> CIContent 'MDRcv
CISndFileInvitation :: FileTransferId -> FilePath -> CIContent 'MDSnd
CIRcvFileInvitation :: RcvFileTransfer -> CIContent 'MDRcv
@@ -199,14 +213,15 @@ deriving instance Show (CIContent d)
ciContentToText :: CIContent d -> Text
ciContentToText = \case
CIMsgContent mc -> msgContentText mc
CISndMsgContent mc -> msgContentText mc
CIRcvMsgContent mc -> msgContentText mc
CISndFileInvitation fId fPath -> "you sent file #" <> T.pack (show fId) <> ": " <> T.pack fPath
CIRcvFileInvitation RcvFileTransfer {fileInvitation = FileInvitation {fileName}} -> "file " <> T.pack fileName
instance MsgDirectionI d => ToField (CIContent d) where
instance ToField (CIContent d) where
toField = toField . decodeLatin1 . LB.toStrict . J.encode
instance MsgDirectionI d => ToJSON (CIContent d) where
instance ToJSON (CIContent d) where
toJSON = J.toJSON . jsonCIContent
toEncoding = J.toEncoding . jsonCIContent
@@ -218,7 +233,8 @@ instance FromJSON ACIContent where
instance FromField ACIContent where fromField = fromTextField_ $ J.decode . LB.fromStrict . encodeUtf8
data JSONCIContent
= JCIMsgContent {msgDir :: MsgDirection, msgContent :: MsgContent}
= JCISndMsgContent {msgContent :: MsgContent}
| JCIRcvMsgContent {msgContent :: MsgContent}
| JCISndFileInvitation {fileId :: FileTransferId, filePath :: FilePath}
| JCIRcvFileInvitation {rcvFileTransfer :: RcvFileTransfer}
deriving (Generic)
@@ -230,19 +246,17 @@ instance ToJSON JSONCIContent where
toJSON = J.genericToJSON . singleFieldJSON $ dropPrefix "JCI"
toEncoding = J.genericToEncoding . singleFieldJSON $ dropPrefix "JCI"
jsonCIContent :: forall d. MsgDirectionI d => CIContent d -> JSONCIContent
jsonCIContent :: CIContent d -> JSONCIContent
jsonCIContent = \case
CIMsgContent mc -> JCIMsgContent md mc
CISndMsgContent mc -> JCISndMsgContent mc
CIRcvMsgContent mc -> JCIRcvMsgContent mc
CISndFileInvitation fId fPath -> JCISndFileInvitation fId fPath
CIRcvFileInvitation ft -> JCIRcvFileInvitation ft
where
md = toMsgDirection $ msgDirection @d
aciContentJSON :: JSONCIContent -> ACIContent
aciContentJSON = \case
JCIMsgContent md mc -> case md of
MDSnd -> ACIContent SMDSnd $ CIMsgContent mc
MDRcv -> ACIContent SMDRcv $ CIMsgContent mc
JCISndMsgContent mc -> ACIContent SMDSnd $ CISndMsgContent mc
JCIRcvMsgContent mc -> ACIContent SMDRcv $ CIRcvMsgContent mc
JCISndFileInvitation fId fPath -> ACIContent SMDSnd $ CISndFileInvitation fId fPath
JCIRcvFileInvitation ft -> ACIContent SMDRcv $ CIRcvFileInvitation ft
@@ -351,8 +365,6 @@ data MsgMetaJSON = MsgMetaJSON
instance ToJSON MsgMetaJSON where toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
-- instance FromJson MsgMetaJSON where fromEncoding = J.genericFromEncoding JSONKeyOptions
msgMetaToJson :: MsgMeta -> MsgMetaJSON
msgMetaToJson MsgMeta {integrity, recipient = (rcvId, rcvTs), broker = (serverId, serverTs), sndMsgId = sndId} =
MsgMetaJSON
+22 -25
View File
@@ -129,6 +129,7 @@ import Data.Maybe (listToMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Time.Clock (UTCTime, getCurrentTime)
import Data.Time.LocalTime (TimeZone, getCurrentTimeZone)
import Database.SQLite.Simple (NamedParam (..), Only (..), Query (..), SQLError, (:.) (..))
import qualified Database.SQLite.Simple as DB
import Database.SQLite.Simple.QQ (sql)
@@ -1810,7 +1811,7 @@ deletePendingGroupMessage st groupMemberId messageId =
liftIO . withTransaction st $ \db ->
DB.execute db "DELETE FROM pending_group_messages WHERE group_member_id = ? AND message_id = ?" (groupMemberId, messageId)
createNewChatItem :: (MsgDirectionI d, MonadUnliftIO m) => SQLiteStore -> UserId -> ChatDirection c d -> NewChatItem d -> m ChatItemId
createNewChatItem :: MonadUnliftIO m => SQLiteStore -> UserId -> ChatDirection c d -> NewChatItem d -> m ChatItemId
createNewChatItem st userId chatDirection NewChatItem {createdByMsgId, itemSent, itemTs, itemContent, itemText, createdAt} =
liftIO . withTransaction st $ \db -> do
let (contactId_, groupId_, groupMemberId_) = ids
@@ -1834,9 +1835,10 @@ createNewChatItem st userId chatDirection NewChatItem {createdByMsgId, itemSent,
where
ids :: (Maybe Int64, Maybe Int64, Maybe Int64)
ids = case chatDirection of
CDDirect Contact {contactId} -> (Just contactId, Nothing, Nothing)
CDSndGroup GroupInfo {groupId} -> (Nothing, Just groupId, Nothing)
CDRcvGroup GroupInfo {groupId} GroupMember {groupMemberId} -> (Nothing, Just groupId, Just groupMemberId)
CDDirectSnd Contact {contactId} -> (Just contactId, Nothing, Nothing)
CDDirectRcv Contact {contactId} -> (Just contactId, Nothing, Nothing)
CDGroupSnd GroupInfo {groupId} -> (Nothing, Just groupId, Nothing)
CDGroupRcv GroupInfo {groupId} GroupMember {groupMemberId} -> (Nothing, Just groupId, Just groupMemberId)
getChatPreviews :: MonadUnliftIO m => SQLiteStore -> User -> m [AChatPreview]
getChatPreviews st user =
@@ -1936,28 +1938,23 @@ getContact_' db User {userId} contactId =
getDirectChatItems_ :: DB.Connection -> User -> Int64 -> IO [CChatItem 'CTDirect]
getDirectChatItems_ db User {userId} contactId = do
chatItems_ <-
liftIO $
DB.query
db
[sql|
SELECT
-- CChatItem
ci.chat_item_id, ci.item_ts, ci.item_content, ci.item_text, ci.created_at
FROM chat_items ci
LEFT JOIN messages m ON m.message_id == ci.created_by_msg_id
LEFT JOIN msg_deliveries md ON md.message_id = m.message_id
WHERE ci.user_id = ? AND ci.contact_id = ?
|]
(userId, contactId)
liftIO $ mapM toDirectChatItem chatItems_
tz <- getCurrentTimeZone
map (toDirectChatItem tz)
<$> DB.query
db
[sql|
SELECT chat_item_id, item_ts, item_content, item_text, created_at
FROM chat_items
WHERE user_id = ? AND contact_id = ?
|]
(userId, contactId)
where
toDirectChatItem :: (Int64, ChatItemTs, ACIContent, Text, UTCTime) -> IO (CChatItem 'CTDirect)
toDirectChatItem (itemId, itemTs, itemContent, itemText, createdAt) = do
ciMeta <- liftIO $ mkCIMetaProps itemId itemTs itemText createdAt
pure $ case itemContent of
ACIContent SMDRcv ciContent -> CChatItem SMDRcv (DirectChatItem (CIRcvMeta ciMeta) ciContent)
ACIContent SMDSnd ciContent -> CChatItem SMDSnd (DirectChatItem (CISndMeta ciMeta) ciContent)
toDirectChatItem :: TimeZone -> (Int64, ChatItemTs, ACIContent, Text, UTCTime) -> (CChatItem 'CTDirect)
toDirectChatItem tz (itemId, itemTs, itemContent, itemText, createdAt) =
let ciMeta = mkCIMeta itemId itemText tz itemTs createdAt
in case itemContent of
ACIContent d@SMDSnd ciContent -> CChatItem d $ ChatItem CIDirectSnd ciMeta ciContent
ACIContent d@SMDRcv ciContent -> CChatItem d $ ChatItem CIDirectRcv ciMeta ciContent
-- getGroupChatItemList :: MonadUnliftIO m => SQLiteStore -> UserId -> Int64 -> m ChatItemList
-- getGroupChatItemList st userId groupId =
+26 -22
View File
@@ -1,3 +1,4 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
@@ -33,6 +34,8 @@ serializeChatResponse = unlines . map unStyle . responseToView ""
responseToView :: String -> ChatResponse -> [StyledString]
responseToView cmd = \case
CRApiChats chats -> [sShow chats]
CRApiDirectChat chat -> [sShow chat]
CRNewChatItem (AChatItem _ _ chat item) -> viewChatItem chat item
CRCmdAccepted _ -> r []
CRChatHelp section -> case section of
@@ -116,31 +119,31 @@ responseToView cmd = \case
r' = r
viewChatItem :: ChatInfo c -> ChatItem c d -> [StyledString]
viewChatItem chat item = case (chat, item) of
(DirectChat c, DirectChatItem ciMeta content) -> case ciMeta of
CISndMeta meta -> case content of
CIMsgContent mc -> viewSentMessage to mc meta
CISndFileInvitation fId fPath -> viewSentFileInvitation to fId fPath meta
CIRcvMeta meta -> case content of
CIMsgContent mc -> viewReceivedMessage from meta mc -- mOk
CIRcvFileInvitation ft -> viewReceivedFileInvitation from meta ft -- mOk
viewChatItem chat (ChatItem cd meta content) = case (chat, cd) of
(DirectChat c, CIDirectSnd) -> case content of
CISndMsgContent mc -> viewSentMessage to mc meta
CISndFileInvitation fId fPath -> viewSentFileInvitation to fId fPath meta
where
to = ttyToContact' c
(DirectChat c, CIDirectRcv) -> case content of
CIRcvMsgContent mc -> viewReceivedMessage from meta mc -- mOk
CIRcvFileInvitation ft -> viewReceivedFileInvitation from meta ft -- mOk
where
from = ttyFromContact' c
(GroupChat g, SndGroupChatItem (CISndMeta meta) content) -> case content of
CIMsgContent mc -> viewSentMessage to mc meta
(GroupChat g, CIGroupSnd) -> case content of
CISndMsgContent mc -> viewSentMessage to mc meta
CISndFileInvitation fId fPath -> viewSentFileInvitation to fId fPath meta
where
to = ttyToGroup g
(GroupChat g, RcvGroupChatItem c (CIRcvMeta meta) content) -> case content of
CIMsgContent mc -> viewReceivedMessage from meta mc -- mOk
(GroupChat g, CIGroupRcv m) -> case content of
CIRcvMsgContent mc -> viewReceivedMessage from meta mc -- mOk
CIRcvFileInvitation ft -> viewReceivedFileInvitation from meta ft -- mOk
where
from = ttyFromGroup' g c
from = ttyFromGroup' g m
where
ttyToContact' Contact {localDisplayName = c} = ttyToContact c
ttyFromContact' Contact {localDisplayName = c} = ttyFromContact c
ttyFromGroup' g GroupMember {localDisplayName = c} = ttyFromGroup g c
ttyFromGroup' g GroupMember {localDisplayName = m} = ttyFromGroup g m
viewInvalidConnReq :: [StyledString]
viewInvalidConnReq =
@@ -289,11 +292,11 @@ viewContactUpdated
where
fullNameUpdate = if T.null fullName' || fullName' == n' then " removed full name" else " updated full name: " <> plain fullName'
viewReceivedMessage :: StyledString -> CIMetaProps -> MsgContent -> [StyledString]
viewReceivedMessage :: StyledString -> CIMeta -> MsgContent -> [StyledString]
viewReceivedMessage from meta mc = receivedWithTime_ from meta (ttyMsgContent mc)
receivedWithTime_ :: StyledString -> CIMetaProps -> [StyledString] -> [StyledString]
receivedWithTime_ from CIMetaProps {localItemTs, createdAt} styledMsg = do
receivedWithTime_ :: StyledString -> CIMeta -> [StyledString] -> [StyledString]
receivedWithTime_ from CIMeta {localItemTs, createdAt} styledMsg = do
prependFirst (formattedTime <> " " <> from) styledMsg -- ++ showIntegrity mOk
where
formattedTime :: StyledString
@@ -318,14 +321,14 @@ receivedWithTime_ from CIMetaProps {localItemTs, createdAt} styledMsg = do
msgError :: String -> [StyledString]
msgError s = [styled (Colored Red) s]
viewSentMessage :: StyledString -> MsgContent -> CIMetaProps -> [StyledString]
viewSentMessage :: StyledString -> MsgContent -> CIMeta -> [StyledString]
viewSentMessage to = sentWithTime_ . prependFirst to . ttyMsgContent
viewSentFileInvitation :: StyledString -> FileTransferId -> FilePath -> CIMetaProps -> [StyledString]
viewSentFileInvitation :: StyledString -> FileTransferId -> FilePath -> CIMeta -> [StyledString]
viewSentFileInvitation to fId fPath = sentWithTime_ $ ttySentFile to fId fPath
sentWithTime_ :: [StyledString] -> CIMetaProps -> [StyledString]
sentWithTime_ styledMsg CIMetaProps {localItemTs} =
sentWithTime_ :: [StyledString] -> CIMeta -> [StyledString]
sentWithTime_ styledMsg CIMeta {localItemTs} =
prependFirst (ttyMsgTime localItemTs <> " ") styledMsg
ttyMsgTime :: ZonedTime -> StyledString
@@ -363,7 +366,7 @@ sendingFile_ status ft@SndFileTransfer {recipientDisplayName = c} =
sndFile :: SndFileTransfer -> StyledString
sndFile SndFileTransfer {fileId, fileName} = fileTransferStr fileId fileName
viewReceivedFileInvitation :: StyledString -> CIMetaProps -> RcvFileTransfer -> [StyledString]
viewReceivedFileInvitation :: StyledString -> CIMeta -> RcvFileTransfer -> [StyledString]
viewReceivedFileInvitation from meta ft = receivedWithTime_ from meta (receivedFileInvitation_ ft)
receivedFileInvitation_ :: RcvFileTransfer -> [StyledString]
@@ -481,6 +484,7 @@ viewChatError = \case
SMP SMP.AUTH -> ["error: this connection is deleted"]
e -> ["smp agent error: " <> sShow e]
ChatErrorMessage e -> ["chat message error: " <> sShow e]
ChatErrorNotImplemented -> ["chat error: not implemented"]
where
fileNotFound fileId = ["file " <> sShow fileId <> " not found"]