Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trading ban #2853

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions backend/api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ import {
} from './get-dashboard-from-slug'
import { unresolve } from './unresolve'
import { referuser } from 'api/refer-user'
import { banuser } from 'api/ban-user'
import { banUserFromPosting } from 'api/ban-user-from-posting'
import { banUserFromTrading } from './ban-user-from-trading'
import { updateMarket } from 'api/update-market'
import { createprivateusermessage } from 'api/create-private-user-message'
import { createprivateusermessagechannel } from 'api/create-private-user-message-channel'
Expand Down Expand Up @@ -516,7 +517,8 @@ app.post('/updatedashboard', ...apiRoute(updatedashboard))
app.post('/delete-dashboard', ...apiRoute(deletedashboard))
app.get('/get-news-dashboards', ...apiRoute(getnews))
app.post('/getdashboardfromslug', ...apiRoute(getdashboardfromslug))
app.post('/ban-user', ...apiRoute(banuser))
app.post('/ban-user-from-posting', ...apiRoute(banUserFromPosting))
app.post('/ban-user-from-trading', ...apiRoute(banUserFromTrading))
app.post('/create-private-user-message', ...apiRoute(createprivateusermessage))
app.post(
'/create-private-user-message-channel',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const bodySchema = z
})
.strict()

export const banuser = authEndpoint(async (req, auth) => {
export const banUserFromPosting = authEndpoint(async (req, auth) => {
const { userId, unban } = validate(bodySchema, req.body)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

best to name properties in the positive valence, like ban rather than unban

const db = createSupabaseDirectClient()
SirSaltyy marked this conversation as resolved.
Show resolved Hide resolved
await throwErrorIfNotMod(auth.uid)
Expand Down
30 changes: 30 additions & 0 deletions backend/api/src/ban-user-from-trading.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { APIError, authEndpoint, validate } from 'api/helpers/endpoint'
import { z } from 'zod'
import { trackPublicEvent } from 'shared/analytics'
import { throwErrorIfNotMod } from 'shared/helpers/auth'
import { isAdminId } from 'common/envs/constants'
import { log } from 'shared/utils'
import { createSupabaseDirectClient } from 'shared/supabase/init'
import { updateUser } from 'shared/supabase/users'

const bodySchema = z
.object({
userId: z.string(),
unban: z.boolean().optional(),
})
.strict()

export const banUserFromTrading = authEndpoint(async (req, auth) => {
const { userId, unban } = validate(bodySchema, req.body)
const db = createSupabaseDirectClient()
await throwErrorIfNotMod(auth.uid)
if (isAdminId(userId)) throw new APIError(403, 'Cannot ban admin')
await trackPublicEvent(auth.uid, 'ban user from trading', {
userId,
})
await updateUser(db, userId, {
isBannedFromTrading: !unban,
})
log(`Updated trading ban status for user ${userId}`)
return { success: true }
})
1 change: 0 additions & 1 deletion backend/api/src/delete-me.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export const deleteMe: APIHandler<'me/delete'> = async (body, auth) => {
const pg = createSupabaseDirectClient()
await updateUser(pg, auth.uid, {
userDeleted: true,
isBannedFromPosting: true,
})
await updatePrivateUser(pg, auth.uid, {
email: FieldVal.delete(),
Expand Down
3 changes: 2 additions & 1 deletion backend/api/src/get-mod-reports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export const getModReports: APIHandler<'get-mod-reports'> = async () => {
owner.username as owner_username,
owner.data->>'avatarUrl' as owner_avatar_url,
owner.name as owner_name,
(owner.data->>'isBannedFromPosting')::boolean as owner_is_banned_from_posting
(owner.data->>'isBannedFromPosting')::boolean as owner_is_banned_from_posting,
(owner.data->>'isBannedFromTrading')::boolean as owner_is_banned_from_trading
from mod_reports mr
join contract_comments cc on cc.comment_id = mr.comment_id
join contracts c on c.id = mr.contract_id
Expand Down
12 changes: 9 additions & 3 deletions backend/api/src/get-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,15 @@ export const getLiteUser = async (
) => {
const pg = createSupabaseDirectClient()
const liteUser = await pg.oneOrNone(
`select id, name, username, data->>'avatarUrl' as "avatarUrl", data->'isBannedFromPosting' as "isBannedFromPosting"
from users
where ${'id' in props ? 'id' : 'username'} = $1`,
`select
id,
name,
username,
data->>'avatarUrl' as "avatarUrl",
(data->>'isBannedFromPosting')::boolean as "isBannedFromPosting",
(data->>'isBannedFromTrading')::boolean as "isBannedFromTrading"
from users
where ${'id' in props ? 'id' : 'username'} = $1`,
['id' in props ? props.id : props.username]
)
if (!liteUser) throw new APIError(404, 'User not found')
Expand Down
10 changes: 7 additions & 3 deletions backend/api/src/place-bet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { Answer } from 'common/answer'
import { CpmmState, getCpmmProbability } from 'common/calculate-cpmm'
import { ValidatedAPIParams } from 'common/api/schema'
import { onCreateBets } from 'api/on-create-bet'
import { BANNED_TRADING_USER_IDS } from 'common/envs/constants'
import { isAdminId } from 'common/envs/constants'
import * as crypto from 'crypto'
import { formatMoneyWithDecimals } from 'common/util/format'
import {
Expand Down Expand Up @@ -654,6 +654,10 @@ export const validateBet = async (
const user = await getUser(uid, pgTrans)
if (!user) throw new APIError(404, 'User not found.')

if (isAdminId(uid) && contract.token === 'CASH') {
throw new APIError(403, 'Admins cannot trade on sweepstakes markets.')
}

const balance = contract.token === 'CASH' ? user.cashBalance : user.balance
if (amount !== undefined && balance < amount)
throw new APIError(403, 'Insufficient balance.')
Expand All @@ -666,8 +670,8 @@ export const validateBet = async (
'You must be kyc verified to trade on sweepstakes markets.'
)
}
if (BANNED_TRADING_USER_IDS.includes(uid) || user.userDeleted) {
throw new APIError(403, 'You are banned or deleted. And not #blessed.')
if (user.isBannedFromTrading || user.userDeleted) {
throw new APIError(403, 'You are banned or deleted.')
}
// if (!isVerified(user)) {
// throw new APIError(403, 'You must verify your phone number to bet.')
Expand Down
1 change: 1 addition & 0 deletions common/src/api/user-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type DisplayUser = {
username: string
avatarUrl: string
isBannedFromPosting?: boolean
isBannedFromTrading?: boolean
}

export type FullUser = User & {
Expand Down
7 changes: 0 additions & 7 deletions common/src/envs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,13 +265,6 @@ export const VERIFIED_USERNAMES = [
'DanHendrycks',
]

export const BANNED_TRADING_USER_IDS = [
'zgCIqq8AmRUYVu6AdQ9vVEJN8On1', //firstuserhere aka _deleted_
'LIBAoi7tpqeNLYM1xxJ1QJBQqW32', //lastuserhere
'p3ADzwIUS3fk0ka80XYEE3OM3S32', //PC
'4JuXgDx47xPagH5mcLDqLzUSN5g2', // BTE
]

export const PARTNER_USER_IDS: string[] = [
'sTUV8ejuM2byukNZp7qKP2OKXMx2', // NFL
'rFJu0EIdR6RP8d1vHKSh62pbnbH2', // SimonGrayson
Expand Down
1 change: 1 addition & 0 deletions common/src/mod-report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ export type ModReport = {
owner_username: string
owner_avatar_url: string
owner_is_banned_from_posting: boolean
owner_is_banned_from_trading: boolean
owner_name: string
}
1 change: 1 addition & 0 deletions common/src/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export type User = {
hasSeenLoanModal?: boolean
hasSeenContractFollowModal?: boolean
isBannedFromPosting?: boolean
isBannedFromTrading?: boolean
userDeleted?: boolean
optOutBetWarnings?: boolean
freeQuestionsCreated?: number
Expand Down
1 change: 1 addition & 0 deletions twitch-bot/common/types/manifold-api-types.ts
SirSaltyy marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export type LiteUser = {
isTrustworthy: boolean;

isBannedFromPosting?: boolean;
isBannedFromTrading?: boolean;
userDeleted?: boolean;

followerCountCached: number;
Expand Down
11 changes: 4 additions & 7 deletions web/components/auth-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ import { createUser } from 'web/lib/api/api'
import { randomString } from 'common/util/random'
import { identifyUser, setUserProperty } from 'web/lib/service/analytics'
import { useStateCheckEquality } from 'web/hooks/use-state-check-equality'
import {
AUTH_COOKIE_NAME,
BANNED_TRADING_USER_IDS,
TEN_YEARS_SECS,
} from 'common/envs/constants'
import { AUTH_COOKIE_NAME, TEN_YEARS_SECS } from 'common/envs/constants'
import { getCookie, setCookie } from 'web/lib/util/cookie'
import {
type PrivateUser,
Expand Down Expand Up @@ -114,12 +110,13 @@ export function AuthProvider(props: {
useEffect(() => {
if (authUser) {
if (
BANNED_TRADING_USER_IDS.includes(authUser.user.id) ||
(authUser.user.isBannedFromPosting &&
authUser.user.isBannedFromTrading) ||
authUser.user.userDeleted
) {
const message = authUser.user.userDeleted
? 'You have deleted the account associated with this email. To restore your account please email [email protected]'
: 'You are banned from trading. To learn more please email [email protected]'
: 'You are banned from Manifold. To learn more please email [email protected]'

firebaseLogout().then(() => {
alert(message)
Expand Down
23 changes: 20 additions & 3 deletions web/components/buttons/user-settings-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
Referrals,
useReferralCount,
} from 'web/components/buttons/referrals-button'
import { banUser } from 'web/lib/api/api'
import { banUserFromPosting, banUserFromTrading } from 'web/lib/api/api'
import SuperBanControl from '../SuperBanControl'
import { buildArray } from 'common/util/array'
import { AccountSettings } from '../profile/settings'
Expand Down Expand Up @@ -86,13 +86,30 @@ export function UserSettingButton(props: { user: User }) {
color={'red'}
size="xs"
onClick={() => {
banUser({
banUserFromPosting({
userId,
unban: user.isBannedFromPosting ?? false,
})
}}
>
{user.isBannedFromPosting ? 'Banned' : 'Ban User'}
{user.isBannedFromPosting
? 'Banned (posting)'
: 'Ban Posting'}
</Button>

<Button
color={'red'}
size="xs"
onClick={() => {
banUserFromTrading({
userId,
unban: user.isBannedFromTrading ?? false,
})
}}
>
{user.isBannedFromTrading
? 'Banned (trading)'
: 'Ban Trading'}
</Button>
</Row>
)}
Expand Down
11 changes: 10 additions & 1 deletion web/components/mod-report-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,17 @@ const ModReportItem: React.FC<ReportItemProps> = ({
avatarUrl={report.owner_avatar_url || ''}
size="sm"
/>

<UserLink user={owner} className="text-ink-800 ml-2" />
{report.owner_is_banned_from_posting && <BannedBadge />}
{(report.owner_is_banned_from_posting ||
report.owner_is_banned_from_trading) && (
<span className="ml-1.5">
<BannedBadge
isBannedFromPosting={report.owner_is_banned_from_posting}
isBannedFromTrading={report.owner_is_banned_from_trading}
/>
</span>
)}
</div>
</UserHovercard>
<Row className="ml-2">commented:</Row>
Expand Down
7 changes: 6 additions & 1 deletion web/components/profile/blocked-user.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ export function BlockedUser(props: { user: User; privateUser: PrivateUser }) {
{user.name}
{' (Blocked) '}
{<UserBadge userId={user.id} username={user.username} />}
{user.isBannedFromPosting && <BannedBadge />}
<span className="ml-1.5">
<BannedBadge
isBannedFromPosting={user.isBannedFromPosting ?? false}
isBannedFromTrading={user.isBannedFromTrading ?? false}
/>
</span>
</span>
<Row className="sm:text-md items-center gap-x-3 text-sm ">
<span className={' text-ink-400'}>@{user.username}</span>
Expand Down
46 changes: 38 additions & 8 deletions web/components/widgets/user-link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,39 @@ function BotBadge() {
)
}

export function BannedBadge() {
export function BannedBadge({
isBannedFromPosting,
isBannedFromTrading,
className,
}: {
isBannedFromPosting: boolean
isBannedFromTrading: boolean
className?: string
}) {
let badgeText = ''
let tooltipText = ''

if (isBannedFromPosting && isBannedFromTrading) {
badgeText = 'Banned from posting & trading'
tooltipText = "Can't create comments, messages, questions, or trade"
} else if (isBannedFromPosting) {
badgeText = 'Banned from posting'
tooltipText = "Can't create comments, messages, or questions"
} else if (isBannedFromTrading) {
badgeText = 'Banned from trading'
tooltipText = "Can't participate in trading"
}

if (!badgeText) return null

return (
<Tooltip
text="Can't create comments, messages, or questions"
placement="bottom"
>
<span className="ml-1.5 rounded-full bg-yellow-100 px-2.5 py-0.5 text-xs font-medium text-yellow-800 dark:bg-yellow-700 dark:text-yellow-100">
Banned
<Tooltip text={tooltipText} placement="bottom">
<span
className={
'rounded-full bg-yellow-100 px-2.5 py-0.5 text-xs font-medium text-yellow-800 dark:bg-yellow-700 dark:text-yellow-100'
}
>
{badgeText}
</span>
</Tooltip>
)
Expand Down Expand Up @@ -260,6 +285,7 @@ export const StackedUserNames = (props: {
username: string
createdTime: number
isBannedFromPosting?: boolean
isBannedFromTrading?: boolean
}
followsYou?: boolean
className?: string
Expand All @@ -277,8 +303,12 @@ export const StackedUserNames = (props: {
fresh={isFresh(user.createdTime)}
/>
}
{user.isBannedFromPosting && <BannedBadge />}
<BannedBadge
isBannedFromPosting={user.isBannedFromPosting ?? false}
isBannedFromTrading={user.isBannedFromTrading ?? false}
/>
</div>

<Row className={'flex-shrink flex-wrap gap-x-2'}>
<span className={clsx('text-ink-400 text-sm', usernameClassName)}>
@{user.username}{' '}
Expand Down
14 changes: 12 additions & 2 deletions web/lib/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,15 +334,25 @@ export const updateMarket = curriedAPI('market/:contractId/update')

export const updateUser = curriedAPI('me/update')

export function banUser(params: { userId: string; unban?: boolean }) {
return call(getApiUrl('ban-user'), 'POST', params)
export function banUserFromPosting(params: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't how we create api endpoints anymore, but I guess this is okay. In the future you should use the schema.ts

userId: string
unban?: boolean
}) {
return call(getApiUrl('ban-user-from-posting'), 'POST', params)
}
export function createPrivateMessageChannelWithUsers(params: {
userIds: string[]
}) {
return call(getApiUrl('create-private-user-message-channel'), 'POST', params)
}

export function banUserFromTrading(params: {
userId: string
unban?: boolean
}) {
return call(getApiUrl('ban-user-from-trading'), 'POST', params)
}

export function sendUserPrivateMessage(params: {
channelId: number
content: JSONContent
Expand Down
4 changes: 2 additions & 2 deletions web/lib/supabase/super-ban-user.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { api, banUser } from 'web/lib/api/api'
import { api, banUserFromPosting } from 'web/lib/api/api'

async function superBanUser(userId: string) {
let marketsStatus = "could not be unlisted nor N/A'd due to an unknown error"
let commentsStatus = 'could not be hidden due to an unknown error'

try {
await banUser({ userId })
await banUserFromPosting({ userId })
await api('unlist-and-cancel-user-contracts', { userId })
marketsStatus = "successfully unlisted & NA'd"
} catch (error) {
Expand Down
Loading