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
onCreateda collection devices é acionado - O trigger então usa o dado
deviceOfpara atualizar a collectioncompaniesusando esse id no.doc(data.deviceOf) - O trigger usa
get()para pegar o documentocompaniese 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
companyexistem para cada company na situação de taintedCount,...firestore().collection("devices").where("deviceOf", "==", "<companyId>").count().get() - O arquivo da collection
companiestem 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
onChangedocompaniessoma todos as contagens emdeviceCounte 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.ownentrega 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
deviceCountTotalvai 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
administratorOfdo 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
companiespelo...doc("<companyId>").get()... - Confere em
company.parentsse algum doscompanyIdque ele éadministradorOf, está listado como parent. Caso sim, está autorizado - Não está em nenhum desses casos, reverte a alteração em
onChangepara a versão que está emdata.before