Denormalize user display fields onto bets (#853)
* Denormalize user display fields onto bets * Make bet denormalization script fast enough to run it on prod * Make `placeBet`/`sellShares` immediately post denormalized info
This commit is contained in:
parent
1ebb505752
commit
7144e57c93
|
@ -15,9 +15,13 @@ import { Answer } from './answer'
|
||||||
|
|
||||||
export const HOUSE_LIQUIDITY_PROVIDER_ID = 'IPTOzEqrpkWmEzh6hwvAyY9PqFb2' // @ManifoldMarkets' id
|
export const HOUSE_LIQUIDITY_PROVIDER_ID = 'IPTOzEqrpkWmEzh6hwvAyY9PqFb2' // @ManifoldMarkets' id
|
||||||
export const DEV_HOUSE_LIQUIDITY_PROVIDER_ID = '94YYTk1AFWfbWMpfYcvnnwI1veP2' // @ManifoldMarkets' id
|
export const DEV_HOUSE_LIQUIDITY_PROVIDER_ID = '94YYTk1AFWfbWMpfYcvnnwI1veP2' // @ManifoldMarkets' id
|
||||||
|
|
||||||
export const UNIQUE_BETTOR_LIQUIDITY_AMOUNT = 20
|
export const UNIQUE_BETTOR_LIQUIDITY_AMOUNT = 20
|
||||||
|
|
||||||
|
type NormalizedBet<T extends Bet = Bet> = Omit<
|
||||||
|
T,
|
||||||
|
'userAvatarUrl' | 'userName' | 'userUsername'
|
||||||
|
>
|
||||||
|
|
||||||
export function getCpmmInitialLiquidity(
|
export function getCpmmInitialLiquidity(
|
||||||
providerId: string,
|
providerId: string,
|
||||||
contract: CPMMBinaryContract,
|
contract: CPMMBinaryContract,
|
||||||
|
@ -53,7 +57,7 @@ export function getAnteBets(
|
||||||
|
|
||||||
const { createdTime } = contract
|
const { createdTime } = contract
|
||||||
|
|
||||||
const yesBet: Bet = {
|
const yesBet: NormalizedBet = {
|
||||||
id: yesAnteId,
|
id: yesAnteId,
|
||||||
userId: creator.id,
|
userId: creator.id,
|
||||||
contractId: contract.id,
|
contractId: contract.id,
|
||||||
|
@ -67,7 +71,7 @@ export function getAnteBets(
|
||||||
fees: noFees,
|
fees: noFees,
|
||||||
}
|
}
|
||||||
|
|
||||||
const noBet: Bet = {
|
const noBet: NormalizedBet = {
|
||||||
id: noAnteId,
|
id: noAnteId,
|
||||||
userId: creator.id,
|
userId: creator.id,
|
||||||
contractId: contract.id,
|
contractId: contract.id,
|
||||||
|
@ -95,7 +99,7 @@ export function getFreeAnswerAnte(
|
||||||
|
|
||||||
const { createdTime } = contract
|
const { createdTime } = contract
|
||||||
|
|
||||||
const anteBet: Bet = {
|
const anteBet: NormalizedBet = {
|
||||||
id: anteBetId,
|
id: anteBetId,
|
||||||
userId: anteBettorId,
|
userId: anteBettorId,
|
||||||
contractId: contract.id,
|
contractId: contract.id,
|
||||||
|
@ -125,7 +129,7 @@ export function getMultipleChoiceAntes(
|
||||||
|
|
||||||
const { createdTime } = contract
|
const { createdTime } = contract
|
||||||
|
|
||||||
const bets: Bet[] = answers.map((answer, i) => ({
|
const bets: NormalizedBet[] = answers.map((answer, i) => ({
|
||||||
id: betDocIds[i],
|
id: betDocIds[i],
|
||||||
userId: creator.id,
|
userId: creator.id,
|
||||||
contractId: contract.id,
|
contractId: contract.id,
|
||||||
|
@ -175,7 +179,7 @@ export function getNumericAnte(
|
||||||
range(0, bucketCount).map((_, i) => [i, betAnte])
|
range(0, bucketCount).map((_, i) => [i, betAnte])
|
||||||
)
|
)
|
||||||
|
|
||||||
const anteBet: NumericBet = {
|
const anteBet: NormalizedBet<NumericBet> = {
|
||||||
id: newBetId,
|
id: newBetId,
|
||||||
userId: anteBettorId,
|
userId: anteBettorId,
|
||||||
contractId: contract.id,
|
contractId: contract.id,
|
||||||
|
|
|
@ -3,6 +3,12 @@ import { Fees } from './fees'
|
||||||
export type Bet = {
|
export type Bet = {
|
||||||
id: string
|
id: string
|
||||||
userId: string
|
userId: string
|
||||||
|
|
||||||
|
// denormalized for bet lists
|
||||||
|
userAvatarUrl?: string
|
||||||
|
userUsername: string
|
||||||
|
userName: string
|
||||||
|
|
||||||
contractId: string
|
contractId: string
|
||||||
createdTime: number
|
createdTime: number
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,10 @@ import {
|
||||||
floatingLesserEqual,
|
floatingLesserEqual,
|
||||||
} from './util/math'
|
} from './util/math'
|
||||||
|
|
||||||
export type CandidateBet<T extends Bet = Bet> = Omit<T, 'id' | 'userId'>
|
export type CandidateBet<T extends Bet = Bet> = Omit<
|
||||||
|
T,
|
||||||
|
'id' | 'userId' | 'userAvatarUrl' | 'userName' | 'userUsername'
|
||||||
|
>
|
||||||
export type BetInfo = {
|
export type BetInfo = {
|
||||||
newBet: CandidateBet
|
newBet: CandidateBet
|
||||||
newPool?: { [outcome: string]: number }
|
newPool?: { [outcome: string]: number }
|
||||||
|
@ -322,7 +325,7 @@ export const getNewBinaryDpmBetInfo = (
|
||||||
export const getNewMultiBetInfo = (
|
export const getNewMultiBetInfo = (
|
||||||
outcome: string,
|
outcome: string,
|
||||||
amount: number,
|
amount: number,
|
||||||
contract: FreeResponseContract | MultipleChoiceContract,
|
contract: FreeResponseContract | MultipleChoiceContract
|
||||||
) => {
|
) => {
|
||||||
const { pool, totalShares, totalBets } = contract
|
const { pool, totalShares, totalBets } = contract
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,10 @@ import { CPMMContract, DPMContract } from './contract'
|
||||||
import { DPM_CREATOR_FEE, DPM_PLATFORM_FEE, Fees } from './fees'
|
import { DPM_CREATOR_FEE, DPM_PLATFORM_FEE, Fees } from './fees'
|
||||||
import { sumBy } from 'lodash'
|
import { sumBy } from 'lodash'
|
||||||
|
|
||||||
export type CandidateBet<T extends Bet> = Omit<T, 'id' | 'userId'>
|
export type CandidateBet<T extends Bet> = Omit<
|
||||||
|
T,
|
||||||
|
'id' | 'userId' | 'userAvatarUrl' | 'userName' | 'userUsername'
|
||||||
|
>
|
||||||
|
|
||||||
export const getSellBetInfo = (bet: Bet, contract: DPMContract) => {
|
export const getSellBetInfo = (bet: Bet, contract: DPMContract) => {
|
||||||
const { pool, totalShares, totalBets } = contract
|
const { pool, totalShares, totalBets } = contract
|
||||||
|
|
|
@ -2,6 +2,7 @@ import * as admin from 'firebase-admin'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
import { getUser } from './utils'
|
import { getUser } from './utils'
|
||||||
|
import { Bet } from '../../common/bet'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from '../../common/contract'
|
||||||
import { Comment } from '../../common/comment'
|
import { Comment } from '../../common/comment'
|
||||||
import { User } from '../../common/user'
|
import { User } from '../../common/user'
|
||||||
|
@ -68,10 +69,21 @@ export const changeUser = async (
|
||||||
.get()
|
.get()
|
||||||
const answerUpdate: Partial<Answer> = removeUndefinedProps(update)
|
const answerUpdate: Partial<Answer> = removeUndefinedProps(update)
|
||||||
|
|
||||||
|
const betsSnap = await firestore
|
||||||
|
.collectionGroup('bets')
|
||||||
|
.where('userId', '==', user.id)
|
||||||
|
.get()
|
||||||
|
const betsUpdate: Partial<Bet> = removeUndefinedProps({
|
||||||
|
userName: update.name,
|
||||||
|
userUsername: update.username,
|
||||||
|
userAvatarUrl: update.avatarUrl,
|
||||||
|
})
|
||||||
|
|
||||||
const bulkWriter = firestore.bulkWriter()
|
const bulkWriter = firestore.bulkWriter()
|
||||||
commentSnap.docs.forEach((d) => bulkWriter.update(d.ref, commentUpdate))
|
commentSnap.docs.forEach((d) => bulkWriter.update(d.ref, commentUpdate))
|
||||||
answerSnap.docs.forEach((d) => bulkWriter.update(d.ref, answerUpdate))
|
answerSnap.docs.forEach((d) => bulkWriter.update(d.ref, answerUpdate))
|
||||||
contracts.docs.forEach((d) => bulkWriter.update(d.ref, contractUpdate))
|
contracts.docs.forEach((d) => bulkWriter.update(d.ref, contractUpdate))
|
||||||
|
betsSnap.docs.forEach((d) => bulkWriter.update(d.ref, betsUpdate))
|
||||||
await bulkWriter.flush()
|
await bulkWriter.flush()
|
||||||
console.log('Done writing!')
|
console.log('Done writing!')
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,12 @@ export const onCreateBet = functions
|
||||||
const bettor = await getUser(bet.userId)
|
const bettor = await getUser(bet.userId)
|
||||||
if (!bettor) return
|
if (!bettor) return
|
||||||
|
|
||||||
|
await change.ref.update({
|
||||||
|
userAvatarUrl: bettor.avatarUrl,
|
||||||
|
userName: bettor.name,
|
||||||
|
userUsername: bettor.username,
|
||||||
|
})
|
||||||
|
|
||||||
await updateUniqueBettorsAndGiveCreatorBonus(contract, eventId, bettor)
|
await updateUniqueBettorsAndGiveCreatorBonus(contract, eventId, bettor)
|
||||||
await notifyFills(bet, contract, eventId, bettor)
|
await notifyFills(bet, contract, eventId, bettor)
|
||||||
await updateBettingStreak(bettor, bet, contract, eventId)
|
await updateBettingStreak(bettor, bet, contract, eventId)
|
||||||
|
|
|
@ -139,7 +139,14 @@ export const placebet = newEndpoint({}, async (req, auth) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const betDoc = contractDoc.collection('bets').doc()
|
const betDoc = contractDoc.collection('bets').doc()
|
||||||
trans.create(betDoc, { id: betDoc.id, userId: user.id, ...newBet })
|
trans.create(betDoc, {
|
||||||
|
id: betDoc.id,
|
||||||
|
userId: user.id,
|
||||||
|
userAvatarUrl: user.avatarUrl,
|
||||||
|
userUsername: user.username,
|
||||||
|
userName: user.name,
|
||||||
|
...newBet,
|
||||||
|
})
|
||||||
log('Created new bet document.')
|
log('Created new bet document.')
|
||||||
|
|
||||||
if (makers) {
|
if (makers) {
|
||||||
|
|
|
@ -3,12 +3,7 @@
|
||||||
|
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
import {
|
import { findDiffs, describeDiff, applyDiff } from './denormalize'
|
||||||
DocumentCorrespondence,
|
|
||||||
findDiffs,
|
|
||||||
describeDiff,
|
|
||||||
applyDiff,
|
|
||||||
} from './denormalize'
|
|
||||||
import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore'
|
import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore'
|
||||||
|
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
@ -79,43 +74,36 @@ if (require.main === module) {
|
||||||
getAnswersByUserId(transaction),
|
getAnswersByUserId(transaction),
|
||||||
])
|
])
|
||||||
|
|
||||||
const usersContracts = Array.from(
|
const usersContracts = Array.from(usersById.entries(), ([id, doc]) => {
|
||||||
usersById.entries(),
|
return [doc, contractsByUserId.get(id) || []] as const
|
||||||
([id, doc]): DocumentCorrespondence => {
|
})
|
||||||
return [doc, contractsByUserId.get(id) || []]
|
const contractDiffs = findDiffs(usersContracts, [
|
||||||
}
|
|
||||||
)
|
|
||||||
const contractDiffs = findDiffs(
|
|
||||||
usersContracts,
|
|
||||||
'avatarUrl',
|
'avatarUrl',
|
||||||
'creatorAvatarUrl'
|
'creatorAvatarUrl',
|
||||||
)
|
])
|
||||||
console.log(`Found ${contractDiffs.length} contracts with mismatches.`)
|
console.log(`Found ${contractDiffs.length} contracts with mismatches.`)
|
||||||
contractDiffs.forEach((d) => {
|
contractDiffs.forEach((d) => {
|
||||||
console.log(describeDiff(d))
|
console.log(describeDiff(d))
|
||||||
applyDiff(transaction, d)
|
applyDiff(transaction, d)
|
||||||
})
|
})
|
||||||
|
|
||||||
const usersComments = Array.from(
|
const usersComments = Array.from(usersById.entries(), ([id, doc]) => {
|
||||||
usersById.entries(),
|
return [doc, commentsByUserId.get(id) || []] as const
|
||||||
([id, doc]): DocumentCorrespondence => {
|
})
|
||||||
return [doc, commentsByUserId.get(id) || []]
|
const commentDiffs = findDiffs(usersComments, [
|
||||||
}
|
'avatarUrl',
|
||||||
)
|
'userAvatarUrl',
|
||||||
const commentDiffs = findDiffs(usersComments, 'avatarUrl', 'userAvatarUrl')
|
])
|
||||||
console.log(`Found ${commentDiffs.length} comments with mismatches.`)
|
console.log(`Found ${commentDiffs.length} comments with mismatches.`)
|
||||||
commentDiffs.forEach((d) => {
|
commentDiffs.forEach((d) => {
|
||||||
console.log(describeDiff(d))
|
console.log(describeDiff(d))
|
||||||
applyDiff(transaction, d)
|
applyDiff(transaction, d)
|
||||||
})
|
})
|
||||||
|
|
||||||
const usersAnswers = Array.from(
|
const usersAnswers = Array.from(usersById.entries(), ([id, doc]) => {
|
||||||
usersById.entries(),
|
return [doc, answersByUserId.get(id) || []] as const
|
||||||
([id, doc]): DocumentCorrespondence => {
|
})
|
||||||
return [doc, answersByUserId.get(id) || []]
|
const answerDiffs = findDiffs(usersAnswers, ['avatarUrl', 'avatarUrl'])
|
||||||
}
|
|
||||||
)
|
|
||||||
const answerDiffs = findDiffs(usersAnswers, 'avatarUrl', 'avatarUrl')
|
|
||||||
console.log(`Found ${answerDiffs.length} answers with mismatches.`)
|
console.log(`Found ${answerDiffs.length} answers with mismatches.`)
|
||||||
answerDiffs.forEach((d) => {
|
answerDiffs.forEach((d) => {
|
||||||
console.log(describeDiff(d))
|
console.log(describeDiff(d))
|
||||||
|
|
38
functions/src/scripts/denormalize-bet-user-data.ts
Normal file
38
functions/src/scripts/denormalize-bet-user-data.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// Filling in the user-based fields on bets.
|
||||||
|
|
||||||
|
import * as admin from 'firebase-admin'
|
||||||
|
import { initAdmin } from './script-init'
|
||||||
|
import { findDiffs, describeDiff, getDiffUpdate } from './denormalize'
|
||||||
|
import { log, writeAsync } from '../utils'
|
||||||
|
|
||||||
|
initAdmin()
|
||||||
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
// not in a transaction for speed -- may need to be run more than once
|
||||||
|
async function denormalize() {
|
||||||
|
const users = await firestore.collection('users').get()
|
||||||
|
log(`Found ${users.size} users.`)
|
||||||
|
for (const userDoc of users.docs) {
|
||||||
|
const userBets = await firestore
|
||||||
|
.collectionGroup('bets')
|
||||||
|
.where('userId', '==', userDoc.id)
|
||||||
|
.get()
|
||||||
|
const mapping = [[userDoc, userBets.docs] as const] as const
|
||||||
|
const diffs = findDiffs(
|
||||||
|
mapping,
|
||||||
|
['avatarUrl', 'userAvatarUrl'],
|
||||||
|
['name', 'userName'],
|
||||||
|
['username', 'userUsername']
|
||||||
|
)
|
||||||
|
log(`Found ${diffs.length} bets with mismatched user data.`)
|
||||||
|
const updates = diffs.map((d) => {
|
||||||
|
log(describeDiff(d))
|
||||||
|
return getDiffUpdate(d)
|
||||||
|
})
|
||||||
|
await writeAsync(firestore, updates)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
denormalize().catch((e) => console.error(e))
|
||||||
|
}
|
|
@ -3,12 +3,7 @@
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import { zip } from 'lodash'
|
import { zip } from 'lodash'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
import {
|
import { findDiffs, describeDiff, applyDiff } from './denormalize'
|
||||||
DocumentCorrespondence,
|
|
||||||
findDiffs,
|
|
||||||
describeDiff,
|
|
||||||
applyDiff,
|
|
||||||
} from './denormalize'
|
|
||||||
import { log } from '../utils'
|
import { log } from '../utils'
|
||||||
import { Transaction } from 'firebase-admin/firestore'
|
import { Transaction } from 'firebase-admin/firestore'
|
||||||
|
|
||||||
|
@ -41,17 +36,20 @@ async function denormalize() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
log(`Found ${bets.length} bets associated with comments.`)
|
log(`Found ${bets.length} bets associated with comments.`)
|
||||||
const mapping = zip(bets, betComments)
|
|
||||||
.map(([bet, comment]): DocumentCorrespondence => {
|
|
||||||
return [bet!, [comment!]] // eslint-disable-line
|
|
||||||
})
|
|
||||||
.filter(([bet, _]) => bet.exists) // dev DB has some invalid bet IDs
|
|
||||||
|
|
||||||
const amountDiffs = findDiffs(mapping, 'amount', 'betAmount')
|
// dev DB has some invalid bet IDs
|
||||||
const outcomeDiffs = findDiffs(mapping, 'outcome', 'betOutcome')
|
const mapping = zip(bets, betComments)
|
||||||
log(`Found ${amountDiffs.length} comments with mismatched amounts.`)
|
.filter(([bet, _]) => bet!.exists) // eslint-disable-line
|
||||||
log(`Found ${outcomeDiffs.length} comments with mismatched outcomes.`)
|
.map(([bet, comment]) => {
|
||||||
const diffs = amountDiffs.concat(outcomeDiffs)
|
return [bet!, [comment!]] as const // eslint-disable-line
|
||||||
|
})
|
||||||
|
|
||||||
|
const diffs = findDiffs(
|
||||||
|
mapping,
|
||||||
|
['amount', 'betAmount'],
|
||||||
|
['outcome', 'betOutcome']
|
||||||
|
)
|
||||||
|
log(`Found ${diffs.length} comments with mismatched data.`)
|
||||||
diffs.slice(0, 500).forEach((d) => {
|
diffs.slice(0, 500).forEach((d) => {
|
||||||
log(describeDiff(d))
|
log(describeDiff(d))
|
||||||
applyDiff(trans, d)
|
applyDiff(trans, d)
|
||||||
|
|
|
@ -2,12 +2,7 @@
|
||||||
|
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
import {
|
import { findDiffs, describeDiff, applyDiff } from './denormalize'
|
||||||
DocumentCorrespondence,
|
|
||||||
findDiffs,
|
|
||||||
describeDiff,
|
|
||||||
applyDiff,
|
|
||||||
} from './denormalize'
|
|
||||||
import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore'
|
import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore'
|
||||||
|
|
||||||
initAdmin()
|
initAdmin()
|
||||||
|
@ -43,16 +38,15 @@ async function denormalize() {
|
||||||
getContractsById(transaction),
|
getContractsById(transaction),
|
||||||
getCommentsByContractId(transaction),
|
getCommentsByContractId(transaction),
|
||||||
])
|
])
|
||||||
const mapping = Object.entries(contractsById).map(
|
const mapping = Object.entries(contractsById).map(([id, doc]) => {
|
||||||
([id, doc]): DocumentCorrespondence => {
|
return [doc, commentsByContractId.get(id) || []] as const
|
||||||
return [doc, commentsByContractId.get(id) || []]
|
})
|
||||||
}
|
const diffs = findDiffs(
|
||||||
|
mapping,
|
||||||
|
['slug', 'contractSlug'],
|
||||||
|
['question', 'contractQuestion']
|
||||||
)
|
)
|
||||||
const slugDiffs = findDiffs(mapping, 'slug', 'contractSlug')
|
console.log(`Found ${diffs.length} comments with mismatched data.`)
|
||||||
const qDiffs = findDiffs(mapping, 'question', 'contractQuestion')
|
|
||||||
console.log(`Found ${slugDiffs.length} comments with mismatched slugs.`)
|
|
||||||
console.log(`Found ${qDiffs.length} comments with mismatched questions.`)
|
|
||||||
const diffs = slugDiffs.concat(qDiffs)
|
|
||||||
diffs.slice(0, 500).forEach((d) => {
|
diffs.slice(0, 500).forEach((d) => {
|
||||||
console.log(describeDiff(d))
|
console.log(describeDiff(d))
|
||||||
applyDiff(transaction, d)
|
applyDiff(transaction, d)
|
||||||
|
|
|
@ -2,32 +2,40 @@
|
||||||
// another set of documents.
|
// another set of documents.
|
||||||
|
|
||||||
import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore'
|
import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore'
|
||||||
|
import { isEqual, zip } from 'lodash'
|
||||||
|
import { UpdateSpec } from '../utils'
|
||||||
|
|
||||||
export type DocumentValue = {
|
export type DocumentValue = {
|
||||||
doc: DocumentSnapshot
|
doc: DocumentSnapshot
|
||||||
field: string
|
fields: string[]
|
||||||
val: unknown
|
vals: unknown[]
|
||||||
}
|
}
|
||||||
export type DocumentCorrespondence = [DocumentSnapshot, DocumentSnapshot[]]
|
export type DocumentMapping = readonly [
|
||||||
|
DocumentSnapshot,
|
||||||
|
readonly DocumentSnapshot[]
|
||||||
|
]
|
||||||
export type DocumentDiff = {
|
export type DocumentDiff = {
|
||||||
src: DocumentValue
|
src: DocumentValue
|
||||||
dest: DocumentValue
|
dest: DocumentValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PathPair = readonly [string, string]
|
||||||
|
|
||||||
export function findDiffs(
|
export function findDiffs(
|
||||||
docs: DocumentCorrespondence[],
|
docs: readonly DocumentMapping[],
|
||||||
srcPath: string,
|
...paths: PathPair[]
|
||||||
destPath: string
|
|
||||||
) {
|
) {
|
||||||
const diffs: DocumentDiff[] = []
|
const diffs: DocumentDiff[] = []
|
||||||
|
const srcPaths = paths.map((p) => p[0])
|
||||||
|
const destPaths = paths.map((p) => p[1])
|
||||||
for (const [srcDoc, destDocs] of docs) {
|
for (const [srcDoc, destDocs] of docs) {
|
||||||
const srcVal = srcDoc.get(srcPath)
|
const srcVals = srcPaths.map((p) => srcDoc.get(p))
|
||||||
for (const destDoc of destDocs) {
|
for (const destDoc of destDocs) {
|
||||||
const destVal = destDoc.get(destPath)
|
const destVals = destPaths.map((p) => destDoc.get(p))
|
||||||
if (destVal !== srcVal) {
|
if (!isEqual(srcVals, destVals)) {
|
||||||
diffs.push({
|
diffs.push({
|
||||||
src: { doc: srcDoc, field: srcPath, val: srcVal },
|
src: { doc: srcDoc, fields: srcPaths, vals: srcVals },
|
||||||
dest: { doc: destDoc, field: destPath, val: destVal },
|
dest: { doc: destDoc, fields: destPaths, vals: destVals },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,12 +45,19 @@ export function findDiffs(
|
||||||
|
|
||||||
export function describeDiff(diff: DocumentDiff) {
|
export function describeDiff(diff: DocumentDiff) {
|
||||||
function describeDocVal(x: DocumentValue): string {
|
function describeDocVal(x: DocumentValue): string {
|
||||||
return `${x.doc.ref.path}.${x.field}: ${x.val}`
|
return `${x.doc.ref.path}.[${x.fields.join('|')}]: [${x.vals.join('|')}]`
|
||||||
}
|
}
|
||||||
return `${describeDocVal(diff.src)} -> ${describeDocVal(diff.dest)}`
|
return `${describeDocVal(diff.src)} -> ${describeDocVal(diff.dest)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyDiff(transaction: Transaction, diff: DocumentDiff) {
|
export function getDiffUpdate(diff: DocumentDiff) {
|
||||||
const { src, dest } = diff
|
return {
|
||||||
transaction.update(dest.doc.ref, dest.field, src.val)
|
doc: diff.dest.doc.ref,
|
||||||
|
fields: Object.fromEntries(zip(diff.dest.fields, diff.src.vals)),
|
||||||
|
} as UpdateSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyDiff(transaction: Transaction, diff: DocumentDiff) {
|
||||||
|
const update = getDiffUpdate(diff)
|
||||||
|
transaction.update(update.doc, update.fields)
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,9 @@ export const sellshares = newEndpoint({}, async (req, auth) => {
|
||||||
transaction.create(newBetDoc, {
|
transaction.create(newBetDoc, {
|
||||||
id: newBetDoc.id,
|
id: newBetDoc.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
userAvatarUrl: user.avatarUrl,
|
||||||
|
userUsername: user.username,
|
||||||
|
userName: user.name,
|
||||||
...newBet,
|
...newBet,
|
||||||
})
|
})
|
||||||
transaction.update(
|
transaction.update(
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { formatMoney } from 'common/util/format'
|
||||||
import { groupBy, mapValues, sumBy, sortBy, keyBy } from 'lodash'
|
import { groupBy, mapValues, sumBy, sortBy, keyBy } from 'lodash'
|
||||||
import { useState, useMemo, useEffect } from 'react'
|
import { useState, useMemo, useEffect } from 'react'
|
||||||
import { CommentTipMap } from 'web/hooks/use-tip-txns'
|
import { CommentTipMap } from 'web/hooks/use-tip-txns'
|
||||||
import { useUserById } from 'web/hooks/use-user'
|
|
||||||
import { listUsers, User } from 'web/lib/firebase/users'
|
import { listUsers, User } from 'web/lib/firebase/users'
|
||||||
import { FeedBet } from '../feed/feed-bets'
|
import { FeedBet } from '../feed/feed-bets'
|
||||||
import { FeedComment } from '../feed/feed-comments'
|
import { FeedComment } from '../feed/feed-comments'
|
||||||
|
@ -88,7 +87,7 @@ export function ContractTopTrades(props: {
|
||||||
|
|
||||||
// Now find the betId with the highest profit
|
// Now find the betId with the highest profit
|
||||||
const topBetId = sortBy(bets, (b) => -profitById[b.id])[0]?.id
|
const topBetId = sortBy(bets, (b) => -profitById[b.id])[0]?.id
|
||||||
const topBettor = useUserById(betsById[topBetId]?.userId)
|
const topBettor = betsById[topBetId]?.userName
|
||||||
|
|
||||||
// And also the commentId of the comment with the highest profit
|
// And also the commentId of the comment with the highest profit
|
||||||
const topCommentId = sortBy(
|
const topCommentId = sortBy(
|
||||||
|
@ -121,7 +120,7 @@ export function ContractTopTrades(props: {
|
||||||
<FeedBet contract={contract} bet={betsById[topBetId]} />
|
<FeedBet contract={contract} bet={betsById[topBetId]} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 ml-2 text-sm text-gray-500">
|
<div className="mt-2 ml-2 text-sm text-gray-500">
|
||||||
{topBettor?.name} made {formatMoney(profitById[topBetId] || 0)}!
|
{topBettor} made {formatMoney(profitById[topBetId] || 0)}!
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { Bet } from 'common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { User } from 'common/user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { useUser, useUserById } from 'web/hooks/use-user'
|
|
||||||
import { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
import { Avatar, EmptyAvatar } from 'web/components/avatar'
|
import { Avatar, EmptyAvatar } from 'web/components/avatar'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
@ -18,29 +17,20 @@ import { UserLink } from 'web/components/user-link'
|
||||||
|
|
||||||
export function FeedBet(props: { contract: Contract; bet: Bet }) {
|
export function FeedBet(props: { contract: Contract; bet: Bet }) {
|
||||||
const { contract, bet } = props
|
const { contract, bet } = props
|
||||||
const { userId, createdTime } = bet
|
const { userAvatarUrl, userUsername, createdTime } = bet
|
||||||
|
const showUser = dayjs(createdTime).isAfter('2022-06-01')
|
||||||
const isBeforeJune2022 = dayjs(createdTime).isBefore('2022-06-01')
|
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
const bettor = isBeforeJune2022 ? undefined : useUserById(userId)
|
|
||||||
|
|
||||||
const user = useUser()
|
|
||||||
const isSelf = user?.id === userId
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row className="items-center gap-2 pt-3">
|
<Row className="items-center gap-2 pt-3">
|
||||||
{isSelf ? (
|
{showUser ? (
|
||||||
<Avatar avatarUrl={user.avatarUrl} username={user.username} />
|
<Avatar avatarUrl={userAvatarUrl} username={userUsername} />
|
||||||
) : bettor ? (
|
|
||||||
<Avatar avatarUrl={bettor.avatarUrl} username={bettor.username} />
|
|
||||||
) : (
|
) : (
|
||||||
<EmptyAvatar className="mx-1" />
|
<EmptyAvatar className="mx-1" />
|
||||||
)}
|
)}
|
||||||
<BetStatusText
|
<BetStatusText
|
||||||
bet={bet}
|
bet={bet}
|
||||||
contract={contract}
|
contract={contract}
|
||||||
isSelf={isSelf}
|
hideUser={!showUser}
|
||||||
bettor={bettor}
|
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -50,13 +40,13 @@ export function FeedBet(props: { contract: Contract; bet: Bet }) {
|
||||||
export function BetStatusText(props: {
|
export function BetStatusText(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
bet: Bet
|
bet: Bet
|
||||||
isSelf: boolean
|
hideUser?: boolean
|
||||||
bettor?: User
|
|
||||||
hideOutcome?: boolean
|
hideOutcome?: boolean
|
||||||
className?: string
|
className?: string
|
||||||
}) {
|
}) {
|
||||||
const { bet, contract, bettor, isSelf, hideOutcome, className } = props
|
const { bet, contract, hideUser, hideOutcome, className } = props
|
||||||
const { outcomeType } = contract
|
const { outcomeType } = contract
|
||||||
|
const self = useUser()
|
||||||
const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC'
|
const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC'
|
||||||
const isFreeResponse = outcomeType === 'FREE_RESPONSE'
|
const isFreeResponse = outcomeType === 'FREE_RESPONSE'
|
||||||
const { amount, outcome, createdTime, challengeSlug } = bet
|
const { amount, outcome, createdTime, challengeSlug } = bet
|
||||||
|
@ -101,10 +91,10 @@ export function BetStatusText(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx('text-sm text-gray-500', className)}>
|
<div className={clsx('text-sm text-gray-500', className)}>
|
||||||
{bettor ? (
|
{!hideUser ? (
|
||||||
<UserLink name={bettor.name} username={bettor.username} />
|
<UserLink name={bet.userName} username={bet.userUsername} />
|
||||||
) : (
|
) : (
|
||||||
<span>{isSelf ? 'You' : 'A trader'}</span>
|
<span>{self?.id === bet.userId ? 'You' : 'A trader'}</span>
|
||||||
)}{' '}
|
)}{' '}
|
||||||
{bought} {money}
|
{bought} {money}
|
||||||
{outOfTotalAmount}
|
{outOfTotalAmount}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { getFormattedMappedValue } from 'common/pseudo-numeric'
|
||||||
import { formatMoney, formatPercent } from 'common/util/format'
|
import { formatMoney, formatPercent } from 'common/util/format'
|
||||||
import { sortBy } from 'lodash'
|
import { sortBy } from 'lodash'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useUser, useUserById } from 'web/hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { cancelBet } from 'web/lib/firebase/api'
|
import { cancelBet } from 'web/lib/firebase/api'
|
||||||
import { Avatar } from './avatar'
|
import { Avatar } from './avatar'
|
||||||
import { Button } from './button'
|
import { Button } from './button'
|
||||||
|
@ -109,16 +109,14 @@ function LimitBet(props: {
|
||||||
setIsCancelling(true)
|
setIsCancelling(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = useUserById(bet.userId)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
{!isYou && (
|
{!isYou && (
|
||||||
<td>
|
<td>
|
||||||
<Avatar
|
<Avatar
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
avatarUrl={user?.avatarUrl}
|
avatarUrl={bet.userAvatarUrl}
|
||||||
username={user?.username}
|
username={bet.userUsername}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user