Migrate createUser function to v2 (#633)

This commit is contained in:
Marshall Polaris 2022-07-10 14:02:32 -07:00 committed by GitHub
parent eb9b14d6d5
commit 6462d4a2ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 77 additions and 99 deletions

View File

@ -1,6 +1,5 @@
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin' import * as admin from 'firebase-admin'
import { z } from 'zod'
import { import {
PrivateUser, PrivateUser,
STARTING_BALANCE, STARTING_BALANCE,
@ -18,83 +17,79 @@ import { isWhitelisted } from '../../common/envs/constants'
import { DEFAULT_CATEGORIES } from '../../common/categories' import { DEFAULT_CATEGORIES } from '../../common/categories'
import { track } from './analytics' import { track } from './analytics'
import { APIError, newEndpoint, validate } from './api'
export const createUser = functions const bodySchema = z.object({
.runWith({ minInstances: 1, secrets: ['MAILGUN_KEY'] }) deviceToken: z.string().optional(),
.https.onCall(async (data: { deviceToken?: string }, context) => { })
const userId = context?.auth?.uid
if (!userId) return { status: 'error', message: 'Not authorized' }
const preexistingUser = await getUser(userId) const opts = { secrets: ['MAILGUN_KEY'] }
if (preexistingUser)
return {
status: 'error',
message: 'User already created',
user: preexistingUser,
}
const fbUser = await admin.auth().getUser(userId) export const createuser = newEndpoint(opts, async (req, auth) => {
const { deviceToken } = validate(bodySchema, req.body)
const preexistingUser = await getUser(auth.uid)
if (preexistingUser)
throw new APIError(400, 'User already exists', { user: preexistingUser })
const email = fbUser.email const fbUser = await admin.auth().getUser(auth.uid)
if (!isWhitelisted(email)) {
return { status: 'error', message: `${email} is not whitelisted` }
}
const emailName = email?.replace(/@.*$/, '')
const rawName = fbUser.displayName || emailName || 'User' + randomString(4) const email = fbUser.email
const name = cleanDisplayName(rawName) if (!isWhitelisted(email)) {
let username = cleanUsername(name) throw new APIError(400, `${email} is not whitelisted`)
}
const emailName = email?.replace(/@.*$/, '')
const sameNameUser = await getUserByUsername(username) const rawName = fbUser.displayName || emailName || 'User' + randomString(4)
if (sameNameUser) { const name = cleanDisplayName(rawName)
username += randomString(4) let username = cleanUsername(name)
}
const avatarUrl = fbUser.photoURL const sameNameUser = await getUserByUsername(username)
if (sameNameUser) {
username += randomString(4)
}
const { deviceToken } = data const avatarUrl = fbUser.photoURL
const deviceUsedBefore = const deviceUsedBefore =
!deviceToken || (await isPrivateUserWithDeviceToken(deviceToken)) !deviceToken || (await isPrivateUserWithDeviceToken(deviceToken))
const ipAddress = context.rawRequest.ip const ipCount = req.ip ? await numberUsersWithIp(req.ip) : 0
const ipCount = ipAddress ? await numberUsersWithIp(ipAddress) : 0
const balance = const balance =
deviceUsedBefore || ipCount > 2 ? SUS_STARTING_BALANCE : STARTING_BALANCE deviceUsedBefore || ipCount > 2 ? SUS_STARTING_BALANCE : STARTING_BALANCE
const user: User = { const user: User = {
id: userId, id: auth.uid,
name, name,
username, username,
avatarUrl, avatarUrl,
balance, balance,
totalDeposits: balance, totalDeposits: balance,
createdTime: Date.now(), createdTime: Date.now(),
profitCached: { daily: 0, weekly: 0, monthly: 0, allTime: 0 }, profitCached: { daily: 0, weekly: 0, monthly: 0, allTime: 0 },
creatorVolumeCached: { daily: 0, weekly: 0, monthly: 0, allTime: 0 }, creatorVolumeCached: { daily: 0, weekly: 0, monthly: 0, allTime: 0 },
followerCountCached: 0, followerCountCached: 0,
followedCategories: DEFAULT_CATEGORIES, followedCategories: DEFAULT_CATEGORIES,
} }
await firestore.collection('users').doc(userId).create(user) await firestore.collection('users').doc(auth.uid).create(user)
console.log('created user', username, 'firebase id:', userId) console.log('created user', username, 'firebase id:', auth.uid)
const privateUser: PrivateUser = { const privateUser: PrivateUser = {
id: userId, id: auth.uid,
username, username,
email, email,
initialIpAddress: ipAddress, initialIpAddress: req.ip,
initialDeviceToken: deviceToken, initialDeviceToken: deviceToken,
} }
await firestore.collection('private-users').doc(userId).create(privateUser) await firestore.collection('private-users').doc(auth.uid).create(privateUser)
await sendWelcomeEmail(user, privateUser) await sendWelcomeEmail(user, privateUser)
await track(userId, 'create user', { username }, { ip: ipAddress }) await track(auth.uid, 'create user', { username }, { ip: req.ip })
return { status: 'success', user } return user
}) })
const firestore = admin.firestore() const firestore = admin.firestore()

View File

@ -3,7 +3,6 @@ import * as admin from 'firebase-admin'
admin.initializeApp() admin.initializeApp()
// v1 // v1
export * from './create-user'
export * from './on-create-bet' export * from './on-create-bet'
export * from './on-create-comment-on-contract' export * from './on-create-comment-on-contract'
export * from './on-view' export * from './on-view'
@ -27,6 +26,7 @@ export * from './on-create-txn'
export * from './health' export * from './health'
export * from './transact' export * from './transact'
export * from './change-user-info' export * from './change-user-info'
export * from './create-user'
export * from './create-answer' export * from './create-answer'
export * from './place-bet' export * from './place-bet'
export * from './cancel-bet' export * from './cancel-bet'

View File

@ -58,6 +58,10 @@ export function transact(params: any) {
return call(getFunctionUrl('transact'), 'POST', params) return call(getFunctionUrl('transact'), 'POST', params)
} }
export function createUser(params: any) {
return call(getFunctionUrl('createuser'), 'POST', params)
}
export function changeUserInfo(params: any) { export function changeUserInfo(params: any) {
return call(getFunctionUrl('changeuserinfo'), 'POST', params) return call(getFunctionUrl('changeuserinfo'), 'POST', params)
} }

View File

@ -1,22 +0,0 @@
import { httpsCallable } from 'firebase/functions'
import { User } from 'common/user'
import { randomString } from 'common/util/random'
import './init'
import { functions } from './init'
import { safeLocalStorage } from '../util/local'
export const cloudFunction = <RequestData, ResponseData>(name: string) =>
httpsCallable<RequestData, ResponseData>(functions, name)
export const createUser: () => Promise<User | null> = () => {
const local = safeLocalStorage()
let deviceToken = local?.getItem('device-token')
if (!deviceToken) {
deviceToken = randomString()
local?.setItem('device-token', deviceToken)
}
return cloudFunction('createUser')({ deviceToken })
.then((r) => (r.data as any)?.user || null)
.catch(() => null)
}

View File

@ -20,11 +20,10 @@ import {
GoogleAuthProvider, GoogleAuthProvider,
signInWithPopup, signInWithPopup,
} from 'firebase/auth' } from 'firebase/auth'
import { throttle, zip } from 'lodash' import { zip } from 'lodash'
import { app, db } from './init' import { app, db } from './init'
import { PortfolioMetrics, PrivateUser, User } from 'common/user' import { PortfolioMetrics, PrivateUser, User } from 'common/user'
import { createUser } from './fn-call' import { createUser } from './api-call'
import { import {
coll, coll,
getValue, getValue,
@ -38,6 +37,7 @@ import { safeLocalStorage } from '../util/local'
import { filterDefined } from 'common/util/array' import { filterDefined } from 'common/util/array'
import { addUserToGroupViaSlug } from 'web/lib/firebase/groups' import { addUserToGroupViaSlug } from 'web/lib/firebase/groups'
import { removeUndefinedProps } from 'common/util/object' import { removeUndefinedProps } from 'common/util/object'
import { randomString } from 'common/util/random'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc' import utc from 'dayjs/plugin/utc'
dayjs.extend(utc) dayjs.extend(utc)
@ -101,11 +101,6 @@ const CACHED_REFERRAL_USERNAME_KEY = 'CACHED_REFERRAL_KEY'
const CACHED_REFERRAL_CONTRACT_ID_KEY = 'CACHED_REFERRAL_CONTRACT_KEY' const CACHED_REFERRAL_CONTRACT_ID_KEY = 'CACHED_REFERRAL_CONTRACT_KEY'
const CACHED_REFERRAL_GROUP_SLUG_KEY = 'CACHED_REFERRAL_GROUP_KEY' const CACHED_REFERRAL_GROUP_SLUG_KEY = 'CACHED_REFERRAL_GROUP_KEY'
// used to avoid weird race condition
let createUserPromise: Promise<User | null> | undefined = undefined
const warmUpCreateUser = throttle(createUser, 5000 /* ms */)
export function writeReferralInfo( export function writeReferralInfo(
defaultReferrerUsername: string, defaultReferrerUsername: string,
contractId?: string, contractId?: string,
@ -183,22 +178,29 @@ async function setCachedReferralInfoForUser(user: User | null) {
local?.removeItem(CACHED_REFERRAL_CONTRACT_ID_KEY) local?.removeItem(CACHED_REFERRAL_CONTRACT_ID_KEY)
} }
// used to avoid weird race condition
let createUserPromise: Promise<User> | undefined = undefined
export function listenForLogin(onUser: (user: User | null) => void) { export function listenForLogin(onUser: (user: User | null) => void) {
const local = safeLocalStorage() const local = safeLocalStorage()
const cachedUser = local?.getItem(CACHED_USER_KEY) const cachedUser = local?.getItem(CACHED_USER_KEY)
onUser(cachedUser && JSON.parse(cachedUser)) onUser(cachedUser && JSON.parse(cachedUser))
if (!cachedUser) warmUpCreateUser()
return onAuthStateChanged(auth, async (fbUser) => { return onAuthStateChanged(auth, async (fbUser) => {
if (fbUser) { if (fbUser) {
let user: User | null = await getUser(fbUser.uid) let user: User | null = await getUser(fbUser.uid)
if (!user) { if (!user) {
if (!createUserPromise) { if (createUserPromise == null) {
createUserPromise = createUser() const local = safeLocalStorage()
let deviceToken = local?.getItem('device-token')
if (!deviceToken) {
deviceToken = randomString()
local?.setItem('device-token', deviceToken)
}
createUserPromise = createUser({ deviceToken }).then((r) => r as User)
} }
user = (await createUserPromise) || null user = await createUserPromise
} }
onUser(user) onUser(user)
@ -211,7 +213,6 @@ export function listenForLogin(onUser: (user: User | null) => void) {
// User logged out; reset to null // User logged out; reset to null
onUser(null) onUser(null)
local?.removeItem(CACHED_USER_KEY) local?.removeItem(CACHED_USER_KEY)
createUserPromise = undefined
} }
}) })
} }