Merge serializeInvoiceable with serialize without bug (#1051)
* Merge serializeInvoiceable with serialize * Rename to verifyPayment We already have a function named checkInvoice in the worker which can be confusing. Also, we don't need to export this function. * Use crypto.timingSafeEqual * Fix missing unwrap for item creation and update
This commit is contained in:
parent
c8480a4996
commit
f3c1ebefcf
|
@ -1,6 +1,6 @@
|
||||||
import { GraphQLError } from 'graphql'
|
import { GraphQLError } from 'graphql'
|
||||||
import { ensureProtocol, removeTracking, stripTrailingSlash } from '@/lib/url'
|
import { ensureProtocol, removeTracking, stripTrailingSlash } from '@/lib/url'
|
||||||
import serialize, { serializeInvoicable } from './serial'
|
import serialize from './serial'
|
||||||
import { decodeCursor, LIMIT, nextCursorEncoded } from '@/lib/cursor'
|
import { decodeCursor, LIMIT, nextCursorEncoded } from '@/lib/cursor'
|
||||||
import { getMetadata, metadataRuleSets } from 'page-metadata-parser'
|
import { getMetadata, metadataRuleSets } from 'page-metadata-parser'
|
||||||
import { ruleSet as publicationDateRuleSet } from '@/lib/timedate-scraper'
|
import { ruleSet as publicationDateRuleSet } from '@/lib/timedate-scraper'
|
||||||
|
@ -849,9 +849,9 @@ export default {
|
||||||
throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } })
|
throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } })
|
||||||
}
|
}
|
||||||
|
|
||||||
await serializeInvoicable(
|
await serialize(
|
||||||
models.$queryRawUnsafe(`${SELECT} FROM poll_vote($1::INTEGER, $2::INTEGER) AS "Item"`, Number(id), Number(me.id)),
|
models.$queryRawUnsafe(`${SELECT} FROM poll_vote($1::INTEGER, $2::INTEGER) AS "Item"`, Number(id), Number(me.id)),
|
||||||
{ me, models, lnd, hash, hmac }
|
{ models, lnd, me, hash, hmac }
|
||||||
)
|
)
|
||||||
|
|
||||||
return id
|
return id
|
||||||
|
@ -883,7 +883,6 @@ export default {
|
||||||
|
|
||||||
if (idempotent) {
|
if (idempotent) {
|
||||||
await serialize(
|
await serialize(
|
||||||
models,
|
|
||||||
models.$queryRaw`
|
models.$queryRaw`
|
||||||
SELECT
|
SELECT
|
||||||
item_act(${Number(id)}::INTEGER, ${me.id}::INTEGER, ${act}::"ItemActType",
|
item_act(${Number(id)}::INTEGER, ${me.id}::INTEGER, ${act}::"ItemActType",
|
||||||
|
@ -891,15 +890,16 @@ export default {
|
||||||
FROM "ItemAct"
|
FROM "ItemAct"
|
||||||
WHERE act IN ('TIP', 'FEE')
|
WHERE act IN ('TIP', 'FEE')
|
||||||
AND "itemId" = ${Number(id)}::INTEGER
|
AND "itemId" = ${Number(id)}::INTEGER
|
||||||
AND "userId" = ${me.id}::INTEGER)::INTEGER)`
|
AND "userId" = ${me.id}::INTEGER)::INTEGER)`,
|
||||||
|
{ models }
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
await serializeInvoicable(
|
await serialize(
|
||||||
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 || ANON_USER_ID}::INTEGER, ${act}::"ItemActType", ${Number(sats)}::INTEGER)`,
|
||||||
{ me, models, lnd, hash, hmac, enforceFee: sats }
|
{ models, lnd, me, hash, hmac, fee: sats }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1282,13 +1282,13 @@ export const updateItem = async (parent, { sub: subName, forward, options, ...it
|
||||||
const fwdUsers = await getForwardUsers(models, forward)
|
const fwdUsers = await getForwardUsers(models, forward)
|
||||||
|
|
||||||
const uploadIds = uploadIdsFromText(item.text, { models })
|
const uploadIds = uploadIdsFromText(item.text, { models })
|
||||||
const { totalFees: imgFees } = await imageFeesInfo(uploadIds, { models, me })
|
const { totalFees: imgFees } = await imageFeesInfo(uploadIds, { models, me });
|
||||||
|
|
||||||
item = await serializeInvoicable(
|
([item] = await serialize(
|
||||||
models.$queryRawUnsafe(`${SELECT} FROM update_item($1::JSONB, $2::JSONB, $3::JSONB, $4::INTEGER[]) AS "Item"`,
|
models.$queryRawUnsafe(`${SELECT} FROM update_item($1::JSONB, $2::JSONB, $3::JSONB, $4::INTEGER[]) AS "Item"`,
|
||||||
JSON.stringify(item), JSON.stringify(fwdUsers), JSON.stringify(options), uploadIds),
|
JSON.stringify(item), JSON.stringify(fwdUsers), JSON.stringify(options), uploadIds),
|
||||||
{ models, lnd, hash, hmac, me, enforceFee: imgFees }
|
{ models, lnd, me, hash, hmac, fee: imgFees }
|
||||||
)
|
))
|
||||||
|
|
||||||
await createMentions(item, models)
|
await createMentions(item, models)
|
||||||
|
|
||||||
|
@ -1320,23 +1320,23 @@ export const createItem = async (parent, { forward, options, ...item }, { me, mo
|
||||||
const uploadIds = uploadIdsFromText(item.text, { models })
|
const uploadIds = uploadIdsFromText(item.text, { models })
|
||||||
const { totalFees: imgFees } = await imageFeesInfo(uploadIds, { models, me })
|
const { totalFees: imgFees } = await imageFeesInfo(uploadIds, { models, me })
|
||||||
|
|
||||||
let enforceFee = 0
|
let fee = 0
|
||||||
if (!me) {
|
if (!me) {
|
||||||
if (item.parentId) {
|
if (item.parentId) {
|
||||||
enforceFee = ANON_FEE_MULTIPLIER
|
fee = ANON_FEE_MULTIPLIER
|
||||||
} else {
|
} else {
|
||||||
const sub = await models.sub.findUnique({ where: { name: item.subName } })
|
const sub = await models.sub.findUnique({ where: { name: item.subName } })
|
||||||
enforceFee = sub.baseCost * ANON_FEE_MULTIPLIER + (item.boost || 0)
|
fee = sub.baseCost * ANON_FEE_MULTIPLIER + (item.boost || 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
enforceFee += imgFees
|
fee += imgFees;
|
||||||
|
|
||||||
item = await serializeInvoicable(
|
([item] = await serialize(
|
||||||
models.$queryRawUnsafe(
|
models.$queryRawUnsafe(
|
||||||
`${SELECT} FROM create_item($1::JSONB, $2::JSONB, $3::JSONB, '${spamInterval}'::INTERVAL, $4::INTEGER[]) AS "Item"`,
|
`${SELECT} FROM create_item($1::JSONB, $2::JSONB, $3::JSONB, '${spamInterval}'::INTERVAL, $4::INTEGER[]) AS "Item"`,
|
||||||
JSON.stringify(item), JSON.stringify(fwdUsers), JSON.stringify(options), uploadIds),
|
JSON.stringify(item), JSON.stringify(fwdUsers), JSON.stringify(options), uploadIds),
|
||||||
{ models, lnd, hash, hmac, me, enforceFee }
|
{ models, lnd, me, hash, hmac, fee }
|
||||||
)
|
))
|
||||||
|
|
||||||
await createMentions(item, models)
|
await createMentions(item, models)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { GraphQLError } from 'graphql'
|
import { GraphQLError } from 'graphql'
|
||||||
import { amountSchema, ssValidate } from '@/lib/validate'
|
import { amountSchema, ssValidate } from '@/lib/validate'
|
||||||
import { serializeInvoicable } from './serial'
|
import serialize from './serial'
|
||||||
import { ANON_USER_ID } from '@/lib/constants'
|
import { ANON_USER_ID } from '@/lib/constants'
|
||||||
import { getItem } from './item'
|
import { getItem } from './item'
|
||||||
import { topUsers } from './user'
|
import { topUsers } from './user'
|
||||||
|
@ -168,9 +168,9 @@ export default {
|
||||||
donateToRewards: async (parent, { sats, hash, hmac }, { me, models, lnd }) => {
|
donateToRewards: async (parent, { sats, hash, hmac }, { me, models, lnd }) => {
|
||||||
await ssValidate(amountSchema, { amount: sats })
|
await ssValidate(amountSchema, { amount: sats })
|
||||||
|
|
||||||
await serializeInvoicable(
|
await serialize(
|
||||||
models.$queryRaw`SELECT donate(${sats}::INTEGER, ${me?.id || ANON_USER_ID}::INTEGER)`,
|
models.$queryRaw`SELECT donate(${sats}::INTEGER, ${me?.id || ANON_USER_ID}::INTEGER)`,
|
||||||
{ models, lnd, hash, hmac, me, enforceFee: sats }
|
{ models, lnd, me, hash, hmac, fee: sats }
|
||||||
)
|
)
|
||||||
|
|
||||||
return sats
|
return sats
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { GraphQLError } from 'graphql'
|
import { GraphQLError } from 'graphql'
|
||||||
|
import { timingSafeEqual } from 'crypto'
|
||||||
import retry from 'async-retry'
|
import retry from 'async-retry'
|
||||||
import Prisma from '@prisma/client'
|
import Prisma from '@prisma/client'
|
||||||
import { settleHodlInvoice } from 'ln-service'
|
import { settleHodlInvoice } from 'ln-service'
|
||||||
|
@ -6,13 +7,30 @@ import { createHmac } from './wallet'
|
||||||
import { msatsToSats, numWithUnits } from '@/lib/format'
|
import { msatsToSats, numWithUnits } from '@/lib/format'
|
||||||
import { BALANCE_LIMIT_MSATS } from '@/lib/constants'
|
import { BALANCE_LIMIT_MSATS } from '@/lib/constants'
|
||||||
|
|
||||||
export default async function serialize (models, ...calls) {
|
export default async function serialize (trx, { models, lnd, me, hash, hmac, fee }) {
|
||||||
return await retry(async bail => {
|
// wrap first argument in array if not array already
|
||||||
|
const isArray = Array.isArray(trx)
|
||||||
|
if (!isArray) trx = [trx]
|
||||||
|
|
||||||
|
// conditional queries can be added inline using && syntax
|
||||||
|
// we filter any falsy value out here
|
||||||
|
trx = trx.filter(q => !!q)
|
||||||
|
|
||||||
|
let invoice
|
||||||
|
if (hash) {
|
||||||
|
invoice = await verifyPayment(models, hash, hmac, fee)
|
||||||
|
trx = [
|
||||||
|
models.$executeRaw`SELECT confirm_invoice(${hash}, ${invoice.msatsReceived})`,
|
||||||
|
...trx
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
let results = await retry(async bail => {
|
||||||
try {
|
try {
|
||||||
const [, ...result] = await models.$transaction(
|
const [, ...results] = await models.$transaction(
|
||||||
[models.$executeRaw`SELECT ASSERT_SERIALIZED()`, ...calls],
|
[models.$executeRaw`SELECT ASSERT_SERIALIZED()`, ...trx],
|
||||||
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable })
|
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable })
|
||||||
return calls.length > 1 ? result : result[0]
|
return results
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
// two cases where we get insufficient funds:
|
// two cases where we get insufficient funds:
|
||||||
|
@ -64,38 +82,20 @@ export default async function serialize (models, ...calls) {
|
||||||
maxTimeout: 100,
|
maxTimeout: 100,
|
||||||
retries: 10
|
retries: 10
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
export async function serializeInvoicable (query, { models, lnd, hash, hmac, me, enforceFee }) {
|
|
||||||
if (!me && !hash) {
|
|
||||||
throw new Error('you must be logged in or pay')
|
|
||||||
}
|
|
||||||
|
|
||||||
let trx = Array.isArray(query) ? query : [query]
|
|
||||||
|
|
||||||
let invoice
|
|
||||||
if (hash) {
|
|
||||||
invoice = await checkInvoice(models, hash, hmac, enforceFee)
|
|
||||||
trx = [
|
|
||||||
models.$executeRaw`SELECT confirm_invoice(${hash}, ${invoice.msatsReceived})`,
|
|
||||||
...trx
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
let results = await serialize(models, ...trx)
|
|
||||||
|
|
||||||
if (hash) {
|
if (hash) {
|
||||||
if (invoice?.isHeld) { await settleHodlInvoice({ secret: invoice.preimage, lnd }) }
|
if (invoice?.isHeld) {
|
||||||
|
await settleHodlInvoice({ secret: invoice.preimage, lnd })
|
||||||
|
}
|
||||||
// remove first element since that is the confirmed invoice
|
// remove first element since that is the confirmed invoice
|
||||||
[, ...results] = results
|
results = results.slice(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there is only one result, return it directly, else the array
|
// if first argument was not an array, unwrap the result
|
||||||
results = results.flat(2)
|
return isArray ? results : results[0]
|
||||||
return results.length > 1 ? results : results[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkInvoice (models, hash, hmac, fee) {
|
async function verifyPayment (models, hash, hmac, fee) {
|
||||||
if (!hash) {
|
if (!hash) {
|
||||||
throw new GraphQLError('hash required', { extensions: { code: 'BAD_INPUT' } })
|
throw new GraphQLError('hash required', { extensions: { code: 'BAD_INPUT' } })
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ export async function checkInvoice (models, hash, hmac, fee) {
|
||||||
throw new GraphQLError('hmac required', { extensions: { code: 'BAD_INPUT' } })
|
throw new GraphQLError('hmac required', { extensions: { code: 'BAD_INPUT' } })
|
||||||
}
|
}
|
||||||
const hmac2 = createHmac(hash)
|
const hmac2 = createHmac(hash)
|
||||||
if (hmac !== hmac2) {
|
if (!timingSafeEqual(Buffer.from(hmac), Buffer.from(hmac2))) {
|
||||||
throw new GraphQLError('bad hmac', { extensions: { code: 'FORBIDDEN' } })
|
throw new GraphQLError('bad hmac', { extensions: { code: 'FORBIDDEN' } })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { GraphQLError } from 'graphql'
|
import { GraphQLError } from 'graphql'
|
||||||
import { serializeInvoicable } from './serial'
|
import serialize from './serial'
|
||||||
import { TERRITORY_COST_MONTHLY, TERRITORY_COST_ONCE, TERRITORY_COST_YEARLY, TERRITORY_PERIOD_COST } from '@/lib/constants'
|
import { TERRITORY_COST_MONTHLY, TERRITORY_COST_ONCE, TERRITORY_COST_YEARLY, TERRITORY_PERIOD_COST } from '@/lib/constants'
|
||||||
import { datePivot, whenRange } from '@/lib/time'
|
import { datePivot, whenRange } from '@/lib/time'
|
||||||
import { ssValidate, territorySchema } from '@/lib/validate'
|
import { ssValidate, territorySchema } from '@/lib/validate'
|
||||||
|
@ -246,9 +246,9 @@ export default {
|
||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = await serializeInvoicable(
|
const results = await serialize(
|
||||||
queries,
|
queries,
|
||||||
{ models, lnd, hash, hmac, me, enforceFee: sub.billingCost })
|
{ models, lnd, me, hash, hmac, fee: sub.billingCost })
|
||||||
return results[1]
|
return results[1]
|
||||||
},
|
},
|
||||||
toggleMuteSub: async (parent, { name }, { me, models }) => {
|
toggleMuteSub: async (parent, { name }, { me, models }) => {
|
||||||
|
@ -344,8 +344,9 @@ export default {
|
||||||
const billPaidUntil = nextBilling(new Date(), data.billingType)
|
const billPaidUntil = nextBilling(new Date(), data.billingType)
|
||||||
const cost = BigInt(1000) * BigInt(billingCost)
|
const cost = BigInt(1000) * BigInt(billingCost)
|
||||||
const newSub = { ...data, billPaidUntil, billingCost, userId: me.id, status: 'ACTIVE' }
|
const newSub = { ...data, billPaidUntil, billingCost, userId: me.id, status: 'ACTIVE' }
|
||||||
|
const isTransfer = oldSub.userId !== me.id
|
||||||
|
|
||||||
await serializeInvoicable([
|
await serialize([
|
||||||
models.user.update({
|
models.user.update({
|
||||||
where: {
|
where: {
|
||||||
id: me.id
|
id: me.id
|
||||||
|
@ -365,11 +366,11 @@ export default {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
models.sub.update({ where: { name }, data: newSub }),
|
models.sub.update({ where: { name }, data: newSub }),
|
||||||
oldSub.userId !== me.id && models.territoryTransfer.create({ data: { subName: name, oldUserId: oldSub.userId, newUserId: me.id } })
|
isTransfer && models.territoryTransfer.create({ data: { subName: name, oldUserId: oldSub.userId, newUserId: me.id } })
|
||||||
].filter(q => !!q),
|
],
|
||||||
{ models, lnd, hash, hmac, me, enforceFee: billingCost })
|
{ models, lnd, hash, me, hmac, fee: billingCost })
|
||||||
|
|
||||||
if (oldSub.userId !== me.id) notifyTerritoryTransfer({ models, sub: newSub, to: me })
|
if (isTransfer) notifyTerritoryTransfer({ models, sub: newSub, to: me })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Sub: {
|
Sub: {
|
||||||
|
@ -417,7 +418,7 @@ async function createSub (parent, data, { me, models, lnd, hash, hmac }) {
|
||||||
const cost = BigInt(1000) * BigInt(billingCost)
|
const cost = BigInt(1000) * BigInt(billingCost)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const results = await serializeInvoicable([
|
const results = await serialize([
|
||||||
// bill 'em
|
// bill 'em
|
||||||
models.user.update({
|
models.user.update({
|
||||||
where: {
|
where: {
|
||||||
|
@ -456,7 +457,7 @@ async function createSub (parent, data, { me, models, lnd, hash, hmac }) {
|
||||||
subName: data.name
|
subName: data.name
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
], { models, lnd, hash, hmac, me, enforceFee: billingCost })
|
], { models, lnd, me, hash, hmac, fee: billingCost })
|
||||||
|
|
||||||
return results[1]
|
return results[1]
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -511,7 +512,7 @@ async function updateSub (parent, { oldName, ...data }, { me, models, lnd, hash,
|
||||||
const proratedCost = proratedBillingCost(oldSub, data.billingType)
|
const proratedCost = proratedBillingCost(oldSub, data.billingType)
|
||||||
if (proratedCost > 0) {
|
if (proratedCost > 0) {
|
||||||
const cost = BigInt(1000) * BigInt(proratedCost)
|
const cost = BigInt(1000) * BigInt(proratedCost)
|
||||||
const results = await serializeInvoicable([
|
const results = await serialize([
|
||||||
models.user.update({
|
models.user.update({
|
||||||
where: {
|
where: {
|
||||||
id: me.id
|
id: me.id
|
||||||
|
@ -537,7 +538,7 @@ async function updateSub (parent, { oldName, ...data }, { me, models, lnd, hash,
|
||||||
userId: me.id
|
userId: me.id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
], { models, lnd, hash, hmac, me, enforceFee: proratedCost })
|
], { models, lnd, me, hash, hmac, fee: proratedCost })
|
||||||
return results[2]
|
return results[2]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -354,10 +354,12 @@ export default {
|
||||||
expires_at: expiresAt
|
expires_at: expiresAt
|
||||||
})
|
})
|
||||||
|
|
||||||
const [inv] = await serialize(models,
|
const [inv] = await serialize(
|
||||||
models.$queryRaw`SELECT * FROM create_invoice(${invoice.id}, ${hodlInvoice ? invoice.secret : null}::TEXT, ${invoice.request},
|
models.$queryRaw`SELECT * FROM create_invoice(${invoice.id}, ${hodlInvoice ? invoice.secret : null}::TEXT, ${invoice.request},
|
||||||
${expiresAt}::timestamp, ${amount * 1000}, ${user.id}::INTEGER, ${description}, NULL, NULL,
|
${expiresAt}::timestamp, ${amount * 1000}, ${user.id}::INTEGER, ${description}, NULL, NULL,
|
||||||
${invLimit}::INTEGER, ${balanceLimit})`)
|
${invLimit}::INTEGER, ${balanceLimit})`,
|
||||||
|
{ models }
|
||||||
|
)
|
||||||
|
|
||||||
// the HMAC is only returned during invoice creation
|
// the HMAC is only returned during invoice creation
|
||||||
// this makes sure that only the person who created this invoice
|
// this makes sure that only the person who created this invoice
|
||||||
|
@ -378,7 +380,7 @@ export default {
|
||||||
throw new GraphQLError('bad hmac', { extensions: { code: 'FORBIDDEN' } })
|
throw new GraphQLError('bad hmac', { extensions: { code: 'FORBIDDEN' } })
|
||||||
}
|
}
|
||||||
await cancelHodlInvoice({ id: hash, lnd })
|
await cancelHodlInvoice({ id: hash, lnd })
|
||||||
const inv = await serialize(models,
|
const inv = await serialize(
|
||||||
models.invoice.update({
|
models.invoice.update({
|
||||||
where: {
|
where: {
|
||||||
hash
|
hash
|
||||||
|
@ -386,7 +388,9 @@ export default {
|
||||||
data: {
|
data: {
|
||||||
cancelled: true
|
cancelled: true
|
||||||
}
|
}
|
||||||
}))
|
}),
|
||||||
|
{ models }
|
||||||
|
)
|
||||||
return inv
|
return inv
|
||||||
},
|
},
|
||||||
dropBolt11: async (parent, { id }, { me, models, lnd }) => {
|
dropBolt11: async (parent, { id }, { me, models, lnd }) => {
|
||||||
|
@ -660,9 +664,11 @@ export async function createWithdrawal (parent, { invoice, maxFee }, { me, model
|
||||||
const user = await models.user.findUnique({ where: { id: me.id } })
|
const user = await models.user.findUnique({ where: { id: me.id } })
|
||||||
|
|
||||||
// create withdrawl transactionally (id, bolt11, amount, fee)
|
// create withdrawl transactionally (id, bolt11, amount, fee)
|
||||||
const [withdrawl] = await serialize(models,
|
const [withdrawl] = await serialize(
|
||||||
models.$queryRaw`SELECT * FROM create_withdrawl(${decoded.id}, ${invoice},
|
models.$queryRaw`SELECT * FROM create_withdrawl(${decoded.id}, ${invoice},
|
||||||
${Number(decoded.mtokens)}, ${msatsFee}, ${user.name}, ${autoWithdraw})`)
|
${Number(decoded.mtokens)}, ${msatsFee}, ${user.name}, ${autoWithdraw})`,
|
||||||
|
{ models }
|
||||||
|
)
|
||||||
|
|
||||||
payViaPaymentRequest({
|
payViaPaymentRequest({
|
||||||
lnd,
|
lnd,
|
||||||
|
|
|
@ -80,11 +80,13 @@ export default async ({ query: { username, amount, nostr, comment, payerdata: pa
|
||||||
expires_at: expiresAt
|
expires_at: expiresAt
|
||||||
})
|
})
|
||||||
|
|
||||||
await serialize(models,
|
await serialize(
|
||||||
models.$queryRaw`SELECT * FROM create_invoice(${invoice.id}, NULL, ${invoice.request},
|
models.$queryRaw`SELECT * FROM create_invoice(${invoice.id}, NULL, ${invoice.request},
|
||||||
${expiresAt}::timestamp, ${Number(amount)}, ${user.id}::INTEGER, ${noteStr || description},
|
${expiresAt}::timestamp, ${Number(amount)}, ${user.id}::INTEGER, ${noteStr || description},
|
||||||
${comment || null}, ${parsedPayerData || null}::JSONB, ${INV_PENDING_LIMIT}::INTEGER,
|
${comment || null}, ${parsedPayerData || null}::JSONB, ${INV_PENDING_LIMIT}::INTEGER,
|
||||||
${USER_IDS_BALANCE_NO_LIMIT.includes(Number(user.id)) ? 0 : BALANCE_LIMIT_MSATS})`)
|
${USER_IDS_BALANCE_NO_LIMIT.includes(Number(user.id)) ? 0 : BALANCE_LIMIT_MSATS})`,
|
||||||
|
{ models }
|
||||||
|
)
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
pr: invoice.request,
|
pr: invoice.request,
|
||||||
|
|
|
@ -36,8 +36,10 @@ export async function getServerSideProps ({ req, res, query: { id, error = null
|
||||||
try {
|
try {
|
||||||
// attempt to send gift
|
// attempt to send gift
|
||||||
// catch any errors and just ignore them for now
|
// catch any errors and just ignore them for now
|
||||||
await serialize(models,
|
await serialize(
|
||||||
models.$queryRawUnsafe('SELECT invite_drain($1::INTEGER, $2::TEXT)', session.user.id, id))
|
models.$queryRawUnsafe('SELECT invite_drain($1::INTEGER, $2::TEXT)', session.user.id, id),
|
||||||
|
{ models }
|
||||||
|
)
|
||||||
const invite = await models.invite.findUnique({ where: { id } })
|
const invite = await models.invite.findUnique({ where: { id } })
|
||||||
notifyInvite(invite.userId)
|
notifyInvite(invite.userId)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -17,7 +17,6 @@ export async function auction ({ models }) {
|
||||||
|
|
||||||
// for each item, run serialized auction function
|
// for each item, run serialized auction function
|
||||||
items.forEach(async item => {
|
items.forEach(async item => {
|
||||||
await serialize(models,
|
await serialize(models.$executeRaw`SELECT run_auction(${item.id}::INTEGER)`, { models })
|
||||||
models.$executeRaw`SELECT run_auction(${item.id}::INTEGER)`)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,9 +79,11 @@ export async function earn ({ name }) {
|
||||||
console.log('stacker', earner.userId, 'earned', earnings, 'proportion', earner.proportion, 'rank', earner.rank, 'type', earner.type)
|
console.log('stacker', earner.userId, 'earned', earnings, 'proportion', earner.proportion, 'rank', earner.rank, 'type', earner.type)
|
||||||
|
|
||||||
if (earnings > 0) {
|
if (earnings > 0) {
|
||||||
await serialize(models,
|
await serialize(
|
||||||
models.$executeRaw`SELECT earn(${earner.userId}::INTEGER, ${earnings},
|
models.$executeRaw`SELECT earn(${earner.userId}::INTEGER, ${earnings},
|
||||||
${now}::timestamp without time zone, ${earner.type}::"EarnType", ${earner.id}::INTEGER, ${earner.rank}::INTEGER)`)
|
${now}::timestamp without time zone, ${earner.type}::"EarnType", ${earner.id}::INTEGER, ${earner.rank}::INTEGER)`,
|
||||||
|
{ models }
|
||||||
|
)
|
||||||
|
|
||||||
const userN = notifications[earner.userId] || {}
|
const userN = notifications[earner.userId] || {}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ export async function territoryBilling ({ data: { subName }, boss, models }) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const queries = paySubQueries(sub, models)
|
const queries = paySubQueries(sub, models)
|
||||||
await serialize(models, ...queries)
|
await serialize(queries, { models })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
await territoryStatusUpdate()
|
await territoryStatusUpdate()
|
||||||
|
@ -42,7 +42,7 @@ export async function territoryBilling ({ data: { subName }, boss, models }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function territoryRevenue ({ models }) {
|
export async function territoryRevenue ({ models }) {
|
||||||
await serialize(models,
|
await serialize(
|
||||||
models.$executeRaw`
|
models.$executeRaw`
|
||||||
WITH revenue AS (
|
WITH revenue AS (
|
||||||
SELECT coalesce(sum(msats), 0) as revenue, "subName", "userId"
|
SELECT coalesce(sum(msats), 0) as revenue, "subName", "userId"
|
||||||
|
@ -69,6 +69,7 @@ export async function territoryRevenue ({ models }) {
|
||||||
)
|
)
|
||||||
UPDATE users SET msats = users.msats + "SubActResult".msats
|
UPDATE users SET msats = users.msats + "SubActResult".msats
|
||||||
FROM "SubActResult"
|
FROM "SubActResult"
|
||||||
WHERE users.id = "SubActResult"."userId"`
|
WHERE users.id = "SubActResult"."userId"`,
|
||||||
|
{ models }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,10 +121,10 @@ async function checkInvoice ({ data: { hash }, boss, models, lnd }) {
|
||||||
// ALSO: is_confirmed and is_held are mutually exclusive
|
// ALSO: is_confirmed and is_held are mutually exclusive
|
||||||
// that is, a hold invoice will first be is_held but not is_confirmed
|
// that is, a hold invoice will first be is_held but not is_confirmed
|
||||||
// and once it's settled it will be is_confirmed but not is_held
|
// and once it's settled it will be is_confirmed but not is_held
|
||||||
const [[{ confirm_invoice: code }]] = await serialize(models,
|
const [[{ confirm_invoice: code }]] = await serialize([
|
||||||
models.$queryRaw`SELECT confirm_invoice(${inv.id}, ${Number(inv.received_mtokens)})`,
|
models.$queryRaw`SELECT confirm_invoice(${inv.id}, ${Number(inv.received_mtokens)})`,
|
||||||
models.invoice.update({ where: { hash }, data: { confirmedIndex: inv.confirmed_index } })
|
models.invoice.update({ where: { hash }, data: { confirmedIndex: inv.confirmed_index } })
|
||||||
)
|
], { models })
|
||||||
|
|
||||||
// don't send notifications for JIT invoices
|
// don't send notifications for JIT invoices
|
||||||
if (dbInv.preimage) return
|
if (dbInv.preimage) return
|
||||||
|
@ -143,7 +143,7 @@ async function checkInvoice ({ data: { hash }, boss, models, lnd }) {
|
||||||
// and without setting the user balance
|
// and without setting the user balance
|
||||||
// those will be set when the invoice is settled by user action
|
// those will be set when the invoice is settled by user action
|
||||||
const expiresAt = new Date(Math.min(dbInv.expiresAt, datePivot(new Date(), { seconds: 60 })))
|
const expiresAt = new Date(Math.min(dbInv.expiresAt, datePivot(new Date(), { seconds: 60 })))
|
||||||
return await serialize(models,
|
return await serialize([
|
||||||
models.$queryRaw`
|
models.$queryRaw`
|
||||||
INSERT INTO pgboss.job (name, data, retrylimit, retrybackoff, startafter)
|
INSERT INTO pgboss.job (name, data, retrylimit, retrybackoff, startafter)
|
||||||
VALUES ('finalizeHodlInvoice', jsonb_build_object('hash', ${hash}), 21, true, ${expiresAt})`,
|
VALUES ('finalizeHodlInvoice', jsonb_build_object('hash', ${hash}), 21, true, ${expiresAt})`,
|
||||||
|
@ -154,11 +154,12 @@ async function checkInvoice ({ data: { hash }, boss, models, lnd }) {
|
||||||
expiresAt,
|
expiresAt,
|
||||||
isHeld: true
|
isHeld: true
|
||||||
}
|
}
|
||||||
}))
|
})
|
||||||
|
], { models })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inv.is_canceled) {
|
if (inv.is_canceled) {
|
||||||
return await serialize(models,
|
return await serialize(
|
||||||
models.invoice.update({
|
models.invoice.update({
|
||||||
where: {
|
where: {
|
||||||
hash: inv.id
|
hash: inv.id
|
||||||
|
@ -166,7 +167,8 @@ async function checkInvoice ({ data: { hash }, boss, models, lnd }) {
|
||||||
data: {
|
data: {
|
||||||
cancelled: true
|
cancelled: true
|
||||||
}
|
}
|
||||||
}))
|
}), { models }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,8 +230,10 @@ async function checkWithdrawal ({ data: { hash }, boss, models, lnd }) {
|
||||||
if (wdrwl?.is_confirmed) {
|
if (wdrwl?.is_confirmed) {
|
||||||
const fee = Number(wdrwl.payment.fee_mtokens)
|
const fee = Number(wdrwl.payment.fee_mtokens)
|
||||||
const paid = Number(wdrwl.payment.mtokens) - fee
|
const paid = Number(wdrwl.payment.mtokens) - fee
|
||||||
const [{ confirm_withdrawl: code }] = await serialize(models, models.$queryRaw`
|
const [{ confirm_withdrawl: code }] = await serialize(
|
||||||
SELECT confirm_withdrawl(${dbWdrwl.id}::INTEGER, ${paid}, ${fee})`)
|
models.$queryRaw`SELECT confirm_withdrawl(${dbWdrwl.id}::INTEGER, ${paid}, ${fee})`,
|
||||||
|
{ models }
|
||||||
|
)
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
notifyWithdrawal(dbWdrwl.userId, wdrwl)
|
notifyWithdrawal(dbWdrwl.userId, wdrwl)
|
||||||
}
|
}
|
||||||
|
@ -245,9 +249,10 @@ async function checkWithdrawal ({ data: { hash }, boss, models, lnd }) {
|
||||||
status = 'ROUTE_NOT_FOUND'
|
status = 'ROUTE_NOT_FOUND'
|
||||||
}
|
}
|
||||||
|
|
||||||
await serialize(models,
|
await serialize(
|
||||||
models.$executeRaw`
|
models.$executeRaw`
|
||||||
SELECT reverse_withdrawl(${dbWdrwl.id}::INTEGER, ${status}::"WithdrawlStatus")`
|
SELECT reverse_withdrawl(${dbWdrwl.id}::INTEGER, ${status}::"WithdrawlStatus")`,
|
||||||
|
{ models }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue