types and migration

This commit is contained in:
Evgeny @ SimpleX Chat committed 2026-09-05 09:44:20 +00:00
1 parent e6ad21801f
commit bf8dc4b3c5
12 files changed
+436 -122

No files matched your search

+1
View File
@@ -120,6 +120,7 @@ defaultChatConfig =
highlyAvailable = False,
deliveryWorkerDelay = 0,
deliveryBucketSize = 10000,
feedBucketSize = 1000,
webPreviewConfig = Nothing,
channelSubscriberRole = GRObserver,
relayChecksInterval = 15 * 60, -- 15 minutes
+3
View File
@@ -165,6 +165,7 @@ data ChatConfig = ChatConfig
ciExpirationInterval :: Int64, -- microseconds
deliveryWorkerDelay :: Int64, -- microseconds
deliveryBucketSize :: Int,
feedBucketSize :: Int,
webPreviewConfig :: Maybe WebPreviewConfig,
channelSubscriberRole :: GroupMemberRole, -- TODO [relays] starting role should be communicated in protocol from owner to relays
relayChecksInterval :: NominalDiffTime,
@@ -392,6 +393,7 @@ data ChatCommand
| APIUpdateChatTag ChatTagId ChatTagData
| APIReorderChatTags (NonEmpty ChatTagId)
| APICreateChatItems {noteFolderId :: NoteFolderId, composedMessages :: NonEmpty ComposedMessage}
| APISendFeedMessage {feedId :: FeedId, composedMessage :: ComposedMessage}
| APIReportMessage {groupId :: GroupId, chatItemId :: ChatItemId, reportReason :: ReportReason, reportText :: Text}
| ReportMessage {groupName :: GroupName, contactName_ :: Maybe ContactName, reportReason :: ReportReason, reportedMessage :: Text}
| APIUpdateChatItem {chatRef :: ChatRef, chatItemId :: ChatItemId, liveMessage :: Bool, updatedMessage :: UpdatedMessage}
@@ -514,6 +516,7 @@ data ChatCommand
| APIEnableGroupMember GroupId GroupMemberId
| SetShowMessages ChatName MsgFilter
| SetSendReceipts ChatName (Maybe Bool)
| SetDropFeed ChatName Bool
| SetShowMemberMessages GroupName ContactName Bool
| ContactInfo ContactName
| ShowGroupInfo GroupName
+37 -3
View File
@@ -10,7 +10,8 @@ import Data.ByteString.Char8 (ByteString)
import Data.Int (Int64)
import Data.Maybe (fromMaybe)
import Data.Time.Clock (UTCTime)
import Simplex.Chat.Messages (GroupChatScopeInfo (..), MessageId, ShowGroupAsSender)
import Simplex.Chat.Messages (ChatItemId, GroupChatScopeInfo (..), MessageId, ShowGroupAsSender)
import Simplex.Chat.Messages.CIContent (CIDeleteMode (..))
import Simplex.Chat.Options.DB (FromField (..), ToField (..))
import Simplex.Chat.Protocol
import Simplex.Chat.Types
@@ -18,11 +19,15 @@ import Simplex.Chat.Types.Shared
import Simplex.Messaging.Agent.Store.DB (fromTextField_)
import Simplex.Messaging.Encoding.String
type DeliveryWorkerKey = (GroupId, DeliveryWorkerScope)
data DeliveryEntity = DEGroup GroupId | DEFeed FeedId
deriving (Eq, Ord, Show)
type DeliveryWorkerKey = (DeliveryEntity, DeliveryWorkerScope)
data DeliveryWorkerScope
= DWSGroup
| DWSMemberSupport
| DWSFeed
-- | DWSMemberProfileUpdate
deriving (Eq, Ord, Show)
@@ -34,11 +39,13 @@ instance TextEncoding DeliveryWorkerScope where
textDecode = \case
"group" -> Just DWSGroup
"member_support" -> Just DWSMemberSupport
"feed" -> Just DWSFeed
-- "member_profile_update" -> Just DWSMemberProfileUpdate
_ -> Nothing
textEncode = \case
DWSGroup -> "group"
DWSMemberSupport -> "member_support"
DWSFeed -> "feed"
-- DWSMemberProfileUpdate -> "member_profile_update"
-- Context for creating a delivery task. Separate from DeliveryJobScope because
@@ -54,17 +61,30 @@ data DeliveryTaskContext = DeliveryTaskContext
data DeliveryJobScope
= DJSGroup {jobSpec :: DeliveryJobSpec}
| DJSMemberSupport {supportGMId :: GroupMemberId}
| DJSFeed {feedItemId :: ChatItemId, feedJobSpec :: FeedJobSpec}
-- | DJSMemberProfileUpdate
deriving (Show)
data DeliveryJobSpec
= DJDeliveryJob {includePending :: Bool}
| DJRelayRemoved
| DJFeed FeedJobSpec
deriving (Show)
data FeedJobSpec
= FJNew
| FJFileDescr
| FJUpdate
| FJDelete CIDeleteMode
deriving (Show)
data DeliveryJobSpecTag
= DJSTDeliveryJob
| DJSTRelayRemoved
| DJSTFeedNew
| DJSTFeedFileDescr
| DJSTFeedUpdate
| DJSTFeedDelete
deriving (Show)
instance FromField DeliveryJobSpecTag where fromField = fromTextField_ textDecode
@@ -75,15 +95,24 @@ instance TextEncoding DeliveryJobSpecTag where
textDecode = \case
"delivery_job" -> Just DJSTDeliveryJob
"relay_removed" -> Just DJSTRelayRemoved
"feed_new" -> Just DJSTFeedNew
"feed_file_descr" -> Just DJSTFeedFileDescr
"feed_update" -> Just DJSTFeedUpdate
"feed_delete" -> Just DJSTFeedDelete
_ -> Nothing
textEncode = \case
DJSTDeliveryJob -> "delivery_job"
DJSTRelayRemoved -> "relay_removed"
DJSTFeedNew -> "feed_new"
DJSTFeedFileDescr -> "feed_file_descr"
DJSTFeedUpdate -> "feed_update"
DJSTFeedDelete -> "feed_delete"
toWorkerScope :: DeliveryJobScope -> DeliveryWorkerScope
toWorkerScope = \case
DJSGroup _ -> DWSGroup
DJSMemberSupport _ -> DWSMemberSupport
DJSFeed {} -> DWSFeed
-- DJSMemberProfileUpdate -> DWSMemberProfileUpdate
isRelayRemoved :: DeliveryJobScope -> Bool
@@ -97,11 +126,13 @@ jobScopeImpliedSpec :: DeliveryJobScope -> DeliveryJobSpec
jobScopeImpliedSpec = \case
DJSGroup {jobSpec} -> jobSpec
DJSMemberSupport {} -> DJDeliveryJob {includePending = False}
DJSFeed {feedJobSpec} -> DJFeed feedJobSpec
jobSpecImpliedPending :: DeliveryJobSpec -> Bool
jobSpecImpliedPending = \case
DJDeliveryJob {includePending} -> includePending
DJRelayRemoved -> True
DJFeed _ -> False
infoToDeliveryContext :: GroupInfo -> Maybe GroupChatScopeInfo -> ShowGroupAsSender -> DeliveryTaskContext
infoToDeliveryContext GroupInfo {membership} scopeInfo sentAsGroup = DeliveryTaskContext {jobScope, sentAsGroup}
@@ -162,8 +193,11 @@ data MessageDeliveryJob = MessageDeliveryJob
{ jobId :: Int64,
jobScope :: DeliveryJobScope,
senderGMIds :: [GroupMemberId],
messageIds :: [MessageId],
body :: ByteString,
cursorGMId_ :: Maybe GroupMemberId
cursorGMId_ :: Maybe GroupMemberId,
cursorContactId_ :: Maybe ContactId,
cursorGroupId_ :: Maybe GroupId
}
deriving (Show)
+37 -5
View File
@@ -58,7 +58,7 @@ import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, parseAll, s
import Simplex.Messaging.Protocol (BlockingInfo, MsgBody, XFTPServer)
import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>))
data ChatType = CTDirect | CTGroup | CTLocal | CTContactRequest | CTContactConnection
data ChatType = CTDirect | CTGroup | CTLocal | CTFeed | CTContactRequest | CTContactConnection
deriving (Eq, Show, Ord)
$(JQ.deriveJSON (enumJSON $ dropPrefix "CT") ''ChatType)
@@ -67,6 +67,7 @@ data SChatType (c :: ChatType) where
SCTDirect :: SChatType 'CTDirect
SCTGroup :: SChatType 'CTGroup
SCTLocal :: SChatType 'CTLocal
SCTFeed :: SChatType 'CTFeed
SCTContactRequest :: SChatType 'CTContactRequest
SCTContactConnection :: SChatType 'CTContactConnection
@@ -76,6 +77,7 @@ instance TestEquality SChatType where
testEquality SCTDirect SCTDirect = Just Refl
testEquality SCTGroup SCTGroup = Just Refl
testEquality SCTLocal SCTLocal = Just Refl
testEquality SCTFeed SCTFeed = Just Refl
testEquality SCTContactRequest SCTContactRequest = Just Refl
testEquality SCTContactConnection SCTContactConnection = Just Refl
testEquality _ _ = Nothing
@@ -91,6 +93,8 @@ instance ChatTypeI 'CTGroup where chatTypeI = SCTGroup
instance ChatTypeI 'CTLocal where chatTypeI = SCTLocal
instance ChatTypeI 'CTFeed where chatTypeI = SCTFeed
instance ChatTypeI 'CTContactRequest where chatTypeI = SCTContactRequest
instance ChatTypeI 'CTContactConnection where chatTypeI = SCTContactConnection
@@ -100,6 +104,7 @@ toChatType = \case
SCTDirect -> CTDirect
SCTGroup -> CTGroup
SCTLocal -> CTLocal
SCTFeed -> CTFeed
SCTContactRequest -> CTContactRequest
SCTContactConnection -> CTContactConnection
@@ -108,6 +113,7 @@ aChatType = \case
CTDirect -> ACT SCTDirect
CTGroup -> ACT SCTGroup
CTLocal -> ACT SCTLocal
CTFeed -> ACT SCTFeed
CTContactRequest -> ACT SCTContactRequest
CTContactConnection -> ACT SCTContactConnection
@@ -157,6 +163,7 @@ chatTypeStr = \case
CTDirect -> "@"
CTGroup -> "#"
CTLocal -> "*"
CTFeed -> "%"
CTContactRequest -> "<@"
CTContactConnection -> ":"
@@ -170,6 +177,7 @@ data ChatInfo (c :: ChatType) where
DirectChat :: Contact -> ChatInfo 'CTDirect
GroupChat :: GroupInfo -> Maybe GroupChatScopeInfo -> ChatInfo 'CTGroup
LocalChat :: NoteFolder -> ChatInfo 'CTLocal
FeedChat :: Feed -> ChatInfo 'CTFeed
ContactRequest :: UserContactRequest -> ChatInfo 'CTContactRequest
ContactConnection :: PendingContactConnection -> ChatInfo 'CTContactConnection
CInfoInvalidJSON :: SChatType c -> J.Object -> ChatInfo c -- this constructor is needed to catch JSON errors for Remote connection parsing
@@ -192,6 +200,7 @@ chatInfoToRef = \case
DirectChat Contact {contactId} -> Just $ ChatRef CTDirect contactId Nothing
GroupChat GroupInfo {groupId} scopeInfo -> Just $ ChatRef CTGroup groupId (toChatScope <$> scopeInfo)
LocalChat NoteFolder {noteFolderId} -> Just $ ChatRef CTLocal noteFolderId Nothing
FeedChat Feed {feedId} -> Just $ ChatRef CTFeed feedId Nothing
ContactRequest UserContactRequest {contactRequestId} -> Just $ ChatRef CTContactRequest contactRequestId Nothing
ContactConnection PendingContactConnection {pccConnId} -> Just $ ChatRef CTContactConnection pccConnId Nothing
CInfoInvalidJSON {} -> Nothing
@@ -205,6 +214,7 @@ data JSONChatInfo
= JCInfoDirect {contact :: Contact}
| JCInfoGroup {groupInfo :: GroupInfo, groupChatScope :: Maybe GroupChatScopeInfo}
| JCInfoLocal {noteFolder :: NoteFolder}
| JCInfoFeed {feed :: Feed}
| JCInfoContactRequest {contactRequest :: UserContactRequest}
| JCInfoContactConnection {contactConnection :: PendingContactConnection}
| JCInfoInvalidJSON {chatType :: ChatType, json :: J.Object}
@@ -231,6 +241,7 @@ jsonChatInfo = \case
DirectChat c -> JCInfoDirect c
GroupChat g s -> JCInfoGroup g s
LocalChat l -> JCInfoLocal l
FeedChat f -> JCInfoFeed f
ContactRequest g -> JCInfoContactRequest g
ContactConnection c -> JCInfoContactConnection c
CInfoInvalidJSON c o -> JCInfoInvalidJSON (toChatType c) o
@@ -244,6 +255,7 @@ jsonAChatInfo = \case
JCInfoDirect c -> AChatInfo SCTDirect $ DirectChat c
JCInfoGroup g s -> AChatInfo SCTGroup $ GroupChat g s
JCInfoLocal l -> AChatInfo SCTLocal $ LocalChat l
JCInfoFeed f -> AChatInfo SCTFeed $ FeedChat f
JCInfoContactRequest g -> AChatInfo SCTContactRequest $ ContactRequest g
JCInfoContactConnection c -> AChatInfo SCTContactConnection $ ContactConnection c
JCInfoInvalidJSON cType o -> case aChatType cType of ACT c -> AChatInfo c $ CInfoInvalidJSON c o
@@ -298,6 +310,7 @@ data CIDirection (c :: ChatType) (d :: MsgDirection) where
CIChannelRcv :: CIDirection 'CTGroup 'MDRcv
CILocalSnd :: CIDirection 'CTLocal 'MDSnd
CILocalRcv :: CIDirection 'CTLocal 'MDRcv
CIFeedSnd :: CIDirection 'CTFeed 'MDSnd
deriving instance Show (CIDirection c d)
@@ -313,6 +326,7 @@ data JSONCIDirection
| JCIChannelRcv
| JCILocalSnd
| JCILocalRcv
| JCIFeedSnd
deriving (Show)
jsonCIDirection :: CIDirection c d -> JSONCIDirection
@@ -324,6 +338,7 @@ jsonCIDirection = \case
CIChannelRcv -> JCIChannelRcv
CILocalSnd -> JCILocalSnd
CILocalRcv -> JCILocalRcv
CIFeedSnd -> JCIFeedSnd
jsonACIDirection :: JSONCIDirection -> ACIDirection
jsonACIDirection = \case
@@ -334,6 +349,7 @@ jsonACIDirection = \case
JCIChannelRcv -> ACID SCTGroup SMDRcv CIChannelRcv
JCILocalSnd -> ACID SCTLocal SMDSnd CILocalSnd
JCILocalRcv -> ACID SCTLocal SMDRcv CILocalRcv
JCIFeedSnd -> ACID SCTFeed SMDSnd CIFeedSnd
data CIReactionCount = CIReactionCount {reaction :: MsgReaction, userReacted :: Bool, totalReacted :: Int}
deriving (Show)
@@ -396,6 +412,7 @@ data ChatDirection (c :: ChatType) (d :: MsgDirection) where
CDChannelRcv :: GroupInfo -> Maybe GroupChatScopeInfo -> ChatDirection 'CTGroup 'MDRcv
CDLocalSnd :: NoteFolder -> ChatDirection 'CTLocal 'MDSnd
CDLocalRcv :: NoteFolder -> ChatDirection 'CTLocal 'MDRcv
CDFeedSnd :: Feed -> ChatDirection 'CTFeed 'MDSnd
toCIDirection :: ChatDirection c d -> CIDirection c d
toCIDirection = \case
@@ -406,6 +423,7 @@ toCIDirection = \case
CDChannelRcv _ _ -> CIChannelRcv
CDLocalSnd _ -> CILocalSnd
CDLocalRcv _ -> CILocalRcv
CDFeedSnd _ -> CIFeedSnd
toChatInfo :: ChatDirection c d -> ChatInfo c
toChatInfo = \case
@@ -416,6 +434,7 @@ toChatInfo = \case
CDChannelRcv g s -> GroupChat g s
CDLocalSnd l -> LocalChat l
CDLocalRcv l -> LocalChat l
CDFeedSnd f -> FeedChat f
signMessagesRequired :: ChatDirection c d -> Bool
signMessagesRequired = \case
@@ -525,6 +544,7 @@ data CIMeta (c :: ChatType) (d :: MsgDirection) = CIMeta
forwardedByMember :: Maybe GroupMemberId,
showGroupAsSender :: ShowGroupAsSender,
msgVerified :: Maybe MsgVerified,
itemFeed :: Maybe CIFeed,
createdAt :: UTCTime,
updatedAt :: UTCTime
}
@@ -532,12 +552,15 @@ data CIMeta (c :: ChatType) (d :: MsgDirection) = CIMeta
type ShowGroupAsSender = Bool
mkCIMeta :: forall c d. ChatTypeI c => ChatItemId -> CIContent d -> Text -> CIStatus d -> Maybe Bool -> Maybe SharedMsgId -> Maybe CIForwardedFrom -> Maybe (CIDeleted c) -> Bool -> Maybe CITimed -> Maybe Bool -> Bool -> Bool -> UTCTime -> ChatItemTs -> Maybe GroupMemberId -> Bool -> Maybe MsgVerified -> UTCTime -> UTCTime -> CIMeta c d
mkCIMeta itemId itemContent itemText itemStatus sentViaProxy itemSharedMsgId itemForwarded itemDeleted itemEdited itemTimed itemLive userMention hasLink_ currentTs itemTs forwardedByMember showGroupAsSender msgVerified createdAt updatedAt =
data CIFeed = CIFLinked | CIFDetached
deriving (Eq, Show)
mkCIMeta :: forall c d. ChatTypeI c => ChatItemId -> CIContent d -> Text -> CIStatus d -> Maybe Bool -> Maybe SharedMsgId -> Maybe CIForwardedFrom -> Maybe (CIDeleted c) -> Bool -> Maybe CITimed -> Maybe Bool -> Bool -> Bool -> UTCTime -> ChatItemTs -> Maybe GroupMemberId -> Bool -> Maybe MsgVerified -> Maybe CIFeed -> UTCTime -> UTCTime -> CIMeta c d
mkCIMeta itemId itemContent itemText itemStatus sentViaProxy itemSharedMsgId itemForwarded itemDeleted itemEdited itemTimed itemLive userMention hasLink_ currentTs itemTs forwardedByMember showGroupAsSender msgVerified itemFeed createdAt updatedAt =
let deletable = deletable' itemContent itemDeleted itemTs nominalDay currentTs
editable = deletable && isNothing itemForwarded
hasLink = BoolDef hasLink_
in CIMeta {itemId, itemTs, itemText, itemStatus, sentViaProxy, itemSharedMsgId, itemForwarded, itemDeleted, itemEdited, itemTimed, itemLive, userMention, hasLink, deletable, editable, forwardedByMember, showGroupAsSender, msgVerified, createdAt, updatedAt}
in CIMeta {itemId, itemTs, itemText, itemStatus, sentViaProxy, itemSharedMsgId, itemForwarded, itemDeleted, itemEdited, itemTimed, itemLive, userMention, hasLink, deletable, editable, forwardedByMember, showGroupAsSender, msgVerified, itemFeed, createdAt, updatedAt}
deletable' :: forall c d. ChatTypeI c => CIContent d -> Maybe (CIDeleted c) -> UTCTime -> NominalDiffTime -> UTCTime -> Bool
deletable' itemContent itemDeleted itemTs allowedInterval currentTs =
@@ -569,6 +592,7 @@ dummyMeta itemId ts itemText =
forwardedByMember = Nothing,
showGroupAsSender = False,
msgVerified = Nothing,
itemFeed = Nothing,
createdAt = ts,
updatedAt = ts
}
@@ -673,6 +697,7 @@ jsonACIQDirection = \case
JCIChannelRcv -> Right $ ACIQDirection SCTGroup $ CIQGroupRcv Nothing
JCILocalSnd -> Left "unquotable"
JCILocalRcv -> Left "unquotable"
JCIFeedSnd -> Left "unquotable"
quoteMsgDirection :: CIQDirection c -> MsgDirection
quoteMsgDirection = \case
@@ -1187,7 +1212,7 @@ data RcvMessage = RcvMessage
type MessageId = Int64
data ConnOrGroupId = ConnectionId Int64 | GroupId Int64
data ConnOrGroupId = ConnectionId Int64 | GroupId Int64 | FeedId Int64
data SndMsgDelivery = SndMsgDelivery
{ connId :: Int64,
@@ -1284,6 +1309,7 @@ data CIDeleted (c :: ChatType) where
CIBlocked :: Maybe UTCTime -> CIDeleted 'CTGroup
CIBlockedByAdmin :: Maybe UTCTime -> CIDeleted 'CTGroup
CIModerated :: Maybe UTCTime -> GroupMember -> CIDeleted 'CTGroup
CIDeleting :: Maybe UTCTime -> CIDeleted 'CTFeed
deriving instance Show (CIDeleted c)
@@ -1294,6 +1320,7 @@ data JSONCIDeleted
| JCIDBlocked {deletedTs :: Maybe UTCTime}
| JCIDBlockedByAdmin {deletedTs :: Maybe UTCTime}
| JCIDModerated {deletedTs :: Maybe UTCTime, byGroupMember :: GroupMember}
| JCIDDeleting {deletedTs :: Maybe UTCTime}
deriving (Show)
jsonCIDeleted :: forall d. ChatTypeI d => CIDeleted d -> JSONCIDeleted
@@ -1302,6 +1329,7 @@ jsonCIDeleted = \case
CIBlocked ts -> JCIDBlocked ts
CIBlockedByAdmin ts -> JCIDBlockedByAdmin ts
CIModerated ts m -> JCIDModerated ts m
CIDeleting ts -> JCIDDeleting ts
jsonACIDeleted :: JSONCIDeleted -> ACIDeleted
jsonACIDeleted = \case
@@ -1309,6 +1337,7 @@ jsonACIDeleted = \case
JCIDBlocked ts -> ACIDeleted SCTGroup $ CIBlocked ts
JCIDBlockedByAdmin ts -> ACIDeleted SCTGroup $ CIBlockedByAdmin ts
JCIDModerated ts m -> ACIDeleted SCTGroup (CIModerated ts m)
JCIDDeleting ts -> ACIDeleted SCTFeed $ CIDeleting ts
itemDeletedTs :: CIDeleted d -> Maybe UTCTime
itemDeletedTs = \case
@@ -1316,6 +1345,7 @@ itemDeletedTs = \case
CIBlocked ts -> ts
CIBlockedByAdmin ts -> ts
CIModerated ts _ -> ts
CIDeleting ts -> ts
data CIForwardedFrom
= CIFFUnknown
@@ -1445,6 +1475,8 @@ $(JQ.deriveJSON defaultJSON ''MemberDeliveryStatus)
$(JQ.deriveJSON defaultJSON ''ChatItemVersion)
$(JQ.deriveJSON (enumJSON $ dropPrefix "CIF") ''CIFeed)
instance (ChatTypeI c, MsgDirectionI d) => FromJSON (CIMeta c d) where
parseJSON = $(JQ.mkParseJSON defaultJSON ''CIMeta)
+8 -5
View File
@@ -447,7 +447,7 @@ signChatMsgBody MsgSigning {bindingTag, bindingData, keyRef, privKey} msgBody =
data ChatMsgEvent (e :: MsgEncoding) where
XMsgNew :: MsgContainer -> ChatMsgEvent 'Json
XMsgFileDescr :: {msgId :: SharedMsgId, fileDescr :: FileDescr, fileExpires :: Maybe UTCTime} -> ChatMsgEvent 'Json
XMsgUpdate :: {msgId :: SharedMsgId, content :: MsgContent, mentions :: Map MemberName MsgMention, ttl :: Maybe Int, live :: Maybe Bool, scope :: Maybe MsgScope, asGroup :: Maybe Bool} -> ChatMsgEvent 'Json
XMsgUpdate :: {msgId :: SharedMsgId, content :: MsgContent, mentions :: Map MemberName MsgMention, ttl :: Maybe Int, live :: Maybe Bool, scope :: Maybe MsgScope, asGroup :: Maybe Bool, feed :: Maybe Bool} -> ChatMsgEvent 'Json
XMsgDel :: {msgId :: SharedMsgId, memberId :: Maybe MemberId, scope :: Maybe MsgScope, onlyHistory :: Bool} -> ChatMsgEvent 'Json
XMsgDeleted :: ChatMsgEvent 'Json
XMsgReact :: {msgId :: SharedMsgId, memberId :: Maybe MemberId, scope :: Maybe MsgScope, reaction :: MsgReaction, add :: Bool} -> ChatMsgEvent 'Json
@@ -689,7 +689,8 @@ data MsgContainer = MsgContainer
quote :: Maybe QuotedMsg,
parent :: Maybe MsgRef,
forward :: Maybe Bool,
forwardLink :: Maybe ForwardLink
forwardLink :: Maybe ForwardLink,
feed :: Maybe Bool
}
deriving (Eq, Show)
@@ -715,7 +716,8 @@ mcSimple content =
quote = Nothing,
parent = Nothing,
forward = Nothing,
forwardLink = Nothing
forwardLink = Nothing,
feed = Nothing
}
mcQuote :: QuotedMsg -> MsgContent -> MsgContainer
@@ -1400,7 +1402,8 @@ appJsonToCM AppMessageJson {v, msgId, event, params} = do
live <- opt "live"
scope <- opt "scope"
asGroup <- opt "asGroup"
pure XMsgUpdate {msgId = msgId', content, mentions, ttl, live, scope, asGroup}
feed <- opt "feed"
pure XMsgUpdate {msgId = msgId', content, mentions, ttl, live, scope, asGroup, feed}
XMsgDel_ -> XMsgDel <$> p "msgId" <*> opt "memberId" <*> opt "scope" <*> (fromMaybe False <$> opt "onlyHistory")
XMsgDeleted_ -> pure XMsgDeleted
XMsgReact_ -> XMsgReact <$> p "msgId" <*> opt "memberId" <*> opt "scope" <*> p "reaction" <*> p "add"
@@ -1483,7 +1486,7 @@ chatToAppMessage chatMsg@ChatMessage {chatVRange, msgId, chatMsgEvent} = case en
J.Object obj -> obj
_ -> JM.empty
XMsgFileDescr msgId' fileDescr fileExpires -> o $ ("fileExpires" .=? fileExpires) ["msgId" .= msgId', "fileDescr" .= fileDescr]
XMsgUpdate {msgId = msgId', content, mentions, ttl, live, scope, asGroup} -> o $ ("asGroup" .=? asGroup) $ ("ttl" .=? ttl) $ ("live" .=? live) $ ("scope" .=? scope) $ ("mentions" .=? nonEmptyMap mentions) ["msgId" .= msgId', "content" .= content]
XMsgUpdate {msgId = msgId', content, mentions, ttl, live, scope, asGroup, feed} -> o $ ("feed" .=? feed) $ ("asGroup" .=? asGroup) $ ("ttl" .=? ttl) $ ("live" .=? live) $ ("scope" .=? scope) $ ("mentions" .=? nonEmptyMap mentions) ["msgId" .= msgId', "content" .= content]
XMsgDel msgId' memberId scope onlyHistory -> o $ ("memberId" .=? memberId) $ ("scope" .=? scope) $ ("onlyHistory" .=? justTrue onlyHistory) ["msgId" .= msgId']
XMsgDeleted -> JM.empty
XMsgReact msgId' memberId scope reaction add -> o $ ("memberId" .=? memberId) $ ("scope" .=? scope) ["msgId" .= msgId', "reaction" .= reaction, "add" .= add]
+3 -1
View File
@@ -172,6 +172,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20260723_contact_request_rejection
import Simplex.Chat.Store.SQLite.Migrations.M20260813_auto_accept_group_invitations
import Simplex.Chat.Store.SQLite.Migrations.M20260822_forward_link
import Simplex.Chat.Store.SQLite.Migrations.M20260828_file_expiry
import Simplex.Chat.Store.SQLite.Migrations.M20260905_feeds
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
schemaMigrations :: [(String, Query, Maybe Query)]
@@ -343,7 +344,8 @@ schemaMigrations =
("20260723_contact_request_rejection", m20260723_contact_request_rejection, Just down_m20260723_contact_request_rejection),
("20260813_auto_accept_group_invitations", m20260813_auto_accept_group_invitations, Just down_m20260813_auto_accept_group_invitations),
("20260822_forward_link", m20260822_forward_link, Just down_m20260822_forward_link),
("20260828_file_expiry", m20260828_file_expiry, Just down_m20260828_file_expiry)
("20260828_file_expiry", m20260828_file_expiry, Just down_m20260828_file_expiry),
("20260905_feeds", m20260905_feeds, Just down_m20260905_feeds)
]
-- | The list of migrations in ascending order by date
@@ -0,0 +1,102 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Store.SQLite.Migrations.M20260905_feeds where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
m20260905_feeds :: Query
m20260905_feeds =
[sql|
CREATE TABLE feeds(
feed_id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
created_at TEXT NOT NULL DEFAULT(datetime('now')),
updated_at TEXT NOT NULL DEFAULT(datetime('now')),
chat_ts TEXT NOT NULL DEFAULT(datetime('now')),
favorite INTEGER NOT NULL DEFAULT 0,
unread_chat INTEGER NOT NULL DEFAULT 0
) STRICT;
CREATE INDEX idx_feeds_user_id ON feeds(user_id);
INSERT INTO feeds (user_id) SELECT user_id FROM users;
ALTER TABLE chat_items ADD COLUMN feed_id INTEGER DEFAULT NULL REFERENCES feeds ON DELETE CASCADE;
ALTER TABLE chat_items ADD COLUMN feed_item_id INTEGER DEFAULT NULL REFERENCES chat_items ON DELETE SET NULL;
ALTER TABLE chat_items ADD COLUMN item_feed INTEGER NOT NULL DEFAULT 0;
CREATE INDEX idx_chat_items_feed_id ON chat_items(feed_id);
CREATE INDEX idx_chat_items_feeds_created_at ON chat_items(user_id, feed_id, created_at);
CREATE INDEX idx_chat_items_feed_item_contact ON chat_items(feed_item_id, contact_id);
CREATE INDEX idx_chat_items_feed_item_group ON chat_items(feed_item_id, group_id);
ALTER TABLE messages ADD COLUMN feed_id INTEGER DEFAULT NULL REFERENCES feeds ON DELETE CASCADE;
CREATE INDEX idx_messages_feed_id ON messages(feed_id);
ALTER TABLE files ADD COLUMN feed_id INTEGER DEFAULT NULL REFERENCES feeds ON DELETE CASCADE;
CREATE INDEX idx_files_feed_id ON files(feed_id);
ALTER TABLE contacts ADD COLUMN drop_feed INTEGER NOT NULL DEFAULT 0;
ALTER TABLE groups ADD COLUMN drop_feed INTEGER NOT NULL DEFAULT 0;
CREATE INDEX idx_contacts_user_id ON contacts(user_id);
CREATE INDEX idx_groups_user_id_business_chat ON groups(user_id, business_chat);
PRAGMA writable_schema=1;
UPDATE sqlite_master
SET sql = replace(sql, 'group_id INTEGER NOT NULL REFERENCES groups ON DELETE CASCADE', 'group_id INTEGER REFERENCES groups ON DELETE CASCADE')
WHERE type = 'table' AND name = 'delivery_jobs';
PRAGMA writable_schema=RESET;
ALTER TABLE delivery_jobs ADD COLUMN feed_id INTEGER REFERENCES feeds ON DELETE CASCADE;
ALTER TABLE delivery_jobs ADD COLUMN chat_item_id INTEGER REFERENCES chat_items ON DELETE CASCADE;
ALTER TABLE delivery_jobs ADD COLUMN delete_mode TEXT;
ALTER TABLE delivery_jobs ADD COLUMN message_ids TEXT;
ALTER TABLE delivery_jobs ADD COLUMN cursor_contact_id INTEGER;
ALTER TABLE delivery_jobs ADD COLUMN cursor_group_id INTEGER;
CREATE INDEX idx_delivery_jobs_feed_next ON delivery_jobs(feed_id, worker_scope, failed, job_status);
CREATE INDEX idx_delivery_jobs_chat_item_id ON delivery_jobs(chat_item_id);
|]
down_m20260905_feeds :: Query
down_m20260905_feeds =
[sql|
DROP INDEX idx_delivery_jobs_chat_item_id;
DROP INDEX idx_delivery_jobs_feed_next;
DELETE FROM delivery_jobs WHERE group_id IS NULL;
ALTER TABLE delivery_jobs DROP COLUMN cursor_group_id;
ALTER TABLE delivery_jobs DROP COLUMN cursor_contact_id;
ALTER TABLE delivery_jobs DROP COLUMN message_ids;
ALTER TABLE delivery_jobs DROP COLUMN delete_mode;
ALTER TABLE delivery_jobs DROP COLUMN chat_item_id;
ALTER TABLE delivery_jobs DROP COLUMN feed_id;
PRAGMA writable_schema=1;
UPDATE sqlite_master
SET sql = replace(sql, 'group_id INTEGER REFERENCES groups ON DELETE CASCADE', 'group_id INTEGER NOT NULL REFERENCES groups ON DELETE CASCADE')
WHERE type = 'table' AND name = 'delivery_jobs';
PRAGMA writable_schema=RESET;
DROP INDEX idx_groups_user_id_business_chat;
DROP INDEX idx_contacts_user_id;
ALTER TABLE groups DROP COLUMN drop_feed;
ALTER TABLE contacts DROP COLUMN drop_feed;
DROP INDEX idx_files_feed_id;
ALTER TABLE files DROP COLUMN feed_id;
DROP INDEX idx_messages_feed_id;
ALTER TABLE messages DROP COLUMN feed_id;
DROP INDEX idx_chat_items_feed_item_group;
DROP INDEX idx_chat_items_feed_item_contact;
DROP INDEX idx_chat_items_feeds_created_at;
DROP INDEX idx_chat_items_feed_id;
ALTER TABLE chat_items DROP COLUMN item_feed;
ALTER TABLE chat_items DROP COLUMN feed_item_id;
ALTER TABLE chat_items DROP COLUMN feed_id;
DROP INDEX idx_feeds_user_id;
DROP TABLE feeds;
|]
@@ -106,6 +106,7 @@ CREATE TABLE contacts(
grp_direct_inv_from_group_member_id INTEGER REFERENCES group_members(group_member_id) ON DELETE SET NULL,
grp_direct_inv_from_member_conn_id INTEGER REFERENCES connections(connection_id) ON DELETE SET NULL,
grp_direct_inv_started_connection INTEGER NOT NULL DEFAULT 0,
drop_feed INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY(user_id, local_display_name)
REFERENCES display_names(user_id, local_display_name)
ON DELETE CASCADE
@@ -208,7 +209,8 @@ CREATE TABLE groups(
roster_blob BLOB,
group_domain_verified INTEGER,
stored_roster_version INTEGER,
applied_complete_roster_version INTEGER, -- received
applied_complete_roster_version INTEGER,
drop_feed INTEGER NOT NULL DEFAULT 0, -- received
FOREIGN KEY(user_id, local_display_name)
REFERENCES display_names(user_id, local_display_name)
ON DELETE CASCADE
@@ -303,6 +305,8 @@ CREATE TABLE files(
roster_transfer_id INTEGER,
file_digest BLOB,
file_expires_at TEXT
,
feed_id INTEGER DEFAULT NULL REFERENCES feeds ON DELETE CASCADE
) STRICT;
CREATE TABLE snd_files(
file_id INTEGER NOT NULL REFERENCES files ON DELETE CASCADE,
@@ -456,6 +460,8 @@ CREATE TABLE messages(
broker_ts TEXT,
msg_chat_binding TEXT,
msg_signatures BLOB
,
feed_id INTEGER DEFAULT NULL REFERENCES feeds ON DELETE CASCADE
) STRICT;
CREATE TABLE pending_group_messages(
pending_group_message_id INTEGER PRIMARY KEY,
@@ -521,6 +527,10 @@ CREATE TABLE chat_items(
fwd_from_public_group_id BLOB,
fwd_from_member_id BLOB,
fwd_from_shared_msg_id BLOB
,
feed_id INTEGER DEFAULT NULL REFERENCES feeds ON DELETE CASCADE,
feed_item_id INTEGER DEFAULT NULL REFERENCES chat_items ON DELETE SET NULL,
item_feed INTEGER NOT NULL DEFAULT 0
) STRICT;
CREATE TABLE sqlite_sequence(name,seq);
CREATE TABLE chat_item_messages(
@@ -785,7 +795,7 @@ CREATE TABLE delivery_tasks(
) STRICT;
CREATE TABLE delivery_jobs(
delivery_job_id INTEGER PRIMARY KEY AUTOINCREMENT,
group_id INTEGER NOT NULL REFERENCES groups ON DELETE CASCADE,
group_id INTEGER REFERENCES groups ON DELETE CASCADE,
worker_scope TEXT NOT NULL,
job_scope_spec_tag TEXT,
job_scope_include_pending INTEGER,
@@ -799,6 +809,13 @@ CREATE TABLE delivery_jobs(
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
,
sender_group_member_ids TEXT
,
feed_id INTEGER REFERENCES feeds ON DELETE CASCADE,
chat_item_id INTEGER REFERENCES chat_items ON DELETE CASCADE,
delete_mode TEXT,
message_ids TEXT,
cursor_contact_id INTEGER,
cursor_group_id INTEGER
) STRICT;
CREATE TABLE group_member_status_predicates(
member_status TEXT NOT NULL PRIMARY KEY,
@@ -1418,3 +1435,41 @@ BEGIN
)
WHERE group_id = NEW.group_id;
END;
CREATE TABLE feeds(
feed_id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
created_at TEXT NOT NULL DEFAULT(datetime('now')),
updated_at TEXT NOT NULL DEFAULT(datetime('now')),
chat_ts TEXT NOT NULL DEFAULT(datetime('now')),
favorite INTEGER NOT NULL DEFAULT 0,
unread_chat INTEGER NOT NULL DEFAULT 0
) STRICT;
CREATE INDEX idx_feeds_user_id ON feeds(user_id);
CREATE INDEX idx_chat_items_feed_id ON chat_items(feed_id);
CREATE INDEX idx_chat_items_feeds_created_at ON chat_items(
user_id,
feed_id,
created_at
);
CREATE INDEX idx_chat_items_feed_item_contact ON chat_items(
feed_item_id,
contact_id
);
CREATE INDEX idx_chat_items_feed_item_group ON chat_items(
feed_item_id,
group_id
);
CREATE INDEX idx_messages_feed_id ON messages(feed_id);
CREATE INDEX idx_files_feed_id ON files(feed_id);
CREATE INDEX idx_contacts_user_id ON contacts(user_id);
CREATE INDEX idx_groups_user_id_business_chat ON groups(
user_id,
business_chat
);
CREATE INDEX idx_delivery_jobs_feed_next ON delivery_jobs(
feed_id,
worker_scope,
failed,
job_status
);
CREATE INDEX idx_delivery_jobs_chat_item_id ON delivery_jobs(chat_item_id);
+4
View File
@@ -73,6 +73,7 @@ data ChatLockEntity
| CLUserContact Int64
| CLContactRequest Int64
| CLFile Int64
| CLFeed FeedId
deriving (Eq, Ord)
-- These error type constructors must be added to mobile apps
@@ -113,6 +114,9 @@ data StoreError
| SENoteFolderAlreadyExists {noteFolderId :: NoteFolderId}
| SENoteFolderNotFound {noteFolderId :: NoteFolderId}
| SEUserNoteFolderNotFound
| SEFeedAlreadyExists {feedId :: FeedId}
| SEFeedNotFound {feedId :: FeedId}
| SEUserFeedNotFound
| SESndFileNotFound {fileId :: FileTransferId}
| SESndFileInvalid {fileId :: FileTransferId}
| SERcvFileNotFound {fileId :: FileTransferId}
+24 -6
View File
@@ -588,20 +588,22 @@ data GroupLink = GroupLink
}
deriving (Show)
data ContactOrGroup = CGContact Contact | CGGroup GroupInfo [GroupMember]
data ContactOrGroup = CGContact Contact | CGGroup GroupInfo [GroupMember] | CGFeed Feed
data PreparedChatEntity = PCEContact Contact | PCEGroup {groupInfo :: GroupInfo, hostMember :: GroupMember}
contactAndGroupIds :: ContactOrGroup -> (Maybe ContactId, Maybe GroupId)
contactAndGroupIds :: ContactOrGroup -> (Maybe ContactId, Maybe GroupId, Maybe FeedId)
contactAndGroupIds = \case
CGContact Contact {contactId} -> (Just contactId, Nothing)
CGGroup GroupInfo {groupId} _ -> (Nothing, Just groupId)
CGContact Contact {contactId} -> (Just contactId, Nothing, Nothing)
CGGroup GroupInfo {groupId} _ -> (Nothing, Just groupId, Nothing)
CGFeed Feed {feedId} -> (Nothing, Nothing, Just feedId)
-- TODO when more settings are added we should create another type to allow partial setting updates (with all Maybe properties)
data ChatSettings = ChatSettings
{ enableNtfs :: MsgFilter,
sendRcpts :: Maybe Bool,
favorite :: Bool
favorite :: Bool,
dropFeed :: BoolDef
}
deriving (Eq, Show)
@@ -610,7 +612,8 @@ defaultChatSettings =
ChatSettings
{ enableNtfs = MFAll,
sendRcpts = Nothing,
favorite = False
favorite = False,
dropFeed = BoolDef False
}
chatHasNtfs :: ChatSettings -> Bool
@@ -2218,6 +2221,19 @@ data NoteFolder = NoteFolder
type NoteFolderId = Int64
data Feed = Feed
{ feedId :: FeedId,
userId :: UserId,
createdAt :: UTCTime,
updatedAt :: UTCTime,
chatTs :: UTCTime,
favorite :: Bool,
unread :: Bool
}
deriving (Eq, Show)
type FeedId = Int64
data ChatVersion
instance VersionScope ChatVersion
@@ -2410,4 +2426,6 @@ $(JQ.deriveJSON defaultJSON ''ContactRef)
$(JQ.deriveJSON defaultJSON ''NoteFolder)
$(JQ.deriveJSON defaultJSON ''Feed)
$(JQ.deriveJSON defaultJSON ''ChatTag)