Skip to content

Instantly share code, notes, and snippets.

@tcha-tcho
Last active February 15, 2024 19:43
Show Gist options
  • Save tcha-tcho/c36c8a6eef59941ba547a3329d8c0efe to your computer and use it in GitHub Desktop.
Save tcha-tcho/c36c8a6eef59941ba547a3329d8c0efe to your computer and use it in GitHub Desktop.
Comportamento do arquivo company e sua relação com devices e users

Organização de devices e companies

Agregação de dispositivos e organização geral entre a hierarquia de empresas.

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:

  1. O device é criado
  2. O trigger onCreate da collection devices é acionado
  3. O trigger então usa o dado deviceOf para atualizar a collection companies usando esse id no .doc(data.deviceOf)
  4. O trigger usa get() para pegar o documento companies e checar se ele ainda não está taintedCount: true.
  5. 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).
  6. 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...
  7. 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()
  8. O arquivo da collection companies tem um objeto chamado deviceCount. Nesse objeto, salvamos o novo número em { deviceCount: {own: [new count]}, taintedCount: false }, {merge: true} – isso a partir da task
  9. O trigger onChange do companies soma todos as contagens em deviceCount e compara o número com o deviceCountTotal.
  10. Se for diferente, existe a necessidade de atualizar o valor de deviceCountTotal, atualiza também deviceCountDirect (a soma dos que são dos clientes diretos), sendo que deviceCount.own entrega os dispositivos dele.
  11. A alteração no deviceCountTotal é, então, informada ao parent identificado em clientOf. ...firebase.firestore().collection("companies").doc(company.clientOf).set({deviceCount: {<id>: company.deviceCountTotal}},{merge: true})
  12. 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.

Segurança

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
    }

  }
}

Parentes para checagens hierárquicas

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:

  1. 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.
  2. Caso não esteja nesse Array, buscamos na collection companies pelo ...doc("<companyId>").get()...
  3. Confere em company.parents se algum dos companyId que ele é administradorOf, está listado como parent. Caso sim, está autorizado
  4. Não está em nenhum desses casos, reverte a alteração em onChange para a versão que está em data.before
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment