Migrate createUser function to v2 (#633)
This commit is contained in:
parent
eb9b14d6d5
commit
6462d4a2ed
|
@ -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,26 +17,25 @@ 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'] }
|
||||||
|
|
||||||
|
export const createuser = newEndpoint(opts, async (req, auth) => {
|
||||||
|
const { deviceToken } = validate(bodySchema, req.body)
|
||||||
|
const preexistingUser = await getUser(auth.uid)
|
||||||
if (preexistingUser)
|
if (preexistingUser)
|
||||||
return {
|
throw new APIError(400, 'User already exists', { user: preexistingUser })
|
||||||
status: 'error',
|
|
||||||
message: 'User already created',
|
|
||||||
user: preexistingUser,
|
|
||||||
}
|
|
||||||
|
|
||||||
const fbUser = await admin.auth().getUser(userId)
|
const fbUser = await admin.auth().getUser(auth.uid)
|
||||||
|
|
||||||
const email = fbUser.email
|
const email = fbUser.email
|
||||||
if (!isWhitelisted(email)) {
|
if (!isWhitelisted(email)) {
|
||||||
return { status: 'error', message: `${email} is not whitelisted` }
|
throw new APIError(400, `${email} is not whitelisted`)
|
||||||
}
|
}
|
||||||
const emailName = email?.replace(/@.*$/, '')
|
const emailName = email?.replace(/@.*$/, '')
|
||||||
|
|
||||||
|
@ -51,19 +49,16 @@ export const createUser = functions
|
||||||
}
|
}
|
||||||
|
|
||||||
const avatarUrl = fbUser.photoURL
|
const avatarUrl = fbUser.photoURL
|
||||||
|
|
||||||
const { deviceToken } = data
|
|
||||||
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,
|
||||||
|
@ -76,25 +71,25 @@ export const createUser = functions
|
||||||
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()
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
user = (await createUserPromise) || null
|
createUserPromise = createUser({ deviceToken }).then((r) => r as User)
|
||||||
|
}
|
||||||
|
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
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user