raise boost minimum to 25k, enhance editing boost, JIT fund editing costs

This commit is contained in:
keyan 2023-09-25 19:54:35 -05:00
parent 374cc26224
commit 370e3c1c48
11 changed files with 95 additions and 60 deletions

View File

@ -12,7 +12,7 @@ import {
import { msatsToSats, numWithUnits } from '../../lib/format' import { msatsToSats, numWithUnits } from '../../lib/format'
import { parse } from 'tldts' import { parse } from 'tldts'
import uu from 'url-unshort' import uu from 'url-unshort'
import { amountSchema, bountySchema, commentSchema, discussionSchema, jobSchema, linkSchema, pollSchema, ssValidate } from '../../lib/validate' import { advSchema, amountSchema, bountySchema, commentSchema, discussionSchema, jobSchema, linkSchema, pollSchema, ssValidate } from '../../lib/validate'
import { sendUserNotification } from '../webPush' import { sendUserNotification } from '../webPush'
import { proxyImages } from './imgproxy' import { proxyImages } from './imgproxy'
import { defaultCommentSort } from '../../lib/item' import { defaultCommentSort } from '../../lib/item'
@ -624,34 +624,34 @@ export default {
return await models.item.update({ where: { id: Number(id) }, data }) return await models.item.update({ where: { id: Number(id) }, data })
}, },
upsertLink: async (parent, { id, hash, hmac, ...item }, { me, models, lnd }) => { upsertLink: async (parent, { id, hash, hmac, ...item }, { me, models, lnd }) => {
await ssValidate(linkSchema, item, models, me) await ssValidate(linkSchema, item, { models, me })
if (id) { if (id) {
return await updateItem(parent, { id, ...item }, { me, models }) return await updateItem(parent, { id, ...item }, { me, models, lnd, hash, hmac })
} else { } else {
return await createItem(parent, item, { me, models, lnd, hash, hmac }) return await createItem(parent, item, { me, models, lnd, hash, hmac })
} }
}, },
upsertDiscussion: async (parent, { id, hash, hmac, ...item }, { me, models, lnd }) => { upsertDiscussion: async (parent, { id, hash, hmac, ...item }, { me, models, lnd }) => {
await ssValidate(discussionSchema, item, models, me) await ssValidate(discussionSchema, item, { models, me })
if (id) { if (id) {
return await updateItem(parent, { id, ...item }, { me, models }) return await updateItem(parent, { id, ...item }, { me, models, lnd, hash, hmac })
} else { } else {
return await createItem(parent, item, { me, models, lnd, hash, hmac }) return await createItem(parent, item, { me, models, lnd, hash, hmac })
} }
}, },
upsertBounty: async (parent, { id, hash, hmac, ...item }, { me, models, lnd }) => { upsertBounty: async (parent, { id, hash, hmac, ...item }, { me, models, lnd }) => {
await ssValidate(bountySchema, item, models, me) await ssValidate(bountySchema, item, { models, me })
if (id) { if (id) {
return await updateItem(parent, { id, ...item }, { me, models }) return await updateItem(parent, { id, ...item }, { me, models, lnd, hash, hmac })
} else { } else {
return await createItem(parent, item, { me, models, lnd, hash, hmac }) return await createItem(parent, item, { me, models, lnd, hash, hmac })
} }
}, },
upsertPoll: async (parent, { id, hash, hmac, ...item }, { me, models, lnd }) => { upsertPoll: async (parent, { id, hash, hmac, ...item }, { me, models, lnd }) => {
const optionCount = id const numExistingChoices = id
? await models.pollOption.count({ ? await models.pollOption.count({
where: { where: {
itemId: Number(id) itemId: Number(id)
@ -659,10 +659,10 @@ export default {
}) })
: 0 : 0
await ssValidate(pollSchema, item, models, me, optionCount) await ssValidate(pollSchema, item, { models, me, numExistingChoices })
if (id) { if (id) {
return await updateItem(parent, { id, ...item }, { me, models }) return await updateItem(parent, { id, ...item }, { me, models, lnd, hash, hmac })
} else { } else {
item.pollCost = item.pollCost || POLL_COST item.pollCost = item.pollCost || POLL_COST
return await createItem(parent, item, { me, models, lnd, hash, hmac }) return await createItem(parent, item, { me, models, lnd, hash, hmac })
@ -674,7 +674,7 @@ export default {
} }
item.location = item.location?.toLowerCase() === 'remote' ? undefined : item.location item.location = item.location?.toLowerCase() === 'remote' ? undefined : item.location
await ssValidate(jobSchema, item, models) await ssValidate(jobSchema, item, { models })
if (item.logo !== undefined) { if (item.logo !== undefined) {
item.uploadId = item.logo item.uploadId = item.logo
delete item.logo delete item.logo
@ -1119,13 +1119,15 @@ export const createMentions = async (item, models) => {
} }
} }
export const updateItem = async (parent, { sub: subName, forward, options, ...item }, { me, models }) => { export const updateItem = async (parent, { sub: subName, forward, options, ...item }, { me, models, lnd, hash, hmac }) => {
// update iff this item belongs to me // update iff this item belongs to me
const old = await models.item.findUnique({ where: { id: Number(item.id) } }) const old = await models.item.findUnique({ where: { id: Number(item.id) } })
if (Number(old.userId) !== Number(me?.id)) { if (Number(old.userId) !== Number(me?.id)) {
throw new GraphQLError('item does not belong to you', { extensions: { code: 'FORBIDDEN' } }) throw new GraphQLError('item does not belong to you', { extensions: { code: 'FORBIDDEN' } })
} }
await ssValidate(advSchema, { boost: item.boost }, { models, me, existingBoost: old.boost })
// if it's not the FAQ, not their bio, and older than 10 minutes // if it's not the FAQ, not their bio, and older than 10 minutes
const user = await models.user.findUnique({ where: { id: me.id } }) const user = await models.user.findUnique({ where: { id: me.id } })
if (![349, 76894, 78763, 81862].includes(old.id) && user.bioId !== old.id && if (![349, 76894, 78763, 81862].includes(old.id) && user.bioId !== old.id &&
@ -1141,18 +1143,43 @@ export const updateItem = async (parent, { sub: subName, forward, options, ...it
item.url = removeTracking(item.url) item.url = removeTracking(item.url)
item.url = await proxyImages(item.url) item.url = await proxyImages(item.url)
} }
// only update item with the boost delta ... this is a bit of hack given the way
// boost used to work
if (item.boost > 0 && old.boost > 0) {
// only update the boost if it is higher than the old boost
if (item.boost > old.boost) {
item.boost = item.boost - old.boost
} else {
delete item.boost
}
}
item = { subName, userId: me.id, ...item } item = { subName, userId: me.id, ...item }
const fwdUsers = await getForwardUsers(models, forward) const fwdUsers = await getForwardUsers(models, forward)
const [rItem] = await serialize(models, let invoice
if (hash) {
invoice = await checkInvoice(models, hash, hmac)
}
const trx = [
models.$queryRawUnsafe(`${SELECT} FROM update_item($1::JSONB, $2::JSONB, $3::JSONB) AS "Item"`, models.$queryRawUnsafe(`${SELECT} FROM update_item($1::JSONB, $2::JSONB, $3::JSONB) AS "Item"`,
JSON.stringify(item), JSON.stringify(fwdUsers), JSON.stringify(options))) JSON.stringify(item), JSON.stringify(fwdUsers), JSON.stringify(options))
]
if (invoice) {
trx.unshift(models.$queryRaw`UPDATE users SET msats = msats + ${invoice.msatsReceived} WHERE id = ${invoice.user.id}`)
trx.push(models.invoice.update({ where: { hash: invoice.hash }, data: { confirmedAt: new Date() } }))
}
const query = await serialize(models, ...trx)
const rItem = trx.length > 1 ? query[1][0] : query[0]
if (invoice?.isHeld) await settleHodlInvoice({ secret: invoice.preimage, lnd })
await createMentions(rItem, models) await createMentions(rItem, models)
item.comments = [] rItem.comments = []
return item return rItem
} }
export const createItem = async (parent, { forward, options, ...item }, { me, models, lnd, hash, hmac }) => { export const createItem = async (parent, { forward, options, ...item }, { me, models, lnd, hash, hmac }) => {

View File

@ -484,7 +484,7 @@ export default {
throw new GraphQLError('you must be logged in', { extensions: { code: 'UNAUTHENTICATED' } }) throw new GraphQLError('you must be logged in', { extensions: { code: 'UNAUTHENTICATED' } })
} }
await ssValidate(userSchema, data, models) await ssValidate(userSchema, data, { models })
try { try {
await models.user.update({ where: { id: me.id }, data }) await models.user.update({ where: { id: me.id }, data })

View File

@ -1,21 +1,21 @@
import AccordianItem from './accordian-item' import AccordianItem from './accordian-item'
import { Input, InputUserSuggest, VariableInput } from './form' import { Input, InputUserSuggest, VariableInput } from './form'
import InputGroup from 'react-bootstrap/InputGroup' import InputGroup from 'react-bootstrap/InputGroup'
import { BOOST_MIN, MAX_FORWARDS } from '../lib/constants' import { BOOST_MIN, BOOST_MULT, MAX_FORWARDS } from '../lib/constants'
import Info from './info' import Info from './info'
import { numWithUnits } from '../lib/format' import { numWithUnits } from '../lib/format'
import styles from './adv-post-form.module.css' import styles from './adv-post-form.module.css'
const EMPTY_FORWARD = { nym: '', pct: '' } const EMPTY_FORWARD = { nym: '', pct: '' }
export function AdvPostInitial ({ forward }) { export function AdvPostInitial ({ forward, boost }) {
return { return {
boost: '', boost: boost || '',
forward: forward?.length ? forward : [EMPTY_FORWARD] forward: forward?.length ? forward : [EMPTY_FORWARD]
} }
} }
export default function AdvPostForm ({ edit }) { export default function AdvPostForm () {
return ( return (
<AccordianItem <AccordianItem
header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>options</div>} header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>options</div>}
@ -23,17 +23,17 @@ export default function AdvPostForm ({ edit }) {
<> <>
<Input <Input
label={ label={
<div className='d-flex align-items-center'>{edit ? 'add boost' : 'boost'} <div className='d-flex align-items-center'>boost
<Info> <Info>
<ol className='fw-bold'> <ol className='fw-bold'>
<li>Boost ranks posts higher temporarily based on the amount</li> <li>Boost ranks posts higher temporarily based on the amount</li>
<li>The minimum boost is {numWithUnits(BOOST_MIN, { abbreviate: false })}</li> <li>The minimum boost is {numWithUnits(BOOST_MIN, { abbreviate: false })}</li>
<li>Each {numWithUnits(BOOST_MIN, { abbreviate: false })} of boost is equivalent to one trusted upvote <li>Each {numWithUnits(BOOST_MULT, { abbreviate: false })} of boost is equivalent to one trusted upvote
<ul> <ul>
<li>e.g. {numWithUnits(BOOST_MIN * 2, { abbreviate: false })} is like 2 votes</li> <li>e.g. {numWithUnits(BOOST_MULT * 5, { abbreviate: false })} is like 5 votes</li>
</ul> </ul>
</li> </li>
<li>The decay of boost "votes" increases at 2x the rate of organic votes <li>The decay of boost "votes" increases at 1.25x the rate of organic votes
<ul> <ul>
<li>i.e. boost votes fall out of ranking faster</li> <li>i.e. boost votes fall out of ranking faster</li>
</ul> </ul>

View File

@ -27,7 +27,7 @@ export function BountyForm ({
const router = useRouter() const router = useRouter()
const client = useApolloClient() const client = useApolloClient()
const me = useMe() const me = useMe()
const schema = bountySchema(client, me) const schema = bountySchema({ client, me, existingBoost: item?.boost })
const [upsertBounty] = useMutation( const [upsertBounty] = useMutation(
gql` gql`
mutation upsertBounty( mutation upsertBounty(
@ -89,7 +89,7 @@ export function BountyForm ({
title: item?.title || '', title: item?.title || '',
text: item?.text || '', text: item?.text || '',
bounty: item?.bounty || 1000, bounty: item?.bounty || 1000,
...AdvPostInitial({ forward: normalizeForwards(item?.forwards) }), ...AdvPostInitial({ forward: normalizeForwards(item?.forwards), boost: item?.boost }),
...SubSelectInitial({ sub: item?.subName || sub?.name }) ...SubSelectInitial({ sub: item?.subName || sub?.name })
}} }}
schema={schema} schema={schema}

View File

@ -25,7 +25,7 @@ export function DiscussionForm ({
const router = useRouter() const router = useRouter()
const client = useApolloClient() const client = useApolloClient()
const me = useMe() const me = useMe()
const schema = discussionSchema(client, me) const schema = discussionSchema({ client, me, existingBoost: item?.boost })
// if Web Share Target API was used // if Web Share Target API was used
const shareTitle = router.query.title const shareTitle = router.query.title
@ -81,7 +81,7 @@ export function DiscussionForm ({
initial={{ initial={{
title: item?.title || shareTitle || '', title: item?.title || shareTitle || '',
text: item?.text || '', text: item?.text || '',
...AdvPostInitial({ forward: normalizeForwards(item?.forwards) }), ...AdvPostInitial({ forward: normalizeForwards(item?.forwards), boost: item?.boost }),
...SubSelectInitial({ sub: item?.subName || sub?.name }) ...SubSelectInitial({ sub: item?.subName || sub?.name })
}} }}
schema={schema} schema={schema}

View File

@ -137,7 +137,7 @@ function EditReceipt ({ cost, paidSats, addImgLink, boost, parentId }) {
export function EditFeeButton ({ paidSats, hadImgLink, hasImgLink, ChildButton, variant, text, alwaysShow, parentId }) { export function EditFeeButton ({ paidSats, hadImgLink, hasImgLink, ChildButton, variant, text, alwaysShow, parentId }) {
const formik = useFormikContext() const formik = useFormikContext()
const boost = formik?.values?.boost || 0 const boost = (formik?.values?.boost || 0) - (formik?.initialValues?.boost || 0)
const addImgLink = hasImgLink && !hadImgLink const addImgLink = hasImgLink && !hadImgLink
const cost = (addImgLink ? paidSats * 9 : 0) + Number(boost) const cost = (addImgLink ? paidSats * 9 : 0) + Number(boost)
@ -148,7 +148,7 @@ export function EditFeeButton ({ paidSats, hadImgLink, hasImgLink, ChildButton,
const show = alwaysShow || !formik?.isSubmitting const show = alwaysShow || !formik?.isSubmitting
return ( return (
<div className='d-flex align-items-center'> <div className='d-flex align-items-center'>
<ActionTooltip overlayText={numWithUnits(cost, { abbreviate: false })}> <ActionTooltip overlayText={numWithUnits(cost >= 0 ? cost : 0, { abbreviate: false })}>
<ChildButton variant={variant}>{text}{cost > 0 && show && <small> {numWithUnits(cost, { abbreviate: false })}</small>}</ChildButton> <ChildButton variant={variant}>{text}{cost > 0 && show && <small> {numWithUnits(cost, { abbreviate: false })}</small>}</ChildButton>
</ActionTooltip> </ActionTooltip>
{cost > 0 && show && {cost > 0 && show &&

View File

@ -22,7 +22,7 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
const router = useRouter() const router = useRouter()
const client = useApolloClient() const client = useApolloClient()
const me = useMe() const me = useMe()
const schema = linkSchema(client, me) const schema = linkSchema({ client, me, existingBoost: item?.boost })
// if Web Share Target API was used // if Web Share Target API was used
const shareUrl = router.query.url const shareUrl = router.query.url
const shareTitle = router.query.title const shareTitle = router.query.title
@ -123,7 +123,7 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
initial={{ initial={{
title: item?.title || shareTitle || '', title: item?.title || shareTitle || '',
url: item?.url || shareUrl || '', url: item?.url || shareUrl || '',
...AdvPostInitial({ forward: normalizeForwards(item?.forwards) }), ...AdvPostInitial({ forward: normalizeForwards(item?.forwards), boost: item?.boost }),
...SubSelectInitial({ sub: item?.subName || sub?.name }) ...SubSelectInitial({ sub: item?.subName || sub?.name })
}} }}
schema={schema} schema={schema}

View File

@ -18,7 +18,7 @@ export function PollForm ({ item, sub, editThreshold, children }) {
const router = useRouter() const router = useRouter()
const client = useApolloClient() const client = useApolloClient()
const me = useMe() const me = useMe()
const schema = pollSchema(client, me) const schema = pollSchema({ client, me, existingBoost: item?.boost })
const [upsertPoll] = useMutation( const [upsertPoll] = useMutation(
gql` gql`
@ -65,7 +65,7 @@ export function PollForm ({ item, sub, editThreshold, children }) {
title: item?.title || '', title: item?.title || '',
text: item?.text || '', text: item?.text || '',
options: initialOptions || ['', ''], options: initialOptions || ['', ''],
...AdvPostInitial({ forward: normalizeForwards(item?.forwards) }), ...AdvPostInitial({ forward: normalizeForwards(item?.forwards), boost: item?.boost }),
...SubSelectInitial({ sub: item?.subName || sub?.name }) ...SubSelectInitial({ sub: item?.subName || sub?.name })
}} }}
schema={schema} schema={schema}

View File

@ -105,7 +105,7 @@ function NymEdit ({ user, setEditting }) {
} }
}) })
const client = useApolloClient() const client = useApolloClient()
const schema = userSchema(client) const schema = userSchema({ client })
return ( return (
<Form <Form

View File

@ -4,7 +4,8 @@ export const SUBS = ['bitcoin', 'nostr', 'tech', 'meta', 'jobs']
export const SUBS_NO_JOBS = SUBS.filter(s => s !== 'jobs') export const SUBS_NO_JOBS = SUBS.filter(s => s !== 'jobs')
export const NOFOLLOW_LIMIT = 100 export const NOFOLLOW_LIMIT = 100
export const BOOST_MIN = 5000 export const BOOST_MULT = 5000
export const BOOST_MIN = BOOST_MULT * 5
export const UPLOAD_SIZE_MAX = 2 * 1024 * 1024 export const UPLOAD_SIZE_MAX = 2 * 1024 * 1024
export const IMAGE_PIXELS_MAX = 35000000 export const IMAGE_PIXELS_MAX = 35000000
export const UPLOAD_TYPES_ALLOW = [ export const UPLOAD_TYPES_ALLOW = [

View File

@ -1,14 +1,14 @@
import { string, ValidationError, number, object, array, addMethod, boolean } from 'yup' import { string, ValidationError, number, object, array, addMethod, boolean } from 'yup'
import { BOOST_MIN, MAX_POLL_CHOICE_LENGTH, MAX_TITLE_LENGTH, MAX_POLL_NUM_CHOICES, MIN_POLL_NUM_CHOICES, SUBS_NO_JOBS, MAX_FORWARDS } from './constants' import { BOOST_MIN, MAX_POLL_CHOICE_LENGTH, MAX_TITLE_LENGTH, MAX_POLL_NUM_CHOICES, MIN_POLL_NUM_CHOICES, SUBS_NO_JOBS, MAX_FORWARDS, BOOST_MULT } from './constants'
import { NAME_QUERY } from '../fragments/users' import { NAME_QUERY } from '../fragments/users'
import { URL_REGEXP, WS_REGEXP } from './url' import { URL_REGEXP, WS_REGEXP } from './url'
import { SUPPORTED_CURRENCIES } from './currency' import { SUPPORTED_CURRENCIES } from './currency'
import { NOSTR_MAX_RELAY_NUM, NOSTR_PUBKEY_BECH32, NOSTR_PUBKEY_HEX } from './nostr' import { NOSTR_MAX_RELAY_NUM, NOSTR_PUBKEY_BECH32, NOSTR_PUBKEY_HEX } from './nostr'
export async function ssValidate (schema, data, ...args) { export async function ssValidate (schema, data, args) {
try { try {
if (typeof schema === 'function') { if (typeof schema === 'function') {
await schema(...args).validate(data) await schema(args).validate(data)
} else { } else {
await schema.validate(data) await schema.validate(data)
} }
@ -43,28 +43,29 @@ const titleValidator = string().required('required').trim().max(
const intValidator = number().typeError('must be a number').integer('must be whole') const intValidator = number().typeError('must be a number').integer('must be whole')
async function usernameExists (client, name) { async function usernameExists (name, { client, models }) {
if (!client) { if (!client && !models) {
throw new Error('cannot check for user') throw new Error('cannot check for user')
} }
// apollo client // apollo client
if (client.query) { if (client) {
const { data } = await client.query({ query: NAME_QUERY, variables: { name } }) const { data } = await client.query({ query: NAME_QUERY, variables: { name } })
return !data.nameAvailable return !data.nameAvailable
} }
// prisma client // prisma client
const user = await client.user.findUnique({ where: { name } }) const user = await models.user.findUnique({ where: { name } })
return !!user return !!user
} }
export function advPostSchemaMembers (client, me) { export function advPostSchemaMembers ({ me, existingBoost = 0, ...args }) {
const boostMin = existingBoost || BOOST_MIN
return { return {
boost: intValidator boost: intValidator
.min(BOOST_MIN, `must be blank or at least ${BOOST_MIN}`).test({ .min(boostMin, `must be ${existingBoost ? '' : 'blank or '}at least ${boostMin}`).test({
name: 'boost', name: 'boost',
test: async boost => !boost || boost % BOOST_MIN === 0, test: async boost => (!existingBoost && !boost) || boost % BOOST_MULT === 0,
message: `must be divisble be ${BOOST_MIN}` message: `must be divisble be ${BOOST_MULT}`
}), }),
forward: array() forward: array()
.max(MAX_FORWARDS, `you can only configure ${MAX_FORWARDS} forward recipients`) .max(MAX_FORWARDS, `you can only configure ${MAX_FORWARDS} forward recipients`)
@ -74,7 +75,7 @@ export function advPostSchemaMembers (client, me) {
name: 'nym', name: 'nym',
test: async name => { test: async name => {
if (!name || !name.length) return false if (!name || !name.length) return false
return await usernameExists(client, name) return await usernameExists(name, args)
}, },
message: 'stacker does not exist' message: 'stacker does not exist'
}) })
@ -101,41 +102,47 @@ export function advPostSchemaMembers (client, me) {
} }
} }
export function subSelectSchemaMembers (client) { export function subSelectSchemaMembers () {
return { return {
sub: string().required('required').oneOf(SUBS_NO_JOBS, 'required') sub: string().required('required').oneOf(SUBS_NO_JOBS, 'required')
} }
} }
// for testing advPostSchemaMembers in isolation
export function advSchema (args) {
return object({
...advPostSchemaMembers(args)
})
}
export function bountySchema (client, me) { export function bountySchema (args) {
return object({ return object({
title: titleValidator, title: titleValidator,
bounty: intValidator bounty: intValidator
.min(1000, 'must be at least 1000') .min(1000, 'must be at least 1000')
.max(1000000, 'must be at most 1m'), .max(1000000, 'must be at most 1m'),
...advPostSchemaMembers(client, me), ...advPostSchemaMembers(args),
...subSelectSchemaMembers() ...subSelectSchemaMembers()
}) })
} }
export function discussionSchema (client, me) { export function discussionSchema (args) {
return object({ return object({
title: titleValidator, title: titleValidator,
...advPostSchemaMembers(client, me), ...advPostSchemaMembers(args),
...subSelectSchemaMembers() ...subSelectSchemaMembers()
}) })
} }
export function linkSchema (client, me) { export function linkSchema (args) {
return object({ return object({
title: titleValidator, title: titleValidator,
url: string().matches(URL_REGEXP, 'invalid url').required('required'), url: string().matches(URL_REGEXP, 'invalid url').required('required'),
...advPostSchemaMembers(client, me), ...advPostSchemaMembers(args),
...subSelectSchemaMembers() ...subSelectSchemaMembers()
}) })
} }
export function pollSchema (client, me, numExistingChoices = 0) { export function pollSchema ({ numExistingChoices = 0, ...args }) {
return object({ return object({
title: titleValidator, title: titleValidator,
options: array().of( options: array().of(
@ -151,12 +158,12 @@ export function pollSchema (client, me, numExistingChoices = 0) {
message: `at least ${MIN_POLL_NUM_CHOICES} choices required`, message: `at least ${MIN_POLL_NUM_CHOICES} choices required`,
test: arr => arr.length >= MIN_POLL_NUM_CHOICES - numExistingChoices test: arr => arr.length >= MIN_POLL_NUM_CHOICES - numExistingChoices
}), }),
...advPostSchemaMembers(client, me), ...advPostSchemaMembers(args),
...subSelectSchemaMembers() ...subSelectSchemaMembers()
}) })
} }
export function userSchema (client) { export function userSchema (args) {
return object({ return object({
name: string() name: string()
.required('required') .required('required')
@ -166,7 +173,7 @@ export function userSchema (client) {
name: 'name', name: 'name',
test: async name => { test: async name => {
if (!name || !name.length) return false if (!name || !name.length) return false
return !(await usernameExists(client, name)) return !(await usernameExists(name, args))
}, },
message: 'taken' message: 'taken'
}) })