mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			205 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			205 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Swift
		
	
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
 | 
						|
 | 
						|
import Foundation
 | 
						|
import GRDB
 | 
						|
import DifferenceKit
 | 
						|
import SessionUtilitiesKit
 | 
						|
 | 
						|
public struct ClosedGroup: Codable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
 | 
						|
    public static var databaseTableName: String { "closedGroup" }
 | 
						|
    internal static let threadForeignKey = ForeignKey([Columns.threadId], to: [SessionThread.Columns.id])
 | 
						|
    public static let thread = belongsTo(SessionThread.self, using: threadForeignKey)
 | 
						|
    internal static let keyPairs = hasMany(
 | 
						|
        ClosedGroupKeyPair.self,
 | 
						|
        using: ClosedGroupKeyPair.closedGroupForeignKey
 | 
						|
    )
 | 
						|
    public static let members = hasMany(GroupMember.self, using: GroupMember.closedGroupForeignKey)
 | 
						|
    
 | 
						|
    public typealias Columns = CodingKeys
 | 
						|
    public enum CodingKeys: String, CodingKey, ColumnExpression {
 | 
						|
        case threadId
 | 
						|
        case name
 | 
						|
        case formationTimestamp
 | 
						|
    }
 | 
						|
    
 | 
						|
    /// The Group public key takes up 32 bytes
 | 
						|
    static let pubkeyByteLength: Int = 32
 | 
						|
    
 | 
						|
    /// The Group secret key takes up 32 bytes
 | 
						|
    static let secretKeyByteLength: Int = 32
 | 
						|
    
 | 
						|
    public var id: String { threadId }  // Identifiable
 | 
						|
    public var publicKey: String { threadId }
 | 
						|
 | 
						|
    /// The id for the thread this closed group belongs to
 | 
						|
    ///
 | 
						|
    /// **Note:** This value will always be publicKey for the closed group
 | 
						|
    public let threadId: String
 | 
						|
    public let name: String
 | 
						|
    public let formationTimestamp: TimeInterval
 | 
						|
    
 | 
						|
    // MARK: - Relationships
 | 
						|
    
 | 
						|
    public var thread: QueryInterfaceRequest<SessionThread> {
 | 
						|
        request(for: ClosedGroup.thread)
 | 
						|
    }
 | 
						|
    
 | 
						|
    public var keyPairs: QueryInterfaceRequest<ClosedGroupKeyPair> {
 | 
						|
        request(for: ClosedGroup.keyPairs)
 | 
						|
    }
 | 
						|
    
 | 
						|
    public var allMembers: QueryInterfaceRequest<GroupMember> {
 | 
						|
        request(for: ClosedGroup.members)
 | 
						|
    }
 | 
						|
    
 | 
						|
    public var members: QueryInterfaceRequest<GroupMember> {
 | 
						|
        request(for: ClosedGroup.members)
 | 
						|
            .filter(GroupMember.Columns.role == GroupMember.Role.standard)
 | 
						|
    }
 | 
						|
    
 | 
						|
    public var zombies: QueryInterfaceRequest<GroupMember> {
 | 
						|
        request(for: ClosedGroup.members)
 | 
						|
            .filter(GroupMember.Columns.role == GroupMember.Role.zombie)
 | 
						|
    }
 | 
						|
    
 | 
						|
    public var moderators: QueryInterfaceRequest<GroupMember> {
 | 
						|
        request(for: ClosedGroup.members)
 | 
						|
            .filter(GroupMember.Columns.role == GroupMember.Role.moderator)
 | 
						|
    }
 | 
						|
    
 | 
						|
    public var admins: QueryInterfaceRequest<GroupMember> {
 | 
						|
        request(for: ClosedGroup.members)
 | 
						|
            .filter(GroupMember.Columns.role == GroupMember.Role.admin)
 | 
						|
    }
 | 
						|
    
 | 
						|
    // MARK: - Initialization
 | 
						|
    
 | 
						|
    public init(
 | 
						|
        threadId: String,
 | 
						|
        name: String,
 | 
						|
        formationTimestamp: TimeInterval
 | 
						|
    ) {
 | 
						|
        self.threadId = threadId
 | 
						|
        self.name = name
 | 
						|
        self.formationTimestamp = formationTimestamp
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// MARK: - GRDB Interactions
 | 
						|
 | 
						|
public extension ClosedGroup {
 | 
						|
    func fetchLatestKeyPair(_ db: Database) throws -> ClosedGroupKeyPair? {
 | 
						|
        return try keyPairs
 | 
						|
            .order(ClosedGroupKeyPair.Columns.receivedTimestamp.desc)
 | 
						|
            .fetchOne(db)
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// MARK: - Convenience
 | 
						|
 | 
						|
public extension ClosedGroup {
 | 
						|
    enum LeaveType {
 | 
						|
        case standard
 | 
						|
        case silent
 | 
						|
        case forced
 | 
						|
    }
 | 
						|
    
 | 
						|
    static func removeKeysAndUnsubscribe(
 | 
						|
        _ db: Database? = nil,
 | 
						|
        threadId: String,
 | 
						|
        removeGroupData: Bool,
 | 
						|
        calledFromConfigHandling: Bool
 | 
						|
    ) throws {
 | 
						|
        try removeKeysAndUnsubscribe(
 | 
						|
            db,
 | 
						|
            threadIds: [threadId],
 | 
						|
            removeGroupData: removeGroupData,
 | 
						|
            calledFromConfigHandling: calledFromConfigHandling
 | 
						|
        )
 | 
						|
    }
 | 
						|
    
 | 
						|
    static func removeKeysAndUnsubscribe(
 | 
						|
        _ db: Database? = nil,
 | 
						|
        threadIds: [String],
 | 
						|
        removeGroupData: Bool,
 | 
						|
        calledFromConfigHandling: Bool
 | 
						|
    ) throws {
 | 
						|
        guard !threadIds.isEmpty else { return }
 | 
						|
        guard let db: Database = db else {
 | 
						|
            Storage.shared.write { db in
 | 
						|
                try ClosedGroup.removeKeysAndUnsubscribe(
 | 
						|
                    db,
 | 
						|
                    threadIds: threadIds,
 | 
						|
                    removeGroupData: removeGroupData,
 | 
						|
                    calledFromConfigHandling: calledFromConfigHandling
 | 
						|
                )
 | 
						|
            }
 | 
						|
            return
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Remove the group from the database and unsubscribe from PNs
 | 
						|
        let userPublicKey: String = getUserHexEncodedPublicKey(db)
 | 
						|
        
 | 
						|
        threadIds.forEach { threadId in
 | 
						|
            ClosedGroupPoller.shared.stopPolling(for: threadId)
 | 
						|
            
 | 
						|
            PushNotificationAPI
 | 
						|
                .performOperation(
 | 
						|
                    .unsubscribe,
 | 
						|
                    for: threadId,
 | 
						|
                    publicKey: userPublicKey
 | 
						|
                )
 | 
						|
                .sinkUntilComplete()
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Remove the keys for the group
 | 
						|
        try ClosedGroupKeyPair
 | 
						|
            .filter(threadIds.contains(ClosedGroupKeyPair.Columns.threadId))
 | 
						|
            .deleteAll(db)
 | 
						|
        
 | 
						|
        struct ThreadIdVariant: Decodable, FetchableRecord {
 | 
						|
            let id: String
 | 
						|
            let variant: SessionThread.Variant
 | 
						|
        }
 | 
						|
        
 | 
						|
        let threadVariants: [ThreadIdVariant] = try SessionThread
 | 
						|
            .select(.id, .variant)
 | 
						|
            .filter(ids: threadIds)
 | 
						|
            .asRequest(of: ThreadIdVariant.self)
 | 
						|
            .fetchAll(db)
 | 
						|
        
 | 
						|
        // Remove the remaining group data if desired
 | 
						|
        if removeGroupData {
 | 
						|
            try SessionThread   // Intentionally use `deleteAll` here as this gets triggered via `deleteOrLeave`
 | 
						|
                .filter(ids: threadIds)
 | 
						|
                .deleteAll(db)
 | 
						|
            
 | 
						|
            try ClosedGroup
 | 
						|
                .filter(ids: threadIds)
 | 
						|
                .deleteAll(db)
 | 
						|
            
 | 
						|
            try GroupMember
 | 
						|
                .filter(threadIds.contains(GroupMember.Columns.groupId))
 | 
						|
                .deleteAll(db)
 | 
						|
        }
 | 
						|
        
 | 
						|
        // If we weren't called from config handling then we need to remove the group
 | 
						|
        // data from the config
 | 
						|
        if !calledFromConfigHandling {
 | 
						|
            try SessionUtil.remove(
 | 
						|
                db,
 | 
						|
                legacyGroupIds: threadVariants
 | 
						|
                    .filter { $0.variant == .legacyGroup }
 | 
						|
                    .map { $0.id }
 | 
						|
            )
 | 
						|
            
 | 
						|
            try SessionUtil.remove(
 | 
						|
                db,
 | 
						|
                groupIds: threadVariants
 | 
						|
                    .filter { $0.variant == .group }
 | 
						|
                    .map { $0.id }
 | 
						|
            )
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |