Clean some API stuff up and instrument placeBet
with a bunch of logging (#521)
* Hoist some variables out of functions * Use built in CORS processing machinery * Instrument `placebet` with a bunch of logging
This commit is contained in:
parent
a8ae724159
commit
0820cc8f4d
|
@ -21,7 +21,6 @@
|
|||
"main": "lib/functions/src/index.js",
|
||||
"dependencies": {
|
||||
"@amplitude/node": "1.10.0",
|
||||
"cors": "2.8.5",
|
||||
"fetch": "1.1.0",
|
||||
"firebase-admin": "10.0.0",
|
||||
"firebase-functions": "3.21.2",
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import * as admin from 'firebase-admin'
|
||||
import { Response } from 'express'
|
||||
import { logger } from 'firebase-functions/v2'
|
||||
import { onRequest, Request } from 'firebase-functions/v2/https'
|
||||
|
||||
import * as Cors from 'cors'
|
||||
import { HttpsOptions, onRequest, Request } from 'firebase-functions/v2/https'
|
||||
import { log } from './utils'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { PrivateUser } from '../../common/user'
|
||||
|
@ -33,6 +31,12 @@ export class APIError {
|
|||
}
|
||||
}
|
||||
|
||||
const auth = admin.auth()
|
||||
const firestore = admin.firestore()
|
||||
const privateUsers = firestore.collection(
|
||||
'private-users'
|
||||
) as admin.firestore.CollectionReference<PrivateUser>
|
||||
|
||||
export const parseCredentials = async (req: Request): Promise<Credentials> => {
|
||||
const authHeader = req.get('Authorization')
|
||||
if (!authHeader) {
|
||||
|
@ -47,8 +51,7 @@ export const parseCredentials = async (req: Request): Promise<Credentials> => {
|
|||
switch (scheme) {
|
||||
case 'Bearer':
|
||||
try {
|
||||
const jwt = await admin.auth().verifyIdToken(payload)
|
||||
return { kind: 'jwt', data: jwt }
|
||||
return { kind: 'jwt', data: await auth.verifyIdToken(payload) }
|
||||
} catch (err) {
|
||||
// This is somewhat suspicious, so get it into the firebase console
|
||||
logger.error('Error verifying Firebase JWT: ', err)
|
||||
|
@ -62,8 +65,6 @@ export const parseCredentials = async (req: Request): Promise<Credentials> => {
|
|||
}
|
||||
|
||||
export const lookupUser = async (creds: Credentials): Promise<AuthedUser> => {
|
||||
const firestore = admin.firestore()
|
||||
const privateUsers = firestore.collection('private-users')
|
||||
switch (creds.kind) {
|
||||
case 'jwt': {
|
||||
if (typeof creds.data.user_id !== 'string') {
|
||||
|
@ -77,8 +78,7 @@ export const lookupUser = async (creds: Credentials): Promise<AuthedUser> => {
|
|||
if (privateUserQ.empty) {
|
||||
throw new APIError(403, `No private user exists with API key ${key}.`)
|
||||
}
|
||||
const privateUserSnap = privateUserQ.docs[0]
|
||||
const privateUser = privateUserSnap.data() as PrivateUser
|
||||
const privateUser = privateUserQ.docs[0].data()
|
||||
return { uid: privateUser.id, creds: { privateUser, ...creds } }
|
||||
}
|
||||
default:
|
||||
|
@ -86,21 +86,6 @@ export const lookupUser = async (creds: Credentials): Promise<AuthedUser> => {
|
|||
}
|
||||
}
|
||||
|
||||
export const applyCors = (
|
||||
req: Request,
|
||||
res: Response,
|
||||
params: Cors.CorsOptions
|
||||
) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
Cors(params)(req, res, (result) => {
|
||||
if (result instanceof Error) {
|
||||
return reject(result)
|
||||
}
|
||||
return resolve(result)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const zTimestamp = () => {
|
||||
return z.preprocess((arg) => {
|
||||
return typeof arg == 'number' ? new Date(arg) : undefined
|
||||
|
@ -122,18 +107,21 @@ export const validate = <T extends z.ZodTypeAny>(schema: T, val: unknown) => {
|
|||
}
|
||||
}
|
||||
|
||||
const DEFAULT_OPTS: HttpsOptions = {
|
||||
minInstances: 1,
|
||||
cors: [CORS_ORIGIN_MANIFOLD, CORS_ORIGIN_LOCALHOST],
|
||||
}
|
||||
|
||||
export const newEndpoint = (methods: [string], fn: Handler) =>
|
||||
onRequest({ minInstances: 1 }, async (req, res) => {
|
||||
onRequest(DEFAULT_OPTS, async (req, res) => {
|
||||
log('Request processing started.')
|
||||
try {
|
||||
await applyCors(req, res, {
|
||||
origin: [CORS_ORIGIN_MANIFOLD, CORS_ORIGIN_LOCALHOST],
|
||||
methods: methods,
|
||||
})
|
||||
if (!methods.includes(req.method)) {
|
||||
const allowed = methods.join(', ')
|
||||
throw new APIError(405, `This endpoint supports only ${allowed}.`)
|
||||
}
|
||||
const authedUser = await lookupUser(await parseCredentials(req))
|
||||
log('User credentials processed.')
|
||||
res.status(200).json(await fn(req, authedUser))
|
||||
} catch (e) {
|
||||
if (e instanceof APIError) {
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
} from '../../common/new-bet'
|
||||
import { addObjects, removeUndefinedProps } from '../../common/util/object'
|
||||
import { redeemShares } from './redeem-shares'
|
||||
import { log } from './utils'
|
||||
|
||||
const bodySchema = z.object({
|
||||
contractId: z.string(),
|
||||
|
@ -33,9 +34,11 @@ const numericSchema = z.object({
|
|||
})
|
||||
|
||||
export const placebet = newEndpoint(['POST'], async (req, auth) => {
|
||||
log('Inside endpoint handler.')
|
||||
const { amount, contractId } = validate(bodySchema, req.body)
|
||||
|
||||
const result = await firestore.runTransaction(async (trans) => {
|
||||
log('Inside main transaction.')
|
||||
const contractDoc = firestore.doc(`contracts/${contractId}`)
|
||||
const userDoc = firestore.doc(`users/${auth.uid}`)
|
||||
const [contractSnap, userSnap] = await Promise.all([
|
||||
|
@ -44,6 +47,7 @@ export const placebet = newEndpoint(['POST'], async (req, auth) => {
|
|||
])
|
||||
if (!contractSnap.exists) throw new APIError(400, 'Contract not found.')
|
||||
if (!userSnap.exists) throw new APIError(400, 'User not found.')
|
||||
log('Loaded user and contract snapshots.')
|
||||
|
||||
const contract = contractSnap.data() as Contract
|
||||
const user = userSnap.data() as User
|
||||
|
@ -82,6 +86,7 @@ export const placebet = newEndpoint(['POST'], async (req, auth) => {
|
|||
throw new APIError(500, 'Contract has invalid type/mechanism.')
|
||||
}
|
||||
})()
|
||||
log('Calculated new bet information.')
|
||||
|
||||
if (
|
||||
mechanism == 'cpmm-1' &&
|
||||
|
@ -95,7 +100,9 @@ export const placebet = newEndpoint(['POST'], async (req, auth) => {
|
|||
const newBalance = user.balance - amount - loanAmount
|
||||
const betDoc = contractDoc.collection('bets').doc()
|
||||
trans.create(betDoc, { id: betDoc.id, userId: user.id, ...newBet })
|
||||
log('Created new bet document.')
|
||||
trans.update(userDoc, { balance: newBalance })
|
||||
log('Updated user balance.')
|
||||
trans.update(
|
||||
contractDoc,
|
||||
removeUndefinedProps({
|
||||
|
@ -108,11 +115,14 @@ export const placebet = newEndpoint(['POST'], async (req, auth) => {
|
|||
volume: volume + amount,
|
||||
})
|
||||
)
|
||||
log('Updated contract properties.')
|
||||
|
||||
return { betId: betDoc.id }
|
||||
})
|
||||
|
||||
log('Main transaction finished.')
|
||||
await redeemShares(auth.uid, contractId)
|
||||
log('Share redemption transaction finished.')
|
||||
return result
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user