core, mobile: add group feature to allow direct messages (#1465)

* core, mobile: split group features to a separate type (to add directAllowed later)

* add directMessages group feature, update tests
This commit is contained in:
Evgeny Poberezkin
2022-11-29 15:19:20 +00:00
committed by GitHub
parent 303aeaaba5
commit 1872744543
25 changed files with 334 additions and 164 deletions
+10 -9
View File
@@ -340,7 +340,7 @@ processChatCommand = \case
Group gInfo@GroupInfo {membership, localDisplayName = gName} ms <- withStore $ \db -> getGroup db user chatId
unless (memberActive membership) $ throwChatError CEGroupMemberUserRemoved
case groupFeatureProhibited gInfo mc of
Just f -> pure $ chatCmdError $ "feature not allowed " <> T.unpack (chatFeatureToText f)
Just f -> pure $ chatCmdError $ "feature not allowed " <> T.unpack (groupFeatureToText f)
_ -> do
(fileInvitation_, ciFile_, ft_) <- unzipMaybe3 <$> setupSndFileTransfer gInfo (length ms)
(msgContainer, quotedItem_) <- prepareMsg fileInvitation_ membership
@@ -2278,7 +2278,7 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage =
newGroupContentMessage gInfo@GroupInfo {chatSettings} m@GroupMember {localDisplayName = c} mc msg msgMeta = do
let (ExtMsgContent content fInv_) = mcExtMsgContent mc
case groupFeatureProhibited gInfo content of
Just f -> void $ newChatItem (CIRcvChatFeatureRejected f) Nothing
Just f -> void $ newChatItem (CIRcvGroupFeatureRejected f) Nothing
_ -> do
ciFile_ <- processFileInvitation fInv_ content $ \db -> createRcvGroupFileTransfer db userId m
ChatItem {formattedText} <- newChatItem (CIRcvMsgContent content) ciFile_
@@ -2531,7 +2531,7 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage =
createGroupFeatureItems :: GroupInfo -> GroupMember -> m ()
createGroupFeatureItems g@GroupInfo {groupProfile} m = do
let prefs = mergeGroupPreferences $ groupPreferences groupProfile
forM_ allChatFeatures $ \f -> do
forM_ allGroupFeatures $ \f -> do
let p = getGroupPreference f prefs
createInternalChatItem user (CDGroupRcv g m) (CIRcvGroupFeature f p) Nothing
@@ -3113,9 +3113,9 @@ createFeatureChangedItems user Contact {mergedPreferences = cups} ct'@Contact {m
unless (enabled == enabled') $
createInternalChatItem user (chatDir ct') (ciContent f enabled') Nothing
createGroupFeatureChangedItems :: (MsgDirectionI d, ChatMonad m) => User -> ChatDirection 'CTGroup d -> (ChatFeature -> GroupPreference -> CIContent d) -> GroupProfile -> GroupProfile -> m ()
createGroupFeatureChangedItems :: (MsgDirectionI d, ChatMonad m) => User -> ChatDirection 'CTGroup d -> (GroupFeature -> GroupPreference -> CIContent d) -> GroupProfile -> GroupProfile -> m ()
createGroupFeatureChangedItems user cd ciContent p p' =
forM_ allChatFeatures $ \f -> do
forM_ allGroupFeatures $ \f -> do
let pref = getGroupPreference f $ groupPreferences p
pref' = getGroupPreference f $ groupPreferences p'
unless (pref == pref') $
@@ -3132,11 +3132,11 @@ featureProhibited forWhom Contact {mergedPreferences} = \case
in if forWhom enabled then Nothing else Just CFVoice
_ -> Nothing
groupFeatureProhibited :: GroupInfo -> MsgContent -> Maybe ChatFeature
groupFeatureProhibited :: GroupInfo -> MsgContent -> Maybe GroupFeature
groupFeatureProhibited GroupInfo {fullGroupPreferences} = \case
MCVoice {} ->
let GroupPreference {enable} = getGroupPreference CFVoice fullGroupPreferences
in case enable of FEOn -> Nothing; FEOff -> Just CFVoice
let GroupPreference {enable} = getGroupPreference GFVoice fullGroupPreferences
in case enable of FEOn -> Nothing; FEOff -> Just GFVoice
_ -> Nothing
createInternalChatItem :: forall c d m. (ChatTypeI c, MsgDirectionI d, ChatMonad m) => User -> ChatDirection c d -> CIContent d -> Maybe UTCTime -> m ()
@@ -3399,9 +3399,10 @@ chatCommandP =
"/profile_image" $> UpdateProfileImage Nothing,
("/profile " <|> "/p ") *> (uncurry UpdateProfile <$> userNames),
("/profile" <|> "/p") $> ShowProfile,
"/voice #" *> (SetGroupFeature CFVoice <$> displayName <*> (A.space *> strP)),
"/voice #" *> (SetGroupFeature GFVoice <$> displayName <*> (A.space *> strP)),
"/voice @" *> (SetContactFeature CFVoice <$> displayName <*> optional (A.space *> strP)),
"/voice " *> (SetUserFeature CFVoice <$> strP),
"/dms #" *> (SetGroupFeature GFDirectMessages <$> displayName <*> (A.space *> strP)),
"/incognito " *> (SetIncognito <$> onOffP),
("/quit" <|> "/q" <|> "/exit") $> QuitChat,
("/version" <|> "/v") $> ShowVersion,
+1 -1
View File
@@ -252,7 +252,7 @@ data ChatCommand
| UpdateProfileImage (Maybe ImageData)
| SetUserFeature ChatFeature FeatureAllowed
| SetContactFeature ChatFeature ContactName (Maybe FeatureAllowed)
| SetGroupFeature ChatFeature GroupName GroupFeatureEnabled
| SetGroupFeature GroupFeature GroupName GroupFeatureEnabled
| QuitChat
| ShowVersion
| DebugLocks
+25 -16
View File
@@ -560,9 +560,10 @@ data CIContent (d :: MsgDirection) where
CISndConnEvent :: SndConnEvent -> CIContent 'MDSnd
CIRcvChatFeature :: ChatFeature -> PrefEnabled -> CIContent 'MDRcv
CISndChatFeature :: ChatFeature -> PrefEnabled -> CIContent 'MDSnd
CIRcvGroupFeature :: ChatFeature -> GroupPreference -> CIContent 'MDRcv
CISndGroupFeature :: ChatFeature -> GroupPreference -> CIContent 'MDSnd
CIRcvGroupFeature :: GroupFeature -> GroupPreference -> CIContent 'MDRcv
CISndGroupFeature :: GroupFeature -> GroupPreference -> CIContent 'MDSnd
CIRcvChatFeatureRejected :: ChatFeature -> CIContent 'MDRcv
CIRcvGroupFeatureRejected :: GroupFeature -> CIContent 'MDRcv
-- ^ This type is used both in API and in DB, so we use different JSON encodings for the database and for the API
-- ! ^ Nested sum types also have to use different encodings for database and API
-- ! ^ to avoid breaking cross-platform compatibility, see RcvGroupEvent and SndGroupEvent
@@ -589,6 +590,7 @@ ciCreateStatus = \case
CIRcvGroupFeature {} -> CISRcvRead
CISndGroupFeature {} -> ciStatusNew
CIRcvChatFeatureRejected _ -> ciStatusNew
CIRcvGroupFeatureRejected _ -> ciStatusNew
data RcvGroupEvent
= RGEMemberAdded {groupMemberId :: GroupMemberId, profile :: Profile} -- CRJoinedGroupMemberConnecting
@@ -750,9 +752,10 @@ ciContentToText = \case
CISndConnEvent event -> sndConnEventToText event
CIRcvChatFeature feature enabled -> chatFeatureToText feature <> ": " <> prefEnabledToText enabled
CISndChatFeature feature enabled -> chatFeatureToText feature <> ": " <> prefEnabledToText enabled
CIRcvGroupFeature feature pref -> chatFeatureToText feature <> ": " <> groupPrefToText pref
CISndGroupFeature feature pref -> chatFeatureToText feature <> ": " <> groupPrefToText pref
CIRcvGroupFeature feature pref -> groupFeatureToText feature <> ": " <> groupPrefToText pref
CISndGroupFeature feature pref -> groupFeatureToText feature <> ": " <> groupPrefToText pref
CIRcvChatFeatureRejected feature -> chatFeatureToText feature <> ": received, prohibited"
CIRcvGroupFeatureRejected feature -> groupFeatureToText feature <> ": received, prohibited"
msgIntegrityError :: MsgErrorType -> Text
msgIntegrityError = \case
@@ -805,9 +808,10 @@ data JSONCIContent
| JCISndConnEvent {sndConnEvent :: SndConnEvent}
| JCIRcvChatFeature {feature :: ChatFeature, enabled :: PrefEnabled}
| JCISndChatFeature {feature :: ChatFeature, enabled :: PrefEnabled}
| JCIRcvGroupFeature {feature :: ChatFeature, preference :: GroupPreference}
| JCISndGroupFeature {feature :: ChatFeature, preference :: GroupPreference}
| JCIRcvGroupFeature {groupFeature :: GroupFeature, preference :: GroupPreference}
| JCISndGroupFeature {groupFeature :: GroupFeature, preference :: GroupPreference}
| JCIRcvChatFeatureRejected {feature :: ChatFeature}
| JCIRcvGroupFeatureRejected {groupFeature :: GroupFeature}
deriving (Generic)
instance FromJSON JSONCIContent where
@@ -834,9 +838,10 @@ jsonCIContent = \case
CISndConnEvent sndConnEvent -> JCISndConnEvent {sndConnEvent}
CIRcvChatFeature feature enabled -> JCIRcvChatFeature {feature, enabled}
CISndChatFeature feature enabled -> JCISndChatFeature {feature, enabled}
CIRcvGroupFeature feature preference -> JCIRcvGroupFeature {feature, preference}
CISndGroupFeature feature preference -> JCISndGroupFeature {feature, preference}
CIRcvGroupFeature groupFeature preference -> JCIRcvGroupFeature {groupFeature, preference}
CISndGroupFeature groupFeature preference -> JCISndGroupFeature {groupFeature, preference}
CIRcvChatFeatureRejected feature -> JCIRcvChatFeatureRejected {feature}
CIRcvGroupFeatureRejected groupFeature -> JCIRcvGroupFeatureRejected {groupFeature}
aciContentJSON :: JSONCIContent -> ACIContent
aciContentJSON = \case
@@ -855,9 +860,10 @@ aciContentJSON = \case
JCISndConnEvent {sndConnEvent} -> ACIContent SMDSnd $ CISndConnEvent sndConnEvent
JCIRcvChatFeature {feature, enabled} -> ACIContent SMDRcv $ CIRcvChatFeature feature enabled
JCISndChatFeature {feature, enabled} -> ACIContent SMDSnd $ CISndChatFeature feature enabled
JCIRcvGroupFeature {feature, preference} -> ACIContent SMDRcv $ CIRcvGroupFeature feature preference
JCISndGroupFeature {feature, preference} -> ACIContent SMDSnd $ CISndGroupFeature feature preference
JCIRcvGroupFeature {groupFeature, preference} -> ACIContent SMDRcv $ CIRcvGroupFeature groupFeature preference
JCISndGroupFeature {groupFeature, preference} -> ACIContent SMDSnd $ CISndGroupFeature groupFeature preference
JCIRcvChatFeatureRejected {feature} -> ACIContent SMDRcv $ CIRcvChatFeatureRejected feature
JCIRcvGroupFeatureRejected {groupFeature} -> ACIContent SMDRcv $ CIRcvGroupFeatureRejected groupFeature
-- platform independent
data DBJSONCIContent
@@ -876,9 +882,10 @@ data DBJSONCIContent
| DBJCISndConnEvent {sndConnEvent :: DBSndConnEvent}
| DBJCIRcvChatFeature {feature :: ChatFeature, enabled :: PrefEnabled}
| DBJCISndChatFeature {feature :: ChatFeature, enabled :: PrefEnabled}
| DBJCIRcvGroupFeature {feature :: ChatFeature, preference :: GroupPreference}
| DBJCISndGroupFeature {feature :: ChatFeature, preference :: GroupPreference}
| DBJCIRcvGroupFeature {groupFeature :: GroupFeature, preference :: GroupPreference}
| DBJCISndGroupFeature {groupFeature :: GroupFeature, preference :: GroupPreference}
| DBJCIRcvChatFeatureRejected {feature :: ChatFeature}
| DBJCIRcvGroupFeatureRejected {groupFeature :: GroupFeature}
deriving (Generic)
instance FromJSON DBJSONCIContent where
@@ -905,9 +912,10 @@ dbJsonCIContent = \case
CISndConnEvent sce -> DBJCISndConnEvent $ SCE sce
CIRcvChatFeature feature enabled -> DBJCIRcvChatFeature {feature, enabled}
CISndChatFeature feature enabled -> DBJCISndChatFeature {feature, enabled}
CIRcvGroupFeature feature preference -> DBJCIRcvGroupFeature {feature, preference}
CISndGroupFeature feature preference -> DBJCISndGroupFeature {feature, preference}
CIRcvGroupFeature groupFeature preference -> DBJCIRcvGroupFeature {groupFeature, preference}
CISndGroupFeature groupFeature preference -> DBJCISndGroupFeature {groupFeature, preference}
CIRcvChatFeatureRejected feature -> DBJCIRcvChatFeatureRejected {feature}
CIRcvGroupFeatureRejected groupFeature -> DBJCIRcvGroupFeatureRejected {groupFeature}
aciContentDBJSON :: DBJSONCIContent -> ACIContent
aciContentDBJSON = \case
@@ -926,9 +934,10 @@ aciContentDBJSON = \case
DBJCISndConnEvent (SCE sce) -> ACIContent SMDSnd $ CISndConnEvent sce
DBJCIRcvChatFeature {feature, enabled} -> ACIContent SMDRcv $ CIRcvChatFeature feature enabled
DBJCISndChatFeature {feature, enabled} -> ACIContent SMDSnd $ CISndChatFeature feature enabled
DBJCIRcvGroupFeature {feature, preference} -> ACIContent SMDRcv $ CIRcvGroupFeature feature preference
DBJCISndGroupFeature {feature, preference} -> ACIContent SMDSnd $ CISndGroupFeature feature preference
DBJCIRcvGroupFeature {groupFeature, preference} -> ACIContent SMDRcv $ CIRcvGroupFeature groupFeature preference
DBJCISndGroupFeature {groupFeature, preference} -> ACIContent SMDSnd $ CISndGroupFeature groupFeature preference
DBJCIRcvChatFeatureRejected {feature} -> ACIContent SMDRcv $ CIRcvChatFeatureRejected feature
DBJCIRcvGroupFeatureRejected {groupFeature} -> ACIContent SMDRcv $ CIRcvGroupFeatureRejected groupFeature
data CICallStatus
= CISCallPending
@@ -0,0 +1,13 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221129_delete_group_feature_items where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
m20221129_delete_group_feature_items :: Query
m20221129_delete_group_feature_items =
[sql|
DELETE FROM chat_items WHERE item_content LIKE '%{"rcvGroupFeature":{%';
DELETE FROM chat_items WHERE item_content LIKE '%{"sndGroupFeature":{%';
|]
+3 -1
View File
@@ -298,6 +298,7 @@ import Simplex.Chat.Migrations.M20221025_chat_settings
import Simplex.Chat.Migrations.M20221029_group_link_id
import Simplex.Chat.Migrations.M20221112_server_password
import Simplex.Chat.Migrations.M20221115_server_cfg
import Simplex.Chat.Migrations.M20221129_delete_group_feature_items
import Simplex.Chat.Protocol
import Simplex.Chat.Types
import Simplex.Messaging.Agent.Protocol (ACorrId, AgentMsgId, ConnId, InvitationId, MsgMeta (..))
@@ -346,7 +347,8 @@ schemaMigrations =
("20221025_chat_settings", m20221025_chat_settings),
("20221029_group_link_id", m20221029_group_link_id),
("20221112_server_password", m20221112_server_password),
("20221115_server_cfg", m20221115_server_cfg)
("20221115_server_cfg", m20221115_server_cfg),
("20221129_delete_group_feature_items", m20221129_delete_group_feature_items)
]
-- | The list of migrations in ascending order by date
+56 -26
View File
@@ -281,12 +281,6 @@ chatPrefSel = \case
-- CFReceipts -> receipts
CFVoice -> voice
chatPrefName :: ChatFeature -> Text
chatPrefName = \case
CFFullDelete -> "full message deletion"
-- CFReceipts -> "delivery receipts"
CFVoice -> "voice messages"
class PreferenceI p where
getPreference :: ChatFeature -> p -> Preference
@@ -329,14 +323,43 @@ instance ToField Preferences where
instance FromField Preferences where
fromField = fromTextField_ decodeJSON
groupPrefSel :: ChatFeature -> GroupPreferences -> Maybe GroupPreference
data GroupFeature
= GFDirectMessages
| GFFullDelete
| -- | GFReceipts
GFVoice
deriving (Show, Generic)
groupFeatureToText :: GroupFeature -> Text
groupFeatureToText = \case
GFDirectMessages -> "Direct messages"
GFFullDelete -> "Full deletion"
GFVoice -> "Voice messages"
instance ToJSON GroupFeature where
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "GF"
toJSON = J.genericToJSON . enumJSON $ dropPrefix "GF"
instance FromJSON GroupFeature where
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "GF"
allGroupFeatures :: [GroupFeature]
allGroupFeatures =
[ GFDirectMessages,
GFFullDelete,
-- GFReceipts,
GFVoice
]
groupPrefSel :: GroupFeature -> GroupPreferences -> Maybe GroupPreference
groupPrefSel = \case
CFFullDelete -> fullDelete
-- CFReceipts -> receipts
CFVoice -> voice
GFDirectMessages -> directMessages
GFFullDelete -> fullDelete
-- GFReceipts -> receipts
GFVoice -> voice
class GroupPreferenceI p where
getGroupPreference :: ChatFeature -> p -> GroupPreference
getGroupPreference :: GroupFeature -> p -> GroupPreference
instance GroupPreferenceI GroupPreferences where
getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt prefs)
@@ -346,14 +369,16 @@ instance GroupPreferenceI (Maybe GroupPreferences) where
instance GroupPreferenceI FullGroupPreferences where
getGroupPreference = \case
CFFullDelete -> fullDelete
-- CFReceipts -> receipts
CFVoice -> voice
GFDirectMessages -> directMessages
GFFullDelete -> fullDelete
-- GFReceipts -> receipts
GFVoice -> voice
{-# INLINE getGroupPreference #-}
-- collection of optional group preferences
data GroupPreferences = GroupPreferences
{ fullDelete :: Maybe GroupPreference,
{ directMessages :: Maybe GroupPreference,
fullDelete :: Maybe GroupPreference,
-- receipts :: Maybe GroupPreference,
voice :: Maybe GroupPreference
}
@@ -369,13 +394,14 @@ instance ToField GroupPreferences where
instance FromField GroupPreferences where
fromField = fromTextField_ decodeJSON
setGroupPreference :: ChatFeature -> GroupFeatureEnabled -> Maybe GroupPreferences -> GroupPreferences
setGroupPreference :: GroupFeature -> GroupFeatureEnabled -> Maybe GroupPreferences -> GroupPreferences
setGroupPreference f enable prefs_ =
let prefs = mergeGroupPreferences prefs_
pref = (getGroupPreference f prefs :: GroupPreference) {enable}
in toGroupPreferences $ case f of
CFVoice -> prefs {voice = pref}
CFFullDelete -> prefs {fullDelete = pref}
GFDirectMessages -> prefs {directMessages = pref}
GFVoice -> prefs {voice = pref}
GFFullDelete -> prefs {fullDelete = pref}
-- full collection of chat preferences defined in the app - it is used to ensure we include all preferences and to simplify processing
-- if some of the preferences are not defined in Preferences, defaults from defaultChatPrefs are used here.
@@ -391,7 +417,8 @@ instance ToJSON FullPreferences where toEncoding = J.genericToEncoding J.default
-- full collection of group preferences defined in the app - it is used to ensure we include all preferences and to simplify processing
-- if some of the preferences are not defined in GroupPreferences, defaults from defaultGroupPrefs are used here.
data FullGroupPreferences = FullGroupPreferences
{ fullDelete :: GroupPreference,
{ directMessages :: GroupPreference,
fullDelete :: GroupPreference,
-- receipts :: GroupPreference,
voice :: GroupPreference
}
@@ -447,7 +474,8 @@ emptyChatPrefs = Preferences Nothing Nothing
defaultGroupPrefs :: FullGroupPreferences
defaultGroupPrefs =
FullGroupPreferences
{ fullDelete = GroupPreference {enable = FEOff},
{ directMessages = GroupPreference {enable = FEOff},
fullDelete = GroupPreference {enable = FEOff},
-- receipts = GroupPreference {enable = FEOff},
voice = GroupPreference {enable = FEOn}
}
@@ -543,9 +571,10 @@ mergeUserChatPrefs' user connectedIncognito userPreferences =
mergeGroupPreferences :: Maybe GroupPreferences -> FullGroupPreferences
mergeGroupPreferences groupPreferences =
FullGroupPreferences
{ fullDelete = pref CFFullDelete,
-- receipts = pref CFReceipts,
voice = pref CFVoice
{ directMessages = pref GFDirectMessages,
fullDelete = pref GFFullDelete,
-- receipts = pref GFReceipts,
voice = pref GFVoice
}
where
pref pt = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPreferences >>= groupPrefSel pt)
@@ -553,9 +582,10 @@ mergeGroupPreferences groupPreferences =
toGroupPreferences :: FullGroupPreferences -> GroupPreferences
toGroupPreferences groupPreferences =
GroupPreferences
{ fullDelete = pref CFFullDelete,
-- receipts = pref CFReceipts,
voice = pref CFVoice
{ directMessages = pref GFDirectMessages,
fullDelete = pref GFFullDelete,
-- receipts = pref GFReceipts,
voice = pref GFVoice
}
where
pref f = Just $ getGroupPreference f groupPreferences
+4 -4
View File
@@ -745,7 +745,7 @@ viewContactPreferences user ct ct' cups =
viewContactPref :: FullPreferences -> FullPreferences -> Maybe Preferences -> ContactUserPreferences -> ChatFeature -> Maybe StyledString
viewContactPref userPrefs userPrefs' ctPrefs cups pt
| userPref == userPref' && ctPref == contactPreference = Nothing
| otherwise = Just $ plain (chatPrefName pt) <> ": " <> plain (prefEnabledToText enabled) <> " (you allow: " <> viewCountactUserPref userPreference <> ", contact allows: " <> viewPreference contactPreference <> ")"
| otherwise = Just $ plain (chatFeatureToText pt) <> ": " <> plain (prefEnabledToText enabled) <> " (you allow: " <> viewCountactUserPref userPreference <> ", contact allows: " <> viewPreference contactPreference <> ")"
where
userPref = getPreference pt userPrefs
userPref' = getPreference pt userPrefs'
@@ -760,7 +760,7 @@ viewPrefsUpdated ps ps'
prefs = mapMaybe viewPref allChatFeatures
viewPref pt
| pref ps == pref ps' = Nothing
| otherwise = Just $ plain (chatPrefName pt) <> " allowed: " <> viewPreference (pref ps')
| otherwise = Just $ plain (chatFeatureToText pt) <> " allowed: " <> viewPreference (pref ps')
where
pref pss = getPreference pt $ mergePreferences pss Nothing
@@ -796,10 +796,10 @@ viewGroupUpdated
| null prefs = []
| otherwise = "updated group preferences:" : prefs
where
prefs = mapMaybe viewPref allChatFeatures
prefs = mapMaybe viewPref allGroupFeatures
viewPref pt
| pref gps == pref gps' = Nothing
| otherwise = Just $ plain (chatPrefName pt) <> " enabled: " <> plain (groupPrefToText $ pref gps')
| otherwise = Just $ plain (groupFeatureToText pt) <> " enabled: " <> plain (groupPrefToText $ pref gps')
where
pref pss = getGroupPreference pt $ mergeGroupPreferences pss