diff --git a/api/resolvers/item.js b/api/resolvers/item.js index 3092bdab..cd65ff84 100644 --- a/api/resolvers/item.js +++ b/api/resolvers/item.js @@ -8,7 +8,8 @@ import { COMMENT_DEPTH_LIMIT, COMMENT_TYPE_QUERY, USER_ID, POLL_COST, ADMIN_ITEMS, GLOBAL_SEED, NOFOLLOW_LIMIT, UNKNOWN_LINK_REL, SN_ADMIN_IDS, - BOOST_MULT + BOOST_MULT, + ITEM_EDIT_SECONDS } from '@/lib/constants' import { msatsToSats } from '@/lib/format' import { parse } from 'tldts' @@ -1350,8 +1351,9 @@ export const updateItem = async (parent, { sub: subName, forward, hash, hmac, .. throw new GqlInputError('item is deleted') } - // author can edit their own item (except anon) const meId = Number(me?.id ?? USER_ID.anon) + + // author can edit their own item (except anon) const authorEdit = !!me && Number(old.userId) === meId // admins can edit special items const adminEdit = ADMIN_ITEMS.includes(old.id) && SN_ADMIN_IDS.includes(meId) @@ -1360,9 +1362,9 @@ export const updateItem = async (parent, { sub: subName, forward, hash, hmac, .. if (old.invoice?.hash && hash && hmac) { hmacEdit = old.invoice.hash === hash && verifyHmac(hash, hmac) } - // ownership permission check - if (!authorEdit && !adminEdit && !hmacEdit) { + const ownerEdit = authorEdit || adminEdit || hmacEdit + if (!ownerEdit) { throw new GqlInputError('item does not belong to you') } @@ -1379,12 +1381,11 @@ export const updateItem = async (parent, { sub: subName, forward, hash, hmac, .. const user = await models.user.findUnique({ where: { id: meId } }) - // prevent update if it's not explicitly allowed, not their bio, not their job and older than 10 minutes + // edits are only allowed for own items within 10 minutes but forever if it's their bio or a job const myBio = user.bioId === old.id - const timer = Date.now() < datePivot(new Date(old.invoicePaidAt ?? old.createdAt), { minutes: 10 }) - - // timer permission check - if (!adminEdit && !myBio && !timer && !isJob(item)) { + const timer = Date.now() < datePivot(new Date(old.invoicePaidAt ?? old.createdAt), { seconds: ITEM_EDIT_SECONDS }) + const canEdit = (timer && ownerEdit) || myBio || isJob(item) + if (!canEdit) { throw new GqlInputError('item can no longer be edited') } diff --git a/components/use-can-edit.js b/components/use-can-edit.js index bc31f17a..b97596e4 100644 --- a/components/use-can-edit.js +++ b/components/use-can-edit.js @@ -1,10 +1,10 @@ import { useEffect, useState } from 'react' import { datePivot } from '@/lib/time' import { useMe } from '@/components/me' -import { USER_ID } from '@/lib/constants' +import { ITEM_EDIT_SECONDS, USER_ID } from '@/lib/constants' export default function useCanEdit (item) { - const editThreshold = datePivot(new Date(item.invoice?.confirmedAt ?? item.createdAt), { minutes: 10 }) + const editThreshold = datePivot(new Date(item.invoice?.confirmedAt ?? item.createdAt), { seconds: ITEM_EDIT_SECONDS }) const { me } = useMe() // deleted items can never be edited and every item has a 10 minute edit window diff --git a/lib/constants.js b/lib/constants.js index 995253dc..066e6e6b 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -46,6 +46,7 @@ export const MAX_POST_TEXT_LENGTH = 100000 // 100k export const MAX_COMMENT_TEXT_LENGTH = 10000 // 10k export const MAX_TERRITORY_DESC_LENGTH = 1000 // 1k export const MAX_POLL_CHOICE_LENGTH = 40 +export const ITEM_EDIT_SECONDS = 600 export const ITEM_SPAM_INTERVAL = '10m' export const ANON_ITEM_SPAM_INTERVAL = '0' export const INV_PENDING_LIMIT = 100 diff --git a/lib/item.js b/lib/item.js index e44b53a3..33b0111e 100644 --- a/lib/item.js +++ b/lib/item.js @@ -10,7 +10,7 @@ export const defaultCommentSort = (pinned, bio, createdAt) => { return 'hot' } -export const isJob = item => item.subName !== 'jobs' +export const isJob = item => item.subName === 'jobs' // a delete directive preceded by a non word character that isn't a backtick const deletePattern = /\B@delete\s+in\s+(\d+)\s+(second|minute|hour|day|week|month|year)s?/gi