Skip to content

Commit 366c069

Browse files
committed
WIP execution logic
1 parent 7e93200 commit 366c069

20 files changed

+2365
-26
lines changed

api/pay/README.md

Lines changed: 371 additions & 0 deletions
Large diffs are not rendered by default.

api/pay/boost.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { PAID_ACTION_PAYMENT_METHODS } from '@/lib/constants'
2+
import { msatsToSats, satsToMsats } from '@/lib/format'
3+
4+
export const anonable = false
5+
6+
export const paymentMethods = [
7+
PAID_ACTION_PAYMENT_METHODS.FEE_CREDIT,
8+
PAID_ACTION_PAYMENT_METHODS.REWARD_SATS,
9+
PAID_ACTION_PAYMENT_METHODS.OPTIMISTIC
10+
]
11+
12+
export async function getCost ({ sats }) {
13+
return satsToMsats(sats)
14+
}
15+
16+
export async function perform ({ invoiceId, sats, id: itemId, ...args }, { me, cost, tx }) {
17+
itemId = parseInt(itemId)
18+
19+
let invoiceData = {}
20+
if (invoiceId) {
21+
invoiceData = { invoiceId, invoiceActionState: 'PENDING' }
22+
// store a reference to the item in the invoice
23+
await tx.invoice.update({
24+
where: { id: invoiceId },
25+
data: { actionId: itemId }
26+
})
27+
}
28+
29+
const act = await tx.itemAct.create({ data: { msats: cost, itemId, userId: me.id, act: 'BOOST', ...invoiceData } })
30+
31+
const [{ path }] = await tx.$queryRaw`
32+
SELECT ltree2text(path) as path FROM "Item" WHERE id = ${itemId}::INTEGER`
33+
return { id: itemId, sats, act: 'BOOST', path, actId: act.id }
34+
}
35+
36+
export async function retry ({ invoiceId, newInvoiceId }, { tx, cost }) {
37+
await tx.itemAct.updateMany({ where: { invoiceId }, data: { invoiceId: newInvoiceId, invoiceActionState: 'PENDING' } })
38+
const [{ id, path }] = await tx.$queryRaw`
39+
SELECT "Item".id, ltree2text(path) as path
40+
FROM "Item"
41+
JOIN "ItemAct" ON "Item".id = "ItemAct"."itemId"
42+
WHERE "ItemAct"."invoiceId" = ${newInvoiceId}::INTEGER`
43+
return { id, sats: msatsToSats(cost), act: 'BOOST', path }
44+
}
45+
46+
export async function onPaid ({ invoice, actId }, { tx }) {
47+
let itemAct
48+
if (invoice) {
49+
await tx.itemAct.updateMany({
50+
where: { invoiceId: invoice.id },
51+
data: {
52+
invoiceActionState: 'PAID'
53+
}
54+
})
55+
itemAct = await tx.itemAct.findFirst({ where: { invoiceId: invoice.id } })
56+
} else if (actId) {
57+
itemAct = await tx.itemAct.findFirst({ where: { id: actId } })
58+
} else {
59+
throw new Error('No invoice or actId')
60+
}
61+
62+
// increment boost on item
63+
await tx.item.update({
64+
where: { id: itemAct.itemId },
65+
data: {
66+
boost: { increment: msatsToSats(itemAct.msats) }
67+
}
68+
})
69+
70+
await tx.$executeRaw`
71+
INSERT INTO pgboss.job (name, data, retrylimit, retrybackoff, startafter, keepuntil)
72+
VALUES ('expireBoost', jsonb_build_object('id', ${itemAct.itemId}::INTEGER), 21, true,
73+
now() + interval '30 days', now() + interval '40 days')`
74+
}
75+
76+
export async function onFail ({ invoice }, { tx }) {
77+
await tx.itemAct.updateMany({ where: { invoiceId: invoice.id }, data: { invoiceActionState: 'FAILED' } })
78+
}
79+
80+
export async function describe ({ id: itemId, sats }, { actionId, cost }) {
81+
return `SN: boost ${sats ?? msatsToSats(cost)} sats to #${itemId ?? actionId}`
82+
}

api/pay/buyCredits.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { PAID_ACTION_PAYMENT_METHODS } from '@/lib/constants'
2+
import { satsToMsats } from '@/lib/format'
3+
4+
export const anonable = false
5+
6+
export const paymentMethods = [
7+
PAID_ACTION_PAYMENT_METHODS.REWARD_SATS,
8+
PAID_ACTION_PAYMENT_METHODS.PESSIMISTIC
9+
]
10+
11+
export async function getCost ({ credits }) {
12+
return satsToMsats(credits)
13+
}
14+
15+
export async function perform ({ credits }, { me, cost, tx }) {
16+
await tx.user.update({
17+
where: { id: me.id },
18+
data: {
19+
mcredits: {
20+
increment: cost
21+
}
22+
}
23+
})
24+
25+
return {
26+
credits
27+
}
28+
}
29+
30+
export async function describe () {
31+
return 'SN: buy fee credits'
32+
}

api/pay/donate.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { PAID_ACTION_PAYMENT_METHODS, USER_ID } from '@/lib/constants'
2+
import { satsToMsats } from '@/lib/format'
3+
4+
export const anonable = true
5+
6+
export const paymentMethods = [
7+
PAID_ACTION_PAYMENT_METHODS.FEE_CREDIT,
8+
PAID_ACTION_PAYMENT_METHODS.REWARD_SATS,
9+
PAID_ACTION_PAYMENT_METHODS.PESSIMISTIC
10+
]
11+
12+
export async function getCost ({ sats }) {
13+
return satsToMsats(sats)
14+
}
15+
16+
export async function perform ({ sats }, { me, tx }) {
17+
await tx.donation.create({
18+
data: {
19+
sats,
20+
userId: me?.id ?? USER_ID.anon
21+
}
22+
})
23+
24+
return { sats }
25+
}
26+
27+
export async function describe (args, context) {
28+
return 'SN: donate to rewards pool'
29+
}

api/pay/downZap.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { PAID_ACTION_PAYMENT_METHODS } from '@/lib/constants'
2+
import { msatsToSats, satsToMsats } from '@/lib/format'
3+
import { Prisma } from '@prisma/client'
4+
5+
export const anonable = false
6+
7+
export const paymentMethods = [
8+
PAID_ACTION_PAYMENT_METHODS.FEE_CREDIT,
9+
PAID_ACTION_PAYMENT_METHODS.REWARD_SATS,
10+
PAID_ACTION_PAYMENT_METHODS.OPTIMISTIC
11+
]
12+
13+
export async function getCost ({ sats }) {
14+
return satsToMsats(sats)
15+
}
16+
17+
export async function perform ({ invoiceId, sats, id: itemId }, { me, cost, tx }) {
18+
itemId = parseInt(itemId)
19+
20+
let invoiceData = {}
21+
if (invoiceId) {
22+
invoiceData = { invoiceId, invoiceActionState: 'PENDING' }
23+
// store a reference to the item in the invoice
24+
await tx.invoice.update({
25+
where: { id: invoiceId },
26+
data: { actionId: itemId }
27+
})
28+
}
29+
30+
const itemAct = await tx.itemAct.create({
31+
data: { msats: cost, itemId, userId: me.id, act: 'DONT_LIKE_THIS', ...invoiceData }
32+
})
33+
34+
const [{ path }] = await tx.$queryRaw`SELECT ltree2text(path) as path FROM "Item" WHERE id = ${itemId}::INTEGER`
35+
return { id: itemId, sats, act: 'DONT_LIKE_THIS', path, actId: itemAct.id }
36+
}
37+
38+
export async function retry ({ invoiceId, newInvoiceId }, { tx, cost }) {
39+
await tx.itemAct.updateMany({ where: { invoiceId }, data: { invoiceId: newInvoiceId, invoiceActionState: 'PENDING' } })
40+
const [{ id, path }] = await tx.$queryRaw`
41+
SELECT "Item".id, ltree2text(path) as path
42+
FROM "Item"
43+
JOIN "ItemAct" ON "Item".id = "ItemAct"."itemId"
44+
WHERE "ItemAct"."invoiceId" = ${newInvoiceId}::INTEGER`
45+
return { id, sats: msatsToSats(cost), act: 'DONT_LIKE_THIS', path }
46+
}
47+
48+
export async function onPaid ({ invoice, actId }, { tx }) {
49+
let itemAct
50+
if (invoice) {
51+
await tx.itemAct.updateMany({ where: { invoiceId: invoice.id }, data: { invoiceActionState: 'PAID' } })
52+
itemAct = await tx.itemAct.findFirst({ where: { invoiceId: invoice.id }, include: { item: true } })
53+
} else if (actId) {
54+
itemAct = await tx.itemAct.findUnique({ where: { id: actId }, include: { item: true } })
55+
} else {
56+
throw new Error('No invoice or actId')
57+
}
58+
59+
const msats = BigInt(itemAct.msats)
60+
const sats = msatsToSats(msats)
61+
62+
// denormalize downzaps
63+
await tx.$executeRaw`
64+
WITH territory AS (
65+
SELECT COALESCE(r."subName", i."subName", 'meta')::CITEXT as "subName"
66+
FROM "Item" i
67+
LEFT JOIN "Item" r ON r.id = i."rootId"
68+
WHERE i.id = ${itemAct.itemId}::INTEGER
69+
), zapper AS (
70+
SELECT
71+
COALESCE(${itemAct.item.parentId
72+
? Prisma.sql`"zapCommentTrust"`
73+
: Prisma.sql`"zapPostTrust"`}, 0) as "zapTrust",
74+
COALESCE(${itemAct.item.parentId
75+
? Prisma.sql`"subZapCommentTrust"`
76+
: Prisma.sql`"subZapPostTrust"`}, 0) as "subZapTrust"
77+
FROM territory
78+
LEFT JOIN "UserSubTrust" ust ON ust."subName" = territory."subName"
79+
AND ust."userId" = ${itemAct.userId}::INTEGER
80+
), zap AS (
81+
INSERT INTO "ItemUserAgg" ("userId", "itemId", "downZapSats")
82+
VALUES (${itemAct.userId}::INTEGER, ${itemAct.itemId}::INTEGER, ${sats}::INTEGER)
83+
ON CONFLICT ("itemId", "userId") DO UPDATE
84+
SET "downZapSats" = "ItemUserAgg"."downZapSats" + ${sats}::INTEGER, updated_at = now()
85+
RETURNING LOG("downZapSats" / GREATEST("downZapSats" - ${sats}::INTEGER, 1)::FLOAT) AS log_sats
86+
)
87+
UPDATE "Item"
88+
SET "weightedDownVotes" = "weightedDownVotes" + zapper."zapTrust" * zap.log_sats,
89+
"subWeightedDownVotes" = "subWeightedDownVotes" + zapper."subZapTrust" * zap.log_sats
90+
FROM zap, zapper
91+
WHERE "Item".id = ${itemAct.itemId}::INTEGER`
92+
}
93+
94+
export async function onFail ({ invoice }, { tx }) {
95+
await tx.itemAct.updateMany({ where: { invoiceId: invoice.id }, data: { invoiceActionState: 'FAILED' } })
96+
}
97+
98+
export async function describe ({ id: itemId, sats }, { cost, actionId }) {
99+
return `SN: downzap of ${sats ?? msatsToSats(cost)} sats to #${itemId ?? actionId}`
100+
}

0 commit comments

Comments
 (0)