mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-02 15:41:44 +00:00
core, ui: public group profile wip (#6734)
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 { "" }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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` |
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+34
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+4
-3
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1014,7 +1014,7 @@ type DirectLink = Bool
|
||||
data GroupShortLinkInfo = GroupShortLinkInfo
|
||||
{ direct :: Bool,
|
||||
groupRelays :: [ShortLinkContact],
|
||||
sharedGroupId :: Maybe B64UrlByteString
|
||||
publicGroupId :: Maybe B64UrlByteString
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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 ()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user