¿Es segura la forma en que el inicio rápido de la base de datos de Firebase maneja los recuentos?
Quiero crear un campo de incremento para los Me gusta de artículos.
Me refiero a este enlace: https://firebase.google.com/docs/database/android/save-data#save_data_as_transactions
En el ejemplo hay código para el campo de incremento:
if (p.stars.containsKey(getUid())) {
// Unstar the post and remove self from stars
p.starCount = p.starCount - 1;
p.stars.remove(getUid());
} else {
// Star the post and add self to stars
p.starCount = p.starCount + 1;
p.stars.put(getUid(), true);
}
Pero, ¿cómo puedo estar seguro de si al usuario ya le gustó o no le gustó el artículo?
En el ejemplo, el usuario (hacker) también podría borrar el mapa de estrellas completo como este y se guardará de todos modos:
p.stars = new HashMap<>();
y arruinará la lógica para otros usuarios a los que ya les gustó.
Ni siquiera creo que se puedan establecer reglas para esto, especialmente para la acción de "disminuir el recuento".
¿Alguna ayuda, sugerencia?
Las reglas de seguridad pueden hacer algunas cosas:
Asegúrese de que un usuario solo pueda agregar o eliminar el suyo propio
uid
en elstars
nodo."stars": { "$uid": { ".write": "$uid == auth.uid" } }
asegúrese de que un usuario solo pueda cambiar el
starCount
cuando agrega el suyo propiouid
alstars
nodo o lo elimina de allí- asegúrese de que el usuario solo pueda aumentar/disminuir
starCount
en 1
Incluso con estos, podría resultar complicado tener una regla de seguridad que garantice que sea starCount
igual a la cantidad de uids en el stars
nodo. Te animo a que lo pruebes y compartas tu resultado.
Sin embargo, la forma en que he visto a la mayoría de los desarrolladores lidiar con esto es:
- Comience a contar con el cliente (si el tamaño del
stars
nodo no es demasiado grande, esto es razonable). - tener un proceso confiable ejecutándose en un servidor que agregue los
stars
archivos enstarCount
. Podría usar eventos child_added/child_removed para incrementar/disminuir.
Actualización: con ejemplo práctico
Escribí un ejemplo práctico de un sistema de votación. La estructura de datos es:
votes: {
uid1: true,
uid2: true,
},
voteCount: 2
Cuando un usuario vota, la aplicación envía una actualización a varias ubicaciones:
{
"/votes/uid3": true,
"voteCount": 3
}
Y luego para eliminar su voto:
{
"/votes/uid3": null,
"voteCount": 2
}
Esto significa que la aplicación necesita leer explícitamente el valor actual de voteCount
, con:
function vote(auth) {
ref.child('voteCount').once('value', function(voteCount) {
var updates = {};
updates['votes/'+auth.uid] = true;
updates.voteCount = voteCount.val() + 1;
ref.update(updates);
});
}
Es esencialmente una transacción de múltiples ubicaciones, pero luego incorpora el código de la aplicación y las reglas de seguridad en lugar del SDK de Firebase y el servidor en sí.
Las reglas de seguridad hacen algunas cosas:
- asegúrese de que el recuento de votos solo pueda subir o bajar en 1
- Asegúrese de que un usuario solo pueda agregar/eliminar su propio voto.
- Garantizar que un aumento del recuento vaya acompañado de una votación.
- garantizar que una disminución del recuento vaya acompañada de un "voto negativo"
- Garantizar que una votación vaya acompañada de un aumento del recuento.
Tenga en cuenta que las reglas no:
- asegúrese de que un "desvoto" vaya acompañado de una disminución del recuento (se puede hacer con una
.write
regla) - reintentar votos/anulaciones fallidos (para manejar votaciones/anulaciones concurrentes)
Las normas:
"votes": {
"$uid": {
".write": "auth.uid == $uid",
".validate": "(!data.exists() && newData.val() == true &&
newData.parent().parent().child('voteCount').val() == data.parent().parent().child('voteCount').val() + 1
)"
}
},
"voteCount": {
".validate": "(newData.val() == data.val() + 1 &&
newData.parent().child('votes').child(auth.uid).val() == true &&
!data.parent().child('votes').child(auth.uid).exists()
) ||
(newData.val() == data.val() - 1 &&
!newData.parent().child('votes').child(auth.uid).exists() &&
data.parent().child('votes').child(auth.uid).val() == true
)",
".write": "auth != null"
}
jsbin con algún código para probar esto: http://jsbin.com/yaxexe/edit?js,console