Jonathan
Jonathan

Reputation: 9151

Firestore pre-deployment script

I'm searching for a way to add pre-deployment scripts to my Firebase project.
I'm using Firestore and my security rules are set up in a way that only cloud functions can write to Firestore.

I've added a user role field to my user table which automatically gets populated on userCreate. This works fine but my prod env still has users without this field.
A logical solution would be to run a pre-deploy command which add this field to all existing users but I have no clue how to do this.

My current best solution is to create a cloud function specifically for this one-time use and trigger it. This doesn't feel like the right way to handle such things.
How do I run a one time update statement on Firestore?

Upvotes: 0

Views: 168

Answers (2)

Jonathan
Jonathan

Reputation: 9151

I've updated @Dharmaraj answer with some extra features in case someone ever needs this.

const admin = require('firebase-admin');
// DEV
const serviceAccount = require('./x-dev-firebase-adminsdk-1234.json');
// PROD
// const serviceAccount = require('./x-firebase-adminsdk-1234.json');
const newRoles = [0];
const emails = ['[email protected]', '[email protected]'];

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
});

const addRoles = async () => {
  try {
    let userColRef = admin.firestore().collection('users');
    if (emails.length) {
      userColRef = userColRef.where('email', 'in', emails);
    }

    const users = await userColRef.get();

    const updates = [];

    users.docs.forEach((doc) => {
      const user = doc.data();
      let existingRoles = [];
      if (user.roles) {
        existingRoles = user.roles;

        if (newRoles.every((role) => existingRoles.includes(role))) {
          return;
        }
      }

      const roles = Array.from(new Set(existingRoles.concat(newRoles)));

      updates.push(doc.ref.set({ roles }, { merge: true }));
    });

    await Promise.all(updates);

    console.log(
      `Role${newRoles.length > 1 ? 's' : ''} added to ${updates.length} user${
        updates.length !== 1 ? 's' : ''
      }.`
    );
    return true;
  } catch (error) {
    console.log(error);
    return error;
  }
};

addRoles().catch((e) => {
  console.log(e);
});

Here's where you create the service account btw. Service account

Upvotes: 0

Dharmaraj
Dharmaraj

Reputation: 50920

You can write a temporary script using Firebase Admin SDK and execute it once. The flow would look something like:

  1. Fetching all documents without the userRole field.
  2. Add update statements in an array and execute all the promises at once.

Here's a demo:

const admin = require("firebase-admin");

const serviceAccount = require("/path/to/serviceAccountKet.json");

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: "https://fate-bot-discord.firebaseio.com"
});

async function addRoles() {
    try {
        const userColRef = admin.firestore().collection("users")
        const users = await userColRef.where("userRole", "==", "").get()
        const updates = []

        users.docs.forEach((user) => {
            updates.push(userColRef.doc(user.id).update({ userRole: "theNewRole" }))
        })
        await Promise.all(updates)
        console.log("Roles added successfully")
        return "Roles Added"
    } catch (error) {
        console.log(error);
        return error
    }
}

//Call the function
addRoles().then((response) => {
  console.log(response)
}).catch((e) => {
  console.log(e)
})

Please let me know if you need further assistance!

Upvotes: 1

Related Questions