Merge branch 'main' of https://github.com/manifoldmarkets/manifold
This commit is contained in:
commit
edbd0feb37
|
@ -15,7 +15,7 @@ import {
|
||||||
import { slugify } from '../../common/util/slugify'
|
import { slugify } from '../../common/util/slugify'
|
||||||
import { randomString } from '../../common/util/random'
|
import { randomString } from '../../common/util/random'
|
||||||
|
|
||||||
import { isProd } from './utils'
|
import { chargeUser, getContract, isProd } from './utils'
|
||||||
import { APIError, AuthedUser, newEndpoint, validate, zTimestamp } from './api'
|
import { APIError, AuthedUser, newEndpoint, validate, zTimestamp } from './api'
|
||||||
|
|
||||||
import { FIXED_ANTE, FREE_MARKETS_PER_USER_MAX } from '../../common/economy'
|
import { FIXED_ANTE, FREE_MARKETS_PER_USER_MAX } from '../../common/economy'
|
||||||
|
@ -36,7 +36,7 @@ import { getPseudoProbability } from '../../common/pseudo-numeric'
|
||||||
import { JSONContent } from '@tiptap/core'
|
import { JSONContent } from '@tiptap/core'
|
||||||
import { uniq, zip } from 'lodash'
|
import { uniq, zip } from 'lodash'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
import { FieldValue, Transaction } from 'firebase-admin/firestore'
|
import { FieldValue } from 'firebase-admin/firestore'
|
||||||
|
|
||||||
const descScehma: z.ZodType<JSONContent> = z.lazy(() =>
|
const descScehma: z.ZodType<JSONContent> = z.lazy(() =>
|
||||||
z.intersection(
|
z.intersection(
|
||||||
|
@ -107,7 +107,6 @@ export async function createMarketHelper(body: any, auth: AuthedUser) {
|
||||||
visibility = 'public',
|
visibility = 'public',
|
||||||
} = validate(bodySchema, body)
|
} = validate(bodySchema, body)
|
||||||
|
|
||||||
return await firestore.runTransaction(async (trans) => {
|
|
||||||
let min, max, initialProb, isLogScale, answers
|
let min, max, initialProb, isLogScale, answers
|
||||||
|
|
||||||
if (outcomeType === 'PSEUDO_NUMERIC' || outcomeType === 'NUMERIC') {
|
if (outcomeType === 'PSEUDO_NUMERIC' || outcomeType === 'NUMERIC') {
|
||||||
|
@ -116,8 +115,7 @@ export async function createMarketHelper(body: any, auth: AuthedUser) {
|
||||||
if (max - min <= 0.01 || initialValue <= min || initialValue >= max)
|
if (max - min <= 0.01 || initialValue <= min || initialValue >= max)
|
||||||
throw new APIError(400, 'Invalid range.')
|
throw new APIError(400, 'Invalid range.')
|
||||||
|
|
||||||
initialProb =
|
initialProb = getPseudoProbability(initialValue, min, max, isLogScale) * 100
|
||||||
getPseudoProbability(initialValue, min, max, isLogScale) * 100
|
|
||||||
|
|
||||||
if (initialProb < 1 || initialProb > 99)
|
if (initialProb < 1 || initialProb > 99)
|
||||||
if (outcomeType === 'PSEUDO_NUMERIC')
|
if (outcomeType === 'PSEUDO_NUMERIC')
|
||||||
|
@ -136,7 +134,7 @@ export async function createMarketHelper(body: any, auth: AuthedUser) {
|
||||||
;({ answers } = validate(multipleChoiceSchema, body))
|
;({ answers } = validate(multipleChoiceSchema, body))
|
||||||
}
|
}
|
||||||
|
|
||||||
const userDoc = await trans.get(firestore.collection('users').doc(auth.uid))
|
const userDoc = await firestore.collection('users').doc(auth.uid).get()
|
||||||
if (!userDoc.exists) {
|
if (!userDoc.exists) {
|
||||||
throw new APIError(400, 'No user exists with the authenticated user ID.')
|
throw new APIError(400, 'No user exists with the authenticated user ID.')
|
||||||
}
|
}
|
||||||
|
@ -152,15 +150,15 @@ export async function createMarketHelper(body: any, auth: AuthedUser) {
|
||||||
let group: Group | null = null
|
let group: Group | null = null
|
||||||
if (groupId) {
|
if (groupId) {
|
||||||
const groupDocRef = firestore.collection('groups').doc(groupId)
|
const groupDocRef = firestore.collection('groups').doc(groupId)
|
||||||
const groupDoc = await trans.get(groupDocRef)
|
const groupDoc = await groupDocRef.get()
|
||||||
if (!groupDoc.exists) {
|
if (!groupDoc.exists) {
|
||||||
throw new APIError(400, 'No group exists with the given group ID.')
|
throw new APIError(400, 'No group exists with the given group ID.')
|
||||||
}
|
}
|
||||||
|
|
||||||
group = groupDoc.data() as Group
|
group = groupDoc.data() as Group
|
||||||
const groupMembersSnap = await trans.get(
|
const groupMembersSnap = await firestore
|
||||||
firestore.collection(`groups/${groupId}/groupMembers`)
|
.collection(`groups/${groupId}/groupMembers`)
|
||||||
)
|
.get()
|
||||||
const groupMemberDocs = groupMembersSnap.docs.map(
|
const groupMemberDocs = groupMembersSnap.docs.map(
|
||||||
(doc) => doc.data() as { userId: string; createdTime: number }
|
(doc) => doc.data() as { userId: string; createdTime: number }
|
||||||
)
|
)
|
||||||
|
@ -175,7 +173,7 @@ export async function createMarketHelper(body: any, auth: AuthedUser) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const slug = await getSlug(trans, question)
|
const slug = await getSlug(question)
|
||||||
const contractRef = firestore.collection('contracts').doc()
|
const contractRef = firestore.collection('contracts').doc()
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
|
@ -226,36 +224,28 @@ export async function createMarketHelper(body: any, auth: AuthedUser) {
|
||||||
: DEV_HOUSE_LIQUIDITY_PROVIDER_ID
|
: DEV_HOUSE_LIQUIDITY_PROVIDER_ID
|
||||||
: user.id
|
: user.id
|
||||||
|
|
||||||
if (ante) {
|
if (ante) await chargeUser(providerId, ante, true)
|
||||||
const delta = FieldValue.increment(-ante)
|
if (deservesFreeMarket)
|
||||||
const providerDoc = firestore.collection('users').doc(providerId)
|
await firestore
|
||||||
await trans.update(providerDoc, { balance: delta, totalDeposits: delta })
|
.collection('users')
|
||||||
}
|
.doc(user.id)
|
||||||
|
.update({ freeMarketsCreated: FieldValue.increment(1) })
|
||||||
if (deservesFreeMarket) {
|
|
||||||
await trans.update(firestore.collection('users').doc(user.id), {
|
|
||||||
freeMarketsCreated: FieldValue.increment(1),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await contractRef.create(contract)
|
await contractRef.create(contract)
|
||||||
|
|
||||||
if (group != null) {
|
if (group != null) {
|
||||||
const groupContractsSnap = await trans.get(
|
const groupContractsSnap = await firestore
|
||||||
firestore.collection(`groups/${groupId}/groupContracts`)
|
.collection(`groups/${groupId}/groupContracts`)
|
||||||
)
|
.get()
|
||||||
const groupContracts = groupContractsSnap.docs.map(
|
const groupContracts = groupContractsSnap.docs.map(
|
||||||
(doc) => doc.data() as { contractId: string; createdTime: number }
|
(doc) => doc.data() as { contractId: string; createdTime: number }
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!groupContracts.map((c) => c.contractId).includes(contractRef.id)) {
|
if (!groupContracts.map((c) => c.contractId).includes(contractRef.id)) {
|
||||||
await createGroupLinks(trans, group, [contractRef.id], auth.uid)
|
await createGroupLinks(group, [contractRef.id], auth.uid)
|
||||||
|
|
||||||
const groupContractRef = firestore
|
const groupContractRef = firestore
|
||||||
.collection(`groups/${groupId}/groupContracts`)
|
.collection(`groups/${groupId}/groupContracts`)
|
||||||
.doc(contract.id)
|
.doc(contract.id)
|
||||||
|
await groupContractRef.set({
|
||||||
await trans.set(groupContractRef, {
|
|
||||||
contractId: contract.id,
|
contractId: contract.id,
|
||||||
createdTime: Date.now(),
|
createdTime: Date.now(),
|
||||||
})
|
})
|
||||||
|
@ -274,7 +264,7 @@ export async function createMarketHelper(body: any, auth: AuthedUser) {
|
||||||
ante
|
ante
|
||||||
)
|
)
|
||||||
|
|
||||||
await trans.set(liquidityDoc, lp)
|
await liquidityDoc.set(lp)
|
||||||
} else if (outcomeType === 'MULTIPLE_CHOICE') {
|
} else if (outcomeType === 'MULTIPLE_CHOICE') {
|
||||||
const betCol = firestore.collection(`contracts/${contract.id}/bets`)
|
const betCol = firestore.collection(`contracts/${contract.id}/bets`)
|
||||||
const betDocs = (answers ?? []).map(() => betCol.doc())
|
const betDocs = (answers ?? []).map(() => betCol.doc())
|
||||||
|
@ -292,23 +282,21 @@ export async function createMarketHelper(body: any, auth: AuthedUser) {
|
||||||
)
|
)
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
zip(bets, betDocs).map(([bet, doc]) =>
|
zip(bets, betDocs).map(([bet, doc]) => doc?.create(bet as Bet))
|
||||||
doc ? trans.create(doc, bet as Bet) : undefined
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
zip(answerObjects, answerDocs).map(([answer, doc]) =>
|
zip(answerObjects, answerDocs).map(([answer, doc]) =>
|
||||||
doc ? trans.create(doc, answer as Answer) : undefined
|
doc?.create(answer as Answer)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
await trans.update(contractRef, { answers: answerObjects })
|
await contractRef.update({ answers: answerObjects })
|
||||||
} else if (outcomeType === 'FREE_RESPONSE') {
|
} else if (outcomeType === 'FREE_RESPONSE') {
|
||||||
const noneAnswerDoc = firestore
|
const noneAnswerDoc = firestore
|
||||||
.collection(`contracts/${contract.id}/answers`)
|
.collection(`contracts/${contract.id}/answers`)
|
||||||
.doc('0')
|
.doc('0')
|
||||||
|
|
||||||
const noneAnswer = getNoneAnswer(contract.id, user)
|
const noneAnswer = getNoneAnswer(contract.id, user)
|
||||||
await trans.set(noneAnswerDoc, noneAnswer)
|
await noneAnswerDoc.set(noneAnswer)
|
||||||
|
|
||||||
const anteBetDoc = firestore
|
const anteBetDoc = firestore
|
||||||
.collection(`contracts/${contract.id}/bets`)
|
.collection(`contracts/${contract.id}/bets`)
|
||||||
|
@ -319,7 +307,7 @@ export async function createMarketHelper(body: any, auth: AuthedUser) {
|
||||||
contract as FreeResponseContract,
|
contract as FreeResponseContract,
|
||||||
anteBetDoc.id
|
anteBetDoc.id
|
||||||
)
|
)
|
||||||
await trans.set(anteBetDoc, anteBet)
|
await anteBetDoc.set(anteBet)
|
||||||
} else if (outcomeType === 'NUMERIC') {
|
} else if (outcomeType === 'NUMERIC') {
|
||||||
const anteBetDoc = firestore
|
const anteBetDoc = firestore
|
||||||
.collection(`contracts/${contract.id}/bets`)
|
.collection(`contracts/${contract.id}/bets`)
|
||||||
|
@ -332,17 +320,16 @@ export async function createMarketHelper(body: any, auth: AuthedUser) {
|
||||||
anteBetDoc.id
|
anteBetDoc.id
|
||||||
)
|
)
|
||||||
|
|
||||||
await trans.set(anteBetDoc, anteBet)
|
await anteBetDoc.set(anteBet)
|
||||||
}
|
}
|
||||||
|
|
||||||
return contract
|
return contract
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSlug = async (trans: Transaction, question: string) => {
|
const getSlug = async (question: string) => {
|
||||||
const proposedSlug = slugify(question)
|
const proposedSlug = slugify(question)
|
||||||
|
|
||||||
const preexistingContract = await getContractFromSlug(trans, proposedSlug)
|
const preexistingContract = await getContractFromSlug(proposedSlug)
|
||||||
|
|
||||||
return preexistingContract
|
return preexistingContract
|
||||||
? proposedSlug + '-' + randomString()
|
? proposedSlug + '-' + randomString()
|
||||||
|
@ -351,31 +338,35 @@ const getSlug = async (trans: Transaction, question: string) => {
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
async function getContractFromSlug(trans: Transaction, slug: string) {
|
export async function getContractFromSlug(slug: string) {
|
||||||
const snap = await trans.get(
|
const snap = await firestore
|
||||||
firestore.collection('contracts').where('slug', '==', slug)
|
.collection('contracts')
|
||||||
)
|
.where('slug', '==', slug)
|
||||||
|
.get()
|
||||||
|
|
||||||
return snap.empty ? undefined : (snap.docs[0].data() as Contract)
|
return snap.empty ? undefined : (snap.docs[0].data() as Contract)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createGroupLinks(
|
async function createGroupLinks(
|
||||||
trans: Transaction,
|
|
||||||
group: Group,
|
group: Group,
|
||||||
contractIds: string[],
|
contractIds: string[],
|
||||||
userId: string
|
userId: string
|
||||||
) {
|
) {
|
||||||
for (const contractId of contractIds) {
|
for (const contractId of contractIds) {
|
||||||
const contractRef = firestore.collection('contracts').doc(contractId)
|
const contract = await getContract(contractId)
|
||||||
const contract = (await trans.get(contractRef)).data() as Contract
|
|
||||||
|
|
||||||
if (!contract?.groupSlugs?.includes(group.slug)) {
|
if (!contract?.groupSlugs?.includes(group.slug)) {
|
||||||
await trans.update(contractRef, {
|
await firestore
|
||||||
|
.collection('contracts')
|
||||||
|
.doc(contractId)
|
||||||
|
.update({
|
||||||
groupSlugs: uniq([group.slug, ...(contract?.groupSlugs ?? [])]),
|
groupSlugs: uniq([group.slug, ...(contract?.groupSlugs ?? [])]),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (!contract?.groupLinks?.map((gl) => gl.groupId).includes(group.id)) {
|
if (!contract?.groupLinks?.map((gl) => gl.groupId).includes(group.id)) {
|
||||||
await trans.update(contractRef, {
|
await firestore
|
||||||
|
.collection('contracts')
|
||||||
|
.doc(contractId)
|
||||||
|
.update({
|
||||||
groupLinks: [
|
groupLinks: [
|
||||||
{
|
{
|
||||||
groupId: group.id,
|
groupId: group.id,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user