Handle uploads in territory descriptions (#2379)
* Remove unused parameter * Mark uploads as paid on territory create and update * Refactor upload expiry check * Check upload expiry on territory create * Include upload fees in territory create/update cost * Also check for expired uploads on edits * Find deleted uploads with one query
This commit is contained in:
parent
45acbaa4fa
commit
6d244a5de6
@ -3,6 +3,7 @@ import { notifyItemMention, notifyItemParents, notifyMention, notifyTerritorySub
|
|||||||
import { getItemMentions, getMentions, performBotBehavior } from './lib/item'
|
import { getItemMentions, getMentions, performBotBehavior } from './lib/item'
|
||||||
import { msatsToSats, satsToMsats } from '@/lib/format'
|
import { msatsToSats, satsToMsats } from '@/lib/format'
|
||||||
import { GqlInputError } from '@/lib/error'
|
import { GqlInputError } from '@/lib/error'
|
||||||
|
import { throwOnExpiredUploads } from '@/api/resolvers/upload'
|
||||||
|
|
||||||
export const anonable = true
|
export const anonable = true
|
||||||
|
|
||||||
@ -61,15 +62,7 @@ export async function perform (args, context) {
|
|||||||
const { tx, me, cost } = context
|
const { tx, me, cost } = context
|
||||||
const boostMsats = satsToMsats(boost)
|
const boostMsats = satsToMsats(boost)
|
||||||
|
|
||||||
const deletedUploads = []
|
await throwOnExpiredUploads(uploadIds, { tx })
|
||||||
for (const uploadId of uploadIds) {
|
|
||||||
if (!await tx.upload.findUnique({ where: { id: uploadId } })) {
|
|
||||||
deletedUploads.push(uploadId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (deletedUploads.length > 0) {
|
|
||||||
throw new Error(`upload(s) ${deletedUploads.join(', ')} are expired, consider reuploading.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
let invoiceData = {}
|
let invoiceData = {}
|
||||||
if (invoiceId) {
|
if (invoiceId) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { PAID_ACTION_PAYMENT_METHODS, USER_ID } from '@/lib/constants'
|
import { PAID_ACTION_PAYMENT_METHODS, USER_ID } from '@/lib/constants'
|
||||||
import { uploadFees } from '../resolvers/upload'
|
import { throwOnExpiredUploads, uploadFees } from '@/api/resolvers/upload'
|
||||||
import { getItemMentions, getMentions, performBotBehavior } from './lib/item'
|
import { getItemMentions, getMentions, performBotBehavior } from './lib/item'
|
||||||
import { notifyItemMention, notifyMention } from '@/lib/webPush'
|
import { notifyItemMention, notifyMention } from '@/lib/webPush'
|
||||||
import { satsToMsats } from '@/lib/format'
|
import { satsToMsats } from '@/lib/format'
|
||||||
@ -60,6 +60,7 @@ export async function perform (args, context) {
|
|||||||
const itemMentions = await getItemMentions(args, context)
|
const itemMentions = await getItemMentions(args, context)
|
||||||
const itemUploads = uploadIds.map(id => ({ uploadId: id }))
|
const itemUploads = uploadIds.map(id => ({ uploadId: id }))
|
||||||
|
|
||||||
|
await throwOnExpiredUploads(uploadIds, { tx })
|
||||||
await tx.upload.updateMany({
|
await tx.upload.updateMany({
|
||||||
where: { id: { in: uploadIds } },
|
where: { id: { in: uploadIds } },
|
||||||
data: { paid: true }
|
data: { paid: true }
|
||||||
|
@ -2,6 +2,7 @@ import { PAID_ACTION_PAYMENT_METHODS, TERRITORY_PERIOD_COST } from '@/lib/consta
|
|||||||
import { satsToMsats } from '@/lib/format'
|
import { satsToMsats } from '@/lib/format'
|
||||||
import { nextBilling } from '@/lib/territory'
|
import { nextBilling } from '@/lib/territory'
|
||||||
import { initialTrust } from './lib/territory'
|
import { initialTrust } from './lib/territory'
|
||||||
|
import { throwOnExpiredUploads, uploadFees } from '@/api/resolvers/upload'
|
||||||
|
|
||||||
export const anonable = false
|
export const anonable = false
|
||||||
|
|
||||||
@ -11,8 +12,9 @@ export const paymentMethods = [
|
|||||||
PAID_ACTION_PAYMENT_METHODS.PESSIMISTIC
|
PAID_ACTION_PAYMENT_METHODS.PESSIMISTIC
|
||||||
]
|
]
|
||||||
|
|
||||||
export async function getCost ({ billingType }) {
|
export async function getCost ({ billingType, uploadIds }, { models, me }) {
|
||||||
return satsToMsats(TERRITORY_PERIOD_COST(billingType))
|
const { totalFees } = await uploadFees(uploadIds, { models, me })
|
||||||
|
return satsToMsats(TERRITORY_PERIOD_COST(billingType) + totalFees)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function perform ({ invoiceId, ...data }, { me, cost, tx }) {
|
export async function perform ({ invoiceId, ...data }, { me, cost, tx }) {
|
||||||
@ -21,6 +23,19 @@ export async function perform ({ invoiceId, ...data }, { me, cost, tx }) {
|
|||||||
const billedLastAt = new Date()
|
const billedLastAt = new Date()
|
||||||
const billPaidUntil = nextBilling(billedLastAt, billingType)
|
const billPaidUntil = nextBilling(billedLastAt, billingType)
|
||||||
|
|
||||||
|
await throwOnExpiredUploads(data.uploadIds, { tx })
|
||||||
|
if (data.uploadIds.length > 0) {
|
||||||
|
await tx.upload.updateMany({
|
||||||
|
where: {
|
||||||
|
id: { in: data.uploadIds }
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
paid: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
delete data.uploadIds
|
||||||
|
|
||||||
const sub = await tx.sub.create({
|
const sub = await tx.sub.create({
|
||||||
data: {
|
data: {
|
||||||
...data,
|
...data,
|
||||||
|
@ -2,6 +2,7 @@ import { PAID_ACTION_PAYMENT_METHODS, TERRITORY_PERIOD_COST } from '@/lib/consta
|
|||||||
import { satsToMsats } from '@/lib/format'
|
import { satsToMsats } from '@/lib/format'
|
||||||
import { proratedBillingCost } from '@/lib/territory'
|
import { proratedBillingCost } from '@/lib/territory'
|
||||||
import { datePivot } from '@/lib/time'
|
import { datePivot } from '@/lib/time'
|
||||||
|
import { throwOnExpiredUploads, uploadFees } from '@/api/resolvers/upload'
|
||||||
|
|
||||||
export const anonable = false
|
export const anonable = false
|
||||||
|
|
||||||
@ -11,18 +12,16 @@ export const paymentMethods = [
|
|||||||
PAID_ACTION_PAYMENT_METHODS.PESSIMISTIC
|
PAID_ACTION_PAYMENT_METHODS.PESSIMISTIC
|
||||||
]
|
]
|
||||||
|
|
||||||
export async function getCost ({ oldName, billingType }, { models }) {
|
export async function getCost ({ oldName, billingType, uploadIds }, { models, me }) {
|
||||||
const oldSub = await models.sub.findUnique({
|
const oldSub = await models.sub.findUnique({
|
||||||
where: {
|
where: {
|
||||||
name: oldName
|
name: oldName
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const cost = proratedBillingCost(oldSub, billingType)
|
const { totalFees } = await uploadFees(uploadIds, { models, me })
|
||||||
if (!cost) {
|
|
||||||
return 0n
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const cost = proratedBillingCost(oldSub, billingType) + totalFees
|
||||||
return satsToMsats(cost)
|
return satsToMsats(cost)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +62,19 @@ export async function perform ({ oldName, invoiceId, ...data }, { me, cost, tx }
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await throwOnExpiredUploads(data.uploadIds, { tx })
|
||||||
|
if (data.uploadIds.length > 0) {
|
||||||
|
await tx.upload.updateMany({
|
||||||
|
where: {
|
||||||
|
id: { in: data.uploadIds }
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
paid: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
delete data.uploadIds
|
||||||
|
|
||||||
return await tx.sub.update({
|
return await tx.sub.update({
|
||||||
data,
|
data,
|
||||||
where: {
|
where: {
|
||||||
|
@ -1506,7 +1506,7 @@ export const updateItem = async (parent, { sub: subName, forward, hash, hmac, ..
|
|||||||
item = { subName, ...item }
|
item = { subName, ...item }
|
||||||
item.forwardUsers = await getForwardUsers(models, forward)
|
item.forwardUsers = await getForwardUsers(models, forward)
|
||||||
}
|
}
|
||||||
item.uploadIds = uploadIdsFromText(item.text, { models })
|
item.uploadIds = uploadIdsFromText(item.text)
|
||||||
|
|
||||||
// never change author of item
|
// never change author of item
|
||||||
item.userId = old.userId
|
item.userId = old.userId
|
||||||
@ -1525,7 +1525,7 @@ export const createItem = async (parent, { forward, ...item }, { me, models, lnd
|
|||||||
item.userId = me ? Number(me.id) : USER_ID.anon
|
item.userId = me ? Number(me.id) : USER_ID.anon
|
||||||
|
|
||||||
item.forwardUsers = await getForwardUsers(models, forward)
|
item.forwardUsers = await getForwardUsers(models, forward)
|
||||||
item.uploadIds = uploadIdsFromText(item.text, { models })
|
item.uploadIds = uploadIdsFromText(item.text)
|
||||||
|
|
||||||
if (item.url && !isJob(item)) {
|
if (item.url && !isJob(item)) {
|
||||||
item.url = ensureProtocol(item.url)
|
item.url = ensureProtocol(item.url)
|
||||||
|
@ -5,6 +5,7 @@ import { viewGroup } from './growth'
|
|||||||
import { notifyTerritoryTransfer } from '@/lib/webPush'
|
import { notifyTerritoryTransfer } from '@/lib/webPush'
|
||||||
import performPaidAction from '../paidAction'
|
import performPaidAction from '../paidAction'
|
||||||
import { GqlAuthenticationError, GqlInputError } from '@/lib/error'
|
import { GqlAuthenticationError, GqlInputError } from '@/lib/error'
|
||||||
|
import { uploadIdsFromText } from './upload'
|
||||||
|
|
||||||
export async function getSub (parent, { name }, { models, me }) {
|
export async function getSub (parent, { name }, { models, me }) {
|
||||||
if (!name) return null
|
if (!name) return null
|
||||||
@ -210,6 +211,8 @@ export default {
|
|||||||
|
|
||||||
await validateSchema(territorySchema, data, { models, me, sub: { name: data.oldName } })
|
await validateSchema(territorySchema, data, { models, me, sub: { name: data.oldName } })
|
||||||
|
|
||||||
|
data.uploadIds = uploadIdsFromText(data.desc)
|
||||||
|
|
||||||
if (data.oldName) {
|
if (data.oldName) {
|
||||||
return await updateSub(parent, data, { me, models, lnd })
|
return await updateSub(parent, data, { me, models, lnd })
|
||||||
} else {
|
} else {
|
||||||
|
@ -54,7 +54,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function uploadIdsFromText (text, { models }) {
|
export function uploadIdsFromText (text) {
|
||||||
if (!text) return []
|
if (!text) return []
|
||||||
return [...new Set([...text.matchAll(AWS_S3_URL_REGEXP)].map(m => Number(m[1])))]
|
return [...new Set([...text.matchAll(AWS_S3_URL_REGEXP)].map(m => Number(m[1])))]
|
||||||
}
|
}
|
||||||
@ -68,3 +68,19 @@ export async function uploadFees (s3Keys, { models, me }) {
|
|||||||
const totalFees = msatsToSats(totalFeesMsats)
|
const totalFees = msatsToSats(totalFeesMsats)
|
||||||
return { ...info, uploadFees, totalFees, totalFeesMsats }
|
return { ...info, uploadFees, totalFees, totalFeesMsats }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function throwOnExpiredUploads (uploadIds, { tx }) {
|
||||||
|
if (uploadIds.length === 0) return
|
||||||
|
|
||||||
|
const existingUploads = await tx.upload.findMany({
|
||||||
|
where: { id: { in: uploadIds } },
|
||||||
|
select: { id: true }
|
||||||
|
})
|
||||||
|
|
||||||
|
const existingIds = new Set(existingUploads.map(upload => upload.id))
|
||||||
|
const deletedIds = uploadIds.filter(id => !existingIds.has(id))
|
||||||
|
|
||||||
|
if (deletedIds.length > 0) {
|
||||||
|
throw new Error(`upload(s) ${deletedIds.join(', ')} are expired, consider reuploading.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,7 +19,7 @@ export function purchasedType (sub) {
|
|||||||
export function proratedBillingCost (sub, newBillingType) {
|
export function proratedBillingCost (sub, newBillingType) {
|
||||||
if (!sub ||
|
if (!sub ||
|
||||||
sub.billingType === 'ONCE' ||
|
sub.billingType === 'ONCE' ||
|
||||||
sub.billingType === newBillingType.toUpperCase()) return null
|
sub.billingType === newBillingType.toUpperCase()) return 0
|
||||||
|
|
||||||
return TERRITORY_PERIOD_COST(newBillingType) - TERRITORY_PERIOD_COST(purchasedType(sub))
|
return TERRITORY_PERIOD_COST(newBillingType) - TERRITORY_PERIOD_COST(purchasedType(sub))
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user