Merge pull request #971 from stackernews/withdrawal-notifications
Withdrawal notifications
This commit is contained in:
commit
acac75230d
@ -221,6 +221,19 @@ export default {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (meFull.noteWithdrawals) {
|
||||||
|
queries.push(
|
||||||
|
`(SELECT "Withdrawl".id::text, "Withdrawl".created_at AS "sortTime", FLOOR("msatsPaid" / 1000) as "earnedSats",
|
||||||
|
'WithdrawlPaid' AS type
|
||||||
|
FROM "Withdrawl"
|
||||||
|
WHERE "Withdrawl"."userId" = $1
|
||||||
|
AND status = 'CONFIRMED'
|
||||||
|
AND created_at < $2
|
||||||
|
ORDER BY "sortTime" DESC
|
||||||
|
LIMIT ${LIMIT})`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (meFull.noteInvites) {
|
if (meFull.noteInvites) {
|
||||||
queries.push(
|
queries.push(
|
||||||
`(SELECT "Invite".id, MAX(users.created_at) AS "sortTime", NULL as "earnedSats",
|
`(SELECT "Invite".id, MAX(users.created_at) AS "sortTime", NULL as "earnedSats",
|
||||||
|
@ -353,6 +353,22 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user.noteWithdrawals) {
|
||||||
|
const wdrwl = await models.withdrawl.findFirst({
|
||||||
|
where: {
|
||||||
|
userId: me.id,
|
||||||
|
status: 'CONFIRMED',
|
||||||
|
updatedAt: {
|
||||||
|
gt: lastChecked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (wdrwl) {
|
||||||
|
foundNotes()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// check if new invites have been redeemed
|
// check if new invites have been redeemed
|
||||||
if (user.noteInvites) {
|
if (user.noteInvites) {
|
||||||
const [newInvites] = await models.$queryRawUnsafe(`
|
const [newInvites] = await models.$queryRawUnsafe(`
|
||||||
|
@ -97,7 +97,7 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
withdrawl: async (parent, { id }, { me, models, lnd }) => {
|
withdrawl: async (parent, { id }, { me, models }) => {
|
||||||
if (!me) {
|
if (!me) {
|
||||||
throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } })
|
throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } })
|
||||||
}
|
}
|
||||||
@ -111,6 +111,10 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!wdrwl) {
|
||||||
|
throw new GraphQLError('withdrawal not found', { extensions: { code: 'BAD_INPUT' } })
|
||||||
|
}
|
||||||
|
|
||||||
if (wdrwl.user.id !== me.id) {
|
if (wdrwl.user.id !== me.id) {
|
||||||
throw new GraphQLError('not ur withdrawal', { extensions: { code: 'FORBIDDEN' } })
|
throw new GraphQLError('not ur withdrawal', { extensions: { code: 'FORBIDDEN' } })
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,12 @@ export default gql`
|
|||||||
sortTime: Date!
|
sortTime: Date!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WithdrawlPaid {
|
||||||
|
id: ID!
|
||||||
|
earnedSats: Int!
|
||||||
|
sortTime: Date!
|
||||||
|
}
|
||||||
|
|
||||||
type Referral {
|
type Referral {
|
||||||
id: ID!
|
id: ID!
|
||||||
sortTime: Date!
|
sortTime: Date!
|
||||||
@ -115,7 +121,7 @@ export default gql`
|
|||||||
}
|
}
|
||||||
|
|
||||||
union Notification = Reply | Votification | Mention
|
union Notification = Reply | Votification | Mention
|
||||||
| Invitification | Earn | JobChanged | InvoicePaid | Referral
|
| Invitification | Earn | JobChanged | InvoicePaid | WithdrawlPaid | Referral
|
||||||
| Streak | FollowActivity | ForwardedVotification | Revenue | SubStatus
|
| Streak | FollowActivity | ForwardedVotification | Revenue | SubStatus
|
||||||
| TerritoryPost | TerritoryTransfer
|
| TerritoryPost | TerritoryTransfer
|
||||||
|
|
||||||
|
@ -82,7 +82,8 @@ export default gql`
|
|||||||
nostrRelays: [String!]
|
nostrRelays: [String!]
|
||||||
noteAllDescendants: Boolean!
|
noteAllDescendants: Boolean!
|
||||||
noteCowboyHat: Boolean!
|
noteCowboyHat: Boolean!
|
||||||
noteDeposits: Boolean!
|
noteDeposits: Boolean!,
|
||||||
|
noteWithdrawals: Boolean!,
|
||||||
noteEarning: Boolean!
|
noteEarning: Boolean!
|
||||||
noteForwardedSats: Boolean!
|
noteForwardedSats: Boolean!
|
||||||
noteInvites: Boolean!
|
noteInvites: Boolean!
|
||||||
@ -148,6 +149,7 @@ export default gql`
|
|||||||
noteAllDescendants: Boolean!
|
noteAllDescendants: Boolean!
|
||||||
noteCowboyHat: Boolean!
|
noteCowboyHat: Boolean!
|
||||||
noteDeposits: Boolean!
|
noteDeposits: Boolean!
|
||||||
|
noteWithdrawals: Boolean!
|
||||||
noteEarning: Boolean!
|
noteEarning: Boolean!
|
||||||
noteForwardedSats: Boolean!
|
noteForwardedSats: Boolean!
|
||||||
noteInvites: Boolean!
|
noteInvites: Boolean!
|
||||||
|
@ -40,6 +40,7 @@ function Notification ({ n, fresh }) {
|
|||||||
(type === 'Revenue' && <RevenueNotification n={n} />) ||
|
(type === 'Revenue' && <RevenueNotification n={n} />) ||
|
||||||
(type === 'Invitification' && <Invitification n={n} />) ||
|
(type === 'Invitification' && <Invitification n={n} />) ||
|
||||||
(type === 'InvoicePaid' && (n.invoice.nostr ? <NostrZap n={n} /> : <InvoicePaid n={n} />)) ||
|
(type === 'InvoicePaid' && (n.invoice.nostr ? <NostrZap n={n} /> : <InvoicePaid n={n} />)) ||
|
||||||
|
(type === 'WithdrawlPaid' && <WithdrawlPaid n={n} />) ||
|
||||||
(type === 'Referral' && <Referral n={n} />) ||
|
(type === 'Referral' && <Referral n={n} />) ||
|
||||||
(type === 'Streak' && <Streak n={n} />) ||
|
(type === 'Streak' && <Streak n={n} />) ||
|
||||||
(type === 'Votification' && <Votification n={n} />) ||
|
(type === 'Votification' && <Votification n={n} />) ||
|
||||||
@ -95,6 +96,7 @@ const defaultOnClick = n => {
|
|||||||
if (type === 'SubStatus') return { href: `/~${n.sub.name}` }
|
if (type === 'SubStatus') return { href: `/~${n.sub.name}` }
|
||||||
if (type === 'Invitification') return { href: '/invites' }
|
if (type === 'Invitification') return { href: '/invites' }
|
||||||
if (type === 'InvoicePaid') return { href: `/invoices/${n.invoice.id}` }
|
if (type === 'InvoicePaid') return { href: `/invoices/${n.invoice.id}` }
|
||||||
|
if (type === 'WithdrawlPaid') return { href: `/withdrawals/${n.id}` }
|
||||||
if (type === 'Referral') return { href: '/referrals/month' }
|
if (type === 'Referral') return { href: '/referrals/month' }
|
||||||
if (type === 'Streak') return {}
|
if (type === 'Streak') return {}
|
||||||
if (type === 'TerritoryTransfer') return { href: `/~${n.sub.name}` }
|
if (type === 'TerritoryTransfer') return { href: `/~${n.sub.name}` }
|
||||||
@ -277,6 +279,15 @@ function InvoicePaid ({ n }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function WithdrawlPaid ({ n }) {
|
||||||
|
return (
|
||||||
|
<div className='fw-bold text-info ms-2 py-1'>
|
||||||
|
<Check className='fill-info me-1' />{numWithUnits(n.earnedSats, { abbreviate: false, unitSingular: 'sat was', unitPlural: 'sats were' })} withdrawn from your account
|
||||||
|
<small className='text-muted ms-1 fw-normal' suppressHydrationWarning>{timeSince(new Date(n.sortTime))}</small>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function Referral ({ n }) {
|
function Referral ({ n }) {
|
||||||
return (
|
return (
|
||||||
<small className='fw-bold text-secondary ms-2'>
|
<small className='fw-bold text-secondary ms-2'>
|
||||||
|
@ -133,6 +133,11 @@ export const NOTIFICATIONS = gql`
|
|||||||
lud18Data
|
lud18Data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
... on WithdrawlPaid {
|
||||||
|
id
|
||||||
|
sortTime
|
||||||
|
earnedSats
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} `
|
} `
|
||||||
|
@ -30,6 +30,7 @@ export const ME = gql`
|
|||||||
noteAllDescendants
|
noteAllDescendants
|
||||||
noteCowboyHat
|
noteCowboyHat
|
||||||
noteDeposits
|
noteDeposits
|
||||||
|
noteWithdrawals
|
||||||
noteEarning
|
noteEarning
|
||||||
noteForwardedSats
|
noteForwardedSats
|
||||||
noteInvites
|
noteInvites
|
||||||
@ -72,6 +73,7 @@ export const SETTINGS_FIELDS = gql`
|
|||||||
noteAllDescendants
|
noteAllDescendants
|
||||||
noteMentions
|
noteMentions
|
||||||
noteDeposits
|
noteDeposits
|
||||||
|
noteWithdrawals
|
||||||
noteInvites
|
noteInvites
|
||||||
noteJobIndicator
|
noteJobIndicator
|
||||||
noteCowboyHat
|
noteCowboyHat
|
||||||
|
@ -43,6 +43,7 @@ const createUserFilter = (tag) => {
|
|||||||
INVITE: 'noteInvites',
|
INVITE: 'noteInvites',
|
||||||
EARN: 'noteEarning',
|
EARN: 'noteEarning',
|
||||||
DEPOSIT: 'noteDeposits',
|
DEPOSIT: 'noteDeposits',
|
||||||
|
WITHDRAWAL: 'noteWithdrawals',
|
||||||
STREAK: 'noteCowboyHat'
|
STREAK: 'noteCowboyHat'
|
||||||
}
|
}
|
||||||
const key = tagMap[tag.split('-')[0]]
|
const key = tagMap[tag.split('-')[0]]
|
||||||
@ -307,7 +308,7 @@ export async function notifyEarner (userId, earnings) {
|
|||||||
export async function notifyDeposit (userId, invoice) {
|
export async function notifyDeposit (userId, invoice) {
|
||||||
try {
|
try {
|
||||||
await sendUserNotification(userId, {
|
await sendUserNotification(userId, {
|
||||||
title: `${numWithUnits(msatsToSats(invoice.received_mtokens), { abbreviate: false })} were deposited in your account`,
|
title: `${numWithUnits(msatsToSats(invoice.received_mtokens), { abbreviate: false, unitSingular: 'sat was', unitPlural: 'sats were' })} deposited in your account`,
|
||||||
body: invoice.comment || undefined,
|
body: invoice.comment || undefined,
|
||||||
tag: 'DEPOSIT',
|
tag: 'DEPOSIT',
|
||||||
data: { sats: msatsToSats(invoice.received_mtokens) }
|
data: { sats: msatsToSats(invoice.received_mtokens) }
|
||||||
@ -317,6 +318,18 @@ export async function notifyDeposit (userId, invoice) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function notifyWithdrawal (userId, wdrwl) {
|
||||||
|
try {
|
||||||
|
await sendUserNotification(userId, {
|
||||||
|
title: `${numWithUnits(msatsToSats(wdrwl.payment.mtokens), { abbreviate: false, unitSingular: 'sat was', unitPlural: 'sats were' })} withdrawn from your account`,
|
||||||
|
tag: 'WITHDRAWAL',
|
||||||
|
data: { sats: msatsToSats(wdrwl.payment.mtokens) }
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function notifyNewStreak (userId, streak) {
|
export async function notifyNewStreak (userId, streak) {
|
||||||
const index = streak.id % FOUND_BLURBS.length
|
const index = streak.id % FOUND_BLURBS.length
|
||||||
const blurb = FOUND_BLURBS[index]
|
const blurb = FOUND_BLURBS[index]
|
||||||
|
@ -75,6 +75,7 @@ export default function Settings ({ ssrData }) {
|
|||||||
noteAllDescendants: settings?.noteAllDescendants,
|
noteAllDescendants: settings?.noteAllDescendants,
|
||||||
noteMentions: settings?.noteMentions,
|
noteMentions: settings?.noteMentions,
|
||||||
noteDeposits: settings?.noteDeposits,
|
noteDeposits: settings?.noteDeposits,
|
||||||
|
noteWithdrawals: settings?.noteWithdrawals,
|
||||||
noteInvites: settings?.noteInvites,
|
noteInvites: settings?.noteInvites,
|
||||||
noteJobIndicator: settings?.noteJobIndicator,
|
noteJobIndicator: settings?.noteJobIndicator,
|
||||||
noteCowboyHat: settings?.noteCowboyHat,
|
noteCowboyHat: settings?.noteCowboyHat,
|
||||||
@ -223,6 +224,11 @@ export default function Settings ({ ssrData }) {
|
|||||||
name='noteDeposits'
|
name='noteDeposits'
|
||||||
groupClassName='mb-0'
|
groupClassName='mb-0'
|
||||||
/>
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label='sats are withdrawn from my account'
|
||||||
|
name='noteWithdrawals'
|
||||||
|
groupClassName='mb-0'
|
||||||
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label='someone mentions me'
|
label='someone mentions me'
|
||||||
name='noteMentions'
|
name='noteMentions'
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "users" ADD COLUMN "noteWithdrawals" BOOLEAN NOT NULL DEFAULT true;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION confirm_withdrawl(wid INTEGER, msats_paid BIGINT, msats_fee_paid BIGINT)
|
||||||
|
RETURNS INTEGER
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
msats_fee_paying BIGINT;
|
||||||
|
user_id INTEGER;
|
||||||
|
BEGIN
|
||||||
|
PERFORM ASSERT_SERIALIZED();
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM "Withdrawl" WHERE id = wid AND status IS NULL) THEN
|
||||||
|
SELECT "msatsFeePaying", "userId" INTO msats_fee_paying, user_id
|
||||||
|
FROM "Withdrawl" WHERE id = wid AND status IS NULL;
|
||||||
|
|
||||||
|
UPDATE "Withdrawl"
|
||||||
|
SET status = 'CONFIRMED', "msatsPaid" = msats_paid,
|
||||||
|
"msatsFeePaid" = msats_fee_paid, updated_at = now_utc()
|
||||||
|
WHERE id = wid AND status IS NULL;
|
||||||
|
|
||||||
|
UPDATE users SET msats = msats + (msats_fee_paying - msats_fee_paid) WHERE id = user_id;
|
||||||
|
RETURN 0;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN 1;
|
||||||
|
END;
|
||||||
|
$$;
|
@ -38,6 +38,7 @@ model User {
|
|||||||
stackedMsats BigInt @default(0)
|
stackedMsats BigInt @default(0)
|
||||||
noteAllDescendants Boolean @default(true)
|
noteAllDescendants Boolean @default(true)
|
||||||
noteDeposits Boolean @default(true)
|
noteDeposits Boolean @default(true)
|
||||||
|
noteWithdrawals Boolean @default(true)
|
||||||
noteEarning Boolean @default(true)
|
noteEarning Boolean @default(true)
|
||||||
noteInvites Boolean @default(true)
|
noteInvites Boolean @default(true)
|
||||||
noteItemSats Boolean @default(true)
|
noteItemSats Boolean @default(true)
|
||||||
|
@ -116,7 +116,7 @@ const mergeAndShowNotification = async (sw, payload, currentNotifications, tag,
|
|||||||
// tags that need to know the amount of notifications with same tag for merging
|
// tags that need to know the amount of notifications with same tag for merging
|
||||||
const AMOUNT_TAGS = ['REPLY', 'MENTION', 'REFERRAL', 'INVITE', 'FOLLOW', 'TERRITORY_POST']
|
const AMOUNT_TAGS = ['REPLY', 'MENTION', 'REFERRAL', 'INVITE', 'FOLLOW', 'TERRITORY_POST']
|
||||||
// tags that need to know the sum of sats of notifications with same tag for merging
|
// tags that need to know the sum of sats of notifications with same tag for merging
|
||||||
const SUM_SATS_TAGS = ['DEPOSIT']
|
const SUM_SATS_TAGS = ['DEPOSIT', 'WITHDRAWAL']
|
||||||
// this should reflect the amount of notifications that were already merged before
|
// this should reflect the amount of notifications that were already merged before
|
||||||
let initialAmount = currentNotifications[0]?.data?.amount || 1
|
let initialAmount = currentNotifications[0]?.data?.amount || 1
|
||||||
if (iOS()) initialAmount = 1
|
if (iOS()) initialAmount = 1
|
||||||
@ -153,8 +153,11 @@ const mergeAndShowNotification = async (sw, payload, currentNotifications, tag,
|
|||||||
title = `you have ${amount} new posts in ~${subName}`
|
title = `you have ${amount} new posts in ~${subName}`
|
||||||
}
|
}
|
||||||
} else if (SUM_SATS_TAGS.includes(compareTag)) {
|
} else if (SUM_SATS_TAGS.includes(compareTag)) {
|
||||||
// there is only DEPOSIT in this array
|
if (compareTag === 'DEPOSIT') {
|
||||||
title = `${numWithUnits(sats, { abbreviate: false })} were deposited in your account`
|
title = `${numWithUnits(sats, { abbreviate: false, unitSingular: 'sat was', unitPlural: 'sats were' })} deposited in your account`
|
||||||
|
} else if (compareTag === 'WITHDRAWAL') {
|
||||||
|
title = `${numWithUnits(sats, { abbreviate: false, unitSingular: 'sat was', unitPlural: 'sats were' })} withdrawn from your account`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
log(`[sw:push] ${nid} - calculated title: ${title}`)
|
log(`[sw:push] ${nid} - calculated title: ${title}`)
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import {
|
|||||||
getInvoice, getPayment, cancelHodlInvoice, deletePayment,
|
getInvoice, getPayment, cancelHodlInvoice, deletePayment,
|
||||||
subscribeToInvoices, subscribeToPayments, subscribeToInvoice
|
subscribeToInvoices, subscribeToPayments, subscribeToInvoice
|
||||||
} from 'ln-service'
|
} from 'ln-service'
|
||||||
import { notifyDeposit } from '@/lib/webPush'
|
import { notifyDeposit, notifyWithdrawal } from '@/lib/webPush'
|
||||||
import { INVOICE_RETENTION_DAYS } from '@/lib/constants'
|
import { INVOICE_RETENTION_DAYS } from '@/lib/constants'
|
||||||
import { datePivot, sleep } from '@/lib/time.js'
|
import { datePivot, sleep } from '@/lib/time.js'
|
||||||
import retry from 'async-retry'
|
import retry from 'async-retry'
|
||||||
@ -228,8 +228,11 @@ 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
|
||||||
await serialize(models, models.$executeRaw`
|
const [{ confirm_withdrawl: code }] = await serialize(models, models.$queryRaw`
|
||||||
SELECT confirm_withdrawl(${dbWdrwl.id}::INTEGER, ${paid}, ${fee})`)
|
SELECT confirm_withdrawl(${dbWdrwl.id}::INTEGER, ${paid}, ${fee})`)
|
||||||
|
if (code === 0) {
|
||||||
|
notifyWithdrawal(dbWdrwl.userId, wdrwl)
|
||||||
|
}
|
||||||
} else if (wdrwl?.is_failed || notFound) {
|
} else if (wdrwl?.is_failed || notFound) {
|
||||||
let status = 'UNKNOWN_FAILURE'
|
let status = 'UNKNOWN_FAILURE'
|
||||||
if (wdrwl?.failed.is_insufficient_balance) {
|
if (wdrwl?.failed.is_insufficient_balance) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user