2022-06-23 08:07:52 +00:00
|
|
|
import * as admin from 'firebase-admin'
|
2022-07-08 22:28:04 +00:00
|
|
|
import { z } from 'zod'
|
2022-06-23 08:07:52 +00:00
|
|
|
|
|
|
|
import { User } from 'common/user'
|
|
|
|
import { Manalink } from 'common/manalink'
|
|
|
|
import { runTxn, TxnData } from './transact'
|
2022-07-08 22:28:04 +00:00
|
|
|
import { APIError, newEndpoint, validate } from './api'
|
2022-06-23 08:07:52 +00:00
|
|
|
|
2022-07-08 22:28:04 +00:00
|
|
|
const bodySchema = z.object({
|
|
|
|
slug: z.string(),
|
|
|
|
})
|
2022-06-23 08:07:52 +00:00
|
|
|
|
2022-07-08 22:28:04 +00:00
|
|
|
export const claimmanalink = newEndpoint({}, async (req, auth) => {
|
|
|
|
const { slug } = validate(bodySchema, req.body)
|
2022-06-23 08:07:52 +00:00
|
|
|
|
2022-07-08 22:28:04 +00:00
|
|
|
// Run as transaction to prevent race conditions.
|
|
|
|
return await firestore.runTransaction(async (transaction) => {
|
|
|
|
// Look up the manalink
|
|
|
|
const manalinkDoc = firestore.doc(`manalinks/${slug}`)
|
|
|
|
const manalinkSnap = await transaction.get(manalinkDoc)
|
|
|
|
if (!manalinkSnap.exists) {
|
|
|
|
throw new APIError(400, 'Manalink not found')
|
|
|
|
}
|
|
|
|
const manalink = manalinkSnap.data() as Manalink
|
2022-06-23 08:07:52 +00:00
|
|
|
|
2022-07-08 22:28:04 +00:00
|
|
|
const { amount, fromId, claimedUserIds } = manalink
|
2022-06-23 08:07:52 +00:00
|
|
|
|
2022-07-08 22:28:04 +00:00
|
|
|
if (amount <= 0 || isNaN(amount) || !isFinite(amount))
|
|
|
|
throw new APIError(500, 'Invalid amount')
|
2022-06-23 08:07:52 +00:00
|
|
|
|
2022-07-08 22:28:04 +00:00
|
|
|
const fromDoc = firestore.doc(`users/${fromId}`)
|
|
|
|
const fromSnap = await transaction.get(fromDoc)
|
|
|
|
if (!fromSnap.exists) {
|
|
|
|
throw new APIError(500, `User ${fromId} not found`)
|
|
|
|
}
|
|
|
|
const fromUser = fromSnap.data() as User
|
2022-06-23 08:07:52 +00:00
|
|
|
|
2022-07-08 22:28:04 +00:00
|
|
|
// Only permit one redemption per user per link
|
|
|
|
if (claimedUserIds.includes(auth.uid)) {
|
|
|
|
throw new APIError(400, `You already redeemed manalink ${slug}`)
|
|
|
|
}
|
2022-06-23 08:07:52 +00:00
|
|
|
|
2022-07-08 22:28:04 +00:00
|
|
|
// Disallow expired or maxed out links
|
|
|
|
if (manalink.expiresTime != null && manalink.expiresTime < Date.now()) {
|
|
|
|
throw new APIError(
|
|
|
|
400,
|
|
|
|
`Manalink ${slug} expired on ${new Date(
|
|
|
|
manalink.expiresTime
|
|
|
|
).toLocaleString()}`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
manalink.maxUses != null &&
|
|
|
|
manalink.maxUses <= manalink.claims.length
|
|
|
|
) {
|
|
|
|
throw new APIError(
|
|
|
|
400,
|
|
|
|
`Manalink ${slug} has reached its max uses of ${manalink.maxUses}`
|
|
|
|
)
|
|
|
|
}
|
2022-06-23 08:07:52 +00:00
|
|
|
|
2022-07-08 22:28:04 +00:00
|
|
|
if (fromUser.balance < amount) {
|
|
|
|
throw new APIError(
|
|
|
|
400,
|
|
|
|
`Insufficient balance: ${fromUser.name} needed ${amount} for this manalink but only had ${fromUser.balance} `
|
|
|
|
)
|
|
|
|
}
|
2022-06-23 08:07:52 +00:00
|
|
|
|
2022-07-08 22:28:04 +00:00
|
|
|
// Actually execute the txn
|
|
|
|
const data: TxnData = {
|
|
|
|
fromId,
|
|
|
|
fromType: 'USER',
|
|
|
|
toId: auth.uid,
|
|
|
|
toType: 'USER',
|
|
|
|
amount,
|
|
|
|
token: 'M$',
|
|
|
|
category: 'MANALINK',
|
|
|
|
description: `Manalink ${slug} claimed: ${amount} from ${fromUser.username} to ${auth.uid}`,
|
|
|
|
}
|
|
|
|
const result = await runTxn(transaction, data)
|
|
|
|
const txnId = result.txn?.id
|
|
|
|
if (!txnId) {
|
|
|
|
throw new APIError(
|
|
|
|
500,
|
|
|
|
result.message ?? 'An error occurred posting the transaction.'
|
|
|
|
)
|
|
|
|
}
|
2022-06-23 08:07:52 +00:00
|
|
|
|
2022-07-08 22:28:04 +00:00
|
|
|
// Update the manalink object with this info
|
|
|
|
const claim = {
|
|
|
|
toId: auth.uid,
|
|
|
|
txnId,
|
|
|
|
claimedTime: Date.now(),
|
|
|
|
}
|
|
|
|
transaction.update(manalinkDoc, {
|
|
|
|
claimedUserIds: [...claimedUserIds, auth.uid],
|
|
|
|
claims: [...manalink.claims, claim],
|
2022-06-23 08:07:52 +00:00
|
|
|
})
|
2022-07-08 22:28:04 +00:00
|
|
|
|
|
|
|
return { message: 'Manalink claimed' }
|
2022-06-23 08:07:52 +00:00
|
|
|
})
|
2022-07-08 22:28:04 +00:00
|
|
|
})
|
2022-06-23 08:07:52 +00:00
|
|
|
|
|
|
|
const firestore = admin.firestore()
|