Add anon comments and posts (link, discussion, poll)
This commit is contained in:
parent
5415c6b0f6
commit
74893b09dd
|
@ -7,7 +7,8 @@ import domino from 'domino'
|
|||
import {
|
||||
BOOST_MIN, ITEM_SPAM_INTERVAL,
|
||||
MAX_TITLE_LENGTH, ITEM_FILTER_THRESHOLD,
|
||||
DONT_LIKE_THIS_COST, COMMENT_DEPTH_LIMIT, COMMENT_TYPE_QUERY
|
||||
DONT_LIKE_THIS_COST, COMMENT_DEPTH_LIMIT, COMMENT_TYPE_QUERY,
|
||||
ANON_COMMENT_FEE, ANON_USER_ID, ANON_POST_FEE
|
||||
} from '../../lib/constants'
|
||||
import { msatsToSats } from '../../lib/format'
|
||||
import { parse } from 'tldts'
|
||||
|
@ -571,7 +572,7 @@ export default {
|
|||
if (id) {
|
||||
return await updateItem(parent, { id, data }, { me, models })
|
||||
} else {
|
||||
return await createItem(parent, data, { me, models })
|
||||
return await createItem(parent, data, { me, models, invoiceId: args.invoiceId })
|
||||
}
|
||||
},
|
||||
upsertDiscussion: async (parent, args, { me, models }) => {
|
||||
|
@ -582,7 +583,7 @@ export default {
|
|||
if (id) {
|
||||
return await updateItem(parent, { id, data }, { me, models })
|
||||
} else {
|
||||
return await createItem(parent, data, { me, models })
|
||||
return await createItem(parent, data, { me, models, invoiceId: args.invoiceId })
|
||||
}
|
||||
},
|
||||
upsertBounty: async (parent, args, { me, models }) => {
|
||||
|
@ -597,8 +598,16 @@ export default {
|
|||
}
|
||||
},
|
||||
upsertPoll: async (parent, { id, ...data }, { me, models }) => {
|
||||
const { forward, sub, boost, title, text, options } = data
|
||||
if (!me) {
|
||||
const { sub, forward, boost, title, text, options, invoiceId } = data
|
||||
let author = me
|
||||
const trx = []
|
||||
if (!me && invoiceId) {
|
||||
const invoice = await checkInvoice(models, invoiceId, ANON_POST_FEE)
|
||||
author = invoice.user
|
||||
trx.push(models.invoice.delete({ where: { id: Number(invoiceId) } }))
|
||||
}
|
||||
|
||||
if (!author) {
|
||||
throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } })
|
||||
}
|
||||
|
||||
|
@ -622,7 +631,7 @@ export default {
|
|||
|
||||
if (id) {
|
||||
const old = await models.item.findUnique({ where: { id: Number(id) } })
|
||||
if (Number(old.userId) !== Number(me?.id)) {
|
||||
if (Number(old.userId) !== Number(author.id)) {
|
||||
throw new GraphQLError('item does not belong to you', { extensions: { code: 'FORBIDDEN' } })
|
||||
}
|
||||
const [item] = await serialize(models,
|
||||
|
@ -633,9 +642,10 @@ export default {
|
|||
item.comments = []
|
||||
return item
|
||||
} else {
|
||||
const [item] = await serialize(models,
|
||||
const [query] = await serialize(models,
|
||||
models.$queryRawUnsafe(`${SELECT} FROM create_poll($1, $2, $3, $4::INTEGER, $5::INTEGER, $6::INTEGER, $7, $8::INTEGER, '${ITEM_SPAM_INTERVAL}') AS "Item"`,
|
||||
sub || 'bitcoin', title, text, 1, Number(boost || 0), Number(me.id), options, Number(fwdUser?.id)))
|
||||
sub || 'bitcoin', title, text, 1, Number(boost || 0), Number(author.id), options, Number(fwdUser?.id)), ...trx)
|
||||
const item = trx.length > 0 ? query[0] : query
|
||||
|
||||
await createMentions(item, models)
|
||||
item.comments = []
|
||||
|
@ -679,13 +689,13 @@ export default {
|
|||
},
|
||||
createComment: async (parent, data, { me, models }) => {
|
||||
await ssValidate(commentSchema, data)
|
||||
const item = await createItem(parent, data, { me, models })
|
||||
const item = await createItem(parent, data, { me, models, invoiceId: data.invoiceId })
|
||||
// fetch user to get up-to-date name
|
||||
const user = await models.user.findUnique({ where: { id: me.id } })
|
||||
const user = await models.user.findUnique({ where: { id: me?.id || ANON_USER_ID } })
|
||||
|
||||
const parents = await models.$queryRawUnsafe(
|
||||
'SELECT DISTINCT p."userId" FROM "Item" i JOIN "Item" p ON p.path @> i.path WHERE i.id = $1 and p."userId" <> $2',
|
||||
Number(item.parentId), Number(me.id))
|
||||
Number(item.parentId), Number(user.id))
|
||||
Promise.allSettled(
|
||||
parents.map(({ userId }) => sendUserNotification(userId, {
|
||||
title: `@${user.name} replied to you`,
|
||||
|
@ -1065,8 +1075,16 @@ export const updateItem = async (parent, { id, data: { sub, title, url, text, bo
|
|||
return item
|
||||
}
|
||||
|
||||
const createItem = async (parent, { sub, title, url, text, boost, forward, bounty, parentId }, { me, models }) => {
|
||||
if (!me) {
|
||||
const createItem = async (parent, { sub, title, url, text, boost, forward, bounty, parentId }, { me, models, invoiceId }) => {
|
||||
let author = me
|
||||
const trx = []
|
||||
if (!me && invoiceId) {
|
||||
const invoice = await checkInvoice(models, invoiceId, parentId ? ANON_COMMENT_FEE : ANON_POST_FEE)
|
||||
author = invoice.user
|
||||
trx.push(models.invoice.delete({ where: { id: Number(invoiceId) } }))
|
||||
}
|
||||
|
||||
if (!author) {
|
||||
throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } })
|
||||
}
|
||||
|
||||
|
@ -1089,7 +1107,7 @@ const createItem = async (parent, { sub, title, url, text, boost, forward, bount
|
|||
url = await proxyImages(url)
|
||||
text = await proxyImages(text)
|
||||
|
||||
const [item] = await serialize(
|
||||
const [query] = await serialize(
|
||||
models,
|
||||
models.$queryRawUnsafe(
|
||||
`${SELECT} FROM create_item($1, $2, $3, $4, $5::INTEGER, $6::INTEGER, $7::INTEGER, $8::INTEGER, $9::INTEGER, '${ITEM_SPAM_INTERVAL}') AS "Item"`,
|
||||
|
@ -1100,8 +1118,10 @@ const createItem = async (parent, { sub, title, url, text, boost, forward, bount
|
|||
Number(boost || 0),
|
||||
bounty ? Number(bounty) : null,
|
||||
Number(parentId),
|
||||
Number(me.id),
|
||||
Number(fwdUser?.id)))
|
||||
Number(author.id),
|
||||
Number(fwdUser?.id)),
|
||||
...trx)
|
||||
const item = trx.length > 0 ? query[0] : query
|
||||
|
||||
await createMentions(item, models)
|
||||
|
||||
|
|
|
@ -26,13 +26,13 @@ export default gql`
|
|||
bookmarkItem(id: ID): Item
|
||||
subscribeItem(id: ID): Item
|
||||
deleteItem(id: ID): Item
|
||||
upsertLink(id: ID, sub: String, title: String!, url: String!, boost: Int, forward: String): Item!
|
||||
upsertDiscussion(id: ID, sub: String, title: String!, text: String, boost: Int, forward: String): Item!
|
||||
upsertLink(id: ID, sub: String, title: String!, url: String!, boost: Int, forward: String, invoiceId: ID): Item!
|
||||
upsertDiscussion(id: ID, sub: String, title: String!, text: String, boost: Int, forward: String, invoiceId: ID): Item!
|
||||
upsertBounty(id: ID, sub: String, title: String!, text: String, bounty: Int!, boost: Int, forward: String): Item!
|
||||
upsertJob(id: ID, sub: String!, title: String!, company: String!, location: String, remote: Boolean,
|
||||
text: String!, url: String!, maxBid: Int!, status: String, logo: Int): Item!
|
||||
upsertPoll(id: ID, sub: String, title: String!, text: String, options: [String!]!, boost: Int, forward: String): Item!
|
||||
createComment(text: String!, parentId: ID!): Item!
|
||||
upsertPoll(id: ID, sub: String, title: String!, text: String, options: [String!]!, boost: Int, forward: String, invoiceId: ID): Item!
|
||||
createComment(text: String!, parentId: ID!, invoiceId: ID): Item!
|
||||
updateComment(id: ID!, text: String!): Item!
|
||||
dontLikeThis(id: ID!): Boolean!
|
||||
act(id: ID!, sats: Int, invoiceId: ID): ItemActResult!
|
||||
|
|
|
@ -12,6 +12,9 @@ import Button from 'react-bootstrap/Button'
|
|||
import { discussionSchema } from '../lib/validate'
|
||||
import { SubSelectInitial } from './sub-select-form'
|
||||
import CancelButton from './cancel-button'
|
||||
import { useCallback } from 'react'
|
||||
import { useAnonymous } from '../lib/anonymous'
|
||||
import { ANON_POST_FEE } from '../lib/constants'
|
||||
|
||||
export function DiscussionForm ({
|
||||
item, sub, editThreshold, titleLabel = 'title',
|
||||
|
@ -27,13 +30,32 @@ export function DiscussionForm ({
|
|||
// const me = useMe()
|
||||
const [upsertDiscussion] = useMutation(
|
||||
gql`
|
||||
mutation upsertDiscussion($sub: String, $id: ID, $title: String!, $text: String, $boost: Int, $forward: String) {
|
||||
upsertDiscussion(sub: $sub, id: $id, title: $title, text: $text, boost: $boost, forward: $forward) {
|
||||
mutation upsertDiscussion($sub: String, $id: ID, $title: String!, $text: String, $boost: Int, $forward: String, $invoiceId: ID) {
|
||||
upsertDiscussion(sub: $sub, id: $id, title: $title, text: $text, boost: $boost, forward: $forward, invoiceId: $invoiceId) {
|
||||
id
|
||||
}
|
||||
}`
|
||||
)
|
||||
|
||||
const submitUpsertDiscussion = useCallback(
|
||||
async (_, boost, values, invoiceId) => {
|
||||
const { error } = await upsertDiscussion({
|
||||
variables: { sub: item?.subName || sub?.name, id: item?.id, boost: boost ? Number(boost) : undefined, ...values, invoiceId }
|
||||
})
|
||||
if (error) {
|
||||
throw new Error({ message: error.toString() })
|
||||
}
|
||||
|
||||
if (item) {
|
||||
await router.push(`/items/${item.id}`)
|
||||
} else {
|
||||
const prefix = sub?.name ? `/~${sub.name}` : ''
|
||||
await router.push(prefix + '/recent')
|
||||
}
|
||||
}, [upsertDiscussion, router])
|
||||
|
||||
const anonUpsertDiscussion = useAnonymous(submitUpsertDiscussion)
|
||||
|
||||
const [getRelated, { data: relatedData }] = useLazyQuery(gql`
|
||||
${ITEM_FIELDS}
|
||||
query related($title: String!) {
|
||||
|
@ -58,19 +80,7 @@ export function DiscussionForm ({
|
|||
}}
|
||||
schema={schema}
|
||||
onSubmit={handleSubmit || (async ({ boost, ...values }) => {
|
||||
const { error } = await upsertDiscussion({
|
||||
variables: { sub: item?.subName || sub?.name, id: item?.id, boost: boost ? Number(boost) : undefined, ...values }
|
||||
})
|
||||
if (error) {
|
||||
throw new Error({ message: error.toString() })
|
||||
}
|
||||
|
||||
if (item) {
|
||||
await router.push(`/items/${item.id}`)
|
||||
} else {
|
||||
const prefix = sub?.name ? `/~${sub.name}` : ''
|
||||
await router.push(prefix + '/recent')
|
||||
}
|
||||
await anonUpsertDiscussion(ANON_POST_FEE, boost, values)
|
||||
})}
|
||||
storageKeyPrefix={item ? undefined : 'discussion'}
|
||||
>
|
||||
|
|
|
@ -4,6 +4,8 @@ import Info from './info'
|
|||
import styles from './fee-button.module.css'
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
import { useFormikContext } from 'formik'
|
||||
import { useMe } from './me'
|
||||
import { ANON_COMMENT_FEE, ANON_POST_FEE } from '../lib/constants'
|
||||
|
||||
function Receipt ({ cost, repetition, hasImgLink, baseFee, parentId, boost }) {
|
||||
return (
|
||||
|
@ -40,11 +42,13 @@ function Receipt ({ cost, repetition, hasImgLink, baseFee, parentId, boost }) {
|
|||
}
|
||||
|
||||
export default function FeeButton ({ parentId, hasImgLink, baseFee, ChildButton, variant, text, alwaysShow, disabled }) {
|
||||
const me = useMe()
|
||||
baseFee = me ? baseFee : (parentId ? ANON_COMMENT_FEE : ANON_POST_FEE)
|
||||
const query = parentId
|
||||
? gql`{ itemRepetition(parentId: "${parentId}") }`
|
||||
: gql`{ itemRepetition }`
|
||||
const { data } = useQuery(query, { pollInterval: 1000, nextFetchPolicy: 'cache-and-network' })
|
||||
const repetition = data?.itemRepetition || 0
|
||||
const repetition = me ? data?.itemRepetition || 0 : 0
|
||||
const formik = useFormikContext()
|
||||
const boost = Number(formik?.values?.boost) || 0
|
||||
const cost = baseFee * (hasImgLink ? 10 : 1) * Math.pow(10, repetition) + Number(boost)
|
||||
|
|
|
@ -212,9 +212,6 @@ function NavItems ({ className, sub, prefix }) {
|
|||
}
|
||||
|
||||
function PostItem ({ className, prefix }) {
|
||||
const me = useMe()
|
||||
if (!me) return null
|
||||
|
||||
return (
|
||||
<Link href={prefix + '/post'} className={`${className} btn btn-md btn-primary px-3 py-1 `}>
|
||||
post
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { Form, Input, SubmitButton } from '../components/form'
|
||||
import { useRouter } from 'next/router'
|
||||
import { gql, useApolloClient, useLazyQuery, useMutation } from '@apollo/client'
|
||||
|
@ -14,6 +14,8 @@ import { linkSchema } from '../lib/validate'
|
|||
import Moon from '../svgs/moon-fill.svg'
|
||||
import { SubSelectInitial } from './sub-select-form'
|
||||
import CancelButton from './cancel-button'
|
||||
import { useAnonymous } from '../lib/anonymous'
|
||||
import { ANON_POST_FEE } from '../lib/constants'
|
||||
|
||||
export function LinkForm ({ item, sub, editThreshold, children }) {
|
||||
const router = useRouter()
|
||||
|
@ -66,13 +68,31 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
|
|||
|
||||
const [upsertLink] = useMutation(
|
||||
gql`
|
||||
mutation upsertLink($sub: String, $id: ID, $title: String!, $url: String!, $boost: Int, $forward: String) {
|
||||
upsertLink(sub: $sub, id: $id, title: $title, url: $url, boost: $boost, forward: $forward) {
|
||||
mutation upsertLink($sub: String, $id: ID, $title: String!, $url: String!, $boost: Int, $forward: String, $invoiceId: ID) {
|
||||
upsertLink(sub: $sub, id: $id, title: $title, url: $url, boost: $boost, forward: $forward, invoiceId: $invoiceId) {
|
||||
id
|
||||
}
|
||||
}`
|
||||
)
|
||||
|
||||
const submitUpsertLink = useCallback(
|
||||
async (_, boost, title, values, invoiceId) => {
|
||||
const { error } = await upsertLink({
|
||||
variables: { sub: item?.subName || sub?.name, id: item?.id, boost: boost ? Number(boost) : undefined, title: title.trim(), invoiceId, ...values }
|
||||
})
|
||||
if (error) {
|
||||
throw new Error({ message: error.toString() })
|
||||
}
|
||||
if (item) {
|
||||
await router.push(`/items/${item.id}`)
|
||||
} else {
|
||||
const prefix = sub?.name ? `/~${sub.name}` : ''
|
||||
await router.push(prefix + '/recent')
|
||||
}
|
||||
}, [upsertLink, router])
|
||||
|
||||
const anonUpsertLink = useAnonymous(submitUpsertLink)
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.pageTitleAndUnshorted?.title) {
|
||||
setTitleOverride(data.pageTitleAndUnshorted.title)
|
||||
|
@ -100,18 +120,7 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
|
|||
}}
|
||||
schema={schema}
|
||||
onSubmit={async ({ boost, title, ...values }) => {
|
||||
const { error } = await upsertLink({
|
||||
variables: { sub: item?.subName || sub?.name, id: item?.id, boost: boost ? Number(boost) : undefined, title: title.trim(), ...values }
|
||||
})
|
||||
if (error) {
|
||||
throw new Error({ message: error.toString() })
|
||||
}
|
||||
if (item) {
|
||||
await router.push(`/items/${item.id}`)
|
||||
} else {
|
||||
const prefix = sub?.name ? `/~${sub.name}` : ''
|
||||
await router.push(prefix + '/recent')
|
||||
}
|
||||
await anonUpsertLink(ANON_POST_FEE, boost, title, values)
|
||||
}}
|
||||
storageKeyPrefix={item ? undefined : 'link'}
|
||||
>
|
||||
|
|
|
@ -3,13 +3,15 @@ import { useRouter } from 'next/router'
|
|||
import { gql, useApolloClient, useMutation } from '@apollo/client'
|
||||
import Countdown from './countdown'
|
||||
import AdvPostForm, { AdvPostInitial } from './adv-post-form'
|
||||
import { MAX_POLL_NUM_CHOICES } from '../lib/constants'
|
||||
import { ANON_POST_FEE, MAX_POLL_NUM_CHOICES } from '../lib/constants'
|
||||
import FeeButton, { EditFeeButton } from './fee-button'
|
||||
import Delete from './delete'
|
||||
import Button from 'react-bootstrap/Button'
|
||||
import { pollSchema } from '../lib/validate'
|
||||
import { SubSelectInitial } from './sub-select-form'
|
||||
import CancelButton from './cancel-button'
|
||||
import { useCallback } from 'react'
|
||||
import { useAnonymous } from '../lib/anonymous'
|
||||
|
||||
export function PollForm ({ item, sub, editThreshold, children }) {
|
||||
const router = useRouter()
|
||||
|
@ -19,14 +21,41 @@ export function PollForm ({ item, sub, editThreshold, children }) {
|
|||
const [upsertPoll] = useMutation(
|
||||
gql`
|
||||
mutation upsertPoll($sub: String, $id: ID, $title: String!, $text: String,
|
||||
$options: [String!]!, $boost: Int, $forward: String) {
|
||||
$options: [String!]!, $boost: Int, $forward: String, $invoiceId: ID) {
|
||||
upsertPoll(sub: $sub, id: $id, title: $title, text: $text,
|
||||
options: $options, boost: $boost, forward: $forward) {
|
||||
options: $options, boost: $boost, forward: $forward, invoiceId: $invoiceId) {
|
||||
id
|
||||
}
|
||||
}`
|
||||
)
|
||||
|
||||
const submitUpsertPoll = useCallback(
|
||||
async (_, boost, title, options, values, invoiceId) => {
|
||||
const optionsFiltered = options.slice(initialOptions?.length).filter(word => word.trim().length > 0)
|
||||
const { error } = await upsertPoll({
|
||||
variables: {
|
||||
id: item?.id,
|
||||
sub: item?.subName || sub?.name,
|
||||
boost: boost ? Number(boost) : undefined,
|
||||
title: title.trim(),
|
||||
options: optionsFiltered,
|
||||
...values,
|
||||
invoiceId
|
||||
}
|
||||
})
|
||||
if (error) {
|
||||
throw new Error({ message: error.toString() })
|
||||
}
|
||||
if (item) {
|
||||
await router.push(`/items/${item.id}`)
|
||||
} else {
|
||||
const prefix = sub?.name ? `/~${sub.name}` : ''
|
||||
await router.push(prefix + '/recent')
|
||||
}
|
||||
}, [upsertPoll, router])
|
||||
|
||||
const anonUpsertPoll = useAnonymous(submitUpsertPoll)
|
||||
|
||||
const initialOptions = item?.poll?.options.map(i => i.option)
|
||||
|
||||
return (
|
||||
|
@ -40,26 +69,7 @@ export function PollForm ({ item, sub, editThreshold, children }) {
|
|||
}}
|
||||
schema={schema}
|
||||
onSubmit={async ({ boost, title, options, ...values }) => {
|
||||
const optionsFiltered = options.slice(initialOptions?.length).filter(word => word.trim().length > 0)
|
||||
const { error } = await upsertPoll({
|
||||
variables: {
|
||||
id: item?.id,
|
||||
sub: item?.subName || sub?.name,
|
||||
boost: boost ? Number(boost) : undefined,
|
||||
title: title.trim(),
|
||||
options: optionsFiltered,
|
||||
...values
|
||||
}
|
||||
})
|
||||
if (error) {
|
||||
throw new Error({ message: error.toString() })
|
||||
}
|
||||
if (item) {
|
||||
await router.push(`/items/${item.id}`)
|
||||
} else {
|
||||
const prefix = sub?.name ? `/~${sub.name}` : ''
|
||||
await router.push(prefix + '/recent')
|
||||
}
|
||||
await anonUpsertPoll(ANON_POST_FEE, boost, title, options, values)
|
||||
}}
|
||||
storageKeyPrefix={item ? undefined : 'poll'}
|
||||
>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import JobForm from './job-form'
|
||||
import Link from 'next/link'
|
||||
import Button from 'react-bootstrap/Button'
|
||||
import Alert from 'react-bootstrap/Alert'
|
||||
import AccordianItem from './accordian-item'
|
||||
import { useMe } from './me'
|
||||
import { useRouter } from 'next/router'
|
||||
|
@ -10,6 +11,7 @@ import { PollForm } from './poll-form'
|
|||
import { BountyForm } from './bounty-form'
|
||||
import SubSelect from './sub-select-form'
|
||||
import Info from './info'
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
function FreebieDialog () {
|
||||
return (
|
||||
|
@ -28,12 +30,24 @@ function FreebieDialog () {
|
|||
|
||||
export function PostForm ({ type, sub, children }) {
|
||||
const me = useMe()
|
||||
const [errorMessage, setErrorMessage] = useState()
|
||||
|
||||
const prefix = sub?.name ? `/~${sub.name}` : ''
|
||||
|
||||
const checkSession = useCallback((e) => {
|
||||
if (!me) {
|
||||
e.preventDefault()
|
||||
setErrorMessage('you must be logged in')
|
||||
}
|
||||
}, [me, setErrorMessage])
|
||||
|
||||
if (!type) {
|
||||
return (
|
||||
<div className='align-items-center'>
|
||||
<div className='position-relative align-items-center'>
|
||||
{errorMessage &&
|
||||
<Alert className='position-absolute' style={{ top: '-6rem' }} variant='danger' onClose={() => setErrorMessage(undefined)} dismissible>
|
||||
{errorMessage}
|
||||
</Alert>}
|
||||
{me?.sats < 1 && <FreebieDialog />}
|
||||
<SubSelect noForm sub={sub?.name} />
|
||||
<Link href={prefix + '/post?type=link'}>
|
||||
|
@ -54,11 +68,11 @@ export function PostForm ({ type, sub, children }) {
|
|||
</Link>
|
||||
<span className='mx-3 fw-bold text-muted'>or</span>
|
||||
<Link href={prefix + '/post?type=bounty'}>
|
||||
<Button variant='info'>bounty</Button>
|
||||
<Button onClick={checkSession} variant='info'>bounty</Button>
|
||||
</Link>
|
||||
<div className='mt-3 d-flex justify-content-center'>
|
||||
<Link href='/~jobs/post'>
|
||||
<Button variant='info'>job</Button>
|
||||
<Button onClick={checkSession} variant='info'>job</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,12 +3,14 @@ import { gql, useMutation } from '@apollo/client'
|
|||
import styles from './reply.module.css'
|
||||
import { COMMENTS } from '../fragments/comments'
|
||||
import { useMe } from './me'
|
||||
import { useEffect, useState, useRef } from 'react'
|
||||
import { useEffect, useState, useRef, useCallback } from 'react'
|
||||
import Link from 'next/link'
|
||||
import FeeButton from './fee-button'
|
||||
import { commentsViewedAfterComment } from '../lib/new-comments'
|
||||
import { commentSchema } from '../lib/validate'
|
||||
import Info from './info'
|
||||
import { useAnonymous } from '../lib/anonymous'
|
||||
import { ANON_COMMENT_FEE } from '../lib/constants'
|
||||
|
||||
export function ReplyOnAnotherPage ({ parentId }) {
|
||||
return (
|
||||
|
@ -45,8 +47,8 @@ export default function Reply ({ item, onSuccess, replyOpen, children, placehold
|
|||
const [createComment] = useMutation(
|
||||
gql`
|
||||
${COMMENTS}
|
||||
mutation createComment($text: String!, $parentId: ID!) {
|
||||
createComment(text: $text, parentId: $parentId) {
|
||||
mutation createComment($text: String!, $parentId: ID!, $invoiceId: ID) {
|
||||
createComment(text: $text, parentId: $parentId, invoiceId: $invoiceId) {
|
||||
...CommentFields
|
||||
comments {
|
||||
...CommentsRecursive
|
||||
|
@ -90,6 +92,18 @@ export default function Reply ({ item, onSuccess, replyOpen, children, placehold
|
|||
}
|
||||
)
|
||||
|
||||
const submitComment = useCallback(
|
||||
async (_, values, parentId, resetForm, invoiceId) => {
|
||||
const { error } = await createComment({ variables: { ...values, parentId, invoiceId } })
|
||||
if (error) {
|
||||
throw new Error({ message: error.toString() })
|
||||
}
|
||||
resetForm({ text: '' })
|
||||
setReply(replyOpen || false)
|
||||
}, [createComment, setReply])
|
||||
|
||||
const anonCreateComment = useAnonymous(submitComment)
|
||||
|
||||
const replyInput = useRef(null)
|
||||
useEffect(() => {
|
||||
if (replyInput.current && reply && !replyOpen) replyInput.current.focus()
|
||||
|
@ -117,12 +131,7 @@ export default function Reply ({ item, onSuccess, replyOpen, children, placehold
|
|||
}}
|
||||
schema={commentSchema}
|
||||
onSubmit={async (values, { resetForm }) => {
|
||||
const { error } = await createComment({ variables: { ...values, parentId } })
|
||||
if (error) {
|
||||
throw new Error({ message: error.toString() })
|
||||
}
|
||||
resetForm({ text: '' })
|
||||
setReply(replyOpen || false)
|
||||
await anonCreateComment(ANON_COMMENT_FEE, values, parentId, resetForm)
|
||||
}}
|
||||
storageKeyPrefix={'reply-' + parentId}
|
||||
>
|
||||
|
|
|
@ -58,7 +58,7 @@ export const useAnonymous = (fn) => {
|
|||
if (me) return fn(amount, ...args)
|
||||
setFnArgs(args)
|
||||
return createInvoice({ variables: { amount } })
|
||||
})
|
||||
}, [fn, setFnArgs, createInvoice])
|
||||
|
||||
return anonFn
|
||||
}
|
||||
|
|
|
@ -46,3 +46,5 @@ export const ITEM_TYPES = context => {
|
|||
export const OLD_ITEM_DAYS = 3
|
||||
|
||||
export const ANON_USER_ID = 27
|
||||
export const ANON_POST_FEE = 1000
|
||||
export const ANON_COMMENT_FEE = 100
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
CREATE OR REPLACE FUNCTION item_spam(parent_id INTEGER, user_id INTEGER, within INTERVAL)
|
||||
RETURNS INTEGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
repeats INTEGER;
|
||||
self_replies INTEGER;
|
||||
BEGIN
|
||||
IF user_id = 27 THEN
|
||||
-- disable fee escalation for anon user
|
||||
RETURN 0;
|
||||
END IF;
|
||||
|
||||
SELECT count(*) INTO repeats
|
||||
FROM "Item"
|
||||
WHERE (parent_id IS NULL AND "parentId" IS NULL OR "parentId" = parent_id)
|
||||
AND "userId" = user_id
|
||||
AND created_at > now_utc() - within;
|
||||
|
||||
IF parent_id IS NULL THEN
|
||||
RETURN repeats;
|
||||
END IF;
|
||||
|
||||
WITH RECURSIVE base AS (
|
||||
SELECT "Item".id, "Item"."parentId", "Item"."userId"
|
||||
FROM "Item"
|
||||
WHERE id = parent_id AND "userId" = user_id AND created_at > now_utc() - within
|
||||
UNION ALL
|
||||
SELECT "Item".id, "Item"."parentId", "Item"."userId"
|
||||
FROM base p
|
||||
JOIN "Item" ON "Item".id = p."parentId" AND "Item"."userId" = p."userId" AND "Item".created_at > now_utc() - within)
|
||||
SELECT count(*) INTO self_replies FROM base;
|
||||
|
||||
RETURN repeats + self_replies;
|
||||
END;
|
||||
$$;
|
Loading…
Reference in New Issue