core: group integrity status types (#3302)

This commit is contained in:
spaced4ndy 2023-11-02 20:07:14 +04:00 committed by GitHub
parent 7473da36a6
commit 212f193a4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 148 additions and 49 deletions

View File

@ -36,6 +36,7 @@ import Database.SQLite.Simple.ToField (ToField (..))
import GHC.Generics (Generic) import GHC.Generics (Generic)
import Simplex.Chat.Markdown import Simplex.Chat.Markdown
import Simplex.Chat.Messages.CIContent import Simplex.Chat.Messages.CIContent
import Simplex.Chat.Messages.Events
import Simplex.Chat.Protocol import Simplex.Chat.Protocol
import Simplex.Chat.Types import Simplex.Chat.Types
import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Preferences
@ -341,8 +342,7 @@ data CIMeta (c :: ChatType) (d :: MsgDirection) = CIMeta
itemTimed :: Maybe CITimed, itemTimed :: Maybe CITimed,
itemLive :: Maybe Bool, itemLive :: Maybe Bool,
editable :: Bool, editable :: Bool,
-- receivedFromAuthor :: Bool, groupIntegrityStatus :: GroupIntegrityStatus,
-- groupDagError :: [GroupEventIntegrityError],
createdAt :: UTCTime, createdAt :: UTCTime,
updatedAt :: UTCTime updatedAt :: UTCTime
} }
@ -353,10 +353,22 @@ mkCIMeta itemId itemContent itemText itemStatus itemSharedMsgId itemDeleted item
let editable = case itemContent of let editable = case itemContent of
CISndMsgContent _ -> diffUTCTime currentTs itemTs < nominalDay && isNothing itemDeleted CISndMsgContent _ -> diffUTCTime currentTs itemTs < nominalDay && isNothing itemDeleted
_ -> False _ -> False
in CIMeta {itemId, itemTs, itemText, itemStatus, itemSharedMsgId, itemDeleted, itemEdited, itemTimed, itemLive, editable, createdAt, updatedAt} groupIntegrityStatus = GISNoEvent
in CIMeta {itemId, itemTs, itemText, itemStatus, itemSharedMsgId, itemDeleted, itemEdited, itemTimed, itemLive, editable, groupIntegrityStatus, createdAt, updatedAt}
instance ToJSON (CIMeta c d) where toEncoding = J.genericToEncoding J.defaultOptions instance ToJSON (CIMeta c d) where toEncoding = J.genericToEncoding J.defaultOptions
data GroupIntegrityStatus
= GISOk -- sent event; or received event with all parents known
| GISIntegrityError GroupEventIntegrityError -- received event has integrity error (if many, order and choose one?)
| GISConfirmedParent GroupEventIntegrityConfirmation -- received event has no errors and was confirmed by other member, higher role is preferred
| GISNoEvent -- direct chat items and group chat items without recorded group events (legacy)
deriving (Show, Generic)
instance ToJSON GroupIntegrityStatus where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "GIS"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "GIS"
data CITimed = CITimed data CITimed = CITimed
{ ttl :: Int, -- seconds { ttl :: Int, -- seconds
deleteAt :: Maybe UTCTime -- this is initially Nothing for received items, the timer starts when they are read deleteAt :: Maybe UTCTime -- this is initially Nothing for received items, the timer starts when they are read

View File

@ -1,21 +1,26 @@
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-} {-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TemplateHaskell #-}
module Simplex.Chat.Messages.Events where module Simplex.Chat.Messages.Events where
import qualified Data.Aeson.TH as JQ
import Data.ByteString.Char8 (ByteString) import Data.ByteString.Char8 (ByteString)
import Data.Time.Clock (UTCTime) import Data.Time.Clock (UTCTime)
import Simplex.Chat.Messages.CIContent import Simplex.Chat.Messages.CIContent
import Simplex.Chat.Protocol import Simplex.Chat.Protocol
import Simplex.Chat.Types import Simplex.Chat.Types
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, sumTypeJSON)
import Simplex.Messaging.Version import Simplex.Messaging.Version
data StoredGroupEvent d = StoredGroupEvent data StoredGroupEvent d = StoredGroupEvent
{ chatVRange :: VersionRange, { chatVRange :: VersionRange,
msgId :: SharedMsgId, msgId :: SharedMsgId,
eventData :: StoredGroupEventData, eventData :: StoredGroupEventData,
dagErrors :: [GroupEventIntegrityError], integrityErrors :: [GroupEventIntegrityError],
integrityConfirmations :: [GroupEventIntegrityConfirmation],
sharedHash :: ByteString, sharedHash :: ByteString,
eventDir :: GEDirection d, eventDir :: GEDirection d,
parents :: [AStoredGroupEvent] parents :: [AStoredGroupEvent]
@ -23,10 +28,25 @@ data StoredGroupEvent d = StoredGroupEvent
data AStoredGroupEvent = forall d. MsgDirectionI d => AStoredGroupEvent (StoredGroupEvent d) data AStoredGroupEvent = forall d. MsgDirectionI d => AStoredGroupEvent (StoredGroupEvent d)
data GroupEventIntegrityError data GroupEventIntegrityError = GroupEventIntegrityError
= GEErrInvalidHash { groupMemberId :: GroupMemberId,
| GEErrMissingParent SharedMsgId memberRole :: GroupMemberRole,
| GEErrParentHashMismatch SharedMsgId error :: GroupEventError
}
deriving (Show)
data GroupEventError
= GEErrInvalidHash -- content hash mismatch
| GEErrUnconfirmedParent SharedMsgId -- referenced parent wasn't previously received from author or admin
| GEErrParentHashMismatch SharedMsgId -- referenced parent has different hash
| GEErrChildHashMismatch SharedMsgId -- child referencing this event has different hash (mirrors GEErrParentHashMismatch)
deriving (Show)
data GroupEventIntegrityConfirmation = GroupEventIntegrityConfirmation
{ groupMemberId :: GroupMemberId,
memberRole :: GroupMemberRole
}
deriving (Show)
data GEDirection (d :: MsgDirection) where data GEDirection (d :: MsgDirection) where
GESent :: GEDirection 'MDSnd GESent :: GEDirection 'MDSnd
@ -37,17 +57,24 @@ data StoredGroupEventData = SGEData (ChatMsgEvent 'Json) | SGEAvailable [GroupMe
data ReceivedEventInfo = ReceivedEventInfo data ReceivedEventInfo = ReceivedEventInfo
{ authorMemberId :: MemberId, { authorMemberId :: MemberId,
authorMemberName :: ContactName, authorMemberName :: ContactName,
authorMember :: Maybe GroupMemberRef, authorMember :: GroupMemberRef,
receivedFrom :: GroupMemberRef, receivedFrom :: GroupMemberRef,
processing :: EventProcessing processing :: EventProcessing
} }
data ReceivedFromRole = RFAuthor | RFSufficientPrivilege | RFLower data ReceivedFromRole = RFAuthor | RFSufficientPrivilege | RFLower
receviedFromRole :: ReceivedEventInfo -> ReceivedFromRole receivedFromRole' :: ReceivedEventInfo -> ReceivedFromRole
receviedFromRole = undefined receivedFromRole' = undefined
data EventProcessing data EventProcessing
= EPProcessed UTCTime = EPProcessed UTCTime
| EPScheduled UTCTime | EPScheduled UTCTime
| EPPendingConfirmation -- e.g. till it's received from author or member with the same or higher privileges (depending on the event) | EPPendingConfirmation -- e.g. till it's received from author or member with the same or higher privileges (depending on the event)
-- platform-specific JSON encoding (used in API)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "GEErr") ''GroupEventError)
$(JQ.deriveJSON defaultJSON ''GroupEventIntegrityError)
$(JQ.deriveJSON defaultJSON ''GroupEventIntegrityConfirmation)

View File

@ -18,24 +18,27 @@ CREATE TABLE group_events (
event_data TEXT NOT NULL, -- eventData :: StoredGroupEventData event_data TEXT NOT NULL, -- eventData :: StoredGroupEventData
shared_hash BLOB NOT NULL, -- sharedHash :: ByteString shared_hash BLOB NOT NULL, -- sharedHash :: ByteString
event_sent INTEGER NOT NULL, -- 0 for received, 1 for sent; below `rcvd_` fields are null for sent event_sent INTEGER NOT NULL, -- 0 for received, 1 for sent; below `rcvd_` fields are null for sent
rcvd_author_member_id BLOB, -- ReceivedEventInfo authorMemberId :: MemberId -- ReceivedEventInfo fields:
rcvd_author_member_name TEXT, -- ReceivedEventInfo authorMemberName :: ContactName rcvd_author_member_id BLOB, -- authorMemberId :: MemberId
-- ReceivedEventInfo authorMember :: Maybe GroupMemberRef; can be null even for received event rcvd_author_member_name TEXT, -- authorMemberName :: ContactName
rcvd_author_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL, -- authorMember :: GroupMemberRef
rcvd_author_contact_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL, rcvd_author_group_member_id INTEGER REFERENCES group_members ON DELETE CASCADE,
-- rcvd_author_role TEXT NOT NULL, -- ReceivedFromRole - store in case it changes? rcvd_author_contact_profile_id INTEGER REFERENCES contact_profiles ON DELETE CASCADE,
-- ReceivedEventInfo receivedFrom :: GroupMemberRef rcvd_author_role TEXT,
rcvd_from_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL, -- receivedFrom :: GroupMemberRef
rcvd_from_contact_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL, rcvd_from_group_member_id INTEGER REFERENCES group_members ON DELETE CASCADE,
rcvd_processing TEXT NOT NULL, -- ReceivedEventInfo processing :: EventProcessing rcvd_from_contact_profile_id INTEGER REFERENCES contact_profiles ON DELETE CASCADE,
rcvd_processed INTEGER NOT NULL DEFAULT 0, -- 1 for processed; when retrieving unprocessed rcvd_from_role TEXT,
-- rcvd_scheduled_at TEXT, -- EPScheduled UTCTime; when retrieving scheduled at near time? -- ReceivedEventInfo processing :: EventProcessing
rcvd_processed_at TEXT, -- EPProcessed UTCTime
rcvd_scheduled_at TEXT, -- EPScheduled UTCTime; both this and rcvd_processed_at are null -> EPPendingConfirmation
created_at TEXT NOT NULL DEFAULT (datetime('now')), created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')) updated_at TEXT NOT NULL DEFAULT (datetime('now'))
); );
CREATE INDEX idx_group_events_user_id ON group_events(user_id); CREATE INDEX idx_group_events_user_id ON group_events(user_id);
CREATE INDEX idx_group_events_chat_item_id ON group_events(chat_item_id); CREATE INDEX idx_group_events_chat_item_id ON group_events(chat_item_id);
CREATE INDEX idx_group_events_shared_msg_id ON group_events(shared_msg_id);
CREATE INDEX idx_group_events_rcvd_author_group_member_id ON group_events(rcvd_author_group_member_id); CREATE INDEX idx_group_events_rcvd_author_group_member_id ON group_events(rcvd_author_group_member_id);
CREATE INDEX idx_group_events_rcvd_author_contact_profile_id ON group_events(rcvd_author_contact_profile_id); CREATE INDEX idx_group_events_rcvd_author_contact_profile_id ON group_events(rcvd_author_contact_profile_id);
CREATE INDEX idx_group_events_rcvd_from_group_member_id ON group_events(rcvd_from_group_member_id); CREATE INDEX idx_group_events_rcvd_from_group_member_id ON group_events(rcvd_from_group_member_id);
@ -52,15 +55,34 @@ CREATE TABLE group_events_availabilities (
CREATE INDEX idx_group_events_availabilities_group_event_id ON group_events_availabilities(group_event_id); CREATE INDEX idx_group_events_availabilities_group_event_id ON group_events_availabilities(group_event_id);
CREATE INDEX idx_group_events_availabilities_available_at_group_member_id ON group_events_availabilities(available_at_group_member_id); CREATE INDEX idx_group_events_availabilities_available_at_group_member_id ON group_events_availabilities(available_at_group_member_id);
CREATE TABLE group_events_dag_errors ( CREATE TABLE group_events_errors (
group_event_dag_error_id INTEGER PRIMARY KEY, group_event_dag_error_id INTEGER PRIMARY KEY,
group_event_id INTEGER NOT NULL REFERENCES group_events ON DELETE CASCADE, group_event_id INTEGER NOT NULL REFERENCES group_events ON DELETE CASCADE,
dag_error TEXT NOT NULL, referred_group_event_id INTEGER REFERENCES group_events ON DELETE SET NULL,
referred_group_member_id INTEGER NOT NULL REFERENCES group_members ON DELETE CASCADE,
referred_group_member_role TEXT NOT NULL,
error TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')), created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')) updated_at TEXT NOT NULL DEFAULT (datetime('now'))
); );
CREATE INDEX idx_group_events_dag_errors_group_event_id ON group_events_dag_errors(group_event_id); CREATE INDEX idx_group_events_errors_group_event_id ON group_events_errors(group_event_id);
CREATE INDEX idx_group_events_errors_referred_group_event_id ON group_events_errors(referred_group_event_id);
CREATE INDEX idx_group_events_errors_referred_group_member_id ON group_events_errors(referred_group_member_id);
CREATE TABLE group_events_confirmations (
group_event_confirmation_id INTEGER PRIMARY KEY,
group_event_id INTEGER NOT NULL REFERENCES group_events ON DELETE CASCADE,
confirming_group_event_id INTEGER REFERENCES group_events ON DELETE SET NULL,
confirmed_by_group_member_id INTEGER NOT NULL REFERENCES group_members ON DELETE CASCADE,
confirmed_by_group_member_role TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX idx_group_events_confirmations_group_event_id ON group_events_confirmations(group_event_id);
CREATE INDEX idx_group_events_confirmations_confirming_group_event_id ON group_events_confirmations(confirming_group_event_id);
CREATE INDEX idx_group_events_confirmations_confirmed_by_group_member_id ON group_events_confirmations(confirmed_by_group_member_id);
CREATE TABLE group_events_parents ( CREATE TABLE group_events_parents (
group_event_parent_id INTEGER NOT NULL REFERENCES group_events ON DELETE CASCADE, group_event_parent_id INTEGER NOT NULL REFERENCES group_events ON DELETE CASCADE,
@ -79,24 +101,28 @@ down_m20231101_group_events =
[sql| [sql|
DROP INDEX idx_group_events_parents_group_event_parent_id; DROP INDEX idx_group_events_parents_group_event_parent_id;
DROP INDEX idx_group_events_parents_group_event_child_id; DROP INDEX idx_group_events_parents_group_event_child_id;
DROP TABLE group_events_parents; DROP TABLE group_events_parents;
DROP INDEX idx_group_events_dag_errors_group_event_id; DROP INDEX idx_group_events_confirmations_group_event_id;
DROP INDEX idx_group_events_confirmations_confirming_group_event_id;
DROP INDEX idx_group_events_confirmations_confirmed_by_group_member_id;
DROP TABLE group_events_confirmations;
DROP TABLE group_events_dag_errors; DROP INDEX idx_group_events_errors_group_event_id;
DROP INDEX idx_group_events_errors_referred_group_event_id;
DROP INDEX idx_group_events_errors_referred_group_member_id;
DROP TABLE group_events_errors;
DROP INDEX idx_group_events_availabilities_group_event_id; DROP INDEX idx_group_events_availabilities_group_event_id;
DROP INDEX idx_group_events_availabilities_available_at_group_member_id; DROP INDEX idx_group_events_availabilities_available_at_group_member_id;
DROP TABLE group_events_availabilities; DROP TABLE group_events_availabilities;
DROP INDEX idx_group_events_user_id; DROP INDEX idx_group_events_user_id;
DROP INDEX idx_group_events_chat_item_id; DROP INDEX idx_group_events_chat_item_id;
DROP INDEX idx_group_events_shared_msg_id;
DROP INDEX idx_group_events_rcvd_author_group_member_id; DROP INDEX idx_group_events_rcvd_author_group_member_id;
DROP INDEX idx_group_events_rcvd_author_contact_profile_id; DROP INDEX idx_group_events_rcvd_author_contact_profile_id;
DROP INDEX idx_group_events_rcvd_from_group_member_id; DROP INDEX idx_group_events_rcvd_from_group_member_id;
DROP INDEX idx_group_events_rcvd_from_contact_profile_id; DROP INDEX idx_group_events_rcvd_from_contact_profile_id;
DROP TABLE group_events; DROP TABLE group_events;
|] |]

View File

@ -530,18 +530,20 @@ CREATE TABLE group_events(
event_data TEXT NOT NULL, -- eventData :: StoredGroupEventData event_data TEXT NOT NULL, -- eventData :: StoredGroupEventData
shared_hash BLOB NOT NULL, -- sharedHash :: ByteString shared_hash BLOB NOT NULL, -- sharedHash :: ByteString
event_sent INTEGER NOT NULL, -- 0 for received, 1 for sent; below `rcvd_` fields are null for sent event_sent INTEGER NOT NULL, -- 0 for received, 1 for sent; below `rcvd_` fields are null for sent
rcvd_author_member_id BLOB, -- ReceivedEventInfo authorMemberId :: MemberId -- ReceivedEventInfo fields:
rcvd_author_member_name TEXT, -- ReceivedEventInfo authorMemberName :: ContactName rcvd_author_member_id BLOB, -- authorMemberId :: MemberId
-- ReceivedEventInfo authorMember :: Maybe GroupMemberRef; can be null even for received event rcvd_author_member_name TEXT, -- authorMemberName :: ContactName
rcvd_author_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL, -- authorMember :: GroupMemberRef
rcvd_author_contact_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL, rcvd_author_group_member_id INTEGER REFERENCES group_members ON DELETE CASCADE,
-- rcvd_author_role TEXT NOT NULL, -- ReceivedFromRole - store in case it changes? rcvd_author_contact_profile_id INTEGER REFERENCES contact_profiles ON DELETE CASCADE,
-- ReceivedEventInfo receivedFrom :: GroupMemberRef rcvd_author_role TEXT,
rcvd_from_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL, -- receivedFrom :: GroupMemberRef
rcvd_from_contact_profile_id INTEGER REFERENCES contact_profiles ON DELETE SET NULL, rcvd_from_group_member_id INTEGER REFERENCES group_members ON DELETE CASCADE,
rcvd_processing TEXT NOT NULL, -- ReceivedEventInfo processing :: EventProcessing rcvd_from_contact_profile_id INTEGER REFERENCES contact_profiles ON DELETE CASCADE,
rcvd_processed INTEGER NOT NULL DEFAULT 0, -- 1 for processed; when retrieving unprocessed rcvd_from_role TEXT,
-- rcvd_scheduled_at TEXT, -- EPScheduled UTCTime; when retrieving scheduled at near time? -- ReceivedEventInfo processing :: EventProcessing
rcvd_processed_at TEXT, -- EPProcessed UTCTime
rcvd_scheduled_at TEXT, -- EPScheduled UTCTime; both this and rcvd_processed_at are null -> EPPendingConfirmation
created_at TEXT NOT NULL DEFAULT(datetime('now')), created_at TEXT NOT NULL DEFAULT(datetime('now')),
updated_at TEXT NOT NULL DEFAULT(datetime('now')) updated_at TEXT NOT NULL DEFAULT(datetime('now'))
); );
@ -552,10 +554,22 @@ CREATE TABLE group_events_availabilities(
created_at TEXT NOT NULL DEFAULT(datetime('now')), created_at TEXT NOT NULL DEFAULT(datetime('now')),
updated_at TEXT NOT NULL DEFAULT(datetime('now')) updated_at TEXT NOT NULL DEFAULT(datetime('now'))
); );
CREATE TABLE group_events_dag_errors( CREATE TABLE group_events_errors(
group_event_dag_error_id INTEGER PRIMARY KEY, group_event_dag_error_id INTEGER PRIMARY KEY,
group_event_id INTEGER NOT NULL REFERENCES group_events ON DELETE CASCADE, group_event_id INTEGER NOT NULL REFERENCES group_events ON DELETE CASCADE,
dag_error TEXT NOT NULL, referred_group_event_id INTEGER REFERENCES group_events ON DELETE SET NULL,
referred_group_member_id INTEGER NOT NULL REFERENCES group_members ON DELETE CASCADE,
referred_group_member_role TEXT NOT NULL,
error TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT(datetime('now')),
updated_at TEXT NOT NULL DEFAULT(datetime('now'))
);
CREATE TABLE group_events_confirmations(
group_event_confirmation_id INTEGER PRIMARY KEY,
group_event_id INTEGER NOT NULL REFERENCES group_events ON DELETE CASCADE,
confirming_group_event_id INTEGER REFERENCES group_events ON DELETE SET NULL,
confirmed_by_group_member_id INTEGER NOT NULL REFERENCES group_members ON DELETE CASCADE,
confirmed_by_group_member_role TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT(datetime('now')), created_at TEXT NOT NULL DEFAULT(datetime('now')),
updated_at TEXT NOT NULL DEFAULT(datetime('now')) updated_at TEXT NOT NULL DEFAULT(datetime('now'))
); );
@ -796,6 +810,7 @@ CREATE INDEX idx_connections_via_contact_uri_hash ON connections(
); );
CREATE INDEX idx_group_events_user_id ON group_events(user_id); CREATE INDEX idx_group_events_user_id ON group_events(user_id);
CREATE INDEX idx_group_events_chat_item_id ON group_events(chat_item_id); CREATE INDEX idx_group_events_chat_item_id ON group_events(chat_item_id);
CREATE INDEX idx_group_events_shared_msg_id ON group_events(shared_msg_id);
CREATE INDEX idx_group_events_rcvd_author_group_member_id ON group_events( CREATE INDEX idx_group_events_rcvd_author_group_member_id ON group_events(
rcvd_author_group_member_id rcvd_author_group_member_id
); );
@ -814,9 +829,24 @@ CREATE INDEX idx_group_events_availabilities_group_event_id ON group_events_avai
CREATE INDEX idx_group_events_availabilities_available_at_group_member_id ON group_events_availabilities( CREATE INDEX idx_group_events_availabilities_available_at_group_member_id ON group_events_availabilities(
available_at_group_member_id available_at_group_member_id
); );
CREATE INDEX idx_group_events_dag_errors_group_event_id ON group_events_dag_errors( CREATE INDEX idx_group_events_errors_group_event_id ON group_events_errors(
group_event_id group_event_id
); );
CREATE INDEX idx_group_events_errors_referred_group_event_id ON group_events_errors(
referred_group_event_id
);
CREATE INDEX idx_group_events_errors_referred_group_member_id ON group_events_errors(
referred_group_member_id
);
CREATE INDEX idx_group_events_confirmations_group_event_id ON group_events_confirmations(
group_event_id
);
CREATE INDEX idx_group_events_confirmations_confirming_group_event_id ON group_events_confirmations(
confirming_group_event_id
);
CREATE INDEX idx_group_events_confirmations_confirmed_by_group_member_id ON group_events_confirmations(
confirmed_by_group_member_id
);
CREATE INDEX idx_group_events_parents_group_event_parent_id ON group_events_parents( CREATE INDEX idx_group_events_parents_group_event_parent_id ON group_events_parents(
group_event_parent_id group_event_parent_id
); );

View File

@ -710,14 +710,14 @@ instance ToJSON GroupMember where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True} toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data GroupMemberRef = GroupMemberRef {groupMemberId :: Int64, profile :: Profile} data GroupMemberRef = GroupMemberRef {groupMemberId :: Int64, role :: GroupMemberRole, profile :: Profile}
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show, Generic, FromJSON)
instance ToJSON GroupMemberRef where toEncoding = J.genericToEncoding J.defaultOptions instance ToJSON GroupMemberRef where toEncoding = J.genericToEncoding J.defaultOptions
groupMemberRef :: GroupMember -> GroupMemberRef groupMemberRef :: GroupMember -> GroupMemberRef
groupMemberRef GroupMember {groupMemberId, memberProfile = p} = groupMemberRef GroupMember {groupMemberId, memberRole, memberProfile = p} =
GroupMemberRef {groupMemberId, profile = fromLocalProfile p} GroupMemberRef {groupMemberId, role = memberRole, profile = fromLocalProfile p}
memberConn :: GroupMember -> Maybe Connection memberConn :: GroupMember -> Maybe Connection
memberConn GroupMember{activeConn} = activeConn memberConn GroupMember{activeConn} = activeConn
@ -791,6 +791,8 @@ fromInvitedBy userCtId = \case
IBContact ctId -> Just ctId IBContact ctId -> Just ctId
IBUser -> Just userCtId IBUser -> Just userCtId
-- add:
-- | GRUnknown -- used for unconfirmed members (learnt through group event parent)
data GroupMemberRole data GroupMemberRole
= GRObserver -- connects to all group members and receives all messages, can't send messages = GRObserver -- connects to all group members and receives all messages, can't send messages
| GRAuthor -- reserved, unused | GRAuthor -- reserved, unused
@ -897,6 +899,8 @@ instance TextEncoding GroupMemberCategory where
GCPreMember -> "pre" GCPreMember -> "pre"
GCPostMember -> "post" GCPostMember -> "post"
-- add:
-- | GSMemUnconfirmed -- used for unconfirmed members (learnt through group event parent)
data GroupMemberStatus data GroupMemberStatus
= GSMemRemoved -- member who was removed from the group = GSMemRemoved -- member who was removed from the group
| GSMemLeft -- member who left the group | GSMemLeft -- member who left the group