Skip to content

Commit 93a16c0

Browse files
authored
Merge pull request #1009 from firebase/colerogers.v2-scheduled
Scheduled v2 Triggers
2 parents 87e297f + 36d7928 commit 93a16c0

File tree

5 files changed

+158
-0
lines changed

5 files changed

+158
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Periodically delete unused accounts
2+
3+
This sample demonstrates how to delete the accounts of users who have not signed-in in the last month.
4+
5+
6+
## Functions Code
7+
8+
See the file [functions/index.js](functions/index.js) for the code.
9+
10+
**Note:** This function uses Cloud Scheduler, which can have associated costs. Your project must be on the Blaze payment plan as these features require billing information. See the [Cloud Scheduler pricing page](https://cloud.google.com/scheduler/pricing) for more information.
11+
12+
The dependencies are listed in [functions/package.json](functions/package.json).
13+
14+
## Deploy and test
15+
16+
To set up the sample:
17+
18+
- Create a Firebase Project using the [Firebase Developer Console](https://console.firebase.google.com)
19+
- Download this sample e.g. `git clone https://github.com/firebase/functions-samples`
20+
- Enter the sample directory `cd functions-samples/2nd-gen/delete-unused-accounts-cron`
21+
- Setup the sample with your project `firebase use --add` and follow the instructions.
22+
- Install node dependencies of your Functions `cd functions; npm install; cd -`
23+
- Deploy your project using `firebase deploy`.
24+
- The Cloud Scheduler job should then run once a day and delete any inactive users. You can manually run the task by [navigating to Cloud Scheduler in the Google Cloud Platform Console](https://console.cloud.google.com/cloudscheduler).
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module.exports = {
2+
root: true,
3+
env: {
4+
es6: true,
5+
node: true,
6+
},
7+
extends: [
8+
"eslint:recommended",
9+
"google",
10+
],
11+
rules: {
12+
quotes: ["error", "double"],
13+
},
14+
};
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* Copyright 2022 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
'use strict';
17+
18+
// [START all]
19+
// [START import]
20+
// The Cloud Functions for Firebase SDK to create v2 Cloud Functions and set up triggers.
21+
const { onSchedule } = require('firebase-functions/v2/scheduler');
22+
const { logger } = require('firebase-functions');
23+
24+
// The Firebase Admin SDK to delete inactive users.
25+
const admin = require('firebase-admin');
26+
admin.initializeApp();
27+
28+
// The es6-promise-pool to limit the concurrency of promises.
29+
const PromisePool = require('es6-promise-pool').default;
30+
// Maximum concurrent account deletions.
31+
const MAX_CONCURRENT = 3;
32+
// [END import]
33+
34+
// [START accountcleanup]
35+
// Run once a day at midnight, to clean up the users
36+
// Manually run the task here https://console.cloud.google.com/cloudscheduler
37+
exports.accountcleanup = onSchedule('every day 00:00', async (event) => {
38+
// Fetch all user details.
39+
const inactiveUsers = await getInactiveUsers();
40+
// Use a pool so that we delete maximum `MAX_CONCURRENT` users in parallel.
41+
const promisePool = new PromisePool(() => deleteInactiveUser(inactiveUsers), MAX_CONCURRENT);
42+
await promisePool.start();
43+
logger.log('User cleanup finished');
44+
});
45+
// [END accountcleanup]
46+
47+
// [START deleteInactiveUser]
48+
// Deletes one inactive user from the list.
49+
function deleteInactiveUser(inactiveUsers) {
50+
if (inactiveUsers.length > 0) {
51+
const userToDelete = inactiveUsers.pop();
52+
53+
// Delete the inactive user.
54+
return admin.auth().deleteUser(userToDelete.uid).then(() => {
55+
return logger.log(
56+
'Deleted user account',
57+
userToDelete.uid,
58+
'because of inactivity'
59+
);
60+
}).catch((error) => {
61+
return logger.error(
62+
'Deletion of inactive user account',
63+
userToDelete.uid,
64+
'failed:',
65+
error
66+
);
67+
});
68+
} else {
69+
return null;
70+
}
71+
}
72+
// [END deleteInactiveUser]
73+
74+
// [START getInactiveUsers]
75+
// Returns the list of all inactive users.
76+
async function getInactiveUsers(users = [], nextPageToken) {
77+
const result = await admin.auth().listUsers(1000, nextPageToken);
78+
// Find users that have not signed in in the last 30 days.
79+
const inactiveUsers = result.users.filter(
80+
user => Date.parse(user.metadata.lastRefreshTime || user.metadata.lastSignInTime) < (Date.now() - 30 * 24 * 60 * 60 * 1000));
81+
82+
// Concat with list of previously found inactive users if there was more than 1000 users.
83+
users = users.concat(inactiveUsers);
84+
85+
// If there are more users to fetch we fetch them.
86+
if (result.pageToken) {
87+
return getInactiveUsers(users, result.pageToken);
88+
}
89+
90+
return users;
91+
}
92+
// [END getInactiveUsers]
93+
// [END all]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "delete-unused-accounts-cron-functions",
3+
"description": "Periodically delete unused Firebase accounts",
4+
"dependencies": {
5+
"es6-promise-pool": "^2.5.0",
6+
"firebase-admin": "^10.2.0",
7+
"firebase-functions": "^3.23.0"
8+
},
9+
"devDependencies": {
10+
"eslint": "^6.8.0",
11+
"eslint-plugin-promise": "^4.2.1"
12+
},
13+
"scripts": {
14+
"lint": "./node_modules/.bin/eslint --max-warnings=0 .",
15+
"serve": "firebase emulators:start --only functions",
16+
"shell": "firebase functions:shell",
17+
"start": "npm run shell",
18+
"deploy": "firebase deploy --only functions",
19+
"logs": "firebase functions:log",
20+
"compile": "cp ../../tsconfig.template.json ./tsconfig-compile.json && tsc --project tsconfig-compile.json"
21+
},
22+
"engines": {
23+
"node": "16"
24+
},
25+
"private": true
26+
}

0 commit comments

Comments
 (0)