0

I'm having a hard time figuring out how to validate a multi-location update where the updates depend on each other.

Consider the following structure:

"votes": {
  "$post_id": {
    "$answer_id": {
      "$user_id": {
        ".write": "...",
        ".validate": "..."
      }
    }
  }
}

"users": {
  "$user_id": {
    "votes": {
      "up": {
        ".write": "...",
        ".validate": "..."
      },
      "down": {
        ".write": "...",
        ".validate": "..."
      }
    }
  }
}

The users can vote on posts' answers with -1 / +1 (or remove their votes, so with null as well). So far so good, I can validate that no problem. My problem comes when I want to validate the user's up/down vote counter as well.

Example scenario: user A votes on an answer with +1, which would also increment user B's up counter with 1. How can I validate the up field so that it only gets incremented (or decremented) when there's an actual new vote for that.

Also there are scenarios like when a user has already voted +1 and then changes it directly to -1. I'm having a really hard time validating updates like this.

Should I just consider adding a server layer and do every single updates through the server? Or is my approach totally wrong here (or the data structure?). Adding a server layer would pretty much solve every validation issue, but also would add one more point of failure so I'm trying to avoid that.

Edit:

Update function

function vote(postID: string, answerID: string, author: string, oldVal: number, newVal: number): firebase.Promise<void> {
    let voteValue: number = newVal == 0 ? null : newVal; // -1, 0, 1, could be changed to boolean

    return this.ref.child(`users/${author}/votes`).once('value', count => {
        let updates = {};
        updates[`votes/${postID}/${answerID}/${this.authService.current.$key}`] = voteValue;

        if (voteValue == 1) {
            updates[`users/${author}/votes/up`] = ++count.val().up;

            if (oldVal == -1) {
                updates[`users/${author}/votes/down`] = --count.val().down;
            }
        }

        if (voteValue == -1) {
            updates[`users/${author}/votes/down`] = ++count.val().down;

            if (oldVal == 1) {
                updates[`users/${author}/votes/up`] = --count.val().up;
            }
        }

        if (voteValue == null && oldVal == -1) {
            updates[`users/${author}/votes/down`] = --count.val().down;
        }

        if (voteValue == null && oldVal == 1) {
            updates[`users/${author}/votes/up`] = --count.val().up;
        }

        this.ref.update(updates);
    });
}

When an answer's author's current votes are 0/0 and another user upvotes one of his answers it would create an update like:

"votes/-KM0CMCIQuBsGWQAjhRQ/-KM0CVmhK_7JQcxtdixl/fxpxj1Ky4yVpEeeB5PZviMjqNZv1": 1
"users/spAnCEKTTjX1GgdybQZIlXRI9IG2/votes/up": 1
Andrew
  • 2,063
  • 3
  • 24
  • 40
  • See http://stackoverflow.com/questions/37954217/is-the-way-the-firebase-database-quickstart-handles-counts-secure/37956590#37956590 – Frank van Puffelen Jul 08 '16 at 00:49
  • @FrankvanPuffelen I saw that and it's the way I've gone, but in my case I'm not sure how can I access the `votes/$post/$answer/$user` part from the `users/$id/votes/up|down` since there is no way to reference it (?). When validating the up/down value I don't have any `$post_id` or `$answer_id`. Changing the structure in `votes/...` is not really possible either. – Andrew Jul 08 '16 at 11:18
  • OK. I'm probably missing something then. Can you post the shortest possible code that reproduces the problem? This includes the actual write operation, your rules and the corresponding JSON. – Frank van Puffelen Jul 08 '16 at 14:44
  • Sure, a couple minutes. – Andrew Jul 08 '16 at 14:47
  • @FrankvanPuffelen Added the actual code and an example update. My rules on the `votes/...` part are working correctly, so I'm not posting that (it's way too long anyway), but on the other hand I don't know how to validate the `users/.../votes/up (or down)` part. I have no idea how can I know in that node that there's an actual vote happening with the actual value. – Andrew Jul 08 '16 at 15:03
  • Isn't this precisely what my linked answer does: ensure that a user can only increase the count if they also cast their vote at the same time? The only thing that seems different in your structure is that you don't track *what* the user voted on. Either that's missing, or it may simply not be needed for your use-case. – Frank van Puffelen Jul 08 '16 at 18:02
  • Well it's almost the same, but the actual value of the vote is down the line in `votes/postid/answerid/userid` which is causing me the problem. How can I be sure that when the `users/authorid/votes/up` value increases there's an actual vote in `votes/postid/answerid/userid` paired with it with value of 1 (or true)? I just don't understand how can I access that value when validating `users/authorid/votes/up` since in that node I don't know the `postid` and `answerid` (userid is `auth.uid`, so that's okay)? Am I missing something easy here? :/ – Andrew Jul 08 '16 at 18:22
  • Sorry, I still have don't have a clear idea. Note how your code translated into a tiny `updates` JSON? Now show the corresponding existing JSON data (that the update runs against) and the minimal rules that show what you're struggling with. Without those, I can't seem to parse your problem. – Frank van Puffelen Jul 08 '16 at 22:12
  • No worries! I'll try to explain with another approach. The rules I'm using on the `votes/...` node are quite long and those really are not the problem. In that node I can do `newData.parent()...` whatever as I have the `$postid` and `$answerid` so with those I can get the author's uid and validate that the answer's author's up/down vote is incremented/decremented as well. **BUT**: I can still update the `users/$user_id/votes/(up|down)` node **without** having a clue that there's an actual vote happening in the other node (`votes/...`). Rules for the users node: http://pastebin.com/Z2BNJ5B5 – Andrew Jul 08 '16 at 23:04
  • So basically with those rules I have I can do a single separate update/set that for example increments any user's votes by 1 without actually voting on anything. That's the main problem I have. No idea how to validate that there's a valid vote paired with the update. – Andrew Jul 08 '16 at 23:06
  • @FrankvanPuffelen I decided to go with a server side validation. Just one question, since the firebase docs are really not helping sometimes... Is there a way to have a service account that respects the `.validate` rules? Or this way I'll always have to prefetch the data and validate myself? Simple example: I only want to update the value if it's bigger than the old one by 3600 - the value is server timestamp. Any way to do this? – Andrew Jul 10 '16 at 15:05
  • Found it, nvm! Thanks for all the help! – Andrew Jul 10 '16 at 15:19

0 Answers0