Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What Firebase rule will prevent duplicates in a collection based on other fields?

I'm creating an application which lets users create items and then allow other users to subscribe to those items. I'm struggling to craft a rule that will prevent users from subscribing more than once to an item.

Here is an example of my data structure (anonymized, hence the "OMITTED" values):

{
    "OMITTED" : {
        "name" : "Second",
        "body" : "this is another",
        "userName" : "Some User",
        "userId" : "OMITTED",
        "created" : 1385602708464,
        "subscribers" : {
            "OMITTED" : {
                "userName" : "Some User",
                "userId" : "OMITTED"
            }
        }
    }
}

Here are my Firebase rules at present:

{
  "rules": {
    ".read": true,
    ".write": "auth != null",
    "items": {
      "$item": {
        ".write": "!data.exists()",
        ".validate": "newData.hasChildren(['name', 'body', 'userId', 'userName']) && newData.child('userId').val() == auth.id",
        "subscribers": {
          "$sub": {
            ".validate": "newData.hasChildren(['userId', 'userName']) && newData.child('userId').val() != data.child('userId').val()"
          }
        }
      }
    }
  }
}

How can I prevent users from subscribing more than once? What is the rule I need to prevent duplicate users within the subscribers list based on userId?

like image 609
Soviut Avatar asked Nov 28 '13 07:11

Soviut


1 Answers

Since security rules can't iterate a list of records to find the one containing a certain bit of data, the trick here is to store the records by an ID which allows for easy access. There is a great article on denormalization which offers some good insights into this practice.

In this case, if your use case allows, you may simply want to switch your data structure so that records are stored by the user's id, rather than storing the ID as a value in the record, like so:

/users/user_id/items/item_id/subscribers/user_id/

In fact, as you'll see in denormalization, you may even benefit from splitting things out even farther, depending on the exact size of your data and how you'll be reading it later:

/users/user_id
/items/user_id/item_id
/subscribers/item_id/user_id

In either of these formats, you can now prevent duplicates and lock down security rather nicely with something like this:

{
   "users": {
      "$user_id": { ".write": "auth.id === $user_id" }
   },
   "subscribers": {
      "$subscriber_id": { ".write": "auth.id === $subscriber_id" }
   }
}
like image 115
Kato Avatar answered Sep 27 '22 19:09

Kato