title: Firestore: Data modelling for activities / feed / timeline
body: <p>I'm trying to find a suitable Firestore data model for some kind of activity feed for my application.</p>
  <p><strong>Example:</strong></p>
  <ul>
  <li>A user from company A creates new task and assigns it to a user of company B.</li>
  </ul>
  <p><strong>Requirements</strong>:</p>
  <ul>
  <li>Users should see activities based on their company_id and user_role_id</li>
  <li>Every activity affects at least two companies (sender and receiver)</li>
  <li>Users should be able to archive activities. If a user archives an activity, it is archived for this particular user only. Other users can still access this activity until they archive it for themselves.</li>
  </ul>
  <p><strong>Current state:</strong></p>
  <pre><code>// data model: activities collection
  affected_companies: Array,
  affected_user_roles: Map,
  description: String
  
  // Example
  affected_companies:['c1', 'c2'],
  affected_user_roles: { '2': true, '5': true },
  description: 'Task A1'
  </code></pre>
  <p>Info: because we can't use multiple <code>array-contains</code> operators and I have a limited amount of <code>user_role_ids</code>, I simply use the field names for <code>affected_user_roles</code>.</p>
  <p>Querying this collection is quite easy:</p>
  <pre><code>let company_id = 'c1';
  let user_role_id = '2';
  
  // Query activities based on given company_id and user_role_id
  db.collection('activities').ref
    .where('affected_companies', 'array-contains', company_id) 
    .where('affected_user_roles.' + user_role_id, '==', true)
  </code></pre>
  <p><strong>Problem: How to query only non-archived activities?</strong>
  As described above users should be able to archive an activity for themselves (= hide it from their feed).</p>
  <p><strong>Approach 1:</strong>
  Practically I would simply add another array to my <code>activity</code> documents with all <code>user_ids</code> who have archived the activity for themselves. I would then add <code>.where('core.archived_by_users', 'array-contains', user_id)</code> to my query. Although this does not work. Multiple <code>array-contains</code> are not allowed.</p>
  <p><strong>Approach 2:</strong>
  I also thought about doing it similar to <code>affected_user_roles</code>: use the user_ids as field names and set boolean values. Although I would need to query for documents where a value does not exist, which is impossible as far as I know:</p>
  <pre><code>// Using user_ids as field names
  archived_by_users: { '111': true, '241': true }
  
  // Does not work because field 'archived_by_users.557' does not exist.
  let user_id = '557';
  .where('archived_by_users.' + user_id, '==', false)
  </code></pre>
  <p><strong>Approach 3:</strong>
  Using a subcollection <code>archived_by_users</code> in every <code>activity</code> document where I would store the <code>user_id</code> of each user who has archived the activity. Although it's not possible to query documents based on a value in their subcollection.</p>
  <p>I did not come up with a good solution. What would work is:</p>
  <ul>
  <li>Filter on client-side (not really an option since the activity feed will grow to thousands of documents. The goal is to fetch only the non-archived ones)</li>
  <li>Add another collection <code>users_activities</code> which keeps track of the <code>activities</code> archived by users. Although this would require additional queries and probably won't save me any reads.</li>
  </ul>
  <p>Can anybody think of a good approach here? Every answer is welcome. Thank you in advance!</p>