core, ui: public group profile wip (#6734)

This commit is contained in:
spaced4ndy
2026-04-01 14:17:27 +00:00
committed by GitHub
parent dfd8e224f6
commit 42fe94752c
26 changed files with 323 additions and 147 deletions
+1 -1
View File
@@ -1361,7 +1361,7 @@ enum ContactAddressPlan: Decodable, Hashable {
public struct GroupShortLinkInfo: Decodable, Hashable {
public var direct: Bool
public var groupRelays: [String]
public var sharedGroupId: String?
public var publicGroupId: String?
}
enum GroupLinkPlan: Decodable, Hashable {
@@ -106,7 +106,7 @@ struct GroupChatInfoView: View {
// TODO [relays] allow other owners to manage channel link (requires protocol changes to share link ownership)
if groupInfo.isOwner && groupLink != nil {
channelLinkButton()
} else if let link = groupInfo.groupProfile.groupLink {
} else if let link = groupInfo.groupProfile.publicGroup?.groupLink {
SimpleXLinkQRCode(uri: link)
Button {
showShareSheet(items: [simplexChatLink(link)])
@@ -118,7 +118,7 @@ struct GroupChatInfoView: View {
channelMembersButton()
}
} footer: {
if !groupInfo.isOwner && groupInfo.groupProfile.groupLink != nil {
if !groupInfo.isOwner && groupInfo.groupProfile.publicGroup?.groupLink != nil {
Text("You can share a link or a QR code - anybody will be able to join the channel.")
.foregroundColor(theme.colors.secondary)
}
+31 -3
View File
@@ -2432,6 +2432,34 @@ public struct GroupRef: Decodable, Hashable {
var localDisplayName: GroupName
}
public enum GroupType: Codable, Hashable {
case channel
case unknown(type: String)
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let type = try container.decode(String.self)
switch type {
case "channel": self = .channel
default: self = .unknown(type: type)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .channel: try container.encode("channel")
case let .unknown(type): try container.encode(type)
}
}
}
public struct PublicGroupProfile: Codable, Hashable {
public var groupType: GroupType
public var groupLink: String
public var publicGroupId: String
}
public struct GroupProfile: Codable, NamedChat, Hashable {
public init(
displayName: String,
@@ -2439,7 +2467,7 @@ public struct GroupProfile: Codable, NamedChat, Hashable {
shortDescr: String? = nil,
description: String? = nil,
image: String? = nil,
groupLink: String? = nil,
publicGroup: PublicGroupProfile? = nil,
groupPreferences: GroupPreferences? = nil,
memberAdmission: GroupMemberAdmission? = nil
) {
@@ -2448,7 +2476,7 @@ public struct GroupProfile: Codable, NamedChat, Hashable {
self.shortDescr = shortDescr
self.description = description
self.image = image
self.groupLink = groupLink
self.publicGroup = publicGroup
self.groupPreferences = groupPreferences
self.memberAdmission = memberAdmission
}
@@ -2458,7 +2486,7 @@ public struct GroupProfile: Codable, NamedChat, Hashable {
public var shortDescr: String?
public var description: String?
public var image: String?
public var groupLink: String?
public var publicGroup: PublicGroupProfile?
public var groupPreferences: GroupPreferences?
public var memberAdmission: GroupMemberAdmission?
public var localAlias: String { "" }
+1 -1
View File
@@ -138,7 +138,7 @@ Establishing contact between two SimpleX Chat users. SimpleX uses no user identi
| `Contact` | `SimpleXChat/ChatTypes.swift` | Full contact model with profile, connection status, preferences |
| `UserContactRequest` | `SimpleXChat/ChatTypes.swift` | Incoming contact request awaiting acceptance |
| `ChatType` | `SimpleXChat/ChatTypes.swift` | `.direct`, `.group`, `.local`, `.contactRequest`, `.contactConnection` |
| `GroupShortLinkInfo` | `Shared/Model/AppAPITypes.swift` | Contains `direct: Bool`, `groupRelays: [String]`, `sharedGroupId: String?`; transient data returned by prepare |
| `GroupShortLinkInfo` | `Shared/Model/AppAPITypes.swift` | Contains `direct: Bool`, `groupRelays: [String]`, `publicGroupId: String?`; transient data returned by prepare |
| `RelayConnectionResult` | `Shared/Model/AppAPITypes.swift` | Contains `relayMember: GroupMember`, `relayError: ChatError?`; per-relay join outcome |
## Error Cases
+1 -1
View File
@@ -138,7 +138,7 @@ The top section splits into a channel-specific branch:
| Element | Owner | Non-owner |
|---|---|---|
| Channel link | NavigationLink "Channel link" to `GroupLinkView` | Inline QR code (`SimpleXLinkQRCode`) + "Share link" button (if `groupProfile.groupLink` exists) |
| Channel link | NavigationLink "Channel link" to `GroupLinkView` | Inline QR code (`SimpleXLinkQRCode`) + "Share link" button (if `groupProfile.publicGroup?.groupLink` exists) |
| Members | NavigationLink "Owners & subscribers" to `ChannelMembersView` | NavigationLink "Owners" to `ChannelMembersView` |
| Relays | NavigationLink "Chat relays" to `ChannelRelaysView` | NavigationLink "Chat relays" to `ChannelRelaysView` |
+2 -2
View File
@@ -322,7 +322,7 @@ When `groupInfo.useRelays == true`, [`GroupChatInfoView`](../../Shared/Views/Cha
| Section | Owner | Subscriber |
|---------|-------|-----------|
| 1. Links & Members | Channel link (manage via GroupLinkView), Owners & subscribers | Channel link (read-only QR from `groupProfile.groupLink`), Owners |
| 1. Links & Members | Channel link (manage via GroupLinkView), Owners & subscribers | Channel link (read-only QR from `groupProfile.publicGroup?.groupLink`), Owners |
| 2. Profile & Welcome | Edit channel profile, Welcome message | Welcome message (if exists) |
| 3. Theme & TTL | Chat theme, Delete messages after | Chat theme, Delete messages after |
| 4. Actions | Chat relays, Clear chat, Delete channel | Chat relays, Clear chat, Leave channel |
@@ -343,7 +343,7 @@ Member rows show profile image, display name (with verified shield), connection
### Channel Link
Owner sees [`channelLinkButton()`](../../Shared/Views/Chat/Group/GroupChatInfoView.swift#L605) (navigates to `GroupLinkView` for full link management), guarded by `groupInfo.isOwner && groupLink != nil` — channel links can only be created during channel creation, not from the info view. A TODO marks the need for protocol changes to allow other owners to manage the same channel link. Non-owner sees read-only QR code displaying `groupProfile.groupLink` via `SimpleXLinkQRCode`. `apiGetGroupLink` is skipped in `onAppear` for non-owner channels.
Owner sees [`channelLinkButton()`](../../Shared/Views/Chat/Group/GroupChatInfoView.swift#L605) (navigates to `GroupLinkView` for full link management), guarded by `groupInfo.isOwner && groupLink != nil` — channel links can only be created during channel creation, not from the info view. A TODO marks the need for protocol changes to allow other owners to manage the same channel link. Non-owner sees read-only QR code displaying `groupProfile.publicGroup?.groupLink` via `SimpleXLinkQRCode`. `apiGetGroupLink` is skipped in `onAppear` for non-owner channels.
Groups use separate [`groupLinkButton()`](../../Shared/Views/Chat/Group/GroupChatInfoView.swift#L593) which supports both "Create group link" and "Group link" labels.
+1 -1
View File
@@ -384,7 +384,7 @@ A **channel** is a group with `groupInfo.useRelays == true`. These types support
| `User` | `userChatRelay` | `Bool` | Whether user acts as a chat relay | [L46](../SimpleXChat/ChatTypes.swift#L46) |
| `GroupInfo` | `useRelays` | `Bool` | Whether group uses relay infrastructure (channel mode) | [L2343](../SimpleXChat/ChatTypes.swift#L2343) |
| `GroupInfo` | `relayOwnStatus` | `RelayStatus?` | Current user's relay status in this group | [L2344](../SimpleXChat/ChatTypes.swift#L2344) |
| `GroupProfile` | `groupLink` | `String?` | Group's short link | [L2452](../SimpleXChat/ChatTypes.swift#L2452) |
| `GroupProfile` | `publicGroup` | `PublicGroupProfile?` | Channel-specific profile data (type, link, ID) | [L2472](../SimpleXChat/ChatTypes.swift#L2472) |
#### New Types
@@ -2163,6 +2163,39 @@ data class PreparedGroup (
@Serializable
data class GroupRef(val groupId: Long, val localDisplayName: String)
@Serializable(with = GroupTypeSerializer::class)
sealed class GroupType {
@Serializable @SerialName("channel") object Channel: GroupType()
@Serializable @SerialName("unknown") data class Unknown(val type: String): GroupType()
}
object GroupTypeSerializer : KSerializer<GroupType> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("GroupType", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): GroupType {
return when (val value = decoder.decodeString()) {
"channel" -> GroupType.Channel
else -> GroupType.Unknown(value)
}
}
override fun serialize(encoder: Encoder, value: GroupType) {
val stringValue = when (value) {
is GroupType.Channel -> "channel"
is GroupType.Unknown -> value.type
}
encoder.encodeString(stringValue)
}
}
@Serializable
data class PublicGroupProfile(
val groupType: GroupType,
val groupLink: String,
val publicGroupId: String
)
@Serializable
data class GroupProfile (
override val displayName: String,
@@ -2170,7 +2203,7 @@ data class GroupProfile (
override val shortDescr: String?,
val description: String? = null,
override val image: String? = null,
val groupLink: String? = null,
val publicGroup: PublicGroupProfile? = null,
override val localAlias: String = "",
val groupPreferences: GroupPreferences? = null,
val memberAdmission: GroupMemberAdmission? = null
@@ -4548,7 +4548,7 @@ data class RelayConnectionResult(
data class GroupShortLinkInfo(
val direct: Boolean,
val groupRelays: List<String>,
val sharedGroupId: String? = null
val publicGroupId: String? = null
)
@Serializable
@@ -545,21 +545,22 @@ fun ModalData.GroupChatInfoLayout(
}
var anyTopSectionRowShow = false
val channelLink = groupInfo.groupProfile.publicGroup?.groupLink
if (groupInfo.useRelays) {
SectionView {
if (groupInfo.isOwner && groupLink != null) {
anyTopSectionRowShow = true
ChannelLinkButton(manageGroupLink)
} else if (groupInfo.groupProfile.groupLink != null) {
} else if (channelLink != null) {
anyTopSectionRowShow = true
ChannelLinkQRCodeSection(groupInfo.groupProfile.groupLink!!)
ChannelLinkQRCodeSection(channelLink)
}
if (groupInfo.isOwner || activeSortedMembers.any { it.memberRole >= GroupMemberRole.Owner }) {
anyTopSectionRowShow = true
ChannelMembersButton(chat.remoteHostId, groupInfo, showMemberInfo)
}
}
if (!groupInfo.isOwner && groupInfo.groupProfile.groupLink != null) {
if (!groupInfo.isOwner && channelLink != null) {
SectionTextFooter(stringResource(MR.strings.you_can_share_channel_link_anybody_will_be_able_to_connect))
}
} else {
+23 -4
View File
@@ -109,6 +109,7 @@ This file is generated automatically.
- [GroupShortLinkInfo](#groupshortlinkinfo)
- [GroupSummary](#groupsummary)
- [GroupSupportChat](#groupsupportchat)
- [GroupType](#grouptype)
- [HandshakeError](#handshakeerror)
- [InlineFileMode](#inlinefilemode)
- [InvitationLinkPlan](#invitationlinkplan)
@@ -138,6 +139,7 @@ This file is generated automatically.
- [ProxyClientError](#proxyclienterror)
- [ProxyError](#proxyerror)
- [PublicGroupData](#publicgroupdata)
- [PublicGroupProfile](#publicgroupprofile)
- [RCErrorType](#rcerrortype)
- [RatchetSyncState](#ratchetsyncstate)
- [RcvConnEvent](#rcvconnevent)
@@ -2199,7 +2201,7 @@ MemberSupport:
## GroupKeys
**Record type**:
- sharedGroupId: string
- publicGroupId: string
- groupRootKey: [GroupRootKey](#grouprootkey)
- memberPrivKey: string
@@ -2382,10 +2384,9 @@ Known:
- shortDescr: string?
- description: string?
- image: string?
- groupLink: string?
- publicGroup: [PublicGroupProfile](#publicgroupprofile)?
- groupPreferences: [GroupPreferences](#grouppreferences)?
- memberAdmission: [GroupMemberAdmission](#groupmemberadmission)?
- sharedGroupId: string?
---
@@ -2431,7 +2432,7 @@ Public:
**Record type**:
- direct: bool
- groupRelays: [string]
- sharedGroupId: string?
- publicGroupId: string?
---
@@ -2455,6 +2456,14 @@ Public:
- lastMsgFromMemberTs: UTCTime?
---
## GroupType
**Enum type**:
- "channel"
---
## HandshakeError
@@ -2915,6 +2924,16 @@ NO_SESSION:
- publicMemberCount: int64
---
## PublicGroupProfile
**Record type**:
- groupType: [GroupType](#grouptype)
- groupLink: string
- publicGroupId: string
---
## RCErrorType
+4
View File
@@ -291,6 +291,7 @@ chatTypesDocsData =
(sti @GroupShortLinkInfo, STRecord, "", [], "", ""),
(sti @GroupSummary, STRecord, "", [], "", ""),
(sti @GroupSupportChat, STRecord, "", [], "", ""),
(sti @GroupType, STEnum1, "GT", ["GTUnknown"], "", ""),
(sti @HandshakeError, STEnum, "", [], "", ""),
(sti @InlineFileMode, STEnum, "IFM", [], "", ""),
(sti @InvitationLinkPlan, STUnion, "ILP", [], "", ""),
@@ -321,6 +322,7 @@ chatTypesDocsData =
(sti @ProxyClientError, STUnion, "Proxy", [], "", ""),
(sti @ProxyError, STUnion, "", [], "", ""),
(sti @PublicGroupData, STRecord, "", [], "", ""),
(sti @PublicGroupProfile, STRecord, "", [], "", ""),
(sti @RatchetSyncState, STEnum, "RS", [], "", ""),
(sti @RCErrorType, STUnion, "RCE", [], "", ""),
(sti @RcvConnEvent, STUnion, "RCE", [], "", ""),
@@ -484,6 +486,7 @@ deriving instance Generic GroupProfile
deriving instance Generic GroupRelay
deriving instance Generic GroupShortLinkData
deriving instance Generic GroupShortLinkInfo
deriving instance Generic GroupType
deriving instance Generic GroupSummary
deriving instance Generic GroupSupportChat
deriving instance Generic HandshakeError
@@ -522,6 +525,7 @@ deriving instance Generic Profile
deriving instance Generic ProxyClientError
deriving instance Generic ProxyError
deriving instance Generic PublicGroupData
deriving instance Generic PublicGroupProfile
deriving instance Generic RatchetSyncState
deriving instance Generic RCErrorType
deriving instance Generic RcvConnEvent
@@ -2519,7 +2519,7 @@ export interface GroupInfo {
}
export interface GroupKeys {
sharedGroupId: string
publicGroupId: string
groupRootKey: GroupRootKey
memberPrivKey: string
}
@@ -2671,10 +2671,9 @@ export interface GroupProfile {
shortDescr?: string
description?: string
image?: string
groupLink?: string
publicGroup?: PublicGroupProfile
groupPreferences?: GroupPreferences
memberAdmission?: GroupMemberAdmission
sharedGroupId?: string
}
export interface GroupRelay {
@@ -2713,7 +2712,7 @@ export interface GroupShortLinkData {
export interface GroupShortLinkInfo {
direct: boolean
groupRelays: string[]
sharedGroupId?: string
publicGroupId?: string
}
export interface GroupSummary {
@@ -2729,6 +2728,10 @@ export interface GroupSupportChat {
lastMsgFromMemberTs?: string // ISO-8601 timestamp
}
export enum GroupType {
Channel = "channel",
}
export enum HandshakeError {
PARSE = "PARSE",
IDENTITY = "IDENTITY",
@@ -3216,6 +3219,12 @@ export interface PublicGroupData {
publicMemberCount: number // int64
}
export interface PublicGroupProfile {
groupType: GroupType
groupLink: string
publicGroupId: string
}
export type RCErrorType =
| RCErrorType.Internal
| RCErrorType.Identity
+1 -1
View File
@@ -1014,7 +1014,7 @@ type DirectLink = Bool
data GroupShortLinkInfo = GroupShortLinkInfo
{ direct :: Bool,
groupRelays :: [ShortLinkContact],
sharedGroupId :: Maybe B64UrlByteString
publicGroupId :: Maybe B64UrlByteString
}
deriving (Show)
+21 -15
View File
@@ -1686,9 +1686,9 @@ processChatCommand vr nm = \case
APIGroupInfo gId -> withUser $ \user ->
CRGroupInfo user <$> withFastStore (\db -> getGroupInfo db vr user gId)
APIGetUpdatedGroupLinkData groupId -> withUser $ \user -> do
gInfo@GroupInfo {groupProfile = GroupProfile {groupLink}} <- withFastStore $ \db -> getGroupInfo db vr user groupId
case groupLink of
Just sLnk | useRelays' gInfo -> do
gInfo@GroupInfo {groupProfile = GroupProfile {publicGroup}} <- withFastStore $ \db -> getGroupInfo db vr user groupId
case publicGroup of
Just PublicGroupProfile {groupLink = sLnk} | useRelays' gInfo -> do
(_, cData) <- getShortLinkConnReq nm user sLnk
groupSLinkData_ <- liftIO $ decodeLinkUserData cData
let publicGroupData_ = groupSLinkData_ >>= \GroupShortLinkData {publicGroupData} -> publicGroupData
@@ -2024,9 +2024,11 @@ processChatCommand vr nm = \case
Nothing -> throwChatError $ CEException "failed to retrieve relays: no short link"
(FixedLinkData {linkConnReq = mainCReq@(CRContactUri crData), linkEntityId, rootKey}, cData@(ContactLinkData _ UserContactData {owners, relays})) <- getShortLinkConnReq nm user sLnk
groupSLinkData_ <- liftIO $ decodeLinkUserData cData
-- Validate link entity ID matches group profile's sharedGroupId (relay groups must have both)
forM_ groupSLinkData_ $ \GroupShortLinkData {groupProfile = GroupProfile {sharedGroupId}} ->
unless ((B64UrlByteString <$> linkEntityId) == sharedGroupId) $ throwChatError CEInvalidConnReq
-- Validate link entity ID matches group profile's publicGroupId (relay groups must have both)
case groupSLinkData_ of
Just GroupShortLinkData {groupProfile = GroupProfile {publicGroup = Just PublicGroupProfile {publicGroupId}}}
| (B64UrlByteString <$> linkEntityId) == Just publicGroupId -> pure ()
_ -> throwChatError CEInvalidConnReq
let publicGroupData_ = groupSLinkData_ >>= \GroupShortLinkData {publicGroupData} -> publicGroupData
publicMemberCount_ = (\PublicGroupData {publicMemberCount} -> publicMemberCount) <$> publicGroupData_
-- Prepare group record once before connecting to relays (updatePreparedRelayedGroup):
@@ -2036,7 +2038,7 @@ processChatCommand vr nm = \case
gVar <- asks random
(_, memberPrivKey) <- liftIO $ atomically $ C.generateKeyPair gVar
gInfo' <- withFastStore $ \db -> do
gInfo' <- updatePreparedRelayedGroup db vr user gInfo mainCReq cReqHash incognitoProfile linkEntityId rootKey memberPrivKey publicMemberCount_
gInfo' <- updatePreparedRelayedGroup db vr user gInfo mainCReq cReqHash incognitoProfile rootKey memberPrivKey publicMemberCount_
-- Pre-emptively create owner members with trusted keys from link data
forM_ owners $ \OwnerAuth {ownerId, ownerKey} ->
void $ createLinkOwnerMember db vr user gInfo' (MemberId ownerId) ownerKey
@@ -2400,12 +2402,12 @@ processChatCommand vr nm = \case
-- generate owner key, OwnerAuth signed by root key
memberId <- MemberId <$> liftIO (encodedRandomBytes gVar 12)
(memberPrivKey, ownerAuth) <- liftIO $ SL.newOwnerAuth gVar (unMemberId memberId) rootPrivKey
let groupProfile' = (groupProfile :: GroupProfile) {groupLink = Just sLnk, sharedGroupId = Just $ B64UrlByteString entityId}
let groupProfile' = (groupProfile :: GroupProfile) {publicGroup = Just PublicGroupProfile {groupType = GTChannel, groupLink = sLnk, publicGroupId = B64UrlByteString entityId}}
userData = encodeShortLinkData $ GroupShortLinkData {groupProfile = groupProfile', publicGroupData = Just (PublicGroupData 1)}
userLinkData = UserContactLinkData UserContactData {direct = False, owners = [ownerAuth], relays = [], userData}
-- create connection with prepared link (single network call)
connId <- withAgent $ \a -> createConnectionForLink a nm (aUserId user) True ccLink preparedParams userLinkData IKPQOff subMode
let groupKeys = GroupKeys {sharedGroupId = B64UrlByteString entityId, groupRootKey = GRKPrivate rootPrivKey, memberPrivKey}
let groupKeys = GroupKeys {publicGroupId = B64UrlByteString entityId, groupRootKey = GRKPrivate rootPrivKey, memberPrivKey}
setupLink gInfo = do
-- TODO [relays] starting role should be communicated in protocol from owner to relays
subRole <- asks $ channelSubscriberRole . config
@@ -3900,12 +3902,16 @@ processChatCommand vr nm = \case
Nothing -> do
(fd, cData@(ContactLinkData _ UserContactData {direct, relays})) <- getShortLinkConnReq nm user l'
let FixedLinkData {linkConnReq = cReq, linkEntityId} = fd
linkInfo = GroupShortLinkInfo {direct, groupRelays = relays, sharedGroupId = B64UrlByteString <$> linkEntityId}
linkInfo = GroupShortLinkInfo {direct, groupRelays = relays, publicGroupId = B64UrlByteString <$> linkEntityId}
groupSLinkData_ <- liftIO $ decodeLinkUserData cData
-- Validate link entity ID matches group profile's sharedGroupId
forM_ groupSLinkData_ $ \GroupShortLinkData {groupProfile = GroupProfile {sharedGroupId}} ->
unless ((B64UrlByteString <$> linkEntityId) == sharedGroupId) $
throwChatError CEInvalidConnReq
-- Cross-validate linkEntityId and publicGroupId from profile:
-- for channels both must be present and match, for p2p groups both must be absent
let profilePGId = groupSLinkData_ >>= \GroupShortLinkData {groupProfile = GroupProfile {publicGroup}} ->
fmap (\PublicGroupProfile {publicGroupId} -> publicGroupId) publicGroup
case (B64UrlByteString <$> linkEntityId, profilePGId) of
(Just entityId, Just publicGroupId) | entityId == publicGroupId -> pure ()
(Nothing, Nothing) -> pure ()
_ -> throwChatError CEInvalidConnReq
plan <- groupJoinRequestPlan user cReq (Just linkInfo) groupSLinkData_
pure (con cReq, plan)
where
@@ -5098,7 +5104,7 @@ chatCommandP =
{ directMessages = Just DirectMessagesGroupPreference {enable = FEOn, role = Nothing},
history = Just HistoryGroupPreference {enable = FEOn}
}
pure GroupProfile {displayName = gName, fullName = "", shortDescr, description = Nothing, image = Nothing, groupLink = Nothing, groupPreferences, memberAdmission = Nothing, sharedGroupId = Nothing}
pure GroupProfile {displayName = gName, fullName = "", shortDescr, description = Nothing, image = Nothing, publicGroup = Nothing, groupPreferences, memberAdmission = Nothing}
memberCriteriaP = ("all" $> Just MCAll) <|> ("off" $> Nothing)
shortDescrP = do
descr <- A.takeWhile1 isSpace *> (T.dropWhileEnd isSpace <$> textP) <|> pure ""
+3 -3
View File
@@ -1054,7 +1054,7 @@ acceptRelayJoinRequestAsync
businessGroupProfile :: Profile -> GroupPreferences -> GroupProfile
businessGroupProfile Profile {displayName, fullName, shortDescr, image} groupPreferences =
GroupProfile {displayName, fullName, description = Nothing, shortDescr, image, groupLink = Nothing, groupPreferences = Just groupPreferences, memberAdmission = Nothing, sharedGroupId = Nothing}
GroupProfile {displayName, fullName, description = Nothing, shortDescr, image, publicGroup = Nothing, groupPreferences = Just groupPreferences, memberAdmission = Nothing}
introduceToModerators :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> CM ()
introduceToModerators vr user gInfo@GroupInfo {groupId} m@GroupMember {memberRole, memberId} = do
@@ -1881,9 +1881,9 @@ createSndMessages idsEvents = do
encodeChatMessage maxEncodedMsgLength ChatMessage {chatVRange = vr, msgId = Just sharedMsgId, chatMsgEvent = evnt}
groupMsgSigning :: GroupInfo -> ChatMsgEvent e -> Maybe MsgSigning
groupMsgSigning gInfo@GroupInfo {membership = GroupMember {memberId}, groupKeys = Just GroupKeys {sharedGroupId, memberPrivKey}} evt
groupMsgSigning gInfo@GroupInfo {membership = GroupMember {memberId}, groupKeys = Just GroupKeys {publicGroupId, memberPrivKey}} evt
| useRelays' gInfo && requiresSignature (toCMEventTag evt) =
Just $ MsgSigning CBGroup (smpEncode (sharedGroupId, memberId)) KRMember memberPrivKey
Just $ MsgSigning CBGroup (smpEncode (publicGroupId, memberId)) KRMember memberPrivKey
groupMsgSigning _ _ = Nothing
sendGroupMemberMessages :: forall e. MsgEncodingI e => User -> GroupInfo -> Connection -> NonEmpty (ChatMsgEvent e) -> CM ()
+19 -14
View File
@@ -743,8 +743,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
_ -> messageError "CONF from invited member must have x.grp.acpt"
GCHostMember ->
case chatMsgEvent of
XGrpLinkInv glInv@GroupLinkInvitation {groupProfile = GroupProfile {sharedGroupId = rcvGId}}
| let GroupInfo {groupProfile = GroupProfile {sharedGroupId = curGId}} = gInfo, rcvGId == curGId -> do
XGrpLinkInv glInv@GroupLinkInvitation {groupProfile = GroupProfile {publicGroup = rcvPG}}
| let GroupInfo {groupProfile = GroupProfile {publicGroup = curPG}} = gInfo
pgId = fmap (\PublicGroupProfile {publicGroupId} -> publicGroupId),
useRelays' gInfo == isJust rcvPG && pgId rcvPG == pgId curPG -> do
-- XGrpLinkInv here means we are connecting via prepared group, and we have to update user and host member records
(gInfo', m') <- withStore $ \db -> updatePreparedUserAndHostMembersInvited db vr user gInfo m glInv
-- [incognito] send saved profile
@@ -752,7 +754,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
let profileToSend = userProfileInGroup user gInfo (fromLocalProfile <$> incognitoProfile)
allowAgentConnectionAsync user conn' confId $ XInfo profileToSend
toView $ CEvtGroupLinkConnecting user gInfo' m'
| otherwise -> messageError "x.grp.link.inv: sharedGroupId mismatch"
| otherwise -> messageError "x.grp.link.inv: publicGroupId mismatch"
XGrpLinkReject glRjct@GroupLinkRejection {rejectionReason} -> do
(gInfo', m') <- withStore $ \db -> updatePreparedUserAndHostMembersRejected db vr user gInfo m glRjct
toView $ CEvtGroupLinkConnecting user gInfo' m'
@@ -2315,7 +2317,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
processGroupInvitation :: Contact -> GroupInvitation -> RcvMessage -> MsgMeta -> CM ()
processGroupInvitation ct inv msg msgMeta
| isJust groupLink || isJust sharedGroupId = messageError "x.grp.inv: can't invite to channel"
| isJust publicGroup = messageError "x.grp.inv: can't invite to channel"
| otherwise = do
let Contact {localDisplayName = c, activeConn} = ct
GroupInvitation {fromMember = (MemberIdRole fromMemId fromRole), invitedMember = (MemberIdRole memId memRole), connRequest, groupLinkId} = inv
@@ -2344,7 +2346,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv cInfo ci]
toView $ CEvtReceivedGroupInvitation {user, groupInfo = gInfo, contact = ct, fromMemberRole = fromRole, memberRole = memRole}
where
GroupInvitation {groupProfile = GroupProfile {groupLink, sharedGroupId}} = inv
GroupInvitation {groupProfile = GroupProfile {publicGroup}} = inv
brokerTs = metaBrokerTs msgMeta
sameGroupLinkId :: Maybe GroupLinkId -> Maybe GroupLinkId -> Bool
sameGroupLinkId (Just gli) (Just gli') = gli == gli'
@@ -3077,10 +3079,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
toView $ CEvtGroupDeleted user gInfo'' {membership = membership {memberStatus = GSMemGroupDeleted}} m' msgSigned
xGrpInfo :: GroupInfo -> GroupMember -> GroupProfile -> RcvMessage -> UTCTime -> CM (Maybe DeliveryJobScope)
xGrpInfo g@GroupInfo {groupProfile = p@GroupProfile {sharedGroupId = gId}, businessChat} m@GroupMember {memberRole} p'@GroupProfile {sharedGroupId = gId'} msg@RcvMessage {msgSigned} brokerTs
xGrpInfo g@GroupInfo {groupProfile = p@GroupProfile {publicGroup = pg}, businessChat} m@GroupMember {memberRole} p'@GroupProfile {publicGroup = pg'} msg@RcvMessage {msgSigned} brokerTs
| memberRole < GROwner = messageError "x.grp.info with insufficient member permissions" $> Nothing
| useRelays' g && gId' /= gId = messageError "x.grp.info: sharedGroupId cannot be changed" $> Nothing
| not (useRelays' g) && isJust gId' = messageError "x.grp.info: sharedGroupId not allowed in p2p groups" $> Nothing
| let pgId = fmap (\PublicGroupProfile {publicGroupId} -> publicGroupId),
useRelays' g && (isNothing pg' || pgId pg' /= pgId pg) = messageError "x.grp.info: publicGroupId mismatch for channel" $> Nothing
| not (useRelays' g) && isJust pg' = messageError "x.grp.info: publicGroup not allowed in p2p groups" $> Nothing
| otherwise = do
case businessChat of
Nothing -> unless (p == p') $ do
@@ -3258,8 +3261,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
Just sm@SignedMsg {chatBinding, signatures, signedBody}
| GroupMember {memberPubKey = Just pubKey, memberId} <- member ->
case chatBinding of
CBGroup | Just GroupKeys {sharedGroupId} <- groupKeys gInfo ->
let prefix = smpEncode chatBinding <> smpEncode (sharedGroupId, memberId)
CBGroup | Just GroupKeys {publicGroupId} <- groupKeys gInfo ->
let prefix = smpEncode chatBinding <> smpEncode (publicGroupId, memberId)
in signed MSSVerified <$ guard (all (\(MsgSignature KRMember sig) -> C.verify (C.APublicVerifyKey C.SEd25519 pubKey) sig (prefix <> signedBody)) signatures)
_ -> signed MSSSignedNoKey <$ guard signatureOptional
| otherwise -> signed MSSSignedNoKey <$ guard (signatureOptional || unverifiedAllowed membership member tag)
@@ -3633,14 +3636,16 @@ runRelayRequestWorker a Worker {doWork} = do
(FixedLinkData {linkEntityId, rootKey}, cData@(ContactLinkData _ UserContactData {owners})) <- getShortLinkConnReq NRMBackground user reqGroupLink
liftIO (decodeLinkUserData cData) >>= \case
Nothing -> throwChatError $ CEException "getLinkDataCreateRelayLink: no group link data"
Just GroupShortLinkData {groupProfile = gp@GroupProfile {sharedGroupId}} -> do
unless ((B64UrlByteString <$> linkEntityId) == sharedGroupId) $
throwChatError $ CEException "getLinkDataCreateRelayLink: linkEntityId does not match profile sharedGroupId"
Just GroupShortLinkData {groupProfile = gp@GroupProfile {publicGroup}} -> do
pg <- case (linkEntityId, publicGroup) of
(Just entityId, Just pg@PublicGroupProfile {publicGroupId})
| B64UrlByteString entityId == publicGroupId -> pure pg
_ -> throwChatError $ CEException "getLinkDataCreateRelayLink: linkEntityId does not match profile publicGroupId"
validateGroupProfile gp
((_, memberPrivKey), sLnk) <- createRelayLink gInfo
gInfo' <- withStore $ \db -> do
void $ updateGroupProfile db user gInfo gp
updateRelayGroupKeys db user gInfo linkEntityId rootKey memberPrivKey owners
updateRelayGroupKeys db user gInfo pg rootKey memberPrivKey owners
getGroupInfo db vr user groupId
pure (gInfo', sLnk)
where
+2 -2
View File
@@ -138,14 +138,14 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
[sql|
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_link,
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_type, gp.group_link, gp.public_group_id,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at,
g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id,
g.business_chat, g.business_member_id, g.customer_member_id,
g.use_relays, g.relay_own_status,
g.ui_themes, g.summary_current_members_count, g.public_member_count, g.custom_data, g.chat_item_ttl, g.members_require_attention, g.via_group_link_uri,
g.shared_group_id, g.root_priv_key, g.root_pub_key, g.member_priv_key,
g.root_priv_key, g.root_pub_key, g.member_priv_key,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.index_in_group, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
+57 -37
View File
@@ -340,28 +340,32 @@ setGroupLinkShortLink db gLnk@GroupLink {userContactLinkId, connLinkContact = CC
-- | creates completely new group with a single member - the current user
createNewGroup :: DB.Connection -> VersionRangeChat -> User -> GroupProfile -> Maybe Profile -> Bool -> MemberId -> Maybe GroupKeys -> Maybe Int64 -> ExceptT StoreError IO GroupInfo
createNewGroup db vr user@User {userId} groupProfile incognitoProfile useRelays memberId groupKeys publicMemberCount_ = ExceptT $ do
let GroupProfile {displayName, fullName, shortDescr, description, image, groupLink, groupPreferences, memberAdmission} = groupProfile
let GroupProfile {displayName, fullName, shortDescr, description, image, publicGroup, groupPreferences, memberAdmission} = groupProfile
(groupType_, groupLink_, publicGroupId_) = case publicGroup of
Just PublicGroupProfile {groupType, groupLink, publicGroupId} -> (Just groupType, Just groupLink, Just publicGroupId)
Nothing -> (Nothing, Nothing, Nothing)
fullGroupPreferences = mergeGroupPreferences groupPreferences
currentTs <- getCurrentTime
customUserProfileId <- mapM (createIncognitoProfile_ db userId currentTs) incognitoProfile
withLocalDisplayName db userId displayName $ \ldn -> runExceptT $ do
let (sharedGroupId_, rootPrivKey_, rootPubKey_, memberPrivKey_) = case groupKeys of
Nothing -> (Nothing, Nothing, Nothing, Nothing)
Just GroupKeys {sharedGroupId, groupRootKey, memberPrivKey} ->
let (rootPrivKey_, rootPubKey_, memberPrivKey_) = case groupKeys of
Nothing -> (Nothing, Nothing, Nothing)
Just GroupKeys {groupRootKey, memberPrivKey} ->
let (rpk, rpub) = case groupRootKey of
GRKPrivate pk -> (Just pk, Nothing)
GRKPublic k -> (Nothing, Just k)
in (Just sharedGroupId, rpk, rpub, Just memberPrivKey)
in (rpk, rpub, Just memberPrivKey)
groupId <- liftIO $ do
DB.execute
db
[sql|
INSERT INTO group_profiles
(display_name, full_name, short_descr, description, image, group_link,
(display_name, full_name, short_descr, description, image,
group_type, group_link, public_group_id,
user_id, preferences, member_admission, created_at, updated_at)
VALUES (?,?,?,?,?,?,?,?,?,?,?)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
((displayName, fullName, shortDescr, description, image, groupLink)
((displayName, fullName, shortDescr, description, image, groupType_, groupLink_, publicGroupId_)
:. (userId, groupPreferences, memberAdmission, currentTs, currentTs))
profileId <- insertedRowId db
DB.execute
@@ -370,11 +374,11 @@ createNewGroup db vr user@User {userId} groupProfile incognitoProfile useRelays
INSERT INTO groups
(use_relays, creating_in_progress, local_display_name, user_id, group_profile_id, enable_ntfs,
created_at, updated_at, chat_ts, user_member_profile_sent_at,
shared_group_id, root_priv_key, root_pub_key, member_priv_key, public_member_count)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
root_priv_key, root_pub_key, member_priv_key, public_member_count)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
( (BI useRelays, BI useRelays, ldn, userId, profileId, BI True, currentTs, currentTs, currentTs, currentTs)
:. (sharedGroupId_, rootPrivKey_, rootPubKey_, memberPrivKey_, publicMemberCount_)
:. (rootPrivKey_, rootPubKey_, memberPrivKey_, publicMemberCount_)
)
insertedRowId db
let memberPubKey = C.publicKey . memberPrivKey <$> groupKeys
@@ -839,18 +843,22 @@ createGroupViaLink'
createGroup_ :: DB.Connection -> UserId -> GroupProfile -> Maybe (CreatedLinkContact, Maybe SharedMsgId) -> Maybe BusinessChatInfo -> Bool -> Maybe RelayStatus -> Maybe Int64 -> UTCTime -> ExceptT StoreError IO (GroupId, Text)
createGroup_ db userId groupProfile prepared business useRelays relayOwnStatus publicMemberCount_ currentTs = ExceptT $ do
let GroupProfile {displayName, fullName, shortDescr, description, image, groupLink, groupPreferences, memberAdmission} = groupProfile
let GroupProfile {displayName, fullName, shortDescr, description, image, publicGroup, groupPreferences, memberAdmission} = groupProfile
(groupType_, groupLink_, publicGroupId_) = case publicGroup of
Just PublicGroupProfile {groupType, groupLink, publicGroupId} -> (Just groupType, Just groupLink, Just publicGroupId)
Nothing -> (Nothing, Nothing, Nothing)
withLocalDisplayName db userId displayName $ \localDisplayName -> runExceptT $ do
liftIO $ do
DB.execute
db
[sql|
INSERT INTO group_profiles
(display_name, full_name, short_descr, description, image, group_link,
(display_name, full_name, short_descr, description, image,
group_type, group_link, public_group_id,
user_id, preferences, member_admission, created_at, updated_at)
VALUES (?,?,?,?,?,?,?,?,?,?,?)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
((displayName, fullName, shortDescr, description, image, groupLink)
((displayName, fullName, shortDescr, description, image, groupType_, groupLink_, publicGroupId_)
:. (userId, groupPreferences, memberAdmission, currentTs, currentTs))
profileId <- insertedRowId db
DB.execute
@@ -1508,10 +1516,9 @@ createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMembe
shortDescr = Nothing,
description = Nothing,
image = Nothing,
groupLink = Nothing,
publicGroup = Nothing,
groupPreferences = Nothing,
memberAdmission = Nothing,
sharedGroupId = Nothing
memberAdmission = Nothing
}
(groupId, _groupLDN) <- createGroup_ db userId placeholderProfile Nothing Nothing True (Just RSInvited) Nothing currentTs
-- Store relay request data for recovery
@@ -1775,13 +1782,13 @@ createMemberConnectionAsync db user@User {userId} groupMemberId (cmdId, agentCon
-- which is used in single-connection flows.
updatePreparedRelayedGroup ::
DB.Connection -> VersionRangeChat -> User -> GroupInfo -> ConnReqContact -> ConnReqUriHash -> Maybe Profile ->
Maybe ByteString -> C.PublicKeyEd25519 -> C.PrivateKeyEd25519 -> Maybe Int64 ->
C.PublicKeyEd25519 -> C.PrivateKeyEd25519 -> Maybe Int64 ->
ExceptT StoreError IO GroupInfo
updatePreparedRelayedGroup db vr user@User {userId} gInfo cReq cReqHash incognitoProfile linkEntityId rootPubKey memberPrivKey publicMemberCount_ = do
updatePreparedRelayedGroup db vr user@User {userId} gInfo cReq cReqHash incognitoProfile rootPubKey memberPrivKey publicMemberCount_ = do
currentTs <- liftIO getCurrentTime
customUserProfileId <- liftIO $ mapM (createIncognitoProfile_ db userId currentTs) incognitoProfile
liftIO $ setPreparedGroupLinkInfo_ db gInfo cReq cReqHash customUserProfileId publicMemberCount_ currentTs
liftIO $ updateGroupMemberKeys db (groupId' gInfo) linkEntityId rootPubKey memberPrivKey (groupMemberId' $ membership gInfo)
liftIO $ updateGroupMemberKeys db (groupId' gInfo) rootPubKey memberPrivKey (groupMemberId' $ membership gInfo)
getGroupInfo db vr user (groupId' gInfo)
updatePublicMemberCount :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> ExceptT StoreError IO GroupInfo
@@ -1809,27 +1816,35 @@ setPublicMemberCount db vr user GroupInfo {groupId} publicCount = do
liftIO $ DB.execute db "UPDATE groups SET public_member_count = ?, updated_at = ? WHERE group_id = ?" (publicCount, currentTs, groupId)
getGroupInfo db vr user groupId
updateGroupMemberKeys :: DB.Connection -> GroupId -> Maybe ByteString -> C.PublicKeyEd25519 -> C.PrivateKeyEd25519 -> GroupMemberId -> IO ()
updateGroupMemberKeys db groupId linkEntityId rootPubKey memberPrivKey membershipGMId = do
updateGroupMemberKeys :: DB.Connection -> GroupId -> C.PublicKeyEd25519 -> C.PrivateKeyEd25519 -> GroupMemberId -> IO ()
updateGroupMemberKeys db groupId rootPubKey memberPrivKey membershipGMId = do
currentTs <- getCurrentTime
DB.execute
db
"UPDATE groups SET shared_group_id = ?, root_pub_key = ?, member_priv_key = ?, updated_at = ? WHERE group_id = ?"
(Binary <$> linkEntityId, rootPubKey, memberPrivKey, currentTs, groupId)
"UPDATE groups SET root_pub_key = ?, member_priv_key = ?, updated_at = ? WHERE group_id = ?"
(rootPubKey, memberPrivKey, currentTs, groupId)
DB.execute
db
"UPDATE group_members SET member_pub_key = ?, updated_at = ? WHERE group_member_id = ?"
(C.publicKey memberPrivKey, currentTs, membershipGMId)
updateRelayGroupKeys :: DB.Connection -> User -> GroupInfo -> Maybe ByteString -> C.PublicKeyEd25519 -> C.PrivateKeyEd25519 -> [OwnerAuth] -> ExceptT StoreError IO ()
updateRelayGroupKeys db user gInfo linkEntityId rootPubKey memberPrivKey owners = do
updateRelayGroupKeys :: DB.Connection -> User -> GroupInfo -> PublicGroupProfile -> C.PublicKeyEd25519 -> C.PrivateKeyEd25519 -> [OwnerAuth] -> ExceptT StoreError IO ()
updateRelayGroupKeys db user@User {userId} gInfo PublicGroupProfile {groupType, groupLink, publicGroupId} rootPubKey memberPrivKey owners = do
currentTs <- liftIO getCurrentTime
let membershipGMId = groupMemberId' $ membership gInfo
groupId = groupId' gInfo
liftIO $ do
DB.execute
db
"UPDATE groups SET shared_group_id = ?, root_pub_key = ?, member_priv_key = ?, updated_at = ? WHERE group_id = ?"
(Binary <$> linkEntityId, rootPubKey, memberPrivKey, currentTs, groupId' gInfo)
[sql|
UPDATE group_profiles SET group_type = ?, group_link = ?, public_group_id = ?, updated_at = ?
WHERE group_profile_id IN (SELECT group_profile_id FROM groups WHERE user_id = ? AND group_id = ?)
|]
(groupType, groupLink, publicGroupId, currentTs, userId, groupId)
DB.execute
db
"UPDATE groups SET root_pub_key = ?, member_priv_key = ?, updated_at = ? WHERE group_id = ?"
(rootPubKey, memberPrivKey, currentTs, groupId)
DB.execute
db
"UPDATE group_members SET member_pub_key = ?, updated_at = ? WHERE group_member_id = ?"
@@ -2207,7 +2222,7 @@ createMemberConnection_ db userId groupMemberId agentConnId chatV peerChatVRange
createConnection_ db userId ConnMember (Just groupMemberId) agentConnId ConnNew chatV peerChatVRange viaContact Nothing Nothing connLevel currentTs subMode PQSupportOff
updateGroupProfile :: DB.Connection -> User -> GroupInfo -> GroupProfile -> ExceptT StoreError IO GroupInfo
updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName, groupProfile = GroupProfile {displayName}} p'@GroupProfile {displayName = newName, fullName, shortDescr, description, image, groupLink, groupPreferences, memberAdmission}
updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName, groupProfile = GroupProfile {displayName}} p'@GroupProfile {displayName = newName, fullName, shortDescr, description, image, publicGroup, groupPreferences, memberAdmission}
| displayName == newName = liftIO $ do
currentTs <- getCurrentTime
updateGroupProfile_ currentTs
@@ -2220,21 +2235,24 @@ updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName,
pure $ Right (g :: GroupInfo) {localDisplayName = ldn, groupProfile = p', fullGroupPreferences}
where
fullGroupPreferences = mergeGroupPreferences groupPreferences
(groupType_, groupLink_) = case publicGroup of
Just PublicGroupProfile {groupType, groupLink} -> (Just groupType, Just groupLink)
Nothing -> (Nothing, Nothing)
updateGroupProfile_ currentTs =
DB.execute
db
[sql|
UPDATE group_profiles
SET display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, group_link = ?, preferences = ?, member_admission = ?, updated_at = ?
SET display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?,
group_type = ?, group_link = ?,
preferences = ?, member_admission = ?, updated_at = ?
WHERE group_profile_id IN (
SELECT group_profile_id
FROM groups
WHERE user_id = ? AND group_id = ?
)
|]
( (newName, fullName, shortDescr, description, image, groupLink)
:. (groupPreferences, memberAdmission, currentTs, userId, groupId)
)
((newName, fullName, shortDescr, description, image, groupType_, groupLink_) :. (groupPreferences, memberAdmission, currentTs, userId, groupId))
updateGroup_ ldn currentTs = do
DB.execute
db
@@ -2272,14 +2290,16 @@ updateGroupProfileFromMember db user g@GroupInfo {groupId} Profile {displayName
DB.query
db
[sql|
SELECT gp.display_name, gp.full_name, gp.short_descr, gp.description, gp.image, gp.group_link, gp.preferences, gp.member_admission
SELECT gp.display_name, gp.full_name, gp.short_descr, gp.description, gp.image,
gp.group_type, gp.group_link, gp.public_group_id,
gp.preferences, gp.member_admission
FROM group_profiles gp
JOIN groups g ON gp.group_profile_id = g.group_profile_id
WHERE g.group_id = ?
|]
(Only groupId)
toGroupProfile (displayName, fullName, shortDescr, description, image, groupLink, groupPreferences, memberAdmission) =
GroupProfile {displayName, fullName, shortDescr, description, image, groupLink, groupPreferences, memberAdmission, sharedGroupId = Nothing}
toGroupProfile (displayName, fullName, shortDescr, description, image, groupType_, groupLink_, publicGroupId_, groupPreferences, memberAdmission) =
GroupProfile {displayName, fullName, shortDescr, description, image, publicGroup = toPublicGroupProfile groupType_ groupLink_ publicGroupId_, groupPreferences, memberAdmission}
getGroupInfoByUserContactLinkConnReq :: DB.Connection -> VersionRangeChat -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe GroupInfo)
getGroupInfoByUserContactLinkConnReq db vr user@User {userId} (cReqSchema1, cReqSchema2) = do
@@ -39,13 +39,15 @@ ALTER TABLE groups
ADD COLUMN relay_request_peer_chat_max_version INTEGER,
ADD COLUMN relay_request_failed SMALLINT DEFAULT 0,
ADD COLUMN relay_request_err_reason TEXT,
ADD COLUMN shared_group_id BYTEA,
ADD COLUMN root_priv_key BYTEA,
ADD COLUMN root_pub_key BYTEA,
ADD COLUMN member_priv_key BYTEA,
ADD COLUMN public_member_count BIGINT;
ALTER TABLE group_profiles ADD COLUMN group_link BYTEA;
ALTER TABLE group_profiles
ADD COLUMN group_type TEXT,
ADD COLUMN group_link BYTEA,
ADD COLUMN public_group_id BYTEA;
CREATE TABLE group_relays(
group_relay_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
@@ -90,13 +92,15 @@ ALTER TABLE groups
DROP COLUMN relay_request_peer_chat_max_version,
DROP COLUMN relay_request_failed,
DROP COLUMN relay_request_err_reason,
DROP COLUMN shared_group_id,
DROP COLUMN root_priv_key,
DROP COLUMN root_pub_key,
DROP COLUMN member_priv_key,
DROP COLUMN public_member_count;
ALTER TABLE group_profiles DROP COLUMN group_link;
ALTER TABLE group_profiles
DROP COLUMN group_type,
DROP COLUMN group_link,
DROP COLUMN public_group_id;
DROP INDEX idx_group_relays_group_id;
DROP INDEX idx_group_relays_group_member_id;
@@ -41,24 +41,22 @@ CREATE UNIQUE INDEX idx_chat_relays_user_id_name ON chat_relays(user_id, name);
ALTER TABLE users ADD COLUMN is_user_chat_relay INTEGER NOT NULL DEFAULT 0;
ALTER TABLE groups ADD COLUMN use_relays INTEGER NOT NULL DEFAULT 0;
ALTER TABLE groups ADD COLUMN creating_in_progress INTEGER NOT NULL DEFAULT 0;
ALTER TABLE groups ADD COLUMN relay_own_status TEXT;
ALTER TABLE groups ADD COLUMN relay_request_inv_id BLOB;
ALTER TABLE groups ADD COLUMN relay_request_group_link BLOB;
ALTER TABLE groups ADD COLUMN relay_request_peer_chat_min_version INTEGER;
ALTER TABLE groups ADD COLUMN relay_request_peer_chat_max_version INTEGER;
ALTER TABLE groups ADD COLUMN relay_request_failed INTEGER DEFAULT 0;
ALTER TABLE groups ADD COLUMN relay_request_err_reason TEXT;
ALTER TABLE groups ADD COLUMN shared_group_id BLOB;
ALTER TABLE groups ADD COLUMN root_priv_key BLOB;
ALTER TABLE groups ADD COLUMN root_pub_key BLOB;
ALTER TABLE groups ADD COLUMN member_priv_key BLOB;
ALTER TABLE groups ADD COLUMN public_member_count INTEGER;
ALTER TABLE group_profiles ADD COLUMN group_type TEXT;
ALTER TABLE group_profiles ADD COLUMN group_link BLOB;
ALTER TABLE group_profiles ADD COLUMN public_group_id BLOB;
CREATE TABLE group_relays(
group_relay_id INTEGER PRIMARY KEY,
@@ -92,24 +90,22 @@ UPDATE group_members SET member_role = 'observer' WHERE member_role = 'relay';
ALTER TABLE users DROP COLUMN is_user_chat_relay;
ALTER TABLE groups DROP COLUMN use_relays;
ALTER TABLE groups DROP COLUMN creating_in_progress;
ALTER TABLE groups DROP COLUMN relay_own_status;
ALTER TABLE groups DROP COLUMN relay_request_inv_id;
ALTER TABLE groups DROP COLUMN relay_request_group_link;
ALTER TABLE groups DROP COLUMN relay_request_peer_chat_min_version;
ALTER TABLE groups DROP COLUMN relay_request_peer_chat_max_version;
ALTER TABLE groups DROP COLUMN relay_request_failed;
ALTER TABLE groups DROP COLUMN relay_request_err_reason;
ALTER TABLE groups DROP COLUMN shared_group_id;
ALTER TABLE groups DROP COLUMN root_priv_key;
ALTER TABLE groups DROP COLUMN root_pub_key;
ALTER TABLE groups DROP COLUMN member_priv_key;
ALTER TABLE groups DROP COLUMN public_member_count;
ALTER TABLE group_profiles DROP COLUMN group_type;
ALTER TABLE group_profiles DROP COLUMN group_link;
ALTER TABLE group_profiles DROP COLUMN public_group_id;
DROP INDEX idx_group_relays_group_id;
DROP INDEX idx_group_relays_group_member_id;
@@ -142,14 +142,14 @@ SEARCH c USING INDEX idx_connections_contact_id (contact_id=?) LEFT-JOIN
Query:
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_link,
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_type, gp.group_link, gp.public_group_id,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at,
g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id,
g.business_chat, g.business_member_id, g.customer_member_id,
g.use_relays, g.relay_own_status,
g.ui_themes, g.summary_current_members_count, g.public_member_count, g.custom_data, g.chat_item_ttl, g.members_require_attention, g.via_group_link_uri,
g.shared_group_id, g.root_priv_key, g.root_pub_key, g.member_priv_key,
g.root_priv_key, g.root_pub_key, g.member_priv_key,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.index_in_group, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
@@ -967,7 +967,9 @@ Plan:
SEARCH delivery_tasks USING COVERING INDEX idx_delivery_tasks_next (group_id=? AND worker_scope=? AND failed=? AND task_status=?)
Query:
SELECT gp.display_name, gp.full_name, gp.short_descr, gp.description, gp.image, gp.group_link, gp.preferences, gp.member_admission
SELECT gp.display_name, gp.full_name, gp.short_descr, gp.description, gp.image,
gp.group_type, gp.group_link, gp.public_group_id,
gp.preferences, gp.member_admission
FROM group_profiles gp
JOIN groups g ON gp.group_profile_id = g.group_profile_id
WHERE g.group_id = ?
@@ -1205,9 +1207,10 @@ SEARCH contacts USING COVERING INDEX idx_contacts_contact_group_member_id (conta
Query:
INSERT INTO group_profiles
(display_name, full_name, short_descr, description, image, group_link,
(display_name, full_name, short_descr, description, image,
group_type, group_link, public_group_id,
user_id, preferences, member_admission, created_at, updated_at)
VALUES (?,?,?,?,?,?,?,?,?,?,?)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
Plan:
@@ -1224,8 +1227,8 @@ Query:
INSERT INTO groups
(use_relays, creating_in_progress, local_display_name, user_id, group_profile_id, enable_ntfs,
created_at, updated_at, chat_ts, user_member_profile_sent_at,
shared_group_id, root_priv_key, root_pub_key, member_priv_key, public_member_count)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
root_priv_key, root_pub_key, member_priv_key, public_member_count)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
Plan:
@@ -1736,7 +1739,9 @@ SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE group_profiles
SET display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, group_link = ?, preferences = ?, member_admission = ?, updated_at = ?
SET display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?,
group_type = ?, group_link = ?,
preferences = ?, member_admission = ?, updated_at = ?
WHERE group_profile_id IN (
SELECT group_profile_id
FROM groups
@@ -3957,6 +3962,15 @@ Query:
Plan:
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE group_profiles SET group_type = ?, group_link = ?, public_group_id = ?, updated_at = ?
WHERE group_profile_id IN (SELECT group_profile_id FROM groups WHERE user_id = ? AND group_id = ?)
Plan:
SEARCH group_profiles USING INTEGER PRIMARY KEY (rowid=?)
LIST SUBQUERY 1
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE groups
SET member_index = member_index + 1
@@ -5171,14 +5185,14 @@ SEARCH pending_group_messages USING COVERING INDEX idx_pending_group_messages_me
Query:
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_link,
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_type, gp.group_link, gp.public_group_id,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at,
g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id,
g.business_chat, g.business_member_id, g.customer_member_id,
g.use_relays, g.relay_own_status,
g.ui_themes, g.summary_current_members_count, g.public_member_count, g.custom_data, g.chat_item_ttl, g.members_require_attention, g.via_group_link_uri,
g.shared_group_id, g.root_priv_key, g.root_pub_key, g.member_priv_key,
g.root_priv_key, g.root_pub_key, g.member_priv_key,
-- GroupMember - membership
mu.group_member_id, mu.group_id, mu.index_in_group, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
@@ -5207,14 +5221,14 @@ SEARCH pu USING INTEGER PRIMARY KEY (rowid=?)
Query:
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_link,
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_type, gp.group_link, gp.public_group_id,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at,
g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id,
g.business_chat, g.business_member_id, g.customer_member_id,
g.use_relays, g.relay_own_status,
g.ui_themes, g.summary_current_members_count, g.public_member_count, g.custom_data, g.chat_item_ttl, g.members_require_attention, g.via_group_link_uri,
g.shared_group_id, g.root_priv_key, g.root_pub_key, g.member_priv_key,
g.root_priv_key, g.root_pub_key, g.member_priv_key,
-- GroupMember - membership
mu.group_member_id, mu.group_id, mu.index_in_group, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
@@ -5236,14 +5250,14 @@ SEARCH pu USING INTEGER PRIMARY KEY (rowid=?)
Query:
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_link,
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_type, gp.group_link, gp.public_group_id,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at,
g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id,
g.business_chat, g.business_member_id, g.customer_member_id,
g.use_relays, g.relay_own_status,
g.ui_themes, g.summary_current_members_count, g.public_member_count, g.custom_data, g.chat_item_ttl, g.members_require_attention, g.via_group_link_uri,
g.shared_group_id, g.root_priv_key, g.root_pub_key, g.member_priv_key,
g.root_priv_key, g.root_pub_key, g.member_priv_key,
-- GroupMember - membership
mu.group_member_id, mu.group_id, mu.index_in_group, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
@@ -7065,14 +7079,14 @@ Query: UPDATE groups SET request_shared_msg_id = ? WHERE group_id = ?
Plan:
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
Query: UPDATE groups SET root_pub_key = ?, member_priv_key = ?, updated_at = ? WHERE group_id = ?
Plan:
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
Query: UPDATE groups SET send_rcpts = NULL
Plan:
SCAN groups
Query: UPDATE groups SET shared_group_id = ?, root_pub_key = ?, member_priv_key = ?, updated_at = ? WHERE group_id = ?
Plan:
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
Query: UPDATE groups SET ui_themes = ?, updated_at = ? WHERE user_id = ? AND group_id = ?
Plan:
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
@@ -123,7 +123,9 @@ CREATE TABLE group_profiles(
description TEXT NULL,
member_admission TEXT,
short_descr TEXT,
group_link BLOB
group_type TEXT,
group_link BLOB,
public_group_id BLOB
) STRICT;
CREATE TABLE groups(
group_id INTEGER PRIMARY KEY, -- local group ID
@@ -168,7 +170,6 @@ CREATE TABLE groups(
relay_request_peer_chat_max_version INTEGER,
relay_request_failed INTEGER DEFAULT 0,
relay_request_err_reason TEXT,
shared_group_id BLOB,
root_priv_key BLOB,
root_pub_key BLOB,
member_priv_key BLOB,
+18 -14
View File
@@ -663,22 +663,22 @@ type PreparedGroupRow = (Maybe ConnReqContact, Maybe ShortLinkContact, BoolInt,
type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe MemberId)
type GroupKeysRow = (Maybe B64UrlByteString, Maybe C.PrivateKeyEd25519, Maybe C.PublicKeyEd25519, Maybe C.PrivateKeyEd25519)
type GroupKeysRow = (Maybe C.PrivateKeyEd25519, Maybe C.PublicKeyEd25519, Maybe C.PrivateKeyEd25519)
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Text, Maybe Text, Maybe ImageData, Maybe ShortLinkContact) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences, Maybe GroupMemberAdmission) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. PreparedGroupRow :. BusinessChatInfoRow :. (BoolInt, Maybe RelayStatus, Maybe UIThemeEntityOverrides, Int64, Maybe Int64, Maybe CustomData, Maybe Int64, Int, Maybe ConnReqContact) :. GroupKeysRow :. GroupMemberRow
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Text, Maybe Text, Maybe ImageData, Maybe GroupType, Maybe ShortLinkContact, Maybe B64UrlByteString) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences, Maybe GroupMemberAdmission) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. PreparedGroupRow :. BusinessChatInfoRow :. (BoolInt, Maybe RelayStatus, Maybe UIThemeEntityOverrides, Int64, Maybe Int64, Maybe CustomData, Maybe Int64, Int, Maybe ConnReqContact) :. GroupKeysRow :. GroupMemberRow
type GroupMemberRow = (GroupMemberId, GroupId, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId) :. ProfileRow :. (UTCTime, UTCTime) :. (Maybe UTCTime, Int64, Int64, Int64, Maybe UTCTime, Maybe C.PublicKeyEd25519, Maybe ShortLinkContact)
type ProfileRow = (ProfileId, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias, Maybe Preferences)
toGroupInfo :: VersionRangeChat -> Int64 -> [ChatTagId] -> GroupInfoRow -> GroupInfo
toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, shortDescr, localAlias, description, image, groupLink) :. (enableNtfs_, sendRcpts, BI favorite, groupPreferences, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. preparedGroupRow :. businessRow :. (BI useRelays, relayOwnStatus, uiThemes, currentMembers, publicMemberCount, customData, chatItemTTL, membersRequireAttention, viaGroupLinkUri) :. groupKeysRow :. userMemberRow) =
toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, shortDescr, localAlias, description, image, groupType_, groupLink_, publicGroupId_) :. (enableNtfs_, sendRcpts, BI favorite, groupPreferences, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. preparedGroupRow :. businessRow :. (BI useRelays, relayOwnStatus, uiThemes, currentMembers, publicMemberCount, customData, chatItemTTL, membersRequireAttention, viaGroupLinkUri) :. groupKeysRow :. userMemberRow) =
let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr}
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite}
fullGroupPreferences = mergeGroupPreferences groupPreferences
groupKeys = toGroupKeys groupKeysRow
sharedGroupId = (\GroupKeys {sharedGroupId = gId} -> gId) <$> groupKeys
groupProfile = GroupProfile {displayName, fullName, shortDescr, description, image, groupPreferences, memberAdmission, groupLink, sharedGroupId}
publicGroup = toPublicGroupProfile groupType_ groupLink_ publicGroupId_
groupKeys = toGroupKeys publicGroupId_ groupKeysRow
groupProfile = GroupProfile {displayName, fullName, shortDescr, description, image, publicGroup, groupPreferences, memberAdmission}
businessChat = toBusinessChatInfo businessRow
preparedGroup = toPreparedGroup preparedGroupRow
groupSummary = GroupSummary {currentMembers, publicMemberCount}
@@ -690,12 +690,16 @@ toPreparedGroup = \case
Just PreparedGroup {connLinkToConnect = CCLink fullLink shortLink_, connLinkPreparedConnection, connLinkStartedConnection, welcomeSharedMsgId, requestSharedMsgId}
_ -> Nothing
toGroupKeys :: GroupKeysRow -> Maybe GroupKeys
toGroupKeys = \case
(Just sharedGroupId, rootPrivKey_, rootPubKey_, Just memberPrivKey) ->
(\grk -> GroupKeys {sharedGroupId, groupRootKey = grk, memberPrivKey})
<$> (GRKPrivate <$> rootPrivKey_ <|> GRKPublic <$> rootPubKey_)
_ -> Nothing
toPublicGroupProfile :: Maybe GroupType -> Maybe ShortLinkContact -> Maybe B64UrlByteString -> Maybe PublicGroupProfile
toPublicGroupProfile (Just groupType) (Just groupLink) (Just publicGroupId) =
Just PublicGroupProfile {groupType, groupLink, publicGroupId}
toPublicGroupProfile _ _ _ = Nothing
toGroupKeys :: Maybe B64UrlByteString -> GroupKeysRow -> Maybe GroupKeys
toGroupKeys (Just publicGroupId) (rootPrivKey_, rootPubKey_, Just memberPrivKey) =
(\grk -> GroupKeys {publicGroupId, groupRootKey = grk, memberPrivKey})
<$> (GRKPrivate <$> rootPrivKey_ <|> GRKPublic <$> rootPubKey_)
toGroupKeys _ _ = Nothing
toGroupMember :: Int64 -> GroupMemberRow -> GroupMember
toGroupMember userContactId ((groupMemberId, groupId, indexInGroup, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, BI showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId) :. profileRow :. (createdAt, updatedAt) :. (supportChatTs_, supportChatUnread, supportChatMemberAttention, supportChatMentions, supportChatLastMsgFromMemberTs, memberPubKey, relayLink)) =
@@ -755,14 +759,14 @@ groupInfoQueryFields =
[sql|
SELECT
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_link,
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, gp.group_type, gp.group_link, gp.public_group_id,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at,
g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id,
g.business_chat, g.business_member_id, g.customer_member_id,
g.use_relays, g.relay_own_status,
g.ui_themes, g.summary_current_members_count, g.public_member_count, g.custom_data, g.chat_item_ttl, g.members_require_attention, g.via_group_link_uri,
g.shared_group_id, g.root_priv_key, g.root_pub_key, g.member_priv_key,
g.root_priv_key, g.root_pub_key, g.member_priv_key,
-- GroupMember - membership
mu.group_member_id, mu.group_id, mu.index_in_group, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
+36 -4
View File
@@ -458,7 +458,7 @@ groupRootPubKey (GRKPrivate pk) = C.publicKey pk
groupRootPubKey (GRKPublic pk) = pk
data GroupKeys = GroupKeys
{ sharedGroupId :: B64UrlByteString,
{ publicGroupId :: B64UrlByteString,
groupRootKey :: GroupRootKey,
memberPrivKey :: C.PrivateKeyEd25519
}
@@ -755,16 +755,39 @@ fromLocalProfile :: LocalProfile -> Profile
fromLocalProfile LocalProfile {displayName, fullName, shortDescr, image, contactLink, preferences, peerType} =
Profile {displayName, fullName, shortDescr, image, contactLink, preferences, peerType}
data GroupType
= GTChannel
| GTUnknown Text
deriving (Eq, Show)
instance TextEncoding GroupType where
textEncode = \case
GTChannel -> "channel"
GTUnknown tag -> tag
textDecode s = Just $ case s of
"channel" -> GTChannel
tag -> GTUnknown tag
instance FromField GroupType where fromField = fromTextField_ textDecode
instance ToField GroupType where toField = toField . textEncode
data PublicGroupProfile = PublicGroupProfile
{ groupType :: GroupType,
groupLink :: ShortLinkContact,
publicGroupId :: B64UrlByteString -- group identity = sha256(genesis root key), immutable
}
deriving (Eq, Show)
data GroupProfile = GroupProfile
{ displayName :: GroupName,
fullName :: Text,
shortDescr :: Maybe Text, -- short description limited to 160 characters
description :: Maybe Text, -- this has been repurposed as welcome message
image :: Maybe ImageData,
groupLink :: Maybe ShortLinkContact,
publicGroup :: Maybe PublicGroupProfile,
groupPreferences :: Maybe GroupPreferences,
memberAdmission :: Maybe GroupMemberAdmission,
sharedGroupId :: Maybe B64UrlByteString -- group identity = sha256(genesis root key), immutable
memberAdmission :: Maybe GroupMemberAdmission
}
deriving (Eq, Show)
@@ -2009,6 +2032,15 @@ instance ToField GroupMemberAdmission where
instance FromField GroupMemberAdmission where
fromField = fromTextField_ decodeJSON
instance FromJSON GroupType where
parseJSON = textParseJSON "GroupType"
instance ToJSON GroupType where
toJSON = textToJSON
toEncoding = textToEncoding
$(JQ.deriveJSON defaultJSON ''PublicGroupProfile)
$(JQ.deriveJSON defaultJSON ''GroupProfile)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "IB") ''InvitedBy)
+1 -1
View File
@@ -107,7 +107,7 @@ testProfile :: Profile
testProfile = Profile {displayName = "alice", fullName = "Alice", shortDescr = Nothing, image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="), peerType = Nothing, contactLink = Nothing, preferences = testChatPreferences}
testGroupProfile :: GroupProfile
testGroupProfile = GroupProfile {displayName = "team", fullName = "Team", description = Nothing, shortDescr = Nothing, image = Nothing, groupLink = Nothing, groupPreferences = testGroupPreferences, memberAdmission = Nothing, sharedGroupId = Nothing}
testGroupProfile = GroupProfile {displayName = "team", fullName = "Team", description = Nothing, shortDescr = Nothing, image = Nothing, publicGroup = Nothing, groupPreferences = testGroupPreferences, memberAdmission = Nothing}
decodeChatMessageTest :: Spec
decodeChatMessageTest = describe "Chat message encoding/decoding" $ do