deduplicate contact requests (#287)

* deprecate XContact

* XInfoId

* xInfoId tests

* merging

* saving on connection

* connectByAddress

* remove old connect

* deduplicate contact requests

* check on contact acceptance

* test

* rename response

* reuse CRContactRequestAlreadyAccepted

* Update src/Simplex/Chat.hs

* createConnReqConnection

* simplify controller logic

* store methods + profile change

* index

* more indices

* unXInfoId

* simplify

* XInfo with ID -> XContact

* sync reply to Connect when contact already exists

* update view for sync CRContactAlreadyExists command response

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
Efim Poberezkin
2022-02-13 09:19:24 +00:00
committed by GitHub
co-authored by Evgeny Poberezkin
parent 8e34d2fbbc
commit c1c55ca700
12 changed files with 426 additions and 77 deletions
+31 -23
View File
@@ -179,12 +179,12 @@ processChatCommand = \case
gs -> throwChatError $ CEContactGroups ct gs
CTGroup -> pure $ chatCmdError "not implemented"
CTContactRequest -> pure $ chatCmdError "not supported"
APIAcceptContact connReqId -> withUser $ \User {userId, profile} -> do
UserContactRequest {agentInvitationId = AgentInvId invId, localDisplayName = cName, profileId, profile = p} <- withStore $ \st ->
getContactRequest st userId connReqId
withChatLock . procCmd $ do
APIAcceptContact connReqId -> withUser $ \User {userId, profile} -> withChatLock $ do
UserContactRequest {agentInvitationId = AgentInvId invId, localDisplayName = cName, profileId, profile = p, xContactId} <-
withStore $ \st -> getContactRequest st userId connReqId
procCmd $ do
connId <- withAgent $ \a -> acceptContact a invId . directMessage $ XInfo profile
acceptedContact <- withStore $ \st -> createAcceptedContact st userId connId cName profileId p
acceptedContact <- withStore $ \st -> createAcceptedContact st userId connId cName profileId p xContactId
pure $ CRAcceptingContactRequest acceptedContact
APIRejectContact connReqId -> withUser $ \User {userId} -> withChatLock $ do
cReq@UserContactRequest {agentContactConnId = AgentConnId connId, agentInvitationId = AgentInvId invId} <-
@@ -200,15 +200,14 @@ processChatCommand = \case
withStore $ \st -> createDirectConnection st userId connId
pure $ CRInvitation cReq
Connect (Just (ACR SCMInvitation cReq)) -> withUser $ \User {userId, profile} -> withChatLock . procCmd $ do
connect userId cReq $ XInfo profile
connId <- withAgent $ \a -> joinConnection a cReq . directMessage $ XInfo profile
withStore $ \st -> createDirectConnection st userId connId
pure CRSentConfirmation
Connect (Just (ACR SCMContact cReq)) -> withUser $ \User {userId, profile} -> withChatLock . procCmd $ do
connect userId cReq $ XContact profile Nothing
pure CRSentInvitation
Connect (Just (ACR SCMContact cReq)) -> withUser $ \User {userId, profile} ->
connectViaContact userId cReq profile
Connect Nothing -> throwChatError CEInvalidConnReq
ConnectAdmin -> withUser $ \User {userId, profile} -> withChatLock . procCmd $ do
connect userId adminContactReq $ XContact profile Nothing
pure CRSentInvitation
ConnectAdmin -> withUser $ \User {userId, profile} ->
connectViaContact userId adminContactReq profile
DeleteContact cName -> withUser $ \User {userId} -> do
contactId <- withStore $ \st -> getContactIdByName st userId cName
processChatCommand $ APIDeleteChat CTDirect contactId
@@ -395,10 +394,17 @@ processChatCommand = \case
-- use function below to make commands "synchronous"
-- procCmd :: m ChatResponse -> m ChatResponse
-- procCmd = id
connect :: UserId -> ConnectionRequestUri c -> ChatMsgEvent -> m ()
connect userId cReq msg = do
connId <- withAgent $ \a -> joinConnection a cReq $ directMessage msg
withStore $ \st -> createDirectConnection st userId connId
connectViaContact :: UserId -> ConnectionRequestUri 'CMContact -> Profile -> m ChatResponse
connectViaContact userId cReq profile = withChatLock $ do
let cReqHash = ConnReqUriHash . C.sha256Hash $ strEncode cReq
withStore (\st -> getConnReqContactXContactId st userId cReqHash) >>= \case
(Just contact, _) -> pure $ CRContactAlreadyExists contact
(_, xContactId_) -> procCmd $ do
let randomXContactId = XContactId <$> (asks idsDrg >>= liftIO . (`randomBytes` 16))
xContactId <- maybe randomXContactId pure xContactId_
connId <- withAgent $ \a -> joinConnection a cReq $ directMessage (XContact profile $ Just xContactId)
withStore $ \st -> createConnReqConnection st userId connId cReqHash xContactId
pure CRSentInvitation
contactMember :: Contact -> [GroupMember] -> Maybe GroupMember
contactMember Contact {contactId} =
find $ \GroupMember {memberContactId = cId, memberStatus = s} ->
@@ -812,8 +818,8 @@ processAgentMessage (Just user@User {userId, profile}) agentConnId agentMessage
REQ invId connInfo -> do
ChatMessage {chatMsgEvent} <- liftEither $ parseChatMessage connInfo
case chatMsgEvent of
XContact p _ -> profileContactRequest invId p
XInfo p -> profileContactRequest invId p
XContact p xContactId_ -> profileContactRequest invId p xContactId_
XInfo p -> profileContactRequest invId p Nothing
-- TODO show/log error, other events in contact request
_ -> pure ()
-- TODO print errors
@@ -822,11 +828,13 @@ processAgentMessage (Just user@User {userId, profile}) agentConnId agentMessage
-- TODO add debugging output
_ -> pure ()
where
profileContactRequest :: InvitationId -> Profile -> m ()
profileContactRequest invId p = do
cReq@UserContactRequest {localDisplayName} <- withStore $ \st -> createContactRequest st userId userContactLinkId invId p
toView $ CRReceivedContactRequest cReq
showToast (localDisplayName <> "> ") "wants to connect to you"
profileContactRequest :: InvitationId -> Profile -> Maybe XContactId -> m ()
profileContactRequest invId p xContactId_ = do
withStore (\st -> createOrUpdateContactRequest st userId userContactLinkId invId p xContactId_) >>= \case
Left contact -> toView $ CRContactRequestAlreadyAccepted contact
Right cReq@UserContactRequest {localDisplayName} -> do
toView $ CRReceivedContactRequest cReq
showToast (localDisplayName <> "> ") "wants to connect to you"
withAckMessage :: ConnId -> MsgMeta -> m () -> m ()
withAckMessage cId MsgMeta {recipient = (msgId, _)} action =
+2
View File
@@ -162,6 +162,8 @@ data ChatResponse
| CRUserContactLinkDeleted
| CRReceivedContactRequest {contactRequest :: UserContactRequest}
| CRAcceptingContactRequest {contact :: Contact}
| CRContactAlreadyExists {contact :: Contact}
| CRContactRequestAlreadyAccepted {contact :: Contact}
| CRLeftMemberUser {groupInfo :: GroupInfo}
| CRGroupDeletedUser {groupInfo :: GroupInfo}
| CRRcvFileAccepted {fileTransfer :: RcvFileTransfer, filePath :: FilePath}
@@ -0,0 +1,23 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20220210_deduplicate_contact_requests where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
m20220210_deduplicate_contact_requests :: Query
m20220210_deduplicate_contact_requests =
[sql|
-- hash of contact address uri used by contact request sender to connect,
-- null for contact request recipient and for both parties when using one-off invitation
ALTER TABLE connections ADD COLUMN via_contact_uri_hash BLOB;
CREATE INDEX idx_connections_via_contact_uri_hash ON connections (via_contact_uri_hash);
ALTER TABLE connections ADD COLUMN xcontact_id BLOB;
ALTER TABLE contact_requests ADD COLUMN xcontact_id BLOB;
CREATE INDEX idx_contact_requests_xcontact_id ON contact_requests (xcontact_id);
ALTER TABLE contacts ADD COLUMN xcontact_id BLOB;
CREATE INDEX idx_contacts_xcontact_id ON contacts (xcontact_id);
|]
+4 -4
View File
@@ -70,7 +70,7 @@ data ChatMsgEvent
| XFile FileInvitation
| XFileAcpt String
| XInfo Profile
| XContact Profile (Maybe MsgContent)
| XContact Profile (Maybe XContactId)
| XGrpInv GroupInvitation
| XGrpAcpt MemberId
| XGrpMemNew MemberInfo
@@ -264,7 +264,7 @@ appToChatMessage AppMessage {event, params} = do
XFile_ -> XFile <$> p "file"
XFileAcpt_ -> XFileAcpt <$> p "fileName"
XInfo_ -> XInfo <$> p "profile"
XContact_ -> XContact <$> p "profile" <*> JT.parseEither (.:? "content") params
XContact_ -> XContact <$> p "profile" <*> JT.parseEither (.:? "contactReqId") params
XGrpInv_ -> XGrpInv <$> p "groupInvitation"
XGrpAcpt_ -> XGrpAcpt <$> p "memberId"
XGrpMemNew_ -> XGrpMemNew <$> p "memberInfo"
@@ -292,8 +292,8 @@ chatToAppMessage ChatMessage {chatMsgEvent} = AppMessage {event, params}
XMsgNew content -> o ["content" .= content]
XFile fileInv -> o ["file" .= fileInv]
XFileAcpt fileName -> o ["fileName" .= fileName]
XInfo profile -> o ["profile" .= profile]
XContact profile content -> o $ maybe id ((:) . ("content" .=)) content ["profile" .= profile]
XInfo profile -> o $ ["profile" .= profile]
XContact profile xContactId -> o $ maybe id ((:) . ("contactReqId" .=)) xContactId ["profile" .= profile]
XGrpInv groupInv -> o ["groupInvitation" .= groupInv]
XGrpAcpt memId -> o ["memberId" .= memId]
XGrpMemNew memInfo -> o ["memberInfo" .= memInfo]
+173 -31
View File
@@ -25,6 +25,8 @@ module Simplex.Chat.Store
getUsers,
setActiveUser,
createDirectConnection,
createConnReqConnection,
getConnReqContactXContactId,
createDirectContact,
getContactGroupNames,
deleteContact,
@@ -38,7 +40,7 @@ module Simplex.Chat.Store
getUserContactLinkConnections,
deleteUserContactLink,
getUserContactLink,
createContactRequest,
createOrUpdateContactRequest,
getContactRequest,
getContactRequestIdByName,
deleteContactRequest,
@@ -153,6 +155,7 @@ import Simplex.Chat.Messages
import Simplex.Chat.Migrations.M20220101_initial
import Simplex.Chat.Migrations.M20220122_v1_1
import Simplex.Chat.Migrations.M20220205_chat_item_status
import Simplex.Chat.Migrations.M20220210_deduplicate_contact_requests
import Simplex.Chat.Protocol
import Simplex.Chat.Types
import Simplex.Chat.Util (eitherToMaybe)
@@ -169,7 +172,8 @@ schemaMigrations :: [(String, Query)]
schemaMigrations =
[ ("20220101_initial", m20220101_initial),
("20220122_v1_1", m20220122_v1_1),
("20220205_chat_item_status", m20220205_chat_item_status)
("20220205_chat_item_status", m20220205_chat_item_status),
("20220210_deduplicate_contact_requests", m20220210_deduplicate_contact_requests)
]
-- | The list of migrations in ascending order by date
@@ -247,6 +251,55 @@ setActiveUser st userId = do
DB.execute_ db "UPDATE users SET active_user = 0"
DB.execute db "UPDATE users SET active_user = 1 WHERE user_id = ?" (Only userId)
createConnReqConnection :: MonadUnliftIO m => SQLiteStore -> UserId -> ConnId -> ConnReqUriHash -> XContactId -> m ()
createConnReqConnection st userId acId cReqHash xContactId = do
liftIO . withTransaction st $ \db -> do
currentTs <- getCurrentTime
DB.execute
db
[sql|
INSERT INTO connections (
user_id, agent_conn_id, conn_status, conn_type,
created_at, updated_at, via_contact_uri_hash, xcontact_id
) VALUES (?,?,?,?,?,?,?,?)
|]
(userId, acId, ConnNew, ConnContact, currentTs, currentTs, cReqHash, xContactId)
getConnReqContactXContactId :: MonadUnliftIO m => SQLiteStore -> UserId -> ConnReqUriHash -> m (Maybe Contact, Maybe XContactId)
getConnReqContactXContactId st userId cReqHash = do
liftIO . withTransaction st $ \db ->
getContact' db >>= \case
c@(Just _) -> pure (c, Nothing)
Nothing -> (Nothing,) <$> getXContactId db
where
getContact' :: DB.Connection -> IO (Maybe Contact)
getContact' db =
fmap toContact . listToMaybe
<$> DB.query
db
[sql|
SELECT
-- Contact
ct.contact_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, ct.created_at,
-- Connection
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.conn_status, c.conn_type,
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at
FROM contacts ct
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
JOIN connections c ON c.contact_id = ct.contact_id
WHERE ct.user_id = ? AND c.via_contact_uri_hash = ?
ORDER BY c.connection_id DESC
LIMIT 1
|]
(userId, cReqHash)
getXContactId :: DB.Connection -> IO (Maybe XContactId)
getXContactId db =
fmap fromOnly . listToMaybe
<$> DB.query
db
"SELECT xcontact_id FROM connections WHERE user_id = ? AND via_contact_uri_hash = ? LIMIT 1"
(userId, cReqHash)
createDirectConnection :: MonadUnliftIO m => SQLiteStore -> UserId -> ConnId -> m ()
createDirectConnection st userId agentConnId =
liftIO . withTransaction st $ \db -> do
@@ -254,7 +307,7 @@ createDirectConnection st userId agentConnId =
void $ createContactConnection_ db userId agentConnId Nothing 0 currentTs
createContactConnection_ :: DB.Connection -> UserId -> ConnId -> Maybe Int64 -> Int -> UTCTime -> IO Connection
createContactConnection_ db userId = do createConnection_ db userId ConnContact Nothing
createContactConnection_ db userId = createConnection_ db userId ConnContact Nothing
createConnection_ :: DB.Connection -> UserId -> ConnType -> Maybe Int64 -> ConnId -> Maybe Int64 -> Int -> UTCTime -> IO Connection
createConnection_ db userId connType entityId acId viaContact connLevel currentTs = do
@@ -519,28 +572,117 @@ getUserContactLink st userId =
connReq [Only cReq] = Right cReq
connReq _ = Left SEUserContactLinkNotFound
createContactRequest :: StoreMonad m => SQLiteStore -> UserId -> Int64 -> InvitationId -> Profile -> m UserContactRequest
createContactRequest st userId userContactId invId Profile {displayName, fullName} =
createOrUpdateContactRequest :: StoreMonad m => SQLiteStore -> UserId -> Int64 -> InvitationId -> Profile -> Maybe XContactId -> m (Either Contact UserContactRequest)
createOrUpdateContactRequest st userId userContactLinkId invId profile xContactId_ =
liftIOEither . withTransaction st $ \db ->
join <$> withLocalDisplayName db userId displayName (createContactRequest' db)
createOrUpdateContactRequest_ db userId userContactLinkId invId profile xContactId_
createOrUpdateContactRequest_ :: DB.Connection -> UserId -> Int64 -> InvitationId -> Profile -> Maybe XContactId -> IO (Either StoreError (Either Contact UserContactRequest))
createOrUpdateContactRequest_ db userId userContactLinkId invId Profile {displayName, fullName} xContactId_ =
maybeM getContact' xContactId_ >>= \case
Just contact -> pure . Right $ Left contact
Nothing -> Right <$$> createOrUpdate_
where
createContactRequest' db ldn = do
maybeM = maybe (pure Nothing)
createOrUpdate_ :: IO (Either StoreError UserContactRequest)
createOrUpdate_ =
maybeM getContactRequest' xContactId_ >>= \case
Nothing -> createContactRequest
Just UserContactRequest {contactRequestId, profile = oldProfile} ->
updateContactRequest contactRequestId oldProfile
createContactRequest :: IO (Either StoreError UserContactRequest)
createContactRequest = do
currentTs <- getCurrentTime
DB.execute
db
"INSERT INTO contact_profiles (display_name, full_name, created_at, updated_at) VALUES (?,?,?,?)"
(displayName, fullName, currentTs, currentTs)
profileId <- insertedRowId db
DB.execute
db
[sql|
INSERT INTO contact_requests
(user_contact_link_id, agent_invitation_id, contact_profile_id, local_display_name, user_id, created_at, updated_at)
VALUES (?,?,?,?,?,?,?)
|]
(userContactId, invId, profileId, ldn, userId, currentTs, currentTs)
contactRequestId <- insertedRowId db
getContactRequest_ db userId contactRequestId
join <$> withLocalDisplayName db userId displayName (createContactRequest_ currentTs)
where
createContactRequest_ currentTs ldn = do
DB.execute
db
"INSERT INTO contact_profiles (display_name, full_name, created_at, updated_at) VALUES (?,?,?,?)"
(displayName, fullName, currentTs, currentTs)
profileId <- insertedRowId db
DB.execute
db
[sql|
INSERT INTO contact_requests
(user_contact_link_id, agent_invitation_id, contact_profile_id, local_display_name, user_id, created_at, updated_at, xcontact_id)
VALUES (?,?,?,?,?,?,?,?)
|]
(userContactLinkId, invId, profileId, ldn, userId, currentTs, currentTs, xContactId_)
contactRequestId <- insertedRowId db
getContactRequest_ db userId contactRequestId
getContact' :: XContactId -> IO (Maybe Contact)
getContact' xContactId =
fmap toContact . listToMaybe
<$> DB.query
db
[sql|
SELECT
-- Contact
ct.contact_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, ct.created_at,
-- Connection
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.conn_status, c.conn_type,
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at
FROM contacts ct
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
LEFT JOIN connections c ON c.contact_id = ct.contact_id
WHERE ct.user_id = ? AND ct.xcontact_id = ?
ORDER BY c.connection_id DESC
LIMIT 1
|]
(userId, xContactId)
getContactRequest' :: XContactId -> IO (Maybe UserContactRequest)
getContactRequest' xContactId =
fmap toContactRequest . listToMaybe
<$> DB.query
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, cr.created_at, cr.xcontact_id
FROM contact_requests cr
JOIN connections c USING (user_contact_link_id)
JOIN contact_profiles p USING (contact_profile_id)
WHERE cr.user_id = ?
AND cr.xcontact_id = ?
LIMIT 1
|]
(userId, xContactId)
updateContactRequest :: Int64 -> Profile -> IO (Either StoreError UserContactRequest)
updateContactRequest cReqId Profile {displayName = oldDisplayName} = do
currentTs <- liftIO getCurrentTime
if displayName == oldDisplayName
then updateContactRequest_ currentTs displayName
else join <$> withLocalDisplayName db userId displayName (updateContactRequest_ currentTs)
where
updateContactRequest_ updatedAt ldn = do
DB.execute
db
[sql|
UPDATE contact_profiles
SET display_name = ?,
full_name = ?,
updated_at = ?
WHERE contact_profile_id IN (
SELECT contact_profile_id
FROM contact_requests
WHERE user_id = ?
AND contact_request_id = ?
)
|]
(ldn, fullName, updatedAt, userId, cReqId)
DB.execute
db
[sql|
UPDATE contact_requests
SET agent_invitation_id = ?,
local_display_name = ?,
updated_at = ?
WHERE user_id = ?
AND contact_request_id = ?
|]
(invId, ldn, updatedAt, userId, cReqId)
getContactRequest_ db userId cReqId
getContactRequest :: StoreMonad m => SQLiteStore -> UserId -> Int64 -> m UserContactRequest
getContactRequest st userId contactRequestId =
@@ -555,7 +697,7 @@ getContactRequest_ db userId contactRequestId =
[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, cr.created_at
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, cr.created_at, cr.xcontact_id
FROM contact_requests cr
JOIN connections c USING (user_contact_link_id)
JOIN contact_profiles p USING (contact_profile_id)
@@ -564,12 +706,12 @@ getContactRequest_ db userId contactRequestId =
|]
(userId, contactRequestId)
type ContactRequestRow = (Int64, ContactName, AgentInvId, Int64, AgentConnId, Int64, ContactName, Text, UTCTime)
type ContactRequestRow = (Int64, ContactName, AgentInvId, Int64, AgentConnId, Int64, ContactName, Text, UTCTime, Maybe XContactId)
toContactRequest :: ContactRequestRow -> UserContactRequest
toContactRequest (contactRequestId, localDisplayName, agentInvitationId, userContactLinkId, agentContactConnId, profileId, displayName, fullName, createdAt) = do
toContactRequest (contactRequestId, localDisplayName, agentInvitationId, userContactLinkId, agentContactConnId, profileId, displayName, fullName, createdAt, xContactId) = do
let profile = Profile {displayName, fullName}
in UserContactRequest {contactRequestId, agentInvitationId, userContactLinkId, agentContactConnId, localDisplayName, profileId, profile, createdAt}
in UserContactRequest {contactRequestId, agentInvitationId, userContactLinkId, agentContactConnId, localDisplayName, profileId, profile, createdAt, xContactId}
getContactRequestIdByName :: StoreMonad m => SQLiteStore -> UserId -> ContactName -> m Int64
getContactRequestIdByName st userId cName =
@@ -592,15 +734,15 @@ deleteContactRequest st userId contactRequestId =
(userId, userId, contactRequestId)
DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND contact_request_id = ?" (userId, contactRequestId)
createAcceptedContact :: MonadUnliftIO m => SQLiteStore -> UserId -> ConnId -> ContactName -> Int64 -> Profile -> m Contact
createAcceptedContact st userId agentConnId localDisplayName profileId profile =
createAcceptedContact :: MonadUnliftIO m => SQLiteStore -> UserId -> ConnId -> ContactName -> Int64 -> Profile -> Maybe XContactId -> m Contact
createAcceptedContact st userId agentConnId localDisplayName profileId profile xContactId =
liftIO . withTransaction st $ \db -> do
DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND local_display_name = ?" (userId, localDisplayName)
currentTs <- getCurrentTime
DB.execute
db
"INSERT INTO contacts (user_id, local_display_name, contact_profile_id, created_at, updated_at) VALUES (?,?,?,?,?)"
(userId, localDisplayName, profileId, currentTs, currentTs)
"INSERT INTO contacts (user_id, local_display_name, contact_profile_id, created_at, updated_at, xcontact_id) VALUES (?,?,?,?,?,?)"
(userId, localDisplayName, profileId, currentTs, currentTs, xContactId)
contactId <- insertedRowId db
activeConn <- createConnection_ db userId ConnContact (Just contactId) agentConnId Nothing 0 currentTs
pure $ Contact {contactId, localDisplayName, profile, activeConn, viaGroup = Nothing, createdAt = currentTs}
@@ -2148,7 +2290,7 @@ getContactRequestChatPreviews_ db User {userId} =
[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, cr.created_at
c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, cr.created_at, cr.xcontact_id
FROM contact_requests cr
JOIN connections c USING (user_contact_link_id)
JOIN contact_profiles p USING (contact_profile_id)
+40 -2
View File
@@ -10,7 +10,6 @@
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE UndecidableInstances #-}
module Simplex.Chat.Types where
@@ -100,13 +99,52 @@ data UserContactRequest = UserContactRequest
localDisplayName :: ContactName,
profileId :: Int64,
profile :: Profile,
createdAt :: UTCTime
createdAt :: UTCTime,
xContactId :: Maybe XContactId
}
deriving (Eq, Show, Generic, FromJSON)
instance ToJSON UserContactRequest where
toEncoding = J.genericToEncoding J.defaultOptions
newtype XContactId = XContactId ByteString
deriving (Eq, Show)
instance FromField XContactId where fromField f = XContactId <$> fromField f
instance ToField XContactId where toField (XContactId m) = toField m
instance StrEncoding XContactId where
strEncode (XContactId m) = strEncode m
strDecode s = XContactId <$> strDecode s
strP = XContactId <$> strP
instance FromJSON XContactId where
parseJSON = strParseJSON "XContactId"
instance ToJSON XContactId where
toJSON = strToJSON
toEncoding = strToJEncoding
newtype ConnReqUriHash = ConnReqUriHash {unConnReqUriHash :: ByteString}
deriving (Eq, Show)
instance FromField ConnReqUriHash where fromField f = ConnReqUriHash <$> fromField f
instance ToField ConnReqUriHash where toField (ConnReqUriHash m) = toField m
instance StrEncoding ConnReqUriHash where
strEncode (ConnReqUriHash m) = strEncode m
strDecode s = ConnReqUriHash <$> strDecode s
strP = ConnReqUriHash <$> strP
instance FromJSON ConnReqUriHash where
parseJSON = strParseJSON "ConnReqUriHash"
instance ToJSON ConnReqUriHash where
toJSON = strToJSON
toEncoding = strToJEncoding
type ContactName = Text
type GroupName = Text
+4 -2
View File
@@ -65,8 +65,10 @@ responseToView cmd testView = \case
CRInvitation cReq -> r' $ viewConnReqInvitation cReq
CRSentConfirmation -> r' ["confirmation sent!"]
CRSentInvitation -> r' ["connection request sent!"]
CRContactDeleted Contact {localDisplayName = c} -> r' [ttyContact c <> ": contact is deleted"]
CRAcceptingContactRequest Contact {localDisplayName = c} -> r' [ttyContact c <> ": accepting contact request..."]
CRContactDeleted c -> r' [ttyContact' c <> ": contact is deleted"]
CRAcceptingContactRequest c -> r' [ttyFullContact c <> ": accepting contact request..."]
CRContactAlreadyExists c -> r [ttyFullContact c <> ": contact already exists"]
CRContactRequestAlreadyAccepted c -> r' [ttyFullContact c <> ": sent you a duplicate contact request, but you are already connected, no action needed"]
CRUserContactLinkCreated cReq -> r' $ connReqContact_ "Your new chat address is created!" cReq
CRUserContactLinkDeleted -> r' viewUserContactLinkDeleted
CRUserAcceptedGroupSent _g -> r' [] -- [ttyGroup' g <> ": joining the group..."]