8b1d132e17
* [Leaderboards] Added period toggle for leaderboards * [Leaderboards] TopBettors now calculates by period correctly * [Leaderboard] Use a subcollection for the portfolio caching * [Leaderboard] Switches to a tab view, temporarily hides the missing topBettors periods * [Leaderboard] Reverts random yarn.lock changes * Fix type error from merge * Increase timeout on update metrics * Update firebase rules to allow reading user portfolioHistory Co-authored-by: Pico2x <pico2x@gmail.com>
118 lines
3.3 KiB
TypeScript
118 lines
3.3 KiB
TypeScript
import * as functions from 'firebase-functions'
|
|
import * as admin from 'firebase-admin'
|
|
|
|
import {
|
|
PrivateUser,
|
|
STARTING_BALANCE,
|
|
SUS_STARTING_BALANCE,
|
|
User,
|
|
} from '../../common/user'
|
|
import { getUser, getUserByUsername } from './utils'
|
|
import { randomString } from '../../common/util/random'
|
|
import {
|
|
cleanDisplayName,
|
|
cleanUsername,
|
|
} from '../../common/util/clean-username'
|
|
import { sendWelcomeEmail } from './emails'
|
|
import { isWhitelisted } from '../../common/envs/constants'
|
|
import { DEFAULT_CATEGORIES } from '../../common/categories'
|
|
|
|
import { track } from './analytics'
|
|
|
|
export const createUser = functions
|
|
.runWith({ minInstances: 1, secrets: ['MAILGUN_KEY'] })
|
|
.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)
|
|
if (preexistingUser)
|
|
return {
|
|
status: 'error',
|
|
message: 'User already created',
|
|
user: preexistingUser,
|
|
}
|
|
|
|
const fbUser = await admin.auth().getUser(userId)
|
|
|
|
const email = fbUser.email
|
|
if (!isWhitelisted(email)) {
|
|
return { status: 'error', message: `${email} is not whitelisted` }
|
|
}
|
|
const emailName = email?.replace(/@.*$/, '')
|
|
|
|
const rawName = fbUser.displayName || emailName || 'User' + randomString(4)
|
|
const name = cleanDisplayName(rawName)
|
|
let username = cleanUsername(name)
|
|
|
|
const sameNameUser = await getUserByUsername(username)
|
|
if (sameNameUser) {
|
|
username += randomString(4)
|
|
}
|
|
|
|
const avatarUrl = fbUser.photoURL
|
|
|
|
const { deviceToken } = data
|
|
const deviceUsedBefore =
|
|
!deviceToken || (await isPrivateUserWithDeviceToken(deviceToken))
|
|
|
|
const ipAddress = context.rawRequest.ip
|
|
const ipCount = ipAddress ? await numberUsersWithIp(ipAddress) : 0
|
|
|
|
const balance =
|
|
deviceUsedBefore || ipCount > 2 ? SUS_STARTING_BALANCE : STARTING_BALANCE
|
|
|
|
const user: User = {
|
|
id: userId,
|
|
name,
|
|
username,
|
|
avatarUrl,
|
|
balance,
|
|
totalDeposits: balance,
|
|
createdTime: Date.now(),
|
|
profitCached: { daily: 0, weekly: 0, monthly: 0, allTime: 0 },
|
|
creatorVolumeCached: { daily: 0, weekly: 0, monthly: 0, allTime: 0 },
|
|
followerCountCached: 0,
|
|
followedCategories: DEFAULT_CATEGORIES,
|
|
}
|
|
|
|
await firestore.collection('users').doc(userId).create(user)
|
|
console.log('created user', username, 'firebase id:', userId)
|
|
|
|
const privateUser: PrivateUser = {
|
|
id: userId,
|
|
username,
|
|
email,
|
|
initialIpAddress: ipAddress,
|
|
initialDeviceToken: deviceToken,
|
|
}
|
|
|
|
await firestore.collection('private-users').doc(userId).create(privateUser)
|
|
|
|
await sendWelcomeEmail(user, privateUser)
|
|
|
|
await track(userId, 'create user', { username }, { ip: ipAddress })
|
|
|
|
return { status: 'success', user }
|
|
})
|
|
|
|
const firestore = admin.firestore()
|
|
|
|
const isPrivateUserWithDeviceToken = async (deviceToken: string) => {
|
|
const snap = await firestore
|
|
.collection('private-users')
|
|
.where('initialDeviceToken', '==', deviceToken)
|
|
.get()
|
|
|
|
return !snap.empty
|
|
}
|
|
|
|
const numberUsersWithIp = async (ipAddress: string) => {
|
|
const snap = await firestore
|
|
.collection('private-users')
|
|
.where('initialIpAddress', '==', ipAddress)
|
|
.get()
|
|
|
|
return snap.docs.length
|
|
}
|