2024-11-13 01:00:51 +00:00
|
|
|
import { PAID_ACTION_PAYMENT_METHODS, USER_ID } from '@/lib/constants'
|
2024-09-13 14:26:08 +00:00
|
|
|
import { uploadFees } from '../resolvers/upload'
|
2024-07-01 17:02:29 +00:00
|
|
|
import { getItemMentions, getMentions, performBotBehavior } from './lib/item'
|
|
|
|
import { notifyItemMention, notifyMention } from '@/lib/webPush'
|
|
|
|
import { satsToMsats } from '@/lib/format'
|
|
|
|
|
2024-09-13 15:11:19 +00:00
|
|
|
export const anonable = true
|
2024-11-13 01:00:51 +00:00
|
|
|
|
|
|
|
export const paymentMethods = [
|
|
|
|
PAID_ACTION_PAYMENT_METHODS.FEE_CREDIT,
|
|
|
|
PAID_ACTION_PAYMENT_METHODS.PESSIMISTIC
|
|
|
|
]
|
2024-07-01 17:02:29 +00:00
|
|
|
|
2024-10-02 23:12:49 +00:00
|
|
|
export async function getCost ({ id, boost = 0, uploadIds, bio }, { me, models }) {
|
2024-07-01 17:02:29 +00:00
|
|
|
// the only reason updating items costs anything is when it has new uploads
|
|
|
|
// or more boost
|
|
|
|
const old = await models.item.findUnique({ where: { id: parseInt(id) } })
|
2024-09-13 14:26:08 +00:00
|
|
|
const { totalFeesMsats } = await uploadFees(uploadIds, { models, me })
|
2024-09-24 20:42:32 +00:00
|
|
|
const cost = BigInt(totalFeesMsats) + satsToMsats(boost - old.boost)
|
|
|
|
|
|
|
|
if (cost > 0 && old.invoiceActionState && old.invoiceActionState !== 'PAID') {
|
|
|
|
throw new Error('creation invoice not paid')
|
|
|
|
}
|
|
|
|
|
2024-10-02 23:14:25 +00:00
|
|
|
return cost
|
2024-07-01 17:02:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export async function perform (args, context) {
|
2024-09-13 15:11:19 +00:00
|
|
|
const { id, boost = 0, uploadIds = [], options: pollOptions = [], forwardUsers: itemForwards = [], ...data } = args
|
2024-10-08 16:48:19 +00:00
|
|
|
const { tx, me } = context
|
2024-07-01 17:02:29 +00:00
|
|
|
const old = await tx.item.findUnique({
|
|
|
|
where: { id: parseInt(id) },
|
|
|
|
include: {
|
|
|
|
threadSubscriptions: true,
|
|
|
|
mentions: true,
|
|
|
|
itemForwards: true,
|
|
|
|
itemReferrers: true,
|
|
|
|
itemUploads: true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2024-09-19 18:13:14 +00:00
|
|
|
const newBoost = boost - old.boost
|
2024-07-01 17:02:29 +00:00
|
|
|
const itemActs = []
|
2024-09-19 18:13:14 +00:00
|
|
|
if (newBoost > 0) {
|
|
|
|
const boostMsats = satsToMsats(newBoost)
|
2024-07-01 17:02:29 +00:00
|
|
|
itemActs.push({
|
|
|
|
msats: boostMsats, act: 'BOOST', userId: me?.id || USER_ID.anon
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// createMany is the set difference of the new - old
|
|
|
|
// deleteMany is the set difference of the old - new
|
|
|
|
// updateMany is the intersection of the old and new
|
|
|
|
const difference = (a = [], b = [], key = 'userId') => a.filter(x => !b.find(y => y[key] === x[key]))
|
|
|
|
const intersectionMerge = (a = [], b = [], key) => a.filter(x => b.find(y => y.userId === x.userId))
|
|
|
|
.map(x => ({ [key]: x[key], ...b.find(y => y.userId === x.userId) }))
|
|
|
|
|
|
|
|
const mentions = await getMentions(args, context)
|
|
|
|
const itemMentions = await getItemMentions(args, context)
|
|
|
|
const itemUploads = uploadIds.map(id => ({ uploadId: id }))
|
|
|
|
|
|
|
|
await tx.upload.updateMany({
|
|
|
|
where: { id: { in: uploadIds } },
|
|
|
|
data: { paid: true }
|
|
|
|
})
|
|
|
|
|
2024-09-19 18:13:14 +00:00
|
|
|
// we put boost in the where clause because we don't want to update the boost
|
|
|
|
// if it has changed concurrently
|
2024-10-08 16:48:19 +00:00
|
|
|
await tx.item.update({
|
2024-09-19 18:13:14 +00:00
|
|
|
where: { id: parseInt(id), boost: old.boost },
|
2024-07-01 17:02:29 +00:00
|
|
|
data: {
|
|
|
|
...data,
|
2024-09-19 18:13:14 +00:00
|
|
|
boost: {
|
|
|
|
increment: newBoost
|
|
|
|
},
|
2024-07-01 17:02:29 +00:00
|
|
|
pollOptions: {
|
|
|
|
createMany: {
|
|
|
|
data: pollOptions?.map(option => ({ option }))
|
|
|
|
}
|
|
|
|
},
|
|
|
|
itemUploads: {
|
|
|
|
create: difference(itemUploads, old.itemUploads, 'uploadId').map(({ uploadId }) => ({ uploadId })),
|
|
|
|
deleteMany: {
|
|
|
|
uploadId: {
|
|
|
|
in: difference(old.itemUploads, itemUploads, 'uploadId').map(({ uploadId }) => uploadId)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
itemActs: {
|
|
|
|
createMany: {
|
|
|
|
data: itemActs
|
|
|
|
}
|
|
|
|
},
|
|
|
|
itemForwards: {
|
|
|
|
deleteMany: {
|
|
|
|
userId: {
|
|
|
|
in: difference(old.itemForwards, itemForwards).map(({ userId }) => userId)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
createMany: {
|
|
|
|
data: difference(itemForwards, old.itemForwards)
|
|
|
|
},
|
|
|
|
update: intersectionMerge(old.itemForwards, itemForwards, 'id').map(({ id, ...data }) => ({
|
|
|
|
where: { id },
|
|
|
|
data
|
|
|
|
}))
|
|
|
|
},
|
|
|
|
threadSubscriptions: {
|
|
|
|
deleteMany: {
|
|
|
|
userId: {
|
|
|
|
in: difference(old.itemForwards, itemForwards).map(({ userId }) => userId)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
createMany: {
|
|
|
|
data: difference(itemForwards, old.itemForwards).map(({ userId }) => ({ userId }))
|
|
|
|
}
|
|
|
|
},
|
|
|
|
mentions: {
|
|
|
|
deleteMany: {
|
|
|
|
userId: {
|
|
|
|
in: difference(old.mentions, mentions).map(({ userId }) => userId)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
createMany: {
|
|
|
|
data: difference(mentions, old.mentions)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
itemReferrers: {
|
|
|
|
deleteMany: {
|
|
|
|
refereeId: {
|
|
|
|
in: difference(old.itemReferrers, itemMentions, 'refereeId').map(({ refereeId }) => refereeId)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
create: difference(itemMentions, old.itemReferrers, 'refereeId')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2024-09-19 18:13:14 +00:00
|
|
|
await tx.$executeRaw`
|
|
|
|
INSERT INTO pgboss.job (name, data, retrylimit, retrybackoff, startafter, expirein)
|
|
|
|
VALUES ('imgproxy', jsonb_build_object('id', ${id}::INTEGER), 21, true,
|
|
|
|
now() + interval '5 seconds', interval '1 day')`
|
|
|
|
|
|
|
|
if (newBoost > 0) {
|
|
|
|
await tx.$executeRaw`
|
|
|
|
INSERT INTO pgboss.job (name, data, retrylimit, retrybackoff, startafter, expirein)
|
|
|
|
VALUES ('expireBoost', jsonb_build_object('id', ${id}::INTEGER), 21, true,
|
|
|
|
now() + interval '30 days', interval '40 days')`
|
|
|
|
}
|
2024-07-01 17:02:29 +00:00
|
|
|
|
|
|
|
await performBotBehavior(args, context)
|
|
|
|
|
2024-10-08 16:48:19 +00:00
|
|
|
// ltree is unsupported in Prisma, so we have to query it manually (FUCK!)
|
|
|
|
return (await tx.$queryRaw`
|
|
|
|
SELECT *, ltree2text(path) AS path, created_at AS "createdAt", updated_at AS "updatedAt"
|
|
|
|
FROM "Item" WHERE id = ${parseInt(id)}::INTEGER`
|
|
|
|
)[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function nonCriticalSideEffects ({ invoice, id }, { models }) {
|
|
|
|
const item = await models.item.findFirst({
|
|
|
|
where: invoice ? { invoiceId: invoice.id } : { id: parseInt(id) },
|
|
|
|
include: {
|
|
|
|
mentions: true,
|
|
|
|
itemReferrers: { include: { refereeItem: true } }
|
|
|
|
}
|
|
|
|
})
|
2024-07-01 20:51:59 +00:00
|
|
|
// compare timestamps to only notify if mention or item referral was just created to avoid duplicates on edits
|
2024-07-01 17:02:29 +00:00
|
|
|
for (const { userId, createdAt } of item.mentions) {
|
2024-07-01 20:51:59 +00:00
|
|
|
if (item.updatedAt.getTime() !== createdAt.getTime()) continue
|
2024-07-01 17:02:29 +00:00
|
|
|
notifyMention({ models, item, userId }).catch(console.error)
|
|
|
|
}
|
|
|
|
for (const { refereeItem, createdAt } of item.itemReferrers) {
|
2024-07-01 20:51:59 +00:00
|
|
|
if (item.updatedAt.getTime() !== createdAt.getTime()) continue
|
2024-07-01 17:02:29 +00:00
|
|
|
notifyItemMention({ models, referrerItem: item, refereeItem }).catch(console.error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function describe ({ id, parentId }, context) {
|
|
|
|
return `SN: update ${parentId ? `reply to #${parentId}` : 'post'}`
|
|
|
|
}
|