Allow SN users to edit special items forever (#1204)
* Allow SN users to edit special items * Refactor item edit validation * Create object for user IDs * Remove anon from SN_USER_IDS * Fix isMine and myBio checks * Don't update author * remove anon from trust graph --------- Co-authored-by: keyan <keyan.kousha+huumn@gmail.com>
This commit is contained in:
parent
e9aa268996
commit
86b857b8d4
@ -1,4 +1,4 @@
|
|||||||
import { ANON_USER_ID, AWS_S3_URL_REGEXP } from '@/lib/constants'
|
import { USER_ID, AWS_S3_URL_REGEXP } from '@/lib/constants'
|
||||||
import { msatsToSats } from '@/lib/format'
|
import { msatsToSats } from '@/lib/format'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -17,7 +17,7 @@ export function uploadIdsFromText (text, { models }) {
|
|||||||
export async function imageFeesInfo (s3Keys, { models, me }) {
|
export async function imageFeesInfo (s3Keys, { models, me }) {
|
||||||
// returns info object in this format:
|
// returns info object in this format:
|
||||||
// { bytes24h: int, bytesUnpaid: int, nUnpaid: int, imageFeeMsats: BigInt }
|
// { bytes24h: int, bytesUnpaid: int, nUnpaid: int, imageFeeMsats: BigInt }
|
||||||
const [info] = await models.$queryRawUnsafe('SELECT * FROM image_fees_info($1::INTEGER, $2::INTEGER[])', me ? me.id : ANON_USER_ID, s3Keys)
|
const [info] = await models.$queryRawUnsafe('SELECT * FROM image_fees_info($1::INTEGER, $2::INTEGER[])', me ? me.id : USER_ID.anon, s3Keys)
|
||||||
const imageFee = msatsToSats(info.imageFeeMsats)
|
const imageFee = msatsToSats(info.imageFeeMsats)
|
||||||
const totalFeesMsats = info.nUnpaid * Number(info.imageFeeMsats)
|
const totalFeesMsats = info.nUnpaid * Number(info.imageFeeMsats)
|
||||||
const totalFees = msatsToSats(totalFeesMsats)
|
const totalFees = msatsToSats(totalFeesMsats)
|
||||||
|
@ -8,8 +8,8 @@ import domino from 'domino'
|
|||||||
import {
|
import {
|
||||||
ITEM_SPAM_INTERVAL, ITEM_FILTER_THRESHOLD,
|
ITEM_SPAM_INTERVAL, ITEM_FILTER_THRESHOLD,
|
||||||
COMMENT_DEPTH_LIMIT, COMMENT_TYPE_QUERY,
|
COMMENT_DEPTH_LIMIT, COMMENT_TYPE_QUERY,
|
||||||
ANON_USER_ID, ANON_ITEM_SPAM_INTERVAL, POLL_COST,
|
USER_ID, ANON_ITEM_SPAM_INTERVAL, POLL_COST,
|
||||||
ITEM_ALLOW_EDITS, GLOBAL_SEED, ANON_FEE_MULTIPLIER, NOFOLLOW_LIMIT, UNKNOWN_LINK_REL
|
ITEM_ALLOW_EDITS, GLOBAL_SEED, ANON_FEE_MULTIPLIER, NOFOLLOW_LIMIT, UNKNOWN_LINK_REL, SN_USER_IDS
|
||||||
} from '@/lib/constants'
|
} from '@/lib/constants'
|
||||||
import { msatsToSats } from '@/lib/format'
|
import { msatsToSats } from '@/lib/format'
|
||||||
import { parse } from 'tldts'
|
import { parse } from 'tldts'
|
||||||
@ -876,7 +876,7 @@ export default {
|
|||||||
models.$queryRaw`
|
models.$queryRaw`
|
||||||
SELECT
|
SELECT
|
||||||
item_act(${Number(id)}::INTEGER,
|
item_act(${Number(id)}::INTEGER,
|
||||||
${me?.id || ANON_USER_ID}::INTEGER, ${act}::"ItemActType", ${Number(sats)}::INTEGER)`,
|
${me?.id || USER_ID.anon}::INTEGER, ${act}::"ItemActType", ${Number(sats)}::INTEGER)`,
|
||||||
{ models, lnd, me, hash, hmac, fee: sats }
|
{ models, lnd, me, hash, hmac, fee: sats }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1157,7 +1157,7 @@ export default {
|
|||||||
return parent.otsHash
|
return parent.otsHash
|
||||||
},
|
},
|
||||||
deleteScheduledAt: async (item, args, { me, models }) => {
|
deleteScheduledAt: async (item, args, { me, models }) => {
|
||||||
const meId = me?.id ?? ANON_USER_ID
|
const meId = me?.id ?? USER_ID.anon
|
||||||
if (meId !== item.userId) {
|
if (meId !== item.userId) {
|
||||||
// Only query for deleteScheduledAt for your own items to keep DB queries minimized
|
// Only query for deleteScheduledAt for your own items to keep DB queries minimized
|
||||||
return null
|
return null
|
||||||
@ -1166,8 +1166,8 @@ export default {
|
|||||||
return deleteJobs[0]?.startafter ?? null
|
return deleteJobs[0]?.startafter ?? null
|
||||||
},
|
},
|
||||||
reminderScheduledAt: async (item, args, { me, models }) => {
|
reminderScheduledAt: async (item, args, { me, models }) => {
|
||||||
const meId = me?.id ?? ANON_USER_ID
|
const meId = me?.id ?? USER_ID.anon
|
||||||
if (meId !== item.userId || meId === ANON_USER_ID) {
|
if (meId !== item.userId || meId === USER_ID.anon) {
|
||||||
// don't show reminders on an item if it isn't yours
|
// don't show reminders on an item if it isn't yours
|
||||||
// don't support reminders for ANON
|
// don't support reminders for ANON
|
||||||
return null
|
return null
|
||||||
@ -1227,10 +1227,21 @@ export const createMentions = async (item, models) => {
|
|||||||
export const updateItem = async (parent, { sub: subName, forward, options, ...item }, { me, models, lnd, hash, hmac }) => {
|
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) }, include: { sub: true } })
|
const old = await models.item.findUnique({ where: { id: Number(item.id) }, include: { sub: true } })
|
||||||
if (Number(old.userId) !== Number(me?.id)) {
|
|
||||||
|
// author can always edit their own item
|
||||||
|
const mid = Number(me?.id)
|
||||||
|
const isMine = Number(old.userId) === mid
|
||||||
|
|
||||||
|
// allow admins to edit special items
|
||||||
|
const allowEdit = ITEM_ALLOW_EDITS.includes(old.id)
|
||||||
|
const adminEdit = SN_USER_IDS.includes(mid) && allowEdit
|
||||||
|
|
||||||
|
if (!isMine && !adminEdit) {
|
||||||
throw new GraphQLError('item does not belong to you', { extensions: { code: 'FORBIDDEN' } })
|
throw new GraphQLError('item does not belong to you', { extensions: { code: 'FORBIDDEN' } })
|
||||||
}
|
}
|
||||||
if (subName && old.subName !== subName) {
|
|
||||||
|
const differentSub = subName && old.subName !== subName
|
||||||
|
if (differentSub) {
|
||||||
const sub = await models.sub.findUnique({ where: { name: subName } })
|
const sub = await models.sub.findUnique({ where: { name: subName } })
|
||||||
if (old.freebie) {
|
if (old.freebie) {
|
||||||
if (!sub.allowFreebies) {
|
if (!sub.allowFreebies) {
|
||||||
@ -1244,10 +1255,13 @@ export const updateItem = async (parent, { sub: subName, forward, options, ...it
|
|||||||
// in case they lied about their existing boost
|
// in case they lied about their existing boost
|
||||||
await ssValidate(advSchema, { boost: item.boost }, { models, me, existingBoost: old.boost })
|
await ssValidate(advSchema, { boost: item.boost }, { models, me, existingBoost: old.boost })
|
||||||
|
|
||||||
// prevent update if it's not explicitly allowed, not their bio, not their job 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 (!ITEM_ALLOW_EDITS.includes(old.id) && user.bioId !== old.id &&
|
|
||||||
!isJob(item) && Date.now() > new Date(old.createdAt).getTime() + 10 * 60000) {
|
// prevent update if it's not explicitly allowed, not their bio, not their job and older than 10 minutes
|
||||||
|
const myBio = user.bioId === old.id
|
||||||
|
const timer = Date.now() < new Date(old.createdAt).getTime() + 10 * 60_000
|
||||||
|
|
||||||
|
if (!allowEdit && !myBio && !timer) {
|
||||||
throw new GraphQLError('item can no longer be editted', { extensions: { code: 'BAD_INPUT' } })
|
throw new GraphQLError('item can no longer be editted', { extensions: { code: 'BAD_INPUT' } })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1266,7 +1280,7 @@ export const updateItem = async (parent, { sub: subName, forward, options, ...it
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
item = { subName, userId: me.id, ...item }
|
item = { subName, userId: old.userId, ...item }
|
||||||
const fwdUsers = await getForwardUsers(models, forward)
|
const fwdUsers = await getForwardUsers(models, forward)
|
||||||
|
|
||||||
const uploadIds = uploadIdsFromText(item.text, { models })
|
const uploadIds = uploadIdsFromText(item.text, { models })
|
||||||
@ -1303,7 +1317,7 @@ export const createItem = async (parent, { forward, options, ...item }, { me, mo
|
|||||||
item.subName = item.sub
|
item.subName = item.sub
|
||||||
delete item.sub
|
delete item.sub
|
||||||
|
|
||||||
item.userId = me ? Number(me.id) : ANON_USER_ID
|
item.userId = me ? Number(me.id) : USER_ID.anon
|
||||||
|
|
||||||
const fwdUsers = await getForwardUsers(models, forward)
|
const fwdUsers = await getForwardUsers(models, forward)
|
||||||
if (item.url && !isJob(item)) {
|
if (item.url && !isJob(item)) {
|
||||||
@ -1367,7 +1381,7 @@ const enqueueDeletionJob = async (item, models) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteReminderAndJob = async ({ me, item, models }) => {
|
const deleteReminderAndJob = async ({ me, item, models }) => {
|
||||||
if (me?.id && me.id !== ANON_USER_ID) {
|
if (me?.id && me.id !== USER_ID.anon) {
|
||||||
await models.$transaction([
|
await models.$transaction([
|
||||||
models.$queryRawUnsafe(`
|
models.$queryRawUnsafe(`
|
||||||
DELETE FROM pgboss.job
|
DELETE FROM pgboss.job
|
||||||
@ -1389,7 +1403,7 @@ const deleteReminderAndJob = async ({ me, item, models }) => {
|
|||||||
|
|
||||||
const createReminderAndJob = async ({ me, item, models }) => {
|
const createReminderAndJob = async ({ me, item, models }) => {
|
||||||
// disallow anon to use reminder
|
// disallow anon to use reminder
|
||||||
if (!me || me.id === ANON_USER_ID) {
|
if (!me || me.id === USER_ID.anon) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const reminderCommand = getReminderCommand(item.text)
|
const reminderCommand = getReminderCommand(item.text)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { GraphQLError } from 'graphql'
|
import { GraphQLError } from 'graphql'
|
||||||
import { amountSchema, ssValidate } from '@/lib/validate'
|
import { amountSchema, ssValidate } from '@/lib/validate'
|
||||||
import serialize from './serial'
|
import serialize from './serial'
|
||||||
import { ANON_USER_ID } from '@/lib/constants'
|
import { USER_ID } from '@/lib/constants'
|
||||||
import { getItem } from './item'
|
import { getItem } from './item'
|
||||||
import { topUsers } from './user'
|
import { topUsers } from './user'
|
||||||
|
|
||||||
@ -165,7 +165,7 @@ export default {
|
|||||||
await ssValidate(amountSchema, { amount: sats })
|
await ssValidate(amountSchema, { amount: sats })
|
||||||
|
|
||||||
await serialize(
|
await serialize(
|
||||||
models.$queryRaw`SELECT donate(${sats}::INTEGER, ${me?.id || ANON_USER_ID}::INTEGER)`,
|
models.$queryRaw`SELECT donate(${sats}::INTEGER, ${me?.id || USER_ID.anon}::INTEGER)`,
|
||||||
{ models, lnd, me, hash, hmac, fee: sats }
|
{ models, lnd, me, hash, hmac, fee: sats }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { GraphQLError } from 'graphql'
|
import { GraphQLError } from 'graphql'
|
||||||
import { ANON_USER_ID, IMAGE_PIXELS_MAX, UPLOAD_SIZE_MAX, UPLOAD_SIZE_MAX_AVATAR, UPLOAD_TYPES_ALLOW } from '@/lib/constants'
|
import { USER_ID, IMAGE_PIXELS_MAX, UPLOAD_SIZE_MAX, UPLOAD_SIZE_MAX_AVATAR, UPLOAD_TYPES_ALLOW } from '@/lib/constants'
|
||||||
import { createPresignedPost } from '@/api/s3'
|
import { createPresignedPost } from '@/api/s3'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -26,7 +26,7 @@ export default {
|
|||||||
size,
|
size,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
userId: me?.id || ANON_USER_ID,
|
userId: me?.id || USER_ID.anon,
|
||||||
paid: false
|
paid: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { decodeCursor, LIMIT, nextCursorEncoded } from '@/lib/cursor'
|
|||||||
import { msatsToSats } from '@/lib/format'
|
import { msatsToSats } from '@/lib/format'
|
||||||
import { bioSchema, emailSchema, settingsSchema, ssValidate, userSchema } from '@/lib/validate'
|
import { bioSchema, emailSchema, settingsSchema, ssValidate, userSchema } from '@/lib/validate'
|
||||||
import { getItem, updateItem, filterClause, createItem, whereClause, muteClause } from './item'
|
import { getItem, updateItem, filterClause, createItem, whereClause, muteClause } from './item'
|
||||||
import { ANON_USER_ID, DELETE_USER_ID, RESERVED_MAX_USER_ID, SN_NO_REWARDS_IDS } from '@/lib/constants'
|
import { USER_ID, RESERVED_MAX_USER_ID, SN_NO_REWARDS_IDS } from '@/lib/constants'
|
||||||
import { viewGroup } from './growth'
|
import { viewGroup } from './growth'
|
||||||
import { timeUnitForRange, whenRange } from '@/lib/time'
|
import { timeUnitForRange, whenRange } from '@/lib/time'
|
||||||
import assertApiKeyNotPermitted from './apiKey'
|
import assertApiKeyNotPermitted from './apiKey'
|
||||||
@ -217,7 +217,7 @@ export default {
|
|||||||
SELECT name
|
SELECT name
|
||||||
FROM users
|
FROM users
|
||||||
WHERE (
|
WHERE (
|
||||||
id > ${RESERVED_MAX_USER_ID} OR id IN (${ANON_USER_ID}, ${DELETE_USER_ID})
|
id > ${RESERVED_MAX_USER_ID} OR id IN (${USER_ID.anon}, ${USER_ID.delete})
|
||||||
)
|
)
|
||||||
AND SIMILARITY(name, ${q}) > 0.1
|
AND SIMILARITY(name, ${q}) > 0.1
|
||||||
ORDER BY SIMILARITY(name, ${q}) DESC
|
ORDER BY SIMILARITY(name, ${q}) DESC
|
||||||
@ -518,7 +518,7 @@ export default {
|
|||||||
return await models.$queryRaw`
|
return await models.$queryRaw`
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM users
|
FROM users
|
||||||
WHERE (id > ${RESERVED_MAX_USER_ID} OR id IN (${ANON_USER_ID}, ${DELETE_USER_ID}))
|
WHERE (id > ${RESERVED_MAX_USER_ID} OR id IN (${USER_ID.anon}, ${USER_ID.delete}))
|
||||||
AND SIMILARITY(name, ${q}) > ${Number(similarity) || 0.1} ORDER BY SIMILARITY(name, ${q}) DESC LIMIT ${Number(limit) || 5}`
|
AND SIMILARITY(name, ${q}) > ${Number(similarity) || 0.1} ORDER BY SIMILARITY(name, ${q}) DESC LIMIT ${Number(limit) || 5}`
|
||||||
},
|
},
|
||||||
userStatsActions: async (parent, { when, from, to }, { me, models }) => {
|
userStatsActions: async (parent, { when, from, to }, { me, models }) => {
|
||||||
|
@ -7,7 +7,7 @@ import { SELECT } from './item'
|
|||||||
import { lnAddrOptions } from '@/lib/lnurl'
|
import { lnAddrOptions } from '@/lib/lnurl'
|
||||||
import { msatsToSats, msatsToSatsDecimal, ensureB64 } from '@/lib/format'
|
import { msatsToSats, msatsToSatsDecimal, ensureB64 } from '@/lib/format'
|
||||||
import { CLNAutowithdrawSchema, LNDAutowithdrawSchema, amountSchema, lnAddrAutowithdrawSchema, lnAddrSchema, ssValidate, withdrawlSchema } from '@/lib/validate'
|
import { CLNAutowithdrawSchema, LNDAutowithdrawSchema, amountSchema, lnAddrAutowithdrawSchema, lnAddrSchema, ssValidate, withdrawlSchema } from '@/lib/validate'
|
||||||
import { ANON_BALANCE_LIMIT_MSATS, ANON_INV_PENDING_LIMIT, ANON_USER_ID, BALANCE_LIMIT_MSATS, INVOICE_RETENTION_DAYS, INV_PENDING_LIMIT, USER_IDS_BALANCE_NO_LIMIT, Wallet } from '@/lib/constants'
|
import { ANON_BALANCE_LIMIT_MSATS, ANON_INV_PENDING_LIMIT, USER_ID, BALANCE_LIMIT_MSATS, INVOICE_RETENTION_DAYS, INV_PENDING_LIMIT, USER_IDS_BALANCE_NO_LIMIT, Wallet } from '@/lib/constants'
|
||||||
import { datePivot } from '@/lib/time'
|
import { datePivot } from '@/lib/time'
|
||||||
import assertGofacYourself from './ofac'
|
import assertGofacYourself from './ofac'
|
||||||
import assertApiKeyNotPermitted from './apiKey'
|
import assertApiKeyNotPermitted from './apiKey'
|
||||||
@ -28,7 +28,7 @@ export async function getInvoice (parent, { id }, { me, models, lnd }) {
|
|||||||
throw new GraphQLError('invoice not found', { extensions: { code: 'BAD_INPUT' } })
|
throw new GraphQLError('invoice not found', { extensions: { code: 'BAD_INPUT' } })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inv.user.id === ANON_USER_ID) {
|
if (inv.user.id === USER_ID.anon) {
|
||||||
return inv
|
return inv
|
||||||
}
|
}
|
||||||
if (!me) {
|
if (!me) {
|
||||||
@ -339,7 +339,7 @@ export default {
|
|||||||
expirePivot = { seconds: Math.min(expireSecs, 180) }
|
expirePivot = { seconds: Math.min(expireSecs, 180) }
|
||||||
invLimit = ANON_INV_PENDING_LIMIT
|
invLimit = ANON_INV_PENDING_LIMIT
|
||||||
balanceLimit = ANON_BALANCE_LIMIT_MSATS
|
balanceLimit = ANON_BALANCE_LIMIT_MSATS
|
||||||
id = ANON_USER_ID
|
id = USER_ID.anon
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await models.user.findUnique({ where: { id } })
|
const user = await models.user.findUnique({ where: { id } })
|
||||||
|
@ -2,7 +2,7 @@ import { useApolloClient } from '@apollo/client'
|
|||||||
import { useMe } from './me'
|
import { useMe } from './me'
|
||||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
||||||
import { datePivot, timeSince } from '@/lib/time'
|
import { datePivot, timeSince } from '@/lib/time'
|
||||||
import { ANON_USER_ID, JIT_INVOICE_TIMEOUT_MS } from '@/lib/constants'
|
import { USER_ID, JIT_INVOICE_TIMEOUT_MS } from '@/lib/constants'
|
||||||
import { HAS_NOTIFICATIONS } from '@/fragments/notifications'
|
import { HAS_NOTIFICATIONS } from '@/fragments/notifications'
|
||||||
import Item from './item'
|
import Item from './item'
|
||||||
import { RootProvider } from './root'
|
import { RootProvider } from './root'
|
||||||
@ -25,7 +25,7 @@ export function ClientNotificationProvider ({ children }) {
|
|||||||
const me = useMe()
|
const me = useMe()
|
||||||
// anons don't have access to /notifications
|
// anons don't have access to /notifications
|
||||||
// but we'll store client notifications anyway for simplicity's sake
|
// but we'll store client notifications anyway for simplicity's sake
|
||||||
const storageKey = `client-notifications:${me?.id || ANON_USER_ID}`
|
const storageKey = `client-notifications:${me?.id || USER_ID.anon}`
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loaded = loadNotifications(storageKey, client)
|
const loaded = loadNotifications(storageKey, client)
|
||||||
|
@ -9,7 +9,7 @@ import Eye from '@/svgs/eye-fill.svg'
|
|||||||
import EyeClose from '@/svgs/eye-close-line.svg'
|
import EyeClose from '@/svgs/eye-close-line.svg'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import CommentEdit from './comment-edit'
|
import CommentEdit from './comment-edit'
|
||||||
import { ANON_USER_ID, COMMENT_DEPTH_LIMIT, UNKNOWN_LINK_REL } from '@/lib/constants'
|
import { USER_ID, COMMENT_DEPTH_LIMIT, UNKNOWN_LINK_REL } from '@/lib/constants'
|
||||||
import PayBounty from './pay-bounty'
|
import PayBounty from './pay-bounty'
|
||||||
import BountyIcon from '@/svgs/bounty-bag.svg'
|
import BountyIcon from '@/svgs/bounty-bag.svg'
|
||||||
import ActionTooltip from './action-tooltip'
|
import ActionTooltip from './action-tooltip'
|
||||||
@ -128,9 +128,9 @@ export default function Comment ({
|
|||||||
|
|
||||||
const bottomedOut = depth === COMMENT_DEPTH_LIMIT
|
const bottomedOut = depth === COMMENT_DEPTH_LIMIT
|
||||||
// Don't show OP badge when anon user comments on anon user posts
|
// Don't show OP badge when anon user comments on anon user posts
|
||||||
const op = root.user.name === item.user.name && Number(item.user.id) !== ANON_USER_ID
|
const op = root.user.name === item.user.name && Number(item.user.id) !== USER_ID.anon
|
||||||
? 'OP'
|
? 'OP'
|
||||||
: root.forwards?.some(f => f.user.name === item.user.name) && Number(item.user.id) !== ANON_USER_ID
|
: root.forwards?.some(f => f.user.name === item.user.name) && Number(item.user.id) !== USER_ID.anon
|
||||||
? 'fwd'
|
? 'fwd'
|
||||||
: null
|
: null
|
||||||
const bountyPaid = root.bountyPaidTo?.includes(Number(item.id))
|
const bountyPaid = root.bountyPaidTo?.includes(Number(item.id))
|
||||||
|
@ -4,11 +4,11 @@ import Tooltip from 'react-bootstrap/Tooltip'
|
|||||||
import CowboyHatIcon from '@/svgs/cowboy.svg'
|
import CowboyHatIcon from '@/svgs/cowboy.svg'
|
||||||
import AnonIcon from '@/svgs/spy-fill.svg'
|
import AnonIcon from '@/svgs/spy-fill.svg'
|
||||||
import { numWithUnits } from '@/lib/format'
|
import { numWithUnits } from '@/lib/format'
|
||||||
import { AD_USER_ID, ANON_USER_ID } from '@/lib/constants'
|
import { USER_ID } from '@/lib/constants'
|
||||||
|
|
||||||
export default function Hat ({ user, badge, className = 'ms-1', height = 16, width = 16 }) {
|
export default function Hat ({ user, badge, className = 'ms-1', height = 16, width = 16 }) {
|
||||||
if (!user || Number(user.id) === AD_USER_ID) return null
|
if (!user || Number(user.id) === USER_ID.ad) return null
|
||||||
if (Number(user.id) === ANON_USER_ID) {
|
if (Number(user.id) === USER_ID.anon) {
|
||||||
return (
|
return (
|
||||||
<HatTooltip overlayText='anonymous'>
|
<HatTooltip overlayText='anonymous'>
|
||||||
{badge
|
{badge
|
||||||
|
@ -15,7 +15,7 @@ import BookmarkDropdownItem from './bookmark'
|
|||||||
import SubscribeDropdownItem from './subscribe'
|
import SubscribeDropdownItem from './subscribe'
|
||||||
import { CopyLinkDropdownItem, CrosspostDropdownItem } from './share'
|
import { CopyLinkDropdownItem, CrosspostDropdownItem } from './share'
|
||||||
import Hat from './hat'
|
import Hat from './hat'
|
||||||
import { AD_USER_ID } from '@/lib/constants'
|
import { USER_ID } from '@/lib/constants'
|
||||||
import ActionDropdown from './action-dropdown'
|
import ActionDropdown from './action-dropdown'
|
||||||
import MuteDropdownItem from './mute'
|
import MuteDropdownItem from './mute'
|
||||||
import { DropdownItemUpVote } from './upvote'
|
import { DropdownItemUpVote } from './upvote'
|
||||||
@ -58,7 +58,7 @@ export default function ItemInfo ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className || `${styles.other}`}>
|
<div className={className || `${styles.other}`}>
|
||||||
{!(item.position && (pinnable || !item.subName)) && !(!item.parentId && Number(item.user?.id) === AD_USER_ID) &&
|
{!(item.position && (pinnable || !item.subName)) && !(!item.parentId && Number(item.user?.id) === USER_ID.ad) &&
|
||||||
<>
|
<>
|
||||||
<span title={`from ${numWithUnits(item.upvotes, {
|
<span title={`from ${numWithUnits(item.upvotes, {
|
||||||
abbreviate: false,
|
abbreviate: false,
|
||||||
|
@ -2,7 +2,7 @@ import Link from 'next/link'
|
|||||||
import styles from './item.module.css'
|
import styles from './item.module.css'
|
||||||
import UpVote from './upvote'
|
import UpVote from './upvote'
|
||||||
import { useRef } from 'react'
|
import { useRef } from 'react'
|
||||||
import { AD_USER_ID, UNKNOWN_LINK_REL } from '@/lib/constants'
|
import { USER_ID, UNKNOWN_LINK_REL } from '@/lib/constants'
|
||||||
import Pin from '@/svgs/pushpin-fill.svg'
|
import Pin from '@/svgs/pushpin-fill.svg'
|
||||||
import reactStringReplace from 'react-string-replace'
|
import reactStringReplace from 'react-string-replace'
|
||||||
import PollIcon from '@/svgs/bar-chart-horizontal-fill.svg'
|
import PollIcon from '@/svgs/bar-chart-horizontal-fill.svg'
|
||||||
@ -64,7 +64,7 @@ export default function Item ({ item, rank, belowTitle, right, full, children, s
|
|||||||
? <Pin width={24} height={24} className={styles.pin} />
|
? <Pin width={24} height={24} className={styles.pin} />
|
||||||
: item.meDontLikeSats > item.meSats
|
: item.meDontLikeSats > item.meSats
|
||||||
? <DownZap width={24} height={24} className={styles.dontLike} item={item} />
|
? <DownZap width={24} height={24} className={styles.dontLike} item={item} />
|
||||||
: Number(item.user?.id) === AD_USER_ID
|
: Number(item.user?.id) === USER_ID.ad
|
||||||
? <AdIcon width={24} height={24} className={styles.ad} />
|
? <AdIcon width={24} height={24} className={styles.ad} />
|
||||||
: <UpVote item={item} className={styles.upvote} />}
|
: <UpVote item={item} className={styles.upvote} />}
|
||||||
<div className={styles.hunk}>
|
<div className={styles.hunk}>
|
||||||
@ -99,7 +99,7 @@ export default function Item ({ item, rank, belowTitle, right, full, children, s
|
|||||||
full={full} item={item}
|
full={full} item={item}
|
||||||
onQuoteReply={onQuoteReply}
|
onQuoteReply={onQuoteReply}
|
||||||
pinnable={pinnable}
|
pinnable={pinnable}
|
||||||
extraBadges={Number(item?.user?.id) === AD_USER_ID && <Badge className={styles.newComment} bg={null}>AD</Badge>}
|
extraBadges={Number(item?.user?.id) === USER_ID.ad && <Badge className={styles.newComment} bg={null}>AD</Badge>}
|
||||||
/>
|
/>
|
||||||
{belowTitle}
|
{belowTitle}
|
||||||
</div>
|
</div>
|
||||||
@ -130,7 +130,7 @@ export function ItemSummary ({ item }) {
|
|||||||
item={item}
|
item={item}
|
||||||
showUser={false}
|
showUser={false}
|
||||||
showActionDropdown={false}
|
showActionDropdown={false}
|
||||||
extraBadges={item.title && Number(item?.user?.id) === AD_USER_ID && <Badge className={styles.newComment} bg={null}>AD</Badge>}
|
extraBadges={item.title && Number(item?.user?.id) === USER_ID.ad && <Badge className={styles.newComment} bg={null}>AD</Badge>}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import BackArrow from '../../svgs/arrow-left-line.svg'
|
|||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import Price from '../price'
|
import Price from '../price'
|
||||||
import SubSelect from '../sub-select'
|
import SubSelect from '../sub-select'
|
||||||
import { ANON_USER_ID, BALANCE_LIMIT_MSATS, Wallet } from '../../lib/constants'
|
import { USER_ID, BALANCE_LIMIT_MSATS, Wallet } from '../../lib/constants'
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
import NoteIcon from '../../svgs/notification-4-fill.svg'
|
import NoteIcon from '../../svgs/notification-4-fill.svg'
|
||||||
import { useMe } from '../me'
|
import { useMe } from '../me'
|
||||||
@ -307,7 +307,7 @@ export function AnonDropdown ({ path }) {
|
|||||||
<Dropdown className={styles.dropdown} align='end'>
|
<Dropdown className={styles.dropdown} align='end'>
|
||||||
<Dropdown.Toggle className='nav-link nav-item' id='profile' variant='custom'>
|
<Dropdown.Toggle className='nav-link nav-item' id='profile' variant='custom'>
|
||||||
<Nav.Link eventKey='anon' as='span' className='p-0 fw-normal'>
|
<Nav.Link eventKey='anon' as='span' className='p-0 fw-normal'>
|
||||||
@anon<Hat user={{ id: ANON_USER_ID }} />
|
@anon<Hat user={{ id: USER_ID.anon }} />
|
||||||
</Nav.Link>
|
</Nav.Link>
|
||||||
</Dropdown.Toggle>
|
</Dropdown.Toggle>
|
||||||
<Dropdown.Menu className='p-3'>
|
<Dropdown.Menu className='p-3'>
|
||||||
|
@ -36,8 +36,16 @@ export const ITEM_SPAM_INTERVAL = '10m'
|
|||||||
export const ANON_ITEM_SPAM_INTERVAL = '0'
|
export const ANON_ITEM_SPAM_INTERVAL = '0'
|
||||||
export const INV_PENDING_LIMIT = 100
|
export const INV_PENDING_LIMIT = 100
|
||||||
export const BALANCE_LIMIT_MSATS = 100000000 // 100k sat
|
export const BALANCE_LIMIT_MSATS = 100000000 // 100k sat
|
||||||
export const SN_USER_IDS = [616, 6030, 4502, 27]
|
export const USER_ID = {
|
||||||
export const SN_NO_REWARDS_IDS = [27, 4502]
|
k00b: 616,
|
||||||
|
ek: 6030,
|
||||||
|
sn: 4502,
|
||||||
|
anon: 27,
|
||||||
|
ad: 9,
|
||||||
|
delete: 106
|
||||||
|
}
|
||||||
|
export const SN_USER_IDS = [USER_ID.k00b, USER_ID.ek, USER_ID.sn]
|
||||||
|
export const SN_NO_REWARDS_IDS = [USER_ID.anon, USER_ID.sn]
|
||||||
export const ANON_INV_PENDING_LIMIT = 1000
|
export const ANON_INV_PENDING_LIMIT = 1000
|
||||||
export const ANON_BALANCE_LIMIT_MSATS = 0 // disable
|
export const ANON_BALANCE_LIMIT_MSATS = 0 // disable
|
||||||
export const MAX_POLL_NUM_CHOICES = 10
|
export const MAX_POLL_NUM_CHOICES = 10
|
||||||
@ -54,17 +62,14 @@ export const ITEM_TYPES_USER = ['all', 'posts', 'comments', 'bounties', 'links',
|
|||||||
export const ITEM_TYPES = ['all', 'posts', 'comments', 'bounties', 'links', 'discussions', 'polls', 'freebies', 'bios', 'jobs']
|
export const ITEM_TYPES = ['all', 'posts', 'comments', 'bounties', 'links', 'discussions', 'polls', 'freebies', 'bios', 'jobs']
|
||||||
export const ITEM_TYPES_UNIVERSAL = ['all', 'posts', 'comments', 'freebies']
|
export const ITEM_TYPES_UNIVERSAL = ['all', 'posts', 'comments', 'freebies']
|
||||||
export const OLD_ITEM_DAYS = 3
|
export const OLD_ITEM_DAYS = 3
|
||||||
export const ANON_USER_ID = 27
|
|
||||||
export const DELETE_USER_ID = 106
|
|
||||||
export const AD_USER_ID = 9
|
|
||||||
export const ANON_FEE_MULTIPLIER = 100
|
export const ANON_FEE_MULTIPLIER = 100
|
||||||
export const SSR = typeof window === 'undefined'
|
export const SSR = typeof window === 'undefined'
|
||||||
export const MAX_FORWARDS = 5
|
export const MAX_FORWARDS = 5
|
||||||
export const LNURLP_COMMENT_MAX_LENGTH = 1000
|
export const LNURLP_COMMENT_MAX_LENGTH = 1000
|
||||||
export const RESERVED_MAX_USER_ID = 615
|
export const RESERVED_MAX_USER_ID = 615
|
||||||
export const GLOBAL_SEED = 616
|
export const GLOBAL_SEED = USER_ID.k00b
|
||||||
export const FREEBIE_BASE_COST_THRESHOLD = 10
|
export const FREEBIE_BASE_COST_THRESHOLD = 10
|
||||||
export const USER_IDS_BALANCE_NO_LIMIT = [...SN_USER_IDS, AD_USER_ID]
|
export const USER_IDS_BALANCE_NO_LIMIT = [...SN_USER_IDS, USER_ID.anon, USER_ID.ad]
|
||||||
|
|
||||||
// WIP ultimately subject to this list: https://ofac.treasury.gov/sanctions-programs-and-country-information
|
// WIP ultimately subject to this list: https://ofac.treasury.gov/sanctions-programs-and-country-information
|
||||||
// From lawyers: north korea, cuba, iran, ukraine, syria
|
// From lawyers: north korea, cuba, iran, ukraine, syria
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import webPush from 'web-push'
|
import webPush from 'web-push'
|
||||||
import removeMd from 'remove-markdown'
|
import removeMd from 'remove-markdown'
|
||||||
import { ANON_USER_ID, COMMENT_DEPTH_LIMIT, FOUND_BLURBS, LOST_BLURBS } from './constants'
|
import { USER_ID, COMMENT_DEPTH_LIMIT, FOUND_BLURBS, LOST_BLURBS } from './constants'
|
||||||
import { msatsToSats, numWithUnits } from './format'
|
import { msatsToSats, numWithUnits } from './format'
|
||||||
import models from '@/api/models'
|
import models from '@/api/models'
|
||||||
import { isMuted } from '@/lib/user'
|
import { isMuted } from '@/lib/user'
|
||||||
@ -179,7 +179,7 @@ export const notifyTerritorySubscribers = async ({ models, item }) => {
|
|||||||
|
|
||||||
export const notifyItemParents = async ({ models, item, me }) => {
|
export const notifyItemParents = async ({ models, item, me }) => {
|
||||||
try {
|
try {
|
||||||
const user = await models.user.findUnique({ where: { id: me?.id || ANON_USER_ID } })
|
const user = await models.user.findUnique({ where: { id: me?.id || USER_ID.anon } })
|
||||||
const parents = await models.$queryRawUnsafe(
|
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 ' +
|
'SELECT DISTINCT p."userId" FROM "Item" i JOIN "Item" p ON p.path @> i.path WHERE i.id = $1 and p."userId" <> $2 ' +
|
||||||
'AND NOT EXISTS (SELECT 1 FROM "Mute" m WHERE m."muterId" = p."userId" AND m."mutedId" = $2)',
|
'AND NOT EXISTS (SELECT 1 FROM "Mute" m WHERE m."muterId" = p."userId" AND m."mutedId" = $2)',
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { deleteObjects } from '@/api/s3'
|
import { deleteObjects } from '@/api/s3'
|
||||||
import { ANON_USER_ID } from '@/lib/constants'
|
import { USER_ID } from '@/lib/constants'
|
||||||
|
|
||||||
export async function deleteUnusedImages ({ models }) {
|
export async function deleteUnusedImages ({ models }) {
|
||||||
// delete all images in database and S3 which weren't paid in the last 24 hours
|
// delete all images in database and S3 which weren't paid in the last 24 hours
|
||||||
@ -14,7 +14,7 @@ export async function deleteUnusedImages ({ models }) {
|
|||||||
AND NOT EXISTS (SELECT * FROM users WHERE "photoId" = "Upload".id)
|
AND NOT EXISTS (SELECT * FROM users WHERE "photoId" = "Upload".id)
|
||||||
AND NOT EXISTS (SELECT * FROM "Item" WHERE "uploadId" = "Upload".id)
|
AND NOT EXISTS (SELECT * FROM "Item" WHERE "uploadId" = "Upload".id)
|
||||||
))
|
))
|
||||||
AND created_at < date_trunc('hour', now() - CASE WHEN "userId" = ${ANON_USER_ID} THEN interval '1 hour' ELSE interval '24 hours' END)`
|
AND created_at < date_trunc('hour', now() - CASE WHEN "userId" = ${USER_ID.anon} THEN interval '1 hour' ELSE interval '24 hours' END)`
|
||||||
|
|
||||||
const s3Keys = unpaidImages.map(({ id }) => id)
|
const s3Keys = unpaidImages.map(({ id }) => id)
|
||||||
if (s3Keys.length === 0) {
|
if (s3Keys.length === 0) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import * as math from 'mathjs'
|
import * as math from 'mathjs'
|
||||||
import { ANON_USER_ID, SN_USER_IDS } from '@/lib/constants.js'
|
import { USER_ID, SN_USER_IDS } from '@/lib/constants.js'
|
||||||
|
|
||||||
export async function trust ({ boss, models }) {
|
export async function trust ({ boss, models }) {
|
||||||
try {
|
try {
|
||||||
@ -127,7 +127,7 @@ async function getGraph (models) {
|
|||||||
FROM "ItemAct"
|
FROM "ItemAct"
|
||||||
JOIN "Item" ON "Item".id = "ItemAct"."itemId" AND "ItemAct".act IN ('FEE', 'TIP', 'DONT_LIKE_THIS')
|
JOIN "Item" ON "Item".id = "ItemAct"."itemId" AND "ItemAct".act IN ('FEE', 'TIP', 'DONT_LIKE_THIS')
|
||||||
AND "Item"."parentId" IS NULL AND NOT "Item".bio AND "Item"."userId" <> "ItemAct"."userId"
|
AND "Item"."parentId" IS NULL AND NOT "Item".bio AND "Item"."userId" <> "ItemAct"."userId"
|
||||||
JOIN users ON "ItemAct"."userId" = users.id AND users.id <> ${ANON_USER_ID}
|
JOIN users ON "ItemAct"."userId" = users.id AND users.id <> ${USER_ID.anon}
|
||||||
GROUP BY user_id, name, item_id, user_at, against
|
GROUP BY user_id, name, item_id, user_at, against
|
||||||
HAVING CASE WHEN
|
HAVING CASE WHEN
|
||||||
"ItemAct".act = 'DONT_LIKE_THIS' THEN sum("ItemAct".msats) > ${AGAINST_MSAT_MIN}
|
"ItemAct".act = 'DONT_LIKE_THIS' THEN sum("ItemAct".msats) > ${AGAINST_MSAT_MIN}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user