Dispositivos sempre ficam lotados em alguma companhia específica. Ela fica identificada no arquivo device:
{// documento device gravado com o nome "<imei>" na collection de devices
"imei": "<imei>"
,"deviceOf": "<companyId>"
,...
}
Ao criar um dispositivo, a company
deve ter sua contagem atualizada e isso é feito através de um processo composto por passos que utilizam triggers do functions + tasks + entradas padronizadas nos arquivos
Descrevendo o processo de contagem por passos:
- O device é criado
- O trigger
onCreate
da collection devices é acionado - O trigger então usa o dado
deviceOf
para atualizar a collectioncompanies
usando esse id no.doc(data.deviceOf)
- O trigger usa
get()
para pegar o documentocompanies
e checar se ele ainda não estátaintedCount: true
. - O trigger atualiza o documento para
set({ taintedCount: true }, {merge: true})
se ele não estiver tainted. (ler é um terço do custo de gravar). - A alteração dessa propriedade sinaliza para uma task que roda a cada 30s que a contagem deve ser refeita
global.admin.firebase.firestore().collection("companies").where("taintedCount", "==", true).get...
- A task usa a busca com count para saber quantos devices em nome da
company
existem para cada company na situação de taintedCount,...firestore().collection("devices").where("deviceOf", "==", "<companyId>").count().get()
- O arquivo da collection
companies
tem um objeto chamadodeviceCount
. Nesse objeto, salvamos o novo número em{ deviceCount: {own: [new count]}, taintedCount: false }, {merge: true}
– isso a partir da task - O trigger
onChange
docompanies
soma todos as contagens emdeviceCount
e compara o número com odeviceCountTotal
. - Se for diferente, existe a necessidade de atualizar o valor de
deviceCountTotal
, atualiza tambémdeviceCountDirect
(a soma dos que são dos clientes diretos), sendo quedeviceCount.own
entrega os dispositivos dele. - A alteração no
deviceCountTotal
é, então, informada ao parent identificado emclientOf
....firebase.firestore().collection("companies").doc(company.clientOf).set({deviceCount: {<id>: company.deviceCountTotal}},{merge: true})
- Essa alteração vai criar uma cadeia de atualizações nas contagens, porque agora o
deviceCountTotal
vai ficar diferente da soma do parent e vai obrigar a atualizar o parent também.
A collection companies
e a collection devices
tem uma guarda compartilhada e as checagens de segurança acontecem por uma conferência que ocorre no trigger onChange
. Um atributo chamado updatedBy
informa o uid
do último usuário a alterar o arquivo e esse uid
é checado nas relações que o usuário tem com as empresas.
Para garantir que tudo fique seguro, ao modificar a rota companies
e devices
o Security Rules
vai conferir se o updatedBy é o usuário que fez a edição:
service cloud.firestore {
match /databases/{database}/documents {
// Make sure later (onChange) that document was changed by an allowed user
match /companies/{companyId} {
allow update: if request.resource.data.updatedBy == request.auth.uid
}
match /devices/{deviceId} {
allow update: if request.resource.data.updatedBy == request.auth.uid
}
}
}
A regra é: O arquivo a ser modificado está ligado à empresa ou às empresas superiores ao nó. Para uma conferência rápida, o ideal seria termos uma lista de todos os parents
da company
em questão.
O arquivo company
tem dois atributos importantes: { "clientOf": "<companyId>", "parents": ["<companyId>", ...] }
Usamos o clientOf
para ir subindo e gravando no array de parents
as companies da cadeia dele.
Esse dado pode ser usado de duas formas:
- Quero saber os children dessa empresa?
...collection("companies").where("parents", "array-contains", company.id)...
- Quero saber os parents dessa empresa?
company.parents
Nota-se que o caminho também concede acesso somente aos dados que interessam ao membro do nó: Saber seus parents e seus children. Precisamos tirar acesso do usuário aos outros nós como children dos seus parents e etc.
Para checar se o usuário tem autorização para criar ou modificar os arquivos pertinentes, precisamos do uid
e o companyId
. Os passos:
- Acessa o array de
administratorOf
do usuário onde estarão listadas as<companyId>
que ele tem permissão para alterar. Se a companyId passada ao método de checagem já estiver listada aí, está autorizado. - Caso não esteja nesse Array, buscamos na collection
companies
pelo...doc("<companyId>").get()...
- Confere em
company.parents
se algum doscompanyId
que ele éadministradorOf
, está listado como parent. Caso sim, está autorizado - Não está em nenhum desses casos, reverte a alteração em
onChange
para a versão que está emdata.before