I want to compare data at one node with data located at an entirely different node.
Please know, in advance I cannot change any front end codes, by instruction of my project lead.
Context:
I'm building for a client which will be an upscale chatting app for people who want to find new roommates on college campuses. There are two user types, leasees (leasee) and leasors (leasor).
leasor users add listings and have profiles, while leasee users also have profiles and initiate communication by reaching out to leasor users first with a roommateRequest - it's a chat message but its basically like a Skype, Facebook or LinkedIn request if you will.
I want both user types to be able to write to or remove UIDs from the roommates node (their roommates list on the app), similar to a Facebook friends list. However, when a leasee has no roommates they cannot write to the roommates node because they send a roommateRequest to leasor users and leasor users cannot send a roommateRequest to a leasee user profile.
On the frontend iOS client, the leasor user accepts a roommateRequest - which simultaneously writes to both the leasor user's roommates node & the requesting leasee user's roommates node.
Here is the front end iOS client code related to the database data I will show you next:
import Foundation
import FirebaseAuth
import FirebaseDatabase
class ChatFunctionService {
func sendRoommateRequestToId(text: String, _ id: String, completionHandler: @escaping() -> Void) {
let timestamp = Date().timeIntervalSince1970
guard let uid = Auth.auth().currentUser?.uid,
let expiration = Calendar.current.date(byAdding: .day, value: 7, to: Date())?.timeIntervalSince1970 else {
return
}
var values: [String: Any] = ["expiration": expiration, "status": "pending"]
values["receiverProfileType"] = otherUserTypeTextString
Database.database().reference().child("roommateRequests").childByAutoId().setValue(values) { _, ref in
let message = UserChatModel(
dictionary: [
"text": text,
"senderId": uid,
"receiverId": id,
"timestamp": timestamp,
"roommateRequestId": ref.key!,
"receiverProfileType": self.otherUserTypeTextString
]
)
self.sendChat(message, toUser: id) {
completionHandler()
}
}
}
///Send message:
private func sendChat(_ message: UserChatModel, toUser receiverId: String, completion: @escaping() -> Void) {
guard let uid = Auth.auth().currentUser?.uid,
let partnerId = message.getPartnerId() else { return }
Database.database().reference().child("messages").childByAutoId().updateChildValues(message.data) { _, ref in
let messageUID = ref.key!
let senderRef = Database.database().reference().child("conversations").child(uid).child(self.userTypeTextString).child(receiverId)
let receiverRef = Database.database().reference().child("conversations").child(receiverId).child(self.otherUserTypeTextString).child(uid)
ref.updateChildValues(["uid": messageUID])
senderRef.updateChildValues([messageUID: 1])
receiverRef.updateChildValues([messageUID: 1])
self.updateChatsData(chat: message, partnerUID: partnerId, messageUID: messageUID)
completion()
}
}
func acceptRoommateRequest(roommateRequestId: String, userTypeTextString: String, receiverUID: String, otherUserTypeTextString: String, completion: @escaping () -> Void) {
guard let uid = Auth.auth().currentUser?.uid else {
completion()
return
}
Database.database().reference().child("roommateRequests").child(roommateRequestId).child("status").setValue("accepted")
let values = [
"/\(uid)/\(userTypeTextString)/\(receiverUID)": 1,
"/\(receiverUID)/\(otherUserTypeTextString)/\(uid)": 1
]
Database.database().reference().child("roommates").updateChildValues(values)
updateChatsMetaDataForNewRoommate(userTypeTextString: userTypeTextString, receiverUID: receiverUID, otherUserTypeTextString: otherUserTypeTextString) {
completion()
}
}
func updateChatsMetaDataForNewRoommate(userTypeTextString: String, receiverUID: String, otherUserTypeTextString: String, completion: @escaping () -> Void ) {
guard let uid = Auth.auth().currentUser?.uid else { return }
Database.database().reference().child("chatsData").child(uid).child(userTypeTextString).child(receiverUID).child("roommates").setValue(true)
Database.database().reference().child("chatsData").child(receiverUID).child(otherUserTypeTextString).child(uid).child("roommates").setValue(true)
sendUserChat(text: "roommate request granted.", receiverUID: receiverUID) { (messageId) in
completion()
}
}
func sendUserChat(text: String, receiverUID: String, completion: @escaping(String?) -> Void) {
let timestamp = Date().timeIntervalSince1970
guard let uid = Auth.auth().currentUser?.uid else { return }
var chatDictionary: [String: Any] = [
"text": text, "timestamp": timestamp,
"senderId": uid, "receiverId": receiverUID
]
chatDictionary["receiverProfileType"] = "leasee"
let chat = UserChatModel(dictionary: chatDictionary)
guard let partnerUID = chat.getPartnerId() else {
return
}
Database.database().reference().child("threads").childByAutoId().updateChildValues(chat.data) { _, ref in
let messageUID = ref.key!
ref.updateChildValues(["uid": messageUID])
self.updateChatsData(
chat: chat,
partnerUID: partnerUID,
messageUID: messageUID
)
completion(messageUID)
}
}
func updateChatsData(chat: UserChatModel, partnerUID: String, messageUID: String) {
guard let uid = Auth.auth().currentUser?.uid else {
return
}
var chatData = [String: Any]()
chatData["lastMessageSenderId"] = uid
chatData["lastMessageId"] = messageUID
chatData["lastMessage"] = chat.text ?? ""
chatData["chatMemberIds"] = [uid, partnerUID]
Database.database().reference().child("chatsMetaData").child(uid).child(userTypeTextString).child(partnerUID).setValue(chatData)
Database.database().reference().child("chatsMetaData").child(partnerUID).child(otherUserTypeTextString).child(uid).setValue(chatData)
}
}
class UserChatModel {
var text: String?
var senderId: String
var receiverId: String
var timeStamp: NSNumber
var data: [String: Any]
var roommateRequestId: String?
init(dictionary: [String: Any]) {
data = dictionary
text = dictionary["text"] as? String
senderId = dictionary["senderId"] as! String
timeStamp = dictionary["timestamp"] as! NSNumber
receiverId = dictionary["receiverId"] as? String ?? ""
roommateRequestId = dictionary["roommateRequestId"] as? String
super.init(dictionary: dictionary)
}
func getPartnerId() -> String? {
guard let uid = Auth.auth().currentUser?.uid else {
return nil
}
return senderId == uid ? receiverId : senderId
}
}
Once the leasee has a single roommate, they should be able to remove that roommate from their roommates list (if they so wish). Continually, only leasor users can accept a roommateRequest which writes to both the roomateRequest sender (leasee) user's UID in the roommates node and the receiver (leasor) user's UID in the roommates node. Moreover, leasor users can also remove leasee users from their roommates node, at anytime.
The issue is I don't want just any user to be able to write or update the roommates node of any user unless it is removing themselves from it.
Here is the full realtime-database structure for the chat portion of the app, in two screenshot images:
I've narrowed my works down (between the iOS front end, database structure and security rules) to matching the lastMessageId value from the chatsMetaData or chats node to then check the threads node to get the roommateRequestId and then compare the roommateRequestId to the 'roommateRequests' node to verify that the leasor can write to the leasee's node. And then I will provide an OR case and match the value at the roommates node to the value of auth.uid for after they are already on each other's roommate's list for when a user wants to write to their roommates node (removing a roommate) - but I have no idea on how to do any of this. I know what to do but I am not sure how to achieve it...
I think I can reference the chatsMetaData node all the way down to the 'lastMessageId' value :

With this value I think I should then search the threads node to get the roommateRequestId and then search the roommateRequests node to match it and allow a write along with some auth.uid checks to match uid values in the node's data. But how can I do all this if they are by autoId way?


