Update txn type
This commit is contained in:
parent
45cfa9bcc7
commit
7aa7271c9f
|
@ -1,4 +1,5 @@
|
||||||
export interface Charity {
|
export interface Charity {
|
||||||
|
id: string
|
||||||
slug: string // Note, slugs double as charity IDs
|
slug: string // Note, slugs double as charity IDs
|
||||||
name: string
|
name: string
|
||||||
website: string
|
website: string
|
||||||
|
@ -232,7 +233,11 @@ export const charities: Charity[] = [
|
||||||
blurb:
|
blurb:
|
||||||
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
|
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
|
||||||
},
|
},
|
||||||
].map((charity) => ({
|
].map((charity) => {
|
||||||
...charity,
|
const slug = charity.name.toLowerCase().replace(/\s/g, '-')
|
||||||
slug: charity.name.replace(/\s/g, '-'),
|
return {
|
||||||
}))
|
...charity,
|
||||||
|
id: slug,
|
||||||
|
slug,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
|
@ -5,15 +5,10 @@ export type Txn = {
|
||||||
createdTime: number
|
createdTime: number
|
||||||
|
|
||||||
fromId: string
|
fromId: string
|
||||||
// TODO: Do we really want to denormalize name/username/avatar here?
|
fromType: 'user' | 'contract' | 'bank_of_manifold'
|
||||||
fromName: string
|
|
||||||
fromUsername: string
|
|
||||||
fromAvatarUrl?: string
|
|
||||||
|
|
||||||
toId: string
|
toId: string
|
||||||
toName: string
|
toType: 'user' | 'contract' | 'charity' | 'bank_of_manifold'
|
||||||
toUsername: string
|
|
||||||
toAvatarUrl?: string
|
|
||||||
|
|
||||||
amount: number
|
amount: number
|
||||||
|
|
||||||
|
|
|
@ -2,30 +2,44 @@ import * as functions from 'firebase-functions'
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
|
|
||||||
import { User } from '../../common/user'
|
import { User } from '../../common/user'
|
||||||
import { Txn, TxnCategory, TxnData } from '../../common/txn'
|
import { Txn } from '../../common/txn'
|
||||||
|
import { removeUndefinedProps } from '../../common/util/object'
|
||||||
|
|
||||||
export const transact = functions.runWith({ minInstances: 1 }).https.onCall(
|
export const transact = functions
|
||||||
async (
|
.runWith({ minInstances: 1 })
|
||||||
data: {
|
.https.onCall(async (data: Exclude<Txn, 'id' | 'createdTime'>, context) => {
|
||||||
amount: number
|
const userId = context?.auth?.uid
|
||||||
toId: string
|
if (!userId) return { status: 'error', message: 'Not authorized' }
|
||||||
category: TxnCategory
|
|
||||||
description?: string
|
|
||||||
txnData?: TxnData
|
|
||||||
},
|
|
||||||
context
|
|
||||||
) => {
|
|
||||||
const fromId = context?.auth?.uid
|
|
||||||
if (!fromId) return { status: 'error', message: 'Not authorized' }
|
|
||||||
|
|
||||||
const { amount, toId, category, description, txnData } = data
|
const {
|
||||||
|
amount,
|
||||||
|
fromType,
|
||||||
|
fromId,
|
||||||
|
toId,
|
||||||
|
toType,
|
||||||
|
category,
|
||||||
|
description,
|
||||||
|
data: txnData,
|
||||||
|
} = 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))
|
if (amount <= 0 || isNaN(amount) || !isFinite(amount))
|
||||||
return { status: 'error', message: 'Invalid amount' }
|
return { status: 'error', message: 'Invalid amount' }
|
||||||
|
|
||||||
// run as transaction to prevent race conditions
|
// Run as transaction to prevent race conditions.
|
||||||
return await firestore.runTransaction(async (transaction) => {
|
return await firestore.runTransaction(async (transaction) => {
|
||||||
const fromDoc = firestore.doc(`users/${fromId}`)
|
const fromDoc = firestore.doc(`users/${userId}`)
|
||||||
const fromSnap = await transaction.get(fromDoc)
|
const fromSnap = await transaction.get(fromDoc)
|
||||||
if (!fromSnap.exists) {
|
if (!fromSnap.exists) {
|
||||||
return { status: 'error', message: 'User not found' }
|
return { status: 'error', message: 'User not found' }
|
||||||
|
@ -48,26 +62,20 @@ export const transact = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
|
|
||||||
const newTxnDoc = firestore.collection(`txns/`).doc()
|
const newTxnDoc = firestore.collection(`txns/`).doc()
|
||||||
|
|
||||||
const txn: Txn = {
|
const txn: Txn = removeUndefinedProps({
|
||||||
id: newTxnDoc.id,
|
id: newTxnDoc.id,
|
||||||
createdTime: Date.now(),
|
createdTime: Date.now(),
|
||||||
|
|
||||||
fromId,
|
fromId,
|
||||||
fromName: fromUser.name,
|
fromType,
|
||||||
fromUsername: fromUser.username,
|
|
||||||
fromAvatarUrl: fromUser.avatarUrl,
|
|
||||||
|
|
||||||
toId,
|
toId,
|
||||||
toName: toUser.name,
|
toType,
|
||||||
toUsername: toUser.username,
|
|
||||||
toAvatarUrl: toUser.avatarUrl,
|
|
||||||
|
|
||||||
amount,
|
amount,
|
||||||
|
|
||||||
category,
|
category,
|
||||||
description,
|
description,
|
||||||
data: txnData,
|
data: txnData,
|
||||||
}
|
})
|
||||||
|
|
||||||
transaction.create(newTxnDoc, txn)
|
transaction.create(newTxnDoc, txn)
|
||||||
transaction.update(fromDoc, { balance: fromUser.balance - amount })
|
transaction.update(fromDoc, { balance: fromUser.balance - amount })
|
||||||
|
@ -75,7 +83,6 @@ export const transact = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
|
|
||||||
return { status: 'success', txnId: newTxnDoc.id }
|
return { status: 'success', txnId: newTxnDoc.id }
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user