parent
e4a2f8acbb
commit
f500064e4e
230
web/pages/api/v0/_types.ts
Normal file
230
web/pages/api/v0/_types.ts
Normal file
|
@ -0,0 +1,230 @@
|
|||
import { Bet } from 'common/bet'
|
||||
import { Answer } from 'common/answer'
|
||||
import { getOutcomeProbability, getProbability } from 'common/calculate'
|
||||
import { Comment } from 'common/comment'
|
||||
import { Contract } from 'common/contract'
|
||||
import { User } from 'common/user'
|
||||
import { removeUndefinedProps } from 'common/util/object'
|
||||
import { ENV_CONFIG } from 'common/envs/constants'
|
||||
import { JSONContent } from '@tiptap/core'
|
||||
|
||||
export type LiteMarket = {
|
||||
// Unique identifer for this market
|
||||
id: string
|
||||
|
||||
// Attributes about the creator
|
||||
creatorUsername: string
|
||||
creatorName: string
|
||||
createdTime: number
|
||||
creatorAvatarUrl?: string
|
||||
|
||||
// Market attributes. All times are in milliseconds since epoch
|
||||
closeTime?: number
|
||||
question: string
|
||||
description: string | JSONContent
|
||||
tags: string[]
|
||||
url: string
|
||||
outcomeType: string
|
||||
mechanism: string
|
||||
|
||||
pool: { [outcome: string]: number }
|
||||
probability?: number
|
||||
p?: number
|
||||
totalLiquidity?: number
|
||||
|
||||
volume: number
|
||||
volume7Days: number
|
||||
volume24Hours: number
|
||||
|
||||
isResolved: boolean
|
||||
resolution?: string
|
||||
resolutionTime?: number
|
||||
resolutionProbability?: number
|
||||
}
|
||||
|
||||
export type ApiAnswer = Answer & {
|
||||
probability?: number
|
||||
}
|
||||
|
||||
export type FullMarket = LiteMarket & {
|
||||
bets: Bet[]
|
||||
comments: Comment[]
|
||||
answers?: ApiAnswer[]
|
||||
}
|
||||
|
||||
export type ApiError = {
|
||||
error: string
|
||||
}
|
||||
|
||||
type ValidationErrorDetail = {
|
||||
field: string | null
|
||||
error: string
|
||||
}
|
||||
export class ValidationError {
|
||||
details: ValidationErrorDetail[]
|
||||
|
||||
constructor(details: ValidationErrorDetail[]) {
|
||||
this.details = details
|
||||
}
|
||||
}
|
||||
|
||||
export function toLiteMarket(contract: Contract): LiteMarket {
|
||||
const {
|
||||
id,
|
||||
creatorUsername,
|
||||
creatorName,
|
||||
createdTime,
|
||||
creatorAvatarUrl,
|
||||
closeTime,
|
||||
question,
|
||||
description,
|
||||
tags,
|
||||
slug,
|
||||
pool,
|
||||
outcomeType,
|
||||
mechanism,
|
||||
volume,
|
||||
volume7Days,
|
||||
volume24Hours,
|
||||
isResolved,
|
||||
resolution,
|
||||
resolutionTime,
|
||||
resolutionProbability,
|
||||
} = contract
|
||||
|
||||
const { p, totalLiquidity } = contract as any
|
||||
|
||||
const probability =
|
||||
contract.outcomeType === 'BINARY' ? getProbability(contract) : undefined
|
||||
|
||||
return removeUndefinedProps({
|
||||
id,
|
||||
creatorUsername,
|
||||
creatorName,
|
||||
createdTime,
|
||||
creatorAvatarUrl,
|
||||
closeTime:
|
||||
resolutionTime && closeTime
|
||||
? Math.min(resolutionTime, closeTime)
|
||||
: closeTime,
|
||||
question,
|
||||
description,
|
||||
tags,
|
||||
url: `https://manifold.markets/${creatorUsername}/${slug}`,
|
||||
pool,
|
||||
probability,
|
||||
p,
|
||||
totalLiquidity,
|
||||
outcomeType,
|
||||
mechanism,
|
||||
volume,
|
||||
volume7Days,
|
||||
volume24Hours,
|
||||
isResolved,
|
||||
resolution,
|
||||
resolutionTime,
|
||||
resolutionProbability,
|
||||
})
|
||||
}
|
||||
|
||||
export function toFullMarket(
|
||||
contract: Contract,
|
||||
comments: Comment[],
|
||||
bets: Bet[]
|
||||
): FullMarket {
|
||||
const liteMarket = toLiteMarket(contract)
|
||||
const answers =
|
||||
contract.outcomeType === 'FREE_RESPONSE'
|
||||
? contract.answers.map((answer) =>
|
||||
augmentAnswerWithProbability(contract, answer)
|
||||
)
|
||||
: undefined
|
||||
|
||||
return {
|
||||
...liteMarket,
|
||||
answers,
|
||||
comments,
|
||||
bets,
|
||||
}
|
||||
}
|
||||
|
||||
function augmentAnswerWithProbability(
|
||||
contract: Contract,
|
||||
answer: Answer
|
||||
): ApiAnswer {
|
||||
const probability = getOutcomeProbability(contract, answer.id)
|
||||
return {
|
||||
...answer,
|
||||
probability,
|
||||
}
|
||||
}
|
||||
|
||||
export type LiteUser = {
|
||||
id: string
|
||||
createdTime: number
|
||||
|
||||
name: string
|
||||
username: string
|
||||
url: string
|
||||
avatarUrl?: string
|
||||
|
||||
bio?: string
|
||||
bannerUrl?: string
|
||||
website?: string
|
||||
twitterHandle?: string
|
||||
discordHandle?: string
|
||||
|
||||
balance: number
|
||||
totalDeposits: number
|
||||
|
||||
profitCached: {
|
||||
daily: number
|
||||
weekly: number
|
||||
monthly: number
|
||||
allTime: number
|
||||
}
|
||||
|
||||
creatorVolumeCached: {
|
||||
daily: number
|
||||
weekly: number
|
||||
monthly: number
|
||||
allTime: number
|
||||
}
|
||||
}
|
||||
|
||||
export function toLiteUser(user: User): LiteUser {
|
||||
const {
|
||||
id,
|
||||
createdTime,
|
||||
name,
|
||||
username,
|
||||
avatarUrl,
|
||||
bio,
|
||||
bannerUrl,
|
||||
website,
|
||||
twitterHandle,
|
||||
discordHandle,
|
||||
balance,
|
||||
totalDeposits,
|
||||
profitCached,
|
||||
creatorVolumeCached,
|
||||
} = user
|
||||
|
||||
return removeUndefinedProps({
|
||||
id,
|
||||
createdTime,
|
||||
name,
|
||||
username,
|
||||
url: `https://${ENV_CONFIG.domain}/${username}`,
|
||||
avatarUrl,
|
||||
bio,
|
||||
bannerUrl,
|
||||
website,
|
||||
twitterHandle,
|
||||
discordHandle,
|
||||
balance,
|
||||
totalDeposits,
|
||||
profitCached,
|
||||
creatorVolumeCached,
|
||||
})
|
||||
}
|
17
web/pages/api/v0/_validate.ts
Normal file
17
web/pages/api/v0/_validate.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { z } from 'zod'
|
||||
import { ValidationError } from './_types'
|
||||
|
||||
export const validate = <T extends z.ZodTypeAny>(schema: T, val: unknown) => {
|
||||
const result = schema.safeParse(val)
|
||||
if (!result.success) {
|
||||
const issues = result.error.issues.map((i) => {
|
||||
return {
|
||||
field: i.path.join('.') || null,
|
||||
error: i.message,
|
||||
}
|
||||
})
|
||||
throw new ValidationError(issues)
|
||||
} else {
|
||||
return result.data as z.infer<T>
|
||||
}
|
||||
}
|
27
web/pages/api/v0/bet/cancel/[betId].ts
Normal file
27
web/pages/api/v0/bet/cancel/[betId].ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import {
|
||||
CORS_ORIGIN_MANIFOLD,
|
||||
CORS_ORIGIN_LOCALHOST,
|
||||
} from 'common/envs/constants'
|
||||
import { applyCorsHeaders } from 'web/lib/api/cors'
|
||||
import { fetchBackend, forwardResponse } from 'web/lib/api/proxy'
|
||||
|
||||
export const config = { api: { bodyParser: true } }
|
||||
|
||||
export default async function route(req: NextApiRequest, res: NextApiResponse) {
|
||||
await applyCorsHeaders(req, res, {
|
||||
origin: [CORS_ORIGIN_MANIFOLD, CORS_ORIGIN_LOCALHOST],
|
||||
methods: 'POST',
|
||||
})
|
||||
|
||||
const { betId } = req.query as { betId: string }
|
||||
|
||||
if (req.body) req.body.betId = betId
|
||||
try {
|
||||
const backendRes = await fetchBackend(req, 'cancelbet')
|
||||
await forwardResponse(res, backendRes)
|
||||
} catch (err) {
|
||||
console.error('Error talking to cloud function: ', err)
|
||||
res.status(500).json({ message: 'Error communicating with backend.' })
|
||||
}
|
||||
}
|
23
web/pages/api/v0/bet/index.ts
Normal file
23
web/pages/api/v0/bet/index.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import {
|
||||
CORS_ORIGIN_MANIFOLD,
|
||||
CORS_ORIGIN_LOCALHOST,
|
||||
} from 'common/envs/constants'
|
||||
import { applyCorsHeaders } from 'web/lib/api/cors'
|
||||
import { fetchBackend, forwardResponse } from 'web/lib/api/proxy'
|
||||
|
||||
export const config = { api: { bodyParser: false } }
|
||||
|
||||
export default async function route(req: NextApiRequest, res: NextApiResponse) {
|
||||
await applyCorsHeaders(req, res, {
|
||||
origin: [CORS_ORIGIN_MANIFOLD, CORS_ORIGIN_LOCALHOST],
|
||||
methods: 'POST',
|
||||
})
|
||||
try {
|
||||
const backendRes = await fetchBackend(req, 'placebet')
|
||||
await forwardResponse(res, backendRes)
|
||||
} catch (err) {
|
||||
console.error('Error talking to cloud function: ', err)
|
||||
res.status(500).json({ message: 'Error communicating with backend.' })
|
||||
}
|
||||
}
|
66
web/pages/api/v0/bets.ts
Normal file
66
web/pages/api/v0/bets.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { applyCorsHeaders, CORS_UNRESTRICTED } from 'web/lib/api/cors'
|
||||
import { Bet, getBets } from 'web/lib/firebase/bets'
|
||||
import { getContractFromSlug } from 'web/lib/firebase/contracts'
|
||||
import { getUserByUsername } from 'web/lib/firebase/users'
|
||||
import { ApiError, ValidationError } from './_types'
|
||||
import { z } from 'zod'
|
||||
import { validate } from './_validate'
|
||||
|
||||
const queryParams = z
|
||||
.object({
|
||||
username: z.string().optional(),
|
||||
market: z.string().optional(),
|
||||
limit: z
|
||||
.number()
|
||||
.default(1000)
|
||||
.or(z.string().regex(/\d+/).transform(Number))
|
||||
.refine((n) => n >= 0 && n <= 1000, 'Limit must be between 0 and 1000'),
|
||||
before: z.string().optional(),
|
||||
})
|
||||
.strict()
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<Bet[] | ValidationError | ApiError>
|
||||
) {
|
||||
await applyCorsHeaders(req, res, CORS_UNRESTRICTED)
|
||||
|
||||
let params: z.infer<typeof queryParams>
|
||||
try {
|
||||
params = validate(queryParams, req.query)
|
||||
} catch (e) {
|
||||
if (e instanceof ValidationError) {
|
||||
return res.status(400).json(e)
|
||||
}
|
||||
console.error(`Unknown error during validation: ${e}`)
|
||||
return res.status(500).json({ error: 'Unknown error during validation' })
|
||||
}
|
||||
|
||||
const { username, market, limit, before } = params
|
||||
|
||||
let userId: string | undefined
|
||||
if (username) {
|
||||
const user = await getUserByUsername(username)
|
||||
if (!user) {
|
||||
res.status(404).json({ error: 'User not found' })
|
||||
return
|
||||
}
|
||||
userId = user.id
|
||||
}
|
||||
|
||||
let contractId: string | undefined
|
||||
if (market) {
|
||||
const contract = await getContractFromSlug(market)
|
||||
if (!contract) {
|
||||
res.status(404).json({ error: 'Contract not found' })
|
||||
return
|
||||
}
|
||||
contractId = contract.id
|
||||
}
|
||||
|
||||
const bets = await getBets({ userId, contractId, limit, before })
|
||||
|
||||
res.setHeader('Cache-Control', 'max-age=0')
|
||||
return res.status(200).json(bets)
|
||||
}
|
18
web/pages/api/v0/group/[slug].ts
Normal file
18
web/pages/api/v0/group/[slug].ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { getGroupBySlug } from 'web/lib/firebase/groups'
|
||||
import { applyCorsHeaders, CORS_UNRESTRICTED } from 'web/lib/api/cors'
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
await applyCorsHeaders(req, res, CORS_UNRESTRICTED)
|
||||
const { slug } = req.query
|
||||
const group = await getGroupBySlug(slug as string)
|
||||
if (!group) {
|
||||
res.status(404).json({ error: 'Group not found' })
|
||||
return
|
||||
}
|
||||
res.setHeader('Cache-Control', 'no-cache')
|
||||
return res.status(200).json(group)
|
||||
}
|
18
web/pages/api/v0/group/by-id/[id].ts
Normal file
18
web/pages/api/v0/group/by-id/[id].ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { getGroup } from 'web/lib/firebase/groups'
|
||||
import { applyCorsHeaders, CORS_UNRESTRICTED } from 'web/lib/api/cors'
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
await applyCorsHeaders(req, res, CORS_UNRESTRICTED)
|
||||
const { id } = req.query
|
||||
const group = await getGroup(id as string)
|
||||
if (!group) {
|
||||
res.status(404).json({ error: 'Group not found' })
|
||||
return
|
||||
}
|
||||
res.setHeader('Cache-Control', 'no-cache')
|
||||
return res.status(200).json(group)
|
||||
}
|
15
web/pages/api/v0/groups.ts
Normal file
15
web/pages/api/v0/groups.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { listAllGroups } from 'web/lib/firebase/groups'
|
||||
import { applyCorsHeaders, CORS_UNRESTRICTED } from 'web/lib/api/cors'
|
||||
|
||||
type Data = any[]
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<Data>
|
||||
) {
|
||||
await applyCorsHeaders(req, res, CORS_UNRESTRICTED)
|
||||
const groups = await listAllGroups()
|
||||
res.setHeader('Cache-Control', 'max-age=0')
|
||||
res.status(200).json(groups)
|
||||
}
|
29
web/pages/api/v0/market/[id]/index.ts
Normal file
29
web/pages/api/v0/market/[id]/index.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { listAllBets } from 'web/lib/firebase/bets'
|
||||
import { listAllComments } from 'web/lib/firebase/comments'
|
||||
import { getContractFromId } from 'web/lib/firebase/contracts'
|
||||
import { applyCorsHeaders, CORS_UNRESTRICTED } from 'web/lib/api/cors'
|
||||
import { FullMarket, ApiError, toFullMarket } from '../../_types'
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<FullMarket | ApiError>
|
||||
) {
|
||||
await applyCorsHeaders(req, res, CORS_UNRESTRICTED)
|
||||
const { id } = req.query
|
||||
const contractId = id as string
|
||||
|
||||
const [contract, bets, comments] = await Promise.all([
|
||||
getContractFromId(contractId),
|
||||
listAllBets(contractId),
|
||||
listAllComments(contractId),
|
||||
])
|
||||
|
||||
if (!contract) {
|
||||
res.status(404).json({ error: 'Contract not found' })
|
||||
return
|
||||
}
|
||||
|
||||
res.setHeader('Cache-Control', 'max-age=0')
|
||||
return res.status(200).json(toFullMarket(contract, comments, bets))
|
||||
}
|
23
web/pages/api/v0/market/[id]/lite.ts
Normal file
23
web/pages/api/v0/market/[id]/lite.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { getContractFromId } from 'web/lib/firebase/contracts'
|
||||
import { applyCorsHeaders, CORS_UNRESTRICTED } from 'web/lib/api/cors'
|
||||
import { ApiError, toLiteMarket, LiteMarket } from '../../_types'
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<LiteMarket | ApiError>
|
||||
) {
|
||||
await applyCorsHeaders(req, res, CORS_UNRESTRICTED)
|
||||
const { id } = req.query
|
||||
const contractId = id as string
|
||||
|
||||
const contract = await getContractFromId(contractId)
|
||||
|
||||
if (!contract) {
|
||||
res.status(404).json({ error: 'Contract not found' })
|
||||
return
|
||||
}
|
||||
|
||||
res.setHeader('Cache-Control', 'max-age=0')
|
||||
return res.status(200).json(toLiteMarket(contract))
|
||||
}
|
28
web/pages/api/v0/market/[id]/resolve.ts
Normal file
28
web/pages/api/v0/market/[id]/resolve.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import {
|
||||
CORS_ORIGIN_MANIFOLD,
|
||||
CORS_ORIGIN_LOCALHOST,
|
||||
} from 'common/envs/constants'
|
||||
import { applyCorsHeaders } from 'web/lib/api/cors'
|
||||
import { fetchBackend, forwardResponse } from 'web/lib/api/proxy'
|
||||
|
||||
export const config = { api: { bodyParser: true } }
|
||||
|
||||
export default async function route(req: NextApiRequest, res: NextApiResponse) {
|
||||
await applyCorsHeaders(req, res, {
|
||||
origin: [CORS_ORIGIN_MANIFOLD, CORS_ORIGIN_LOCALHOST],
|
||||
methods: 'POST',
|
||||
})
|
||||
|
||||
const { id } = req.query
|
||||
const contractId = id as string
|
||||
|
||||
if (req.body) req.body.contractId = contractId
|
||||
try {
|
||||
const backendRes = await fetchBackend(req, 'resolvemarket')
|
||||
await forwardResponse(res, backendRes)
|
||||
} catch (err) {
|
||||
console.error('Error talking to cloud function: ', err)
|
||||
res.status(500).json({ message: 'Error communicating with backend.' })
|
||||
}
|
||||
}
|
28
web/pages/api/v0/market/[id]/sell.ts
Normal file
28
web/pages/api/v0/market/[id]/sell.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import {
|
||||
CORS_ORIGIN_MANIFOLD,
|
||||
CORS_ORIGIN_LOCALHOST,
|
||||
} from 'common/envs/constants'
|
||||
import { applyCorsHeaders } from 'web/lib/api/cors'
|
||||
import { fetchBackend, forwardResponse } from 'web/lib/api/proxy'
|
||||
|
||||
export const config = { api: { bodyParser: true } }
|
||||
|
||||
export default async function route(req: NextApiRequest, res: NextApiResponse) {
|
||||
await applyCorsHeaders(req, res, {
|
||||
origin: [CORS_ORIGIN_MANIFOLD, CORS_ORIGIN_LOCALHOST],
|
||||
methods: 'POST',
|
||||
})
|
||||
|
||||
const { id } = req.query
|
||||
const contractId = id as string
|
||||
|
||||
if (req.body) req.body.contractId = contractId
|
||||
try {
|
||||
const backendRes = await fetchBackend(req, 'sellshares')
|
||||
await forwardResponse(res, backendRes)
|
||||
} catch (err) {
|
||||
console.error('Error talking to cloud function: ', err)
|
||||
res.status(500).json({ message: 'Error communicating with backend.' })
|
||||
}
|
||||
}
|
23
web/pages/api/v0/market/index.ts
Normal file
23
web/pages/api/v0/market/index.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import {
|
||||
CORS_ORIGIN_MANIFOLD,
|
||||
CORS_ORIGIN_LOCALHOST,
|
||||
} from 'common/envs/constants'
|
||||
import { applyCorsHeaders } from 'web/lib/api/cors'
|
||||
import { fetchBackend, forwardResponse } from 'web/lib/api/proxy'
|
||||
|
||||
export const config = { api: { bodyParser: false } }
|
||||
|
||||
export default async function route(req: NextApiRequest, res: NextApiResponse) {
|
||||
await applyCorsHeaders(req, res, {
|
||||
origin: [CORS_ORIGIN_MANIFOLD, CORS_ORIGIN_LOCALHOST],
|
||||
methods: 'POST',
|
||||
})
|
||||
try {
|
||||
const backendRes = await fetchBackend(req, 'createmarket')
|
||||
await forwardResponse(res, backendRes)
|
||||
} catch (err) {
|
||||
console.error('Error talking to cloud function: ', err)
|
||||
res.status(500).json({ message: 'Error communicating with backend.' })
|
||||
}
|
||||
}
|
51
web/pages/api/v0/markets.ts
Normal file
51
web/pages/api/v0/markets.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
// Next.js API route support: https://vercel.com/docs/concepts/functions/serverless-functions
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { listAllContracts } from 'web/lib/firebase/contracts'
|
||||
import { applyCorsHeaders, CORS_UNRESTRICTED } from 'web/lib/api/cors'
|
||||
import { toLiteMarket, ValidationError } from './_types'
|
||||
import { z } from 'zod'
|
||||
import { validate } from './_validate'
|
||||
|
||||
const queryParams = z
|
||||
.object({
|
||||
limit: z
|
||||
.number()
|
||||
.default(1000)
|
||||
.or(z.string().regex(/\d+/).transform(Number))
|
||||
.refine((n) => n >= 0 && n <= 1000, 'Limit must be between 0 and 1000'),
|
||||
before: z.string().optional(),
|
||||
})
|
||||
.strict()
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
await applyCorsHeaders(req, res, CORS_UNRESTRICTED)
|
||||
|
||||
let params: z.infer<typeof queryParams>
|
||||
try {
|
||||
params = validate(queryParams, req.query)
|
||||
} catch (e) {
|
||||
if (e instanceof ValidationError) {
|
||||
return res.status(400).json(e)
|
||||
}
|
||||
console.error(`Unknown error during validation: ${e}`)
|
||||
return res.status(500).json({ error: 'Unknown error during validation' })
|
||||
}
|
||||
|
||||
const { limit, before } = params
|
||||
|
||||
try {
|
||||
const contracts = await listAllContracts(limit, before)
|
||||
// Serve from Vercel cache, then update. see https://vercel.com/docs/concepts/functions/edge-caching
|
||||
res.setHeader('Cache-Control', 's-maxage=1, stale-while-revalidate')
|
||||
res.status(200).json(contracts.map(toLiteMarket))
|
||||
} catch (e) {
|
||||
res.status(400).json({
|
||||
error:
|
||||
'Failed to fetch markets (did you pass an invalid ID as the before parameter?)',
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
16
web/pages/api/v0/me.ts
Normal file
16
web/pages/api/v0/me.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { fetchBackend, forwardResponse } from 'web/lib/api/proxy'
|
||||
import { LiteUser, ApiError } from './_types'
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<LiteUser | ApiError>
|
||||
) {
|
||||
try {
|
||||
const backendRes = await fetchBackend(req, 'getcurrentuser')
|
||||
await forwardResponse(res, backendRes)
|
||||
} catch (err) {
|
||||
console.error('Error talking to cloud function: ', err)
|
||||
res.status(500).json({ error: 'Error communicating with backend.' })
|
||||
}
|
||||
}
|
29
web/pages/api/v0/slug/[slug].ts
Normal file
29
web/pages/api/v0/slug/[slug].ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { applyCorsHeaders, CORS_UNRESTRICTED } from 'web/lib/api/cors'
|
||||
import { listAllBets } from 'web/lib/firebase/bets'
|
||||
import { listAllComments } from 'web/lib/firebase/comments'
|
||||
import { getContractFromSlug } from 'web/lib/firebase/contracts'
|
||||
import { FullMarket, ApiError, toFullMarket } from '../_types'
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<FullMarket | ApiError>
|
||||
) {
|
||||
await applyCorsHeaders(req, res, CORS_UNRESTRICTED)
|
||||
const { slug } = req.query
|
||||
|
||||
const contract = await getContractFromSlug(slug as string)
|
||||
|
||||
if (!contract) {
|
||||
res.status(404).json({ error: 'Contract not found' })
|
||||
return
|
||||
}
|
||||
|
||||
const [bets, comments] = await Promise.all([
|
||||
listAllBets(contract.id),
|
||||
listAllComments(contract.id),
|
||||
])
|
||||
|
||||
res.setHeader('Cache-Control', 'max-age=0')
|
||||
return res.status(200).json(toFullMarket(contract, comments, bets))
|
||||
}
|
25
web/pages/api/v0/user/[username]/bets/index.ts
Normal file
25
web/pages/api/v0/user/[username]/bets/index.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { applyCorsHeaders, CORS_UNRESTRICTED } from 'web/lib/api/cors'
|
||||
import { Bet, getUserBets } from 'web/lib/firebase/bets'
|
||||
import { getUserByUsername } from 'web/lib/firebase/users'
|
||||
import { ApiError } from '../../../_types'
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<Bet[] | ApiError>
|
||||
) {
|
||||
await applyCorsHeaders(req, res, CORS_UNRESTRICTED)
|
||||
const { username } = req.query
|
||||
|
||||
const user = await getUserByUsername(username as string)
|
||||
|
||||
if (!user) {
|
||||
res.status(404).json({ error: 'User not found' })
|
||||
return
|
||||
}
|
||||
|
||||
const bets = await getUserBets(user.id, { includeRedemptions: false })
|
||||
|
||||
res.setHeader('Cache-Control', 'max-age=0')
|
||||
return res.status(200).json(bets)
|
||||
}
|
19
web/pages/api/v0/user/[username]/index.ts
Normal file
19
web/pages/api/v0/user/[username]/index.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { getUserByUsername } from 'web/lib/firebase/users'
|
||||
import { applyCorsHeaders, CORS_UNRESTRICTED } from 'web/lib/api/cors'
|
||||
import { LiteUser, ApiError, toLiteUser } from '../../_types'
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<LiteUser | ApiError>
|
||||
) {
|
||||
await applyCorsHeaders(req, res, CORS_UNRESTRICTED)
|
||||
const { username } = req.query
|
||||
const user = await getUserByUsername(username as string)
|
||||
if (!user) {
|
||||
res.status(404).json({ error: 'User not found' })
|
||||
return
|
||||
}
|
||||
res.setHeader('Cache-Control', 'no-cache')
|
||||
return res.status(200).json(toLiteUser(user))
|
||||
}
|
19
web/pages/api/v0/user/by-id/[id].ts
Normal file
19
web/pages/api/v0/user/by-id/[id].ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { getUser } from 'web/lib/firebase/users'
|
||||
import { applyCorsHeaders, CORS_UNRESTRICTED } from 'web/lib/api/cors'
|
||||
import { LiteUser, ApiError, toLiteUser } from '../../_types'
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<LiteUser | ApiError>
|
||||
) {
|
||||
await applyCorsHeaders(req, res, CORS_UNRESTRICTED)
|
||||
const { id } = req.query
|
||||
const user = await getUser(id as string)
|
||||
if (!user) {
|
||||
res.status(404).json({ error: 'User not found' })
|
||||
return
|
||||
}
|
||||
res.setHeader('Cache-Control', 'no-cache')
|
||||
return res.status(200).json(toLiteUser(user))
|
||||
}
|
17
web/pages/api/v0/users.ts
Normal file
17
web/pages/api/v0/users.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Next.js API route support: https://vercel.com/docs/concepts/functions/serverless-functions
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { listAllUsers } from 'web/lib/firebase/users'
|
||||
import { applyCorsHeaders, CORS_UNRESTRICTED } from 'web/lib/api/cors'
|
||||
import { toLiteUser } from './_types'
|
||||
|
||||
type Data = any[]
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<Data>
|
||||
) {
|
||||
await applyCorsHeaders(req, res, CORS_UNRESTRICTED)
|
||||
const users = await listAllUsers()
|
||||
res.setHeader('Cache-Control', 'max-age=0')
|
||||
res.status(200).json(users.map(toLiteUser))
|
||||
}
|
Loading…
Reference in New Issue
Block a user