Sneh
Sneh

Reputation: 453

Trigger Firebase Cloud Function on inserting to Cloud Firestore document for the first time with given uid

I want to Trigger a Cloud Funtion on insert to Cloud Firestore document for the first time with given uid

The below code triggers on insert of newUserId

functions.firestore
  .document(`teamProfile/{teamId}/teamMemberList/{newUserId}`)
  .onCreate()

Requirement --

id is the document id.

Here, the Cloud function should trigger if it's the first post by uid(user).

Upvotes: 1

Views: 852

Answers (1)

samthecodingman
samthecodingman

Reputation: 26196

Edited response (following clarification in comments):

Data structure:

users/{userId}/posts/
 ◙ post18sfge89s
   - title: My first post!
   - t: 1572967518
 ◙ post2789sdjnf
   - title: I like posting
   - t: 1572967560

posts/
 ◙ post18sfge89s
   - title: My first post!
   - uid: someUid1
   - comments/
     ◙ comment237492384
       ...
     ◙ comment234234235
       ...
 ◙ post2789sdjnf
   - title: I like posting
   - uid: someUid1

Cloud Function Code:

With the above structure, you will need two Cloud Functions to manage it – one for handling each new post (to copy information to the author's post list) and one for checking if it's the author's first post.

// Function: New post handler
exports.newPost = functions.firestore
  .document('posts/{postId}')
  .onCreate((postDocSnap, context) => {

    // get relevant information
    const postId = postDocSnap.id; // provided automatically
    const postTitle = postDocSnap.get('title');
    const userId = postDocSnap.get('uid');
    const postedAt = postDocSnap.createTime; // provided automatically

    // add to user's post list/index
    return firestore.doc(`users/${userId}/posts/${postId}`)
      .set({
        title: postTitle,
        t: postedAt
      });
  });

// Function: New post by user handler
exports.newPostByUser = functions.firestore
  .document('users/{userId}/posts/{postId}')
  .onCreate((postDocSnap, context) => {

    // get references
    const userPostsColRef = postDocSnap.ref.parent;
    const userDocRef = userPostsCol.parent;

    // get snapshot of user data
    return userDocRef.get()
      .then((userDocSnap) => {

        // check if "First Post Event" has already taken place
        if (userDocSnap.get('madeFirstPostEvent') != true) {
          return getCollectionSize(userPostsColRef).then((length) => {
            if (length == 1) {
              return handleFirstPostByUser(userDocSnap, postDocSnap);
            } else {
              return; // could return "false" here
            }
          });
        }
      });
  });

// Pseudo-function: First post by user handler
function handleFirstPostByUser(userDocSnap, postDocSnap) {
  return new Promise(() => {
      const postId = postDocSnap.id;
      const postTitle = postDocSnap.get('title');
      const userId = userDocSnap.id;

      // do something

      // mark event handled
      return userDocSnap.ref.update({madeFirstPostEvent: true});
    });
}

// returns a promise containing the length of the given collection
// note: doesn't filter out missing (deleted) documents
function getCollectionSize(colRef) {
  return colRef.listDocuments()
     .then(docRefArray => {
       return docRefArray.length;
     });
}


Original response (for posts that are private to each team):

Assumptions:

  • Checking for a user's first post in a specific team, not platform wide.
  • Unknown data structure – I have used what I think will work well with your existing structure.

Data Structure:

The teamContent/ collection is structured so that it can contain subcollections for different items such as posts, attachments, pictures, etc.

teamProfile/{teamId}/teamMemberList/{userId}/posts/
 ◙ post18sfge89s
   - title: My first post!
   - t: 1572967518
 ◙ post2789sdjnf
   - title: I like posting
   - t: 1572967560

teamContent/{teamId}/posts/
 ◙ post18sfge89s
   - title: My first post!
   - author: someUid1
   - comments/
     ◙ comment237492384
       ...
     ◙ comment234234235
       ...
 ◙ post2789sdjnf
   - title: I like posting
   - author: someUid1

Cloud Function Code:

With the above structure, you will need two Cloud Functions to manage it – one for handling each new post (to copy information to the author's post list) and one for checking if it's the author's first post in that particular team.

// Function: New team post handler
exports.newPostInTeam = functions.firestore
  .document('teamContent/{teamId}/posts/{postId}')
  .onCreate((postDocSnap, context) => {

    // get relevant information
    const postId = postDocSnap.id; // provided automatically
    const postTitle = postDocSnap.get('title');
    const authorId = postDocSnap.get('author');
    const postedAt = postDocSnap.createTime; // provided automatically
    const teamId = context.params.teamId;

    // add to user's post list/index
    return firestore.doc(`teamProfile/${teamId}/teamMemberList/${authorId}/posts/${postId}`)
      .set({
        title: postTitle,
        t: postedAt
      });
  });

// Function: New post by team member handler
exports.newPostByTeamMember = functions.firestore
  .document('teamProfile/{teamId}/teamMemberList/{userId}/posts/{postId}')
  .onCreate((postDocSnap, context) => {

    // get references
    const userPostsColRef = postDocSnap.ref.parent;
    const userDocRef = userPostsCol.parent;

    // get snapshot of user data
    return userDocRef.get()
      .then((userDocSnap) => {

        // check if "First Post Event" has already taken place
        if (userDocSnap.get('madeFirstPostEvent') != true) {
          return getCollectionSize(userPostsColRef).then((length) => {
            if (length == 1) {
              return handleFirstPostInTeamEvent(userDocSnap, postDocSnap);
            } else {
              return; // could return "false" here
            }
          });
        }
      });
  });

// Pseudo-function: First post by team member handler
function handleFirstPostInTeamEvent(userDocSnap, postDocSnap) {
  return new Promise(() => {
      const postId = postDocSnap.id;
      const postTitle = postDocSnap.get('title');
      const userId = userDocSnap.id;

      // do something

      // mark event handled
      return userDocSnap.update({madeFirstPostEvent: true});
    });
}

// returns a promise containing the length of the given collection
// note: doesn't filter out missing (deleted) documents
function getCollectionSize(colRef) {
  return colRef.listDocuments()
     .then(docRefArray => {
       return docRefArray.length;
     });
}


Notes:

  • Above code is not completely idempotent. If multiple posts from the same user are all uploaded at once, there is a possibility that the handleFirstPost* functions will be called multiple times.
  • Above code doesn't account for missing documents returned from listDocuments() in the getCollectionSize() function. This is not a concern in the above sample because the {userId}/posts collection doesn't have any subcollections. Be wary if you call it elsewhere though.
  • No error handling is included
  • Use of async/await syntax can make it cleaner
  • Above code was written without deploying it, bugs/typos may be present

Upvotes: 2

Related Questions