Skip to content

Commit

Permalink
Add use-bets to mani
Browse files Browse the repository at this point in the history
  • Loading branch information
IanPhilips committed Jan 15, 2025
1 parent 445bede commit e91b4b0
Show file tree
Hide file tree
Showing 17 changed files with 543 additions and 187 deletions.
19 changes: 17 additions & 2 deletions backend/api/src/get-market-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ export const getMarketProps: APIHandler<'get-market-props'> = async (
limit 1;
select ${contractColumnsToSelect} from contracts
where id = (select data->>'siblingContractId' from contracts where id = $1);
select count(*) from contract_bets where contract_id = $1 and not is_redemption;
select count(*) from contract_bets where contract_id = (select data->>'siblingContractId' from contracts where id = $1) and not is_redemption;
`,
[contractId]
)
Expand All @@ -138,8 +142,17 @@ export const getMarketProps: APIHandler<'get-market-props'> = async (
const totalPositions = results[8]?.[0]?.count ?? 0
const dashboards = results[9]
const siblingContract = first(results[10]?.map(convertContract))
if (!siblingContract) throw new APIError(404, 'Sibiling contract not found')
const manaContract = contract.token === 'MANA' ? contract : siblingContract
const cashContract = contract.token === 'CASH' ? contract : siblingContract
const totalOriginalBets = results[11]?.[0]?.count ?? 0
const totalSiblingsBets = results[12]?.[0]?.count ?? 0
const totalManaBets =
contract.token === 'MANA' ? totalOriginalBets : totalSiblingsBets
const totalCashBets =
contract.token === 'CASH' ? totalOriginalBets : totalSiblingsBets
return {
contract,
manaContract,
chartAnnotations,
topics,
comments,
Expand All @@ -151,7 +164,9 @@ export const getMarketProps: APIHandler<'get-market-props'> = async (
topContractMetrics,
totalPositions,
dashboards,
siblingContract,
cashContract,
totalManaBets,
totalCashBets,
}
}

Expand Down
14 changes: 14 additions & 0 deletions backend/api/src/get-users-by-ids.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { type APIHandler } from './helpers/endpoint'
import { createSupabaseDirectClient } from 'shared/supabase/init'
import { removeNullOrUndefinedProps } from 'common/util/object'

export const getUsersByIds: APIHandler<'users/by-id'> = async (props) => {
const pg = createSupabaseDirectClient()
const users = await pg.manyOrNone(
`select id, name, username, data->>'avatarUrl' as "avatarUrl", data->'isBannedFromPosting' as "isBannedFromPosting"
from users
where id = any($1)`,
[props.ids]
)
return users.map((user) => removeNullOrUndefinedProps(user))
}
2 changes: 2 additions & 0 deletions backend/api/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { getComments } from './get-comments'
import { getBets } from './get-bets'
import { getLiteUser, getUser } from './get-user'
import { getUsers } from './get-users'
import { getUsersByIds } from './get-users-by-ids'
import { getMarket } from './get-market'
import { getGroup } from './get-group'
import { getPositions } from './get-positions'
Expand Down Expand Up @@ -232,6 +233,7 @@ export const handlers: { [k in APIPath]: APIHandler<k> } = {
'user/by-id/:id/block': blockUser,
'user/by-id/:id/unblock': unblockUser,
users: getUsers,
'users/by-id': getUsersByIds,
'search-users': searchUsers,
react: addOrRemoveReaction,
'save-twitch': saveTwitchCredentials,
Expand Down
121 changes: 121 additions & 0 deletions client-common/src/hooks/use-bets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { APIParams, APIPath, APIResponse } from 'common/api/schema'
import { usePersistentInMemoryState } from './use-persistent-in-memory-state'
import { Bet } from 'common/bet'
import { useEffect } from 'react'
import { useApiSubscription } from './use-api-subscription'
import { sortBy, uniqBy } from 'lodash'
import { LimitBet } from 'common/bet'

export const useContractBets = (
contractId: string,
opts: APIParams<'bets'> & { enabled?: boolean },
useIsPageVisible: () => boolean,
api: (params: APIParams<'bets'>) => Promise<APIResponse<'bets'>>
) => {
const { enabled = true, ...apiOptions } = {
contractId,
...opts,
}
const optionsKey = JSON.stringify(apiOptions)

const [newBets, setNewBets] = usePersistentInMemoryState<Bet[]>(
[],
`${optionsKey}-bets`
)

const addBets = (bets: Bet[]) => {
setNewBets((currentBets) => {
const uniqueBets = sortBy(
uniqBy([...currentBets, ...bets], 'id'),
'createdTime'
)
return uniqueBets.filter((b) => !betShouldBeFiltered(b, apiOptions))
})
}

const isPageVisible = useIsPageVisible()

useEffect(() => {
if (isPageVisible && enabled) {
api(apiOptions).then(addBets)
}
}, [optionsKey, enabled, isPageVisible])

useApiSubscription({
topics: [`contract/${contractId}/new-bet`],
onBroadcast: (msg) => {
addBets(msg.data.bets as Bet[])
},
enabled,
})

// We have to listen to cancels as well, since we don't get them in the `new-bet` topic.
useApiSubscription({
topics: [`contract/${contractId}/orders`],
onBroadcast: (msg) => {
const betUpdates = msg.data.bets as LimitBet[]
const cancelledBets = betUpdates.filter(
(bet: LimitBet) => bet.isCancelled
)
setNewBets((currentBets) => {
return currentBets.map((bet) => {
const cancelledBet = cancelledBets.find(
(cancelledBet) => cancelledBet.id === bet.id
)
return cancelledBet ? { ...bet, isCancelled: true } : bet
})
})
},
enabled,
})

return newBets
}

export function betShouldBeFiltered(bet: Bet, options?: APIParams<'bets'>) {
if (!options) {
return false
}
const shouldBeFiltered =
// if contract filter exists, and bet doesn't match contract
(options.contractId && bet.contractId != options.contractId) ||
// if user filter exists, and bet doesn't match user
(options.userId && bet.userId != options.userId) ||
// if afterTime filter exists, and bet is before that time
(options.afterTime && bet.createdTime <= options.afterTime) ||
// if beforeTime filter exists, and bet is after that time
(options.beforeTime !== undefined &&
bet.createdTime >= options.beforeTime) ||
// if redemption filter is true, and bet is redemption
(options.filterRedemptions && bet.isRedemption) ||
// if open-limit kind exists, and bet is not filled/cancelled
(options.kinds === 'open-limit' && (bet.isFilled || bet.isCancelled))

return shouldBeFiltered
}

export const useSubscribeGlobalBets = (options?: APIParams<'bets'>) => {
const [newBets, setNewBets] = usePersistentInMemoryState<Bet[]>(
[],
'global-new-bets'
)

const addBets = (bets: Bet[]) => {
setNewBets((currentBets) => {
const uniqueBets = sortBy(
uniqBy([...currentBets, ...bets], 'id'),
'createdTime'
)
return uniqueBets.filter((b) => !betShouldBeFiltered(b, options))
})
}

useApiSubscription({
topics: [`global/new-bet`],
onBroadcast: (msg) => {
addBets(msg.data.bets as Bet[])
},
})

return newBets
}
8 changes: 8 additions & 0 deletions client-common/src/lib/choice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Bet } from 'common/bet'
import { groupBy, mapValues } from 'lodash'

export const getMultiBetPointsFromBets = (bets: Bet[]) => {
return mapValues(groupBy(bets, 'answerId'), (bets) =>
bets.map((bet) => ({ x: bet.createdTime, y: bet.probAfter }))
)
}
14 changes: 12 additions & 2 deletions common/src/api/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,14 @@ export const API = (_apiTypeCheck = {
returns: {} as DisplayUser,
props: z.object({ id: z.string() }).strict(),
},
'users/by-id': {
method: 'GET',
visibility: 'public',
authed: false,
cache: DEFAULT_CACHE_STRATEGY,
returns: [] as DisplayUser[],
props: z.object({ ids: z.array(z.string()) }).strict(),
},
'user/by-id/:id/block': {
method: 'POST',
visibility: 'public',
Expand Down Expand Up @@ -2029,7 +2037,7 @@ export const API = (_apiTypeCheck = {
// Could set authed false and preferAuth with an api secret if we want it to replace static props
authed: true,
returns: {} as {
contract: Contract
manaContract: Contract
chartAnnotations: ChartAnnotation[]
topics: Topic[]
comments: ContractComment[]
Expand All @@ -2041,7 +2049,9 @@ export const API = (_apiTypeCheck = {
topContractMetrics: ContractMetric[]
totalPositions: number
dashboards: Dashboard[]
siblingContract: Contract | undefined
cashContract: Contract
totalManaBets: number
totalCashBets: number
},
props: z.object({
slug: z.string().optional(),
Expand Down
2 changes: 1 addition & 1 deletion knowledge.md
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ export const getContractIdFromSlug = async (
We have many useful hooks that should be reused rather than rewriting them again. For example, to get the live global bets, you should use
```ts
import { useSubscribeGlobalBets } from 'web/hooks/use-bets'
import { useSubscribeGlobalBets } from 'client-common/hooks/use-bets'

...

Expand Down
Loading

0 comments on commit e91b4b0

Please sign in to comment.