Provide option to clear withdrawal invoices (#591)
* add settings option * add auto-drop worker * add manual delete option * add warning and note * cleanup * incorporate most review feedback * add warning to settings option * remove debugging tweaks and simplify * refine auto delete bolt11s * refine UI --------- Co-authored-by: rleed <rleed1@pm.me> Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
This commit is contained in:
parent
5b14606b39
commit
d86d8b3bac
|
@ -80,6 +80,18 @@ export default {
|
||||||
|
|
||||||
return wdrwl
|
return wdrwl
|
||||||
},
|
},
|
||||||
|
numBolt11s: async (parent, args, { me, models, lnd }) => {
|
||||||
|
if (!me) {
|
||||||
|
throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } })
|
||||||
|
}
|
||||||
|
|
||||||
|
return await models.withdrawl.count({
|
||||||
|
where: {
|
||||||
|
userId: me.id,
|
||||||
|
hash: { not: null }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
connectAddress: async (parent, args, { lnd }) => {
|
connectAddress: async (parent, args, { lnd }) => {
|
||||||
return process.env.LND_CONNECT_ADDRESS
|
return process.env.LND_CONNECT_ADDRESS
|
||||||
},
|
},
|
||||||
|
@ -359,6 +371,23 @@ export default {
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
return inv
|
return inv
|
||||||
|
},
|
||||||
|
dropBolt11: async (parent, { id }, { me, models }) => {
|
||||||
|
if (!me) {
|
||||||
|
throw new GraphQLError('you must be logged in', { extensions: { code: 'UNAUTHENTICATED' } })
|
||||||
|
}
|
||||||
|
|
||||||
|
await models.withdrawl.update({
|
||||||
|
where: {
|
||||||
|
userId: me.id,
|
||||||
|
id: Number(id),
|
||||||
|
createdAt: {
|
||||||
|
lte: datePivot(new Date(), { days: -7 })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: { bolt11: null, hash: null }
|
||||||
|
})
|
||||||
|
return { id }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ export default gql`
|
||||||
setName(name: String!): String
|
setName(name: String!): String
|
||||||
setSettings(tipDefault: Int!, turboTipping: Boolean!, fiatCurrency: String!, withdrawMaxFeeDefault: Int!, noteItemSats: Boolean!,
|
setSettings(tipDefault: Int!, turboTipping: Boolean!, fiatCurrency: String!, withdrawMaxFeeDefault: Int!, noteItemSats: Boolean!,
|
||||||
noteEarning: Boolean!, noteAllDescendants: Boolean!, noteMentions: Boolean!, noteDeposits: Boolean!,
|
noteEarning: Boolean!, noteAllDescendants: Boolean!, noteMentions: Boolean!, noteDeposits: Boolean!,
|
||||||
noteInvites: Boolean!, noteJobIndicator: Boolean!, noteCowboyHat: Boolean!, hideInvoiceDesc: Boolean!,
|
noteInvites: Boolean!, noteJobIndicator: Boolean!, noteCowboyHat: Boolean!, hideInvoiceDesc: Boolean!, autoDropBolt11s: Boolean!,
|
||||||
hideFromTopUsers: Boolean!, hideCowboyHat: Boolean!, imgproxyOnly: Boolean!,
|
hideFromTopUsers: Boolean!, hideCowboyHat: Boolean!, imgproxyOnly: Boolean!,
|
||||||
wildWestMode: Boolean!, greeterMode: Boolean!, nostrPubkey: String, nostrCrossposting: Boolean, nostrRelays: [String!], hideBookmarks: Boolean!,
|
wildWestMode: Boolean!, greeterMode: Boolean!, nostrPubkey: String, nostrCrossposting: Boolean, nostrRelays: [String!], hideBookmarks: Boolean!,
|
||||||
noteForwardedSats: Boolean!, hideWalletBalance: Boolean!, hideIsContributor: Boolean!, diagnostics: Boolean!): User
|
noteForwardedSats: Boolean!, hideWalletBalance: Boolean!, hideIsContributor: Boolean!, diagnostics: Boolean!): User
|
||||||
|
@ -97,6 +97,7 @@ export default gql`
|
||||||
noteCowboyHat: Boolean!
|
noteCowboyHat: Boolean!
|
||||||
noteForwardedSats: Boolean!
|
noteForwardedSats: Boolean!
|
||||||
hideInvoiceDesc: Boolean!
|
hideInvoiceDesc: Boolean!
|
||||||
|
autoDropBolt11s: Boolean!
|
||||||
hideFromTopUsers: Boolean!
|
hideFromTopUsers: Boolean!
|
||||||
hideCowboyHat: Boolean!
|
hideCowboyHat: Boolean!
|
||||||
hideBookmarks: Boolean!
|
hideBookmarks: Boolean!
|
||||||
|
|
|
@ -4,6 +4,7 @@ export default gql`
|
||||||
extend type Query {
|
extend type Query {
|
||||||
invoice(id: ID!): Invoice!
|
invoice(id: ID!): Invoice!
|
||||||
withdrawl(id: ID!): Withdrawl!
|
withdrawl(id: ID!): Withdrawl!
|
||||||
|
numBolt11s: Int!
|
||||||
connectAddress: String!
|
connectAddress: String!
|
||||||
walletHistory(cursor: String, inc: String): History
|
walletHistory(cursor: String, inc: String): History
|
||||||
}
|
}
|
||||||
|
@ -13,6 +14,7 @@ export default gql`
|
||||||
createWithdrawl(invoice: String!, maxFee: Int!): Withdrawl!
|
createWithdrawl(invoice: String!, maxFee: Int!): Withdrawl!
|
||||||
sendToLnAddr(addr: String!, amount: Int!, maxFee: Int!, comment: String, identifier: Boolean, name: String, email: String): Withdrawl!
|
sendToLnAddr(addr: String!, amount: Int!, maxFee: Int!, comment: String, identifier: Boolean, name: String, email: String): Withdrawl!
|
||||||
cancelInvoice(hash: String!, hmac: String!): Invoice!
|
cancelInvoice(hash: String!, hmac: String!): Invoice!
|
||||||
|
dropBolt11(id: ID): Withdrawl
|
||||||
}
|
}
|
||||||
|
|
||||||
type Invoice {
|
type Invoice {
|
||||||
|
@ -36,8 +38,8 @@ export default gql`
|
||||||
type Withdrawl {
|
type Withdrawl {
|
||||||
id: ID!
|
id: ID!
|
||||||
createdAt: Date!
|
createdAt: Date!
|
||||||
hash: String!
|
hash: String
|
||||||
bolt11: String!
|
bolt11: String
|
||||||
satsPaying: Int!
|
satsPaying: Int!
|
||||||
satsPaid: Int
|
satsPaid: Int
|
||||||
satsFeePaying: Int!
|
satsFeePaying: Int!
|
||||||
|
|
|
@ -2,8 +2,11 @@ import { decode } from 'bolt11'
|
||||||
import AccordianItem from './accordian-item'
|
import AccordianItem from './accordian-item'
|
||||||
import { CopyInput } from './form'
|
import { CopyInput } from './form'
|
||||||
|
|
||||||
export default ({ bolt11, preimage }) => {
|
export default ({ bolt11, preimage, children }) => {
|
||||||
const { tagsObject: { description, payment_hash: paymentHash } } = decode(bolt11)
|
let description, paymentHash
|
||||||
|
if (bolt11) {
|
||||||
|
({ tagsObject: { description, payment_hash: paymentHash } } = decode(bolt11))
|
||||||
|
}
|
||||||
if (!description && !paymentHash && !preimage) {
|
if (!description && !paymentHash && !preimage) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -41,6 +44,7 @@ export default ({ bolt11, preimage }) => {
|
||||||
noForm
|
noForm
|
||||||
placeholder={preimage}
|
placeholder={preimage}
|
||||||
/>}
|
/>}
|
||||||
|
{children}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -61,7 +61,7 @@ export default function Delete ({ itemId, children, onDelete, type = 'post' }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DeleteConfirm ({ onConfirm, type }) {
|
export function DeleteConfirm ({ onConfirm, type }) {
|
||||||
const [error, setError] = useState()
|
const [error, setError] = useState()
|
||||||
const toaster = useToast()
|
const toaster = useToast()
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ export const ME = gql`
|
||||||
noteCowboyHat
|
noteCowboyHat
|
||||||
noteForwardedSats
|
noteForwardedSats
|
||||||
hideInvoiceDesc
|
hideInvoiceDesc
|
||||||
|
autoDropBolt11s
|
||||||
hideFromTopUsers
|
hideFromTopUsers
|
||||||
hideCowboyHat
|
hideCowboyHat
|
||||||
imgproxyOnly
|
imgproxyOnly
|
||||||
|
@ -60,6 +61,7 @@ export const SETTINGS_FIELDS = gql`
|
||||||
noteCowboyHat
|
noteCowboyHat
|
||||||
noteForwardedSats
|
noteForwardedSats
|
||||||
hideInvoiceDesc
|
hideInvoiceDesc
|
||||||
|
autoDropBolt11s
|
||||||
hideFromTopUsers
|
hideFromTopUsers
|
||||||
hideCowboyHat
|
hideCowboyHat
|
||||||
hideBookmarks
|
hideBookmarks
|
||||||
|
@ -94,14 +96,14 @@ gql`
|
||||||
${SETTINGS_FIELDS}
|
${SETTINGS_FIELDS}
|
||||||
mutation setSettings($tipDefault: Int!, $turboTipping: Boolean!, $fiatCurrency: String!, $withdrawMaxFeeDefault: Int!, $noteItemSats: Boolean!,
|
mutation setSettings($tipDefault: Int!, $turboTipping: Boolean!, $fiatCurrency: String!, $withdrawMaxFeeDefault: Int!, $noteItemSats: Boolean!,
|
||||||
$noteEarning: Boolean!, $noteAllDescendants: Boolean!, $noteMentions: Boolean!, $noteDeposits: Boolean!,
|
$noteEarning: Boolean!, $noteAllDescendants: Boolean!, $noteMentions: Boolean!, $noteDeposits: Boolean!,
|
||||||
$noteInvites: Boolean!, $noteJobIndicator: Boolean!, $noteCowboyHat: Boolean!, $hideInvoiceDesc: Boolean!,
|
$noteInvites: Boolean!, $noteJobIndicator: Boolean!, $noteCowboyHat: Boolean!, $hideInvoiceDesc: Boolean!, $autoDropBolt11s: Boolean!,
|
||||||
$hideFromTopUsers: Boolean!, $hideCowboyHat: Boolean!, $imgproxyOnly: Boolean!,
|
$hideFromTopUsers: Boolean!, $hideCowboyHat: Boolean!, $imgproxyOnly: Boolean!,
|
||||||
$wildWestMode: Boolean!, $greeterMode: Boolean!, $nostrPubkey: String, $nostrCrossposting: Boolean!, $nostrRelays: [String!], $hideBookmarks: Boolean!,
|
$wildWestMode: Boolean!, $greeterMode: Boolean!, $nostrPubkey: String, $nostrCrossposting: Boolean!, $nostrRelays: [String!], $hideBookmarks: Boolean!,
|
||||||
$noteForwardedSats: Boolean!, $hideWalletBalance: Boolean!, $hideIsContributor: Boolean!, $diagnostics: Boolean!) {
|
$noteForwardedSats: Boolean!, $hideWalletBalance: Boolean!, $hideIsContributor: Boolean!, $diagnostics: Boolean!) {
|
||||||
setSettings(tipDefault: $tipDefault, turboTipping: $turboTipping, fiatCurrency: $fiatCurrency, withdrawMaxFeeDefault: $withdrawMaxFeeDefault,
|
setSettings(tipDefault: $tipDefault, turboTipping: $turboTipping, fiatCurrency: $fiatCurrency, withdrawMaxFeeDefault: $withdrawMaxFeeDefault,
|
||||||
noteItemSats: $noteItemSats, noteEarning: $noteEarning, noteAllDescendants: $noteAllDescendants,
|
noteItemSats: $noteItemSats, noteEarning: $noteEarning, noteAllDescendants: $noteAllDescendants,
|
||||||
noteMentions: $noteMentions, noteDeposits: $noteDeposits, noteInvites: $noteInvites,
|
noteMentions: $noteMentions, noteDeposits: $noteDeposits, noteInvites: $noteInvites,
|
||||||
noteJobIndicator: $noteJobIndicator, noteCowboyHat: $noteCowboyHat, hideInvoiceDesc: $hideInvoiceDesc,
|
noteJobIndicator: $noteJobIndicator, noteCowboyHat: $noteCowboyHat, hideInvoiceDesc: $hideInvoiceDesc, autoDropBolt11s: $autoDropBolt11s,
|
||||||
hideFromTopUsers: $hideFromTopUsers, hideCowboyHat: $hideCowboyHat, imgproxyOnly: $imgproxyOnly,
|
hideFromTopUsers: $hideFromTopUsers, hideCowboyHat: $hideCowboyHat, imgproxyOnly: $imgproxyOnly,
|
||||||
wildWestMode: $wildWestMode, greeterMode: $greeterMode, nostrPubkey: $nostrPubkey, nostrCrossposting: $nostrCrossposting, nostrRelays: $nostrRelays, hideBookmarks: $hideBookmarks,
|
wildWestMode: $wildWestMode, greeterMode: $greeterMode, nostrPubkey: $nostrPubkey, nostrCrossposting: $nostrCrossposting, nostrRelays: $nostrRelays, hideBookmarks: $hideBookmarks,
|
||||||
noteForwardedSats: $noteForwardedSats, hideWalletBalance: $hideWalletBalance, hideIsContributor: $hideIsContributor, diagnostics: $diagnostics) {
|
noteForwardedSats: $noteForwardedSats, hideWalletBalance: $hideWalletBalance, hideIsContributor: $hideIsContributor, diagnostics: $diagnostics) {
|
||||||
|
|
|
@ -24,6 +24,7 @@ export const WITHDRAWL = gql`
|
||||||
query Withdrawl($id: ID!) {
|
query Withdrawl($id: ID!) {
|
||||||
withdrawl(id: $id) {
|
withdrawl(id: $id) {
|
||||||
id
|
id
|
||||||
|
createdAt
|
||||||
bolt11
|
bolt11
|
||||||
satsPaid
|
satsPaid
|
||||||
satsFeePaying
|
satsFeePaying
|
||||||
|
@ -40,6 +41,7 @@ export const WALLET_HISTORY = gql`
|
||||||
facts {
|
facts {
|
||||||
id
|
id
|
||||||
factId
|
factId
|
||||||
|
bolt11
|
||||||
type
|
type
|
||||||
createdAt
|
createdAt
|
||||||
sats
|
sats
|
||||||
|
|
|
@ -127,6 +127,12 @@ function getClient (uri) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
numBolt11s: {
|
||||||
|
keyArgs: [],
|
||||||
|
merge (existing, incoming) {
|
||||||
|
return incoming
|
||||||
|
}
|
||||||
|
},
|
||||||
walletHistory: {
|
walletHistory: {
|
||||||
keyArgs: ['inc'],
|
keyArgs: ['inc'],
|
||||||
merge (existing, incoming) {
|
merge (existing, incoming) {
|
||||||
|
|
|
@ -79,3 +79,5 @@ export const ITEM_ALLOW_EDITS = [
|
||||||
// FAQ, privacy policy, changelog, content guidelines
|
// FAQ, privacy policy, changelog, content guidelines
|
||||||
349, 76894, 78763, 81862
|
349, 76894, 78763, 81862
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const INVOICE_RETENTION_DAYS = 7
|
||||||
|
|
|
@ -115,11 +115,12 @@ function Detail ({ fact }) {
|
||||||
return (
|
return (
|
||||||
<div className='px-3'>
|
<div className='px-3'>
|
||||||
<Link className={satusClass(fact.status)} href={`/${fact.type}s/${fact.factId}`}>
|
<Link className={satusClass(fact.status)} href={`/${fact.type}s/${fact.factId}`}>
|
||||||
{(zap && <span className='d-block'>nostr zap{zap.content && `: ${zap.content}`}</span>) ||
|
{(!fact.bolt11 && <span className='d-block text-muted fw-bold fst-italic'>invoice deleted</span>) ||
|
||||||
|
(zap && <span className='d-block'>nostr zap{zap.content && `: ${zap.content}`}</span>) ||
|
||||||
(fact.description && <span className='d-block'>{fact.description}</span>)}
|
(fact.description && <span className='d-block'>{fact.description}</span>)}
|
||||||
<PayerData data={fact.invoicePayerData} className='text-muted' header />
|
<PayerData data={fact.invoicePayerData} className='text-muted' header />
|
||||||
{fact.invoiceComment && <small className='text-muted'><b>sender says:</b> {fact.invoiceComment}</small>}
|
{fact.invoiceComment && <small className='text-muted'><b>sender says:</b> {fact.invoiceComment}</small>}
|
||||||
{!fact.invoiceComment && !fact.description && <span className='d-block'>no description</span>}
|
{!fact.invoiceComment && !fact.description && fact.bolt11 && <span className='d-block'>no description</span>}
|
||||||
<Satus status={fact.status} />
|
<Satus status={fact.status} />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { NostrAuth } from '../components/nostr-auth'
|
||||||
import { useToast } from '../components/toast'
|
import { useToast } from '../components/toast'
|
||||||
import { useLogger } from '../components/logger'
|
import { useLogger } from '../components/logger'
|
||||||
import { useMe } from '../components/me'
|
import { useMe } from '../components/me'
|
||||||
|
import { INVOICE_RETENTION_DAYS } from '../lib/constants'
|
||||||
|
|
||||||
export const getServerSideProps = getGetServerSideProps({ query: SETTINGS, authRequired: true })
|
export const getServerSideProps = getGetServerSideProps({ query: SETTINGS, authRequired: true })
|
||||||
|
|
||||||
|
@ -74,6 +75,7 @@ export default function Settings ({ ssrData }) {
|
||||||
noteCowboyHat: settings?.noteCowboyHat,
|
noteCowboyHat: settings?.noteCowboyHat,
|
||||||
noteForwardedSats: settings?.noteForwardedSats,
|
noteForwardedSats: settings?.noteForwardedSats,
|
||||||
hideInvoiceDesc: settings?.hideInvoiceDesc,
|
hideInvoiceDesc: settings?.hideInvoiceDesc,
|
||||||
|
autoDropBolt11s: settings?.autoDropBolt11s,
|
||||||
hideFromTopUsers: settings?.hideFromTopUsers,
|
hideFromTopUsers: settings?.hideFromTopUsers,
|
||||||
hideCowboyHat: settings?.hideCowboyHat,
|
hideCowboyHat: settings?.hideCowboyHat,
|
||||||
imgproxyOnly: settings?.imgproxyOnly,
|
imgproxyOnly: settings?.imgproxyOnly,
|
||||||
|
@ -236,6 +238,23 @@ export default function Settings ({ ssrData }) {
|
||||||
name='hideInvoiceDesc'
|
name='hideInvoiceDesc'
|
||||||
groupClassName='mb-0'
|
groupClassName='mb-0'
|
||||||
/>
|
/>
|
||||||
|
<DropBolt11sCheckbox
|
||||||
|
ssrData={ssrData}
|
||||||
|
label={
|
||||||
|
<div className='d-flex align-items-center'>autodelete withdrawal invoices
|
||||||
|
<Info>
|
||||||
|
<ul className='fw-bold'>
|
||||||
|
<li>use this to protect receiver privacy</li>
|
||||||
|
<li>applies retroactively, cannot be reversed</li>
|
||||||
|
<li>withdrawal invoices are kept at least {INVOICE_RETENTION_DAYS} days for security and debugging purposes</li>
|
||||||
|
<li>autodeletions are run a daily basis at night</li>
|
||||||
|
</ul>
|
||||||
|
</Info>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
name='autoDropBolt11s'
|
||||||
|
groupClassName='mb-0'
|
||||||
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={<>hide me from <Link href='/top/stackers/day'>top stackers</Link></>}
|
label={<>hide me from <Link href='/top/stackers/day'>top stackers</Link></>}
|
||||||
name='hideFromTopUsers'
|
name='hideFromTopUsers'
|
||||||
|
@ -377,6 +396,38 @@ export default function Settings ({ ssrData }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DropBolt11sCheckbox = ({ ssrData, ...props }) => {
|
||||||
|
const showModal = useShowModal()
|
||||||
|
const { data } = useQuery(gql`{ numBolt11s }`)
|
||||||
|
const { numBolt11s } = data || ssrData
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
onClick={e => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
showModal(onClose => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p className='fw-bolder'>{numBolt11s} withdrawal invoices will be deleted with this setting.</p>
|
||||||
|
<p className='fw-bolder'>You sure? This is a gone forever kind of delete.</p>
|
||||||
|
<div className='d-flex justify-content-end'>
|
||||||
|
<Button
|
||||||
|
variant='danger' onClick={async () => {
|
||||||
|
await onClose()
|
||||||
|
}}
|
||||||
|
>I am sure
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function QRLinkButton ({ provider, unlink, status }) {
|
function QRLinkButton ({ provider, unlink, status }) {
|
||||||
const showModal = useShowModal()
|
const showModal = useShowModal()
|
||||||
const text = status ? 'Unlink' : 'Link'
|
const text = status ? 'Unlink' : 'Link'
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useQuery } from '@apollo/client'
|
import { useQuery, useMutation } from '@apollo/client'
|
||||||
import { CenterLayout } from '../../components/layout'
|
import { CenterLayout } from '../../components/layout'
|
||||||
import { CopyInput, Input, InputSkeleton } from '../../components/form'
|
import { CopyInput, Input, InputSkeleton } from '../../components/form'
|
||||||
import InputGroup from 'react-bootstrap/InputGroup'
|
import InputGroup from 'react-bootstrap/InputGroup'
|
||||||
|
@ -6,9 +6,15 @@ import InvoiceStatus from '../../components/invoice-status'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { WITHDRAWL } from '../../fragments/wallet'
|
import { WITHDRAWL } from '../../fragments/wallet'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { SSR } from '../../lib/constants'
|
import { SSR, INVOICE_RETENTION_DAYS } from '../../lib/constants'
|
||||||
import { numWithUnits } from '../../lib/format'
|
import { numWithUnits } from '../../lib/format'
|
||||||
import Bolt11Info from '../../components/bolt11-info'
|
import Bolt11Info from '../../components/bolt11-info'
|
||||||
|
import { datePivot, timeLeft } from '../../lib/time'
|
||||||
|
import { useMe } from '../../components/me'
|
||||||
|
import { useToast } from '../../components/toast'
|
||||||
|
import { gql } from 'graphql-tag'
|
||||||
|
import { useShowModal } from '../../components/modal'
|
||||||
|
import { DeleteConfirm } from '../../components/delete'
|
||||||
|
|
||||||
export default function Withdrawl () {
|
export default function Withdrawl () {
|
||||||
return (
|
return (
|
||||||
|
@ -87,7 +93,7 @@ function LoadWithdrawl () {
|
||||||
<div className='w-100'>
|
<div className='w-100'>
|
||||||
<CopyInput
|
<CopyInput
|
||||||
label='invoice' type='text'
|
label='invoice' type='text'
|
||||||
placeholder={data.withdrawl.bolt11} readOnly noForm
|
placeholder={data.withdrawl.bolt11 || 'deleted'} readOnly noForm
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='w-100'>
|
<div className='w-100'>
|
||||||
|
@ -98,7 +104,70 @@ function LoadWithdrawl () {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<InvoiceStatus variant={variant} status={status} />
|
<InvoiceStatus variant={variant} status={status} />
|
||||||
<Bolt11Info bolt11={data.withdrawl.bolt11} />
|
<Bolt11Info bolt11={data.withdrawl.bolt11}>
|
||||||
|
<PrivacyOption wd={data.withdrawl} />
|
||||||
|
</Bolt11Info>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PrivacyOption ({ wd }) {
|
||||||
|
if (!wd.bolt11) return
|
||||||
|
|
||||||
|
const me = useMe()
|
||||||
|
const keepUntil = datePivot(new Date(wd.createdAt), { days: INVOICE_RETENTION_DAYS })
|
||||||
|
const oldEnough = new Date() >= keepUntil
|
||||||
|
if (!oldEnough) {
|
||||||
|
return (
|
||||||
|
<span className='text-muted fst-italic'>
|
||||||
|
{`this invoice ${me.autoDropBolt11s ? 'will be auto-deleted' : 'can be deleted'} in ${timeLeft(keepUntil)}`}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const showModal = useShowModal()
|
||||||
|
const toaster = useToast()
|
||||||
|
const [dropBolt11] = useMutation(
|
||||||
|
gql`
|
||||||
|
mutation dropBolt11($id: ID!) {
|
||||||
|
dropBolt11(id: $id) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}`, {
|
||||||
|
update (cache) {
|
||||||
|
cache.modify({
|
||||||
|
id: `Withdrawl:${wd.id}`,
|
||||||
|
fields: {
|
||||||
|
bolt11: () => null,
|
||||||
|
hash: () => null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className='btn btn-md btn-danger' onClick={() => {
|
||||||
|
showModal(onClose => {
|
||||||
|
return (
|
||||||
|
<DeleteConfirm
|
||||||
|
type='invoice'
|
||||||
|
onConfirm={async () => {
|
||||||
|
if (me) {
|
||||||
|
try {
|
||||||
|
await dropBolt11({ variables: { id: wd.id } })
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
toaster.danger('unable to delete invoice')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onClose()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>delete invoice
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "users" ADD COLUMN "autoDropBolt11s" BOOLEAN NOT NULL DEFAULT false;
|
||||||
|
ALTER TABLE "Withdrawl" ALTER COLUMN "hash" DROP NOT NULL;
|
||||||
|
ALTER TABLE "Withdrawl" ALTER COLUMN "bolt11" DROP NOT NULL;
|
||||||
|
|
||||||
|
-- hack ... prisma doesn't know about our other schemas (e.g. pgboss)
|
||||||
|
-- and this is only really a problem on their "shadow database"
|
||||||
|
-- so we catch the exception it throws and ignore it
|
||||||
|
CREATE OR REPLACE FUNCTION create_autodrop_bolt11s_job()
|
||||||
|
RETURNS INTEGER
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO pgboss.schedule (name, cron, timezone) VALUES ('autoDropBolt11s', '1 1 * * *', 'America/Chicago') ON CONFLICT DO NOTHING;
|
||||||
|
return 0;
|
||||||
|
EXCEPTION WHEN OTHERS THEN
|
||||||
|
return 0;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
SELECT create_autodrop_bolt11s_job();
|
||||||
|
DROP FUNCTION create_autodrop_bolt11s_job();
|
|
@ -50,6 +50,7 @@ model User {
|
||||||
greeterMode Boolean @default(false)
|
greeterMode Boolean @default(false)
|
||||||
fiatCurrency String @default("USD")
|
fiatCurrency String @default("USD")
|
||||||
withdrawMaxFeeDefault Int @default(10)
|
withdrawMaxFeeDefault Int @default(10)
|
||||||
|
autoDropBolt11s Boolean @default(false)
|
||||||
hideFromTopUsers Boolean @default(false)
|
hideFromTopUsers Boolean @default(false)
|
||||||
turboTipping Boolean @default(false)
|
turboTipping Boolean @default(false)
|
||||||
imgproxyOnly Boolean @default(false)
|
imgproxyOnly Boolean @default(false)
|
||||||
|
@ -493,8 +494,8 @@ model Withdrawl {
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||||
userId Int
|
userId Int
|
||||||
hash String
|
hash String?
|
||||||
bolt11 String
|
bolt11 String?
|
||||||
msatsPaying BigInt
|
msatsPaying BigInt
|
||||||
msatsPaid BigInt?
|
msatsPaid BigInt?
|
||||||
msatsFeePaying BigInt
|
msatsFeePaying BigInt
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import PgBoss from 'pg-boss'
|
import PgBoss from 'pg-boss'
|
||||||
import nextEnv from '@next/env'
|
import nextEnv from '@next/env'
|
||||||
import { PrismaClient } from '@prisma/client'
|
import { PrismaClient } from '@prisma/client'
|
||||||
import { checkInvoice, checkWithdrawal } from './wallet.js'
|
import { checkInvoice, checkWithdrawal, autoDropBolt11s } from './wallet.js'
|
||||||
import { repin } from './repin.js'
|
import { repin } from './repin.js'
|
||||||
import { trust } from './trust.js'
|
import { trust } from './trust.js'
|
||||||
import { auction } from './auction.js'
|
import { auction } from './auction.js'
|
||||||
|
@ -57,6 +57,7 @@ async function work () {
|
||||||
await boss.start()
|
await boss.start()
|
||||||
await boss.work('checkInvoice', checkInvoice(args))
|
await boss.work('checkInvoice', checkInvoice(args))
|
||||||
await boss.work('checkWithdrawal', checkWithdrawal(args))
|
await boss.work('checkWithdrawal', checkWithdrawal(args))
|
||||||
|
await boss.work('autoDropBolt11s', autoDropBolt11s(args))
|
||||||
await boss.work('repin-*', repin(args))
|
await boss.work('repin-*', repin(args))
|
||||||
await boss.work('trust', trust(args))
|
await boss.work('trust', trust(args))
|
||||||
await boss.work('timestampItem', timestampItem(args))
|
await boss.work('timestampItem', timestampItem(args))
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { getInvoice, getPayment, cancelHodlInvoice } from 'ln-service'
|
||||||
import { datePivot } from '../lib/time.js'
|
import { datePivot } from '../lib/time.js'
|
||||||
import { sendUserNotification } from '../api/webPush/index.js'
|
import { sendUserNotification } from '../api/webPush/index.js'
|
||||||
import { msatsToSats, numWithUnits } from '../lib/format'
|
import { msatsToSats, numWithUnits } from '../lib/format'
|
||||||
|
import { INVOICE_RETENTION_DAYS } from '../lib/constants'
|
||||||
|
|
||||||
const walletOptions = { startAfter: 5, retryLimit: 21, retryBackoff: true }
|
const walletOptions = { startAfter: 5, retryLimit: 21, retryBackoff: true }
|
||||||
|
|
||||||
|
@ -118,3 +119,20 @@ export function checkWithdrawal ({ boss, models, lnd }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function autoDropBolt11s ({ models }) {
|
||||||
|
return async function () {
|
||||||
|
console.log('deleting invoices')
|
||||||
|
try {
|
||||||
|
await serialize(models, models.$executeRaw`
|
||||||
|
UPDATE "Withdrawl"
|
||||||
|
SET hash = NULL, bolt11 = NULL
|
||||||
|
WHERE "userId" IN (SELECT id FROM users WHERE "autoDropBolt11s")
|
||||||
|
AND now() > created_at + interval '${INVOICE_RETENTION_DAYS} days'
|
||||||
|
AND hash IS NOT NULL;`
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue