73fc67955d
* Add components for CPM landing and charity pages * Remove misc.ts to fix build * Set up cloud function for writing txns * More plumbing for txns * Fix up API call * Use Date.now() to keep timestamps simple * Some styles for charity list page * Hard code charities data * Pass charity data to charity page * Update txn type * Listen for charity txns * Handle txn to non-user by burning it * Read txns for charity card and charity page. * Set images to object contain * Clean up txn types * Move pic to top of card. Other misc styling. * Update charity short & long descriptions * Add `token` and `category` to Txn * Fix breakages * Show Charity link in the sidebar * Fix typing issues * Fix not reading from the right type * Switch out icon * Also show Charity icon on mobile * Update copy Co-authored-by: Austin Chen <akrolsmir@gmail.com> Co-authored-by: James Grugett <jahooma@gmail.com>
83 lines
2.5 KiB
TypeScript
83 lines
2.5 KiB
TypeScript
import * as functions from 'firebase-functions'
|
|
import * as admin from 'firebase-admin'
|
|
|
|
import { User } from '../../common/user'
|
|
import { Txn } from '../../common/txn'
|
|
import { removeUndefinedProps } from '../../common/util/object'
|
|
|
|
export const transact = functions
|
|
.runWith({ minInstances: 1 })
|
|
.https.onCall(async (data: Omit<Txn, 'id' | 'createdTime'>, context) => {
|
|
const userId = context?.auth?.uid
|
|
if (!userId) return { status: 'error', message: 'Not authorized' }
|
|
|
|
const { amount, fromType, fromId, toId, toType, description } = data
|
|
|
|
if (fromType !== 'USER')
|
|
return {
|
|
status: 'error',
|
|
message: "From type is only implemented for type 'user'.",
|
|
}
|
|
|
|
if (fromId !== userId)
|
|
return {
|
|
status: 'error',
|
|
message: 'Must be authenticated with userId equal to specified fromId.',
|
|
}
|
|
|
|
if (amount <= 0 || isNaN(amount) || !isFinite(amount))
|
|
return { status: 'error', message: 'Invalid amount' }
|
|
|
|
// Run as transaction to prevent race conditions.
|
|
return await firestore.runTransaction(async (transaction) => {
|
|
const fromDoc = firestore.doc(`users/${userId}`)
|
|
const fromSnap = await transaction.get(fromDoc)
|
|
if (!fromSnap.exists) {
|
|
return { status: 'error', message: 'User not found' }
|
|
}
|
|
const fromUser = fromSnap.data() as User
|
|
|
|
if (fromUser.balance < amount) {
|
|
return {
|
|
status: 'error',
|
|
message: `Insufficient balance: ${fromUser.username} needed ${amount} but only had ${fromUser.balance} `,
|
|
}
|
|
}
|
|
|
|
if (toType === 'USER') {
|
|
const toDoc = firestore.doc(`users/${toId}`)
|
|
const toSnap = await transaction.get(toDoc)
|
|
if (!toSnap.exists) {
|
|
return { status: 'error', message: 'User not found' }
|
|
}
|
|
const toUser = toSnap.data() as User
|
|
transaction.update(toDoc, { balance: toUser.balance + amount })
|
|
}
|
|
|
|
const newTxnDoc = firestore.collection(`txns/`).doc()
|
|
|
|
const txn: Txn = removeUndefinedProps({
|
|
id: newTxnDoc.id,
|
|
createdTime: Date.now(),
|
|
|
|
fromId,
|
|
fromType,
|
|
toId,
|
|
toType,
|
|
|
|
amount,
|
|
// TODO: Unhardcode once we have non-donation txns
|
|
token: 'M$',
|
|
category: 'CHARITY',
|
|
description,
|
|
})
|
|
|
|
transaction.create(newTxnDoc, txn)
|
|
transaction.update(fromDoc, { balance: fromUser.balance - amount })
|
|
|
|
return { status: 'success', txn }
|
|
})
|
|
})
|
|
|
|
const firestore = admin.firestore()
|