Skip to content

Commit

Permalink
increase function timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
OronNadiv committed Sep 6, 2024
1 parent 6e3817b commit 5a6cd36
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 110 deletions.
150 changes: 81 additions & 69 deletions functions/src/auth2Users.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import * as Admin from 'firebase-admin'
import { Firestore } from 'firebase-admin/firestore'
import { Auth } from 'firebase-admin/auth'
import { UserRecord } from 'firebase-functions/lib/providers/auth'
import { info, error } from "firebase-functions/logger"
import { User } from './User'
Expand All @@ -8,88 +10,98 @@ import got from 'got'
const moment = require('moment')
const gravatar = require('gravatar')


export interface Auth2UsersOptions {
syncGravatar: boolean
}

const Auth2Users = (admin: Admin.app.App) => {
const firestore = admin.firestore()
const auth = admin.auth()
export default class Auth2Users {
private firestore: Firestore
private auth: Auth

const listAllUsers = async (options: Auth2UsersOptions, nextPageToken?: string) => {
// List batch of users, 1000 at a time.
const listUsersResult = await auth.listUsers(1000, nextPageToken)
// Since this function is called upon creation of a new account, we want to
// sync the newest acccounts first.
listUsersResult.users.sort((userRecord1: UserRecord, userRecord2: UserRecord) => {
const createdAt1 = moment(userRecord1.metadata.creationTime)
const createdAt2 = moment(userRecord2.metadata.creationTime)
return createdAt2.diff(createdAt1)
})
await each(listUsersResult.users, async (userRecord: UserRecord) => {
try {
const {
uid,
email,
emailVerified,
displayName,
metadata: { creationTime, lastSignInTime },
} = userRecord
constructor(admin: Admin.app.App) {
this.firestore = admin.firestore()
this.auth = admin.auth()
}

async Sync(userRecord: UserRecord, options: Auth2UsersOptions) {
try {
const {
uid,
email,
emailVerified,
displayName,
metadata: { creationTime, lastSignInTime },
} = userRecord

const createdAt = moment(creationTime)
.utc()
.format()
const lastSignedInAt: string = moment(lastSignInTime)
.utc()
.format()
const userRef = firestore.doc(`users/${uid}`)
const data: User = {
uid,
createdAt,
email: email || '',
emailVerified,
displayName: displayName || '',
lastSignedInAt
}

if (options.syncGravatar) {
let hasGravatar = false
const gravatarUrl = gravatar.url(email, {
protocol: 'https',
default: '404'
})
try {
await got.get(gravatarUrl)
info('found gravatar.', { gravatarUrl })
hasGravatar = true
} catch (err) {
info('Error while fetching gravatar.', { gravatarUrl, error: err })
}
data.gravatarUrl = hasGravatar ? gravatarUrl : null
}
const createdAt = moment(creationTime)
.utc()
.format()
const lastSignedInAt: string = moment(lastSignInTime)
.utc()
.format()
const userRef = this.firestore.doc(`users/${uid}`)
const data: User = {
uid,
createdAt,
email: email || '',
emailVerified,
displayName: displayName || '',
lastSignedInAt
}

await userRef.set(data, { merge: true })
info(`Updated ${uid} ${createdAt} ${lastSignedInAt}`)
} catch (err) {
error('Error while syncing auth2user.', { 'uid': userRecord.uid, 'error': err });
if (options.syncGravatar) {
let hasGravatar = false
const gravatarUrl = gravatar.url(email, {
protocol: 'https',
default: '404'
})
try {
await got.get(gravatarUrl)
info('found gravatar.', { gravatarUrl })
hasGravatar = true
} catch (err) {
info('Error while fetching gravatar.', { gravatarUrl, error: err })
}
data.gravatarUrl = hasGravatar ? gravatarUrl : null
}
})
info(
`checking listUsersResult.pageToken: ${listUsersResult.pageToken} num of results previously found: ${listUsersResult.users.length}`)
if (listUsersResult.pageToken) {
// List next batch of users.
await listAllUsers(options, listUsersResult.pageToken)
} else {
info('Done. Exiting...')
return

await userRef.set(data, { merge: true })
info(`Updated ${uid} ${createdAt} ${lastSignedInAt}`)
} catch (err) {
error('Error while syncing auth2user.', { 'uid': userRecord.uid, 'error': err });
}
}

// Start listing users from the beginning, 1000 at a time.
async SyncAll(options: Auth2UsersOptions) {

return listAllUsers
const listAllUsers = async (nextPageToken?: string) => {
// List batch of users, 1000 at a time.
const listUsersResult = await this.auth.listUsers(1000, nextPageToken)
// We want to sync the most active acccounts first.
listUsersResult.users.sort((userRecord1: UserRecord, userRecord2: UserRecord) => {
const lastSignInTime1 = moment(userRecord1.metadata.lastSignInTime)
const lastSignInTime2 = moment(userRecord2.metadata.lastSignInTime)
return lastSignInTime2.diff(lastSignInTime1)
})
await each(listUsersResult.users, async (userRecord: UserRecord) => {
await this.Sync(userRecord, options);
})
info(
`checking listUsersResult.pageToken: ${listUsersResult.pageToken} num of results previously found: ${listUsersResult.users.length}`)
if (listUsersResult.pageToken) {
// List next batch of users.
await listAllUsers(listUsersResult.pageToken)
} else {
info('Done. Exiting...')
return
}
}

// Start listing users from the beginning, 1000 at a time.

return listAllUsers()
}
}

export default Auth2Users

4 changes: 2 additions & 2 deletions functions/src/auth2UsersCMD.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const admin: Admin.app.App = Admin.initializeApp({
databaseURL: 'https://belmont-runners-1548537264040.firebaseio.com'
})

const auth2Users = Auth2Users(admin)
auth2Users({ syncGravatar: true })
const auth2Users = new Auth2Users(admin)
auth2Users.SyncAll({ syncGravatar: true })
.then((res) => {
console.info('done', res)
return
Expand Down
78 changes: 41 additions & 37 deletions functions/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import AddContact from './addContact'
import Auth2Users, { Auth2UsersOptions } from './auth2Users'
import Auth2Users from './auth2Users'
import Contacts2MailChimp from './contacts2MailChimp'
import DeleteUser from './deleteUser'
import GenerateICal from './generateICal'
Expand All @@ -11,12 +11,14 @@ import UpdateEvents from './updateEvents'
import Users2Contacts from './users2Contacts'
import * as functions from 'firebase-functions'
import { info, error } from "firebase-functions/logger"
import { UserRecord } from 'firebase-functions/lib/providers/auth'
import * as Admin from 'firebase-admin'
import { EMAIL } from './fields'
import { props } from 'bluebird'
import { Firestore } from 'firebase-admin/firestore'

const admin: Admin.app.App = Admin.initializeApp()
const firestore = admin.firestore()
const firestore: Firestore = admin.firestore()

const apiKey = functions.config().mailchimp.apikey
const { app_id, city_id } = functions.config().openweathermap
Expand All @@ -26,7 +28,7 @@ const {
} = functions.config().stripe

const addContactImpl = AddContact(admin)
const auth2Users = Auth2Users(admin)
const auth2Users = new Auth2Users(admin)
const contacts2MailChimp = Contacts2MailChimp(admin, apiKey)
const deleteUserImpl = DeleteUser(admin, apiKey)
const generateICal = GenerateICal()
Expand All @@ -40,38 +42,29 @@ const stripeImpl = Stripe(admin, {
const users2Contacts = Users2Contacts(admin)
const updateEvents = UpdateEvents(admin, app_id, city_id)

const AUTH_2_USERS_TIMEOUT_IN_SECONDS = 180

const auth2UsersExec = (options: Auth2UsersOptions) => async () => {
try {
await auth2Users(options)
info('Calling process.exit(0)')
setTimeout(function () {
process.exit(0)
}, 5000)
} catch (err) {
error('While calling auth2UsersExec', { err })
info('Calling process.exit(1)')
setTimeout(function () {
process.exit(1)
}, 5000)
}
}

export const purgeUsersUnder13CronJob = functions.pubsub
const ITERATION_ON_ACCOUNTS_TIMEOUT_IN_SECONDS = 180

export const purgeUsersUnder13CronJob = functions
.runWith({ timeoutSeconds: ITERATION_ON_ACCOUNTS_TIMEOUT_IN_SECONDS })
.pubsub
.schedule('10 */6 * * *')
.onRun(async () => await purgeUsersUnder13())

export const auth2UsersCronJob = functions
.runWith({ timeoutSeconds: AUTH_2_USERS_TIMEOUT_IN_SECONDS })
.runWith({ timeoutSeconds: ITERATION_ON_ACCOUNTS_TIMEOUT_IN_SECONDS })
.pubsub
.schedule('20 */6 * * *')
.onRun(async () => await auth2UsersExec({ syncGravatar: true }))
.onRun(async () => await auth2Users.SyncAll({ syncGravatar: true }))

export const auth2UsersOnCreate = functions
.runWith({ timeoutSeconds: AUTH_2_USERS_TIMEOUT_IN_SECONDS })
.runWith({ timeoutSeconds: ITERATION_ON_ACCOUNTS_TIMEOUT_IN_SECONDS })
.auth
.user().onCreate(auth2UsersExec({ syncGravatar: false }))
.user()
.onCreate(async (userRecord: UserRecord) => await auth2Users.Sync(userRecord, { syncGravatar: false }))

export const users2ContactsCronJob = functions.pubsub
export const users2ContactsCronJob = functions
.runWith({ timeoutSeconds: ITERATION_ON_ACCOUNTS_TIMEOUT_IN_SECONDS })
.pubsub
.schedule('30 */6 * * *')
.onRun(async () => {
try {
Expand All @@ -83,8 +76,9 @@ export const users2ContactsCronJob = functions.pubsub
})

export const contacts2MailChimpCronJob = functions
.runWith({ timeoutSeconds: 180 })
.pubsub.schedule('40 */6 * * *')
.runWith({ timeoutSeconds: ITERATION_ON_ACCOUNTS_TIMEOUT_IN_SECONDS })
.pubsub
.schedule('40 */6 * * *')
.onRun(async () => {
try {
await contacts2MailChimp()
Expand All @@ -101,18 +95,21 @@ export const contacts2MailChimpCronJob = functions
}
})

export const updateEventsCronJob = functions.pubsub
export const updateEventsCronJob = functions
.pubsub
.schedule('*/20 * * * *')
.onRun(async () => await updateEvents())

export const waiver = functions
.https.onRequest(async (req: functions.https.Request, res: functions.Response) => {
.https
.onRequest(async (req: functions.https.Request, res: functions.Response) => {
res.redirect('https://docs.google.com/forms/d/e/1FAIpQLSfYxlbWAzK1jAcdE_5-ijxORNVz2YU4BdSVt2Dk-DByncIEkw/viewform')
})

export const ical = functions
.runWith({ memory: '512MB' })
.https.onRequest(async (req: functions.https.Request, res: functions.Response) => {
.https
.onRequest(async (req: functions.https.Request, res: functions.Response) => {
try {
const body = await generateICal()
res.set({
Expand All @@ -136,19 +133,24 @@ export const ical = functions
}
})

export const stripe = functions.runWith({ memory: '512MB' }).https.onCall(stripeImpl)
export const stripe = functions
.runWith({ memory: '512MB' })
.https.onCall(stripeImpl)

export const addContact = functions
.runWith({ memory: '512MB' })
.https.onCall(addContactImpl)
.https
.onCall(addContactImpl)

export const getMembers = functions
.runWith({ timeoutSeconds: 30, memory: '512MB' })
.https.onCall(getMembersImpl)
.https
.onCall(getMembersImpl)

export const deleteUser = functions
.runWith({ timeoutSeconds: 30, memory: '512MB' })
.https.onCall(async (data, context) => {
.https
.onCall(async (data, context) => {
if (!context || !context.auth || !context.auth.uid) {
throw new functions.https.HttpsError(
'unauthenticated',
Expand Down Expand Up @@ -182,6 +184,8 @@ export const deleteUser = functions
await deleteUserImpl({ uid: targetUID, email: targetEmail })
})

export const sendMembershipRemindersCronJob = functions.pubsub
export const sendMembershipRemindersCronJob = functions
.runWith({ timeoutSeconds: ITERATION_ON_ACCOUNTS_TIMEOUT_IN_SECONDS })
.pubsub
.schedule('0 19 * * *')
.onRun(async () => await sendMembershipReminders())
5 changes: 3 additions & 2 deletions functions/src/users2Contacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import calc from './membershipUtils'
import { ARRAY_KEY, UID } from './fields'
import { props } from 'bluebird'

const normalizeEmail = require('normalize-email')
const _ = require('underscore')

const Users2Contacts = (admin: Admin.app.App) => {
Expand Down Expand Up @@ -55,7 +54,9 @@ const Users2Contacts = (admin: Admin.app.App) => {
*/
contacts.forEach((contact: Contact) => {
const foundUser: User | undefined = users.find(user => {
return normalizeEmail(contact.email) === normalizeEmail(user.email)
// Do not "normalize" the email addresses since some users may have
// created multiple different accounts with the same normalized email.
return contact.email.trim().toLowerCase() === user.email.trim().toLowerCase()
})
if (foundUser) {
contact.uid = foundUser.uid
Expand Down

0 comments on commit 5a6cd36

Please sign in to comment.