diff --git a/api/resolvers/notifications.js b/api/resolvers/notifications.js index 5fe0832d..3d2e75c6 100644 --- a/api/resolvers/notifications.js +++ b/api/resolvers/notifications.js @@ -1,7 +1,7 @@ import { GraphQLError } from 'graphql' import { decodeCursor, LIMIT, nextNoteCursorEncoded } from '@/lib/cursor' import { getItem, filterClause, whereClause, muteClause } from './item' -import { getInvoice } from './wallet' +import { getInvoice, getWithdrawal } from './wallet' import { pushSubscriptionSchema, ssValidate } from '@/lib/validate' import { replyToSubscription } from '@/lib/webPush' import { getSub } from './sub' @@ -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) { queries.push( `(SELECT "Invite".id, MAX(users.created_at) AS "sortTime", NULL as "earnedSats", @@ -430,6 +443,9 @@ export default { InvoicePaid: { invoice: async (n, args, { me, models }) => getInvoice(n, { id: n.id }, { me, models }) }, + WithdrawlPaid: { + withdrawl: async (n, args, { me, models }) => getWithdrawal(n, { id: n.id }, { me, models }) + }, Invitification: { invite: async (n, args, { models }) => { return await models.invite.findUnique({ diff --git a/api/resolvers/user.js b/api/resolvers/user.js index 8032b99a..efbfe642 100644 --- a/api/resolvers/user.js +++ b/api/resolvers/user.js @@ -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 if (user.noteInvites) { const [newInvites] = await models.$queryRawUnsafe(` diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js index bfca346b..e2876d50 100644 --- a/api/resolvers/wallet.js +++ b/api/resolvers/wallet.js @@ -53,6 +53,31 @@ export async function getInvoice (parent, { id }, { me, models, lnd }) { return inv } +export async function getWithdrawal (parent, { id }, { me, models }) { + if (!me) { + throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } }) + } + + const wdrwl = await models.withdrawl.findUnique({ + where: { + id: Number(id) + }, + include: { + user: true + } + }) + + if (!wdrwl) { + throw new GraphQLError('withdrawal not found', { extensions: { code: 'BAD_INPUT' } }) + } + + if (wdrwl.user.id !== me.id) { + throw new GraphQLError('not ur withdrawal', { extensions: { code: 'FORBIDDEN' } }) + } + + return wdrwl +} + export function createHmac (hash) { const key = Buffer.from(process.env.INVOICE_HMAC_KEY, 'hex') return crypto.createHmac('sha256', key).update(Buffer.from(hash, 'hex')).digest('hex') @@ -97,26 +122,7 @@ export default { } }) }, - withdrawl: async (parent, { id }, { me, models, lnd }) => { - if (!me) { - throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } }) - } - - const wdrwl = await models.withdrawl.findUnique({ - where: { - id: Number(id) - }, - include: { - user: true - } - }) - - if (wdrwl.user.id !== me.id) { - throw new GraphQLError('not ur withdrawal', { extensions: { code: 'FORBIDDEN' } }) - } - - return wdrwl - }, + withdrawl: getWithdrawal, numBolt11s: async (parent, args, { me, models, lnd }) => { if (!me) { throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } }) diff --git a/api/typeDefs/notifications.js b/api/typeDefs/notifications.js index 07d94701..5f237767 100644 --- a/api/typeDefs/notifications.js +++ b/api/typeDefs/notifications.js @@ -91,6 +91,13 @@ export default gql` sortTime: Date! } + type WithdrawlPaid { + id: ID! + earnedSats: Int! + withdrawl: Withdrawl! + sortTime: Date! + } + type Referral { id: ID! sortTime: Date! @@ -115,7 +122,7 @@ export default gql` } union Notification = Reply | Votification | Mention - | Invitification | Earn | JobChanged | InvoicePaid | Referral + | Invitification | Earn | JobChanged | InvoicePaid | WithdrawlPaid | Referral | Streak | FollowActivity | ForwardedVotification | Revenue | SubStatus | TerritoryPost | TerritoryTransfer diff --git a/api/typeDefs/user.js b/api/typeDefs/user.js index 800eb648..2300fdc3 100644 --- a/api/typeDefs/user.js +++ b/api/typeDefs/user.js @@ -82,7 +82,8 @@ export default gql` nostrRelays: [String!] noteAllDescendants: Boolean! noteCowboyHat: Boolean! - noteDeposits: Boolean! + noteDeposits: Boolean!, + noteWithdrawals: Boolean!, noteEarning: Boolean! noteForwardedSats: Boolean! noteInvites: Boolean! @@ -148,6 +149,7 @@ export default gql` noteAllDescendants: Boolean! noteCowboyHat: Boolean! noteDeposits: Boolean! + noteWithdrawals: Boolean! noteEarning: Boolean! noteForwardedSats: Boolean! noteInvites: Boolean! diff --git a/components/notifications.js b/components/notifications.js index 9a9d3360..2a99661d 100644 --- a/components/notifications.js +++ b/components/notifications.js @@ -40,6 +40,7 @@ function Notification ({ n, fresh }) { (type === 'Revenue' && ) || (type === 'Invitification' && ) || (type === 'InvoicePaid' && (n.invoice.nostr ? : )) || + (type === 'WithdrawlPaid' && ) || (type === 'Referral' && ) || (type === 'Streak' && ) || (type === 'Votification' && ) || @@ -95,6 +96,7 @@ const defaultOnClick = n => { if (type === 'SubStatus') return { href: `/~${n.sub.name}` } if (type === 'Invitification') return { href: '/invites' } if (type === 'InvoicePaid') return { href: `/invoices/${n.invoice.id}` } + if (type === 'WithdrawlPaid') return { href: `/withdrawals/${n.withdrawl.id}` } if (type === 'Referral') return { href: '/referrals/month' } if (type === 'Streak') return {} if (type === 'TerritoryTransfer') return { href: `/~${n.sub.name}` } @@ -277,6 +279,15 @@ function InvoicePaid ({ n }) { ) } +function WithdrawlPaid ({ n }) { + return ( +
+ {numWithUnits(n.earnedSats, { abbreviate: false, unitSingular: 'sat was', unitPlural: 'sats were' })} withdrawn from your account + {timeSince(new Date(n.sortTime))} +
+ ) +} + function Referral ({ n }) { return ( diff --git a/fragments/notifications.js b/fragments/notifications.js index 247506da..bc91ac42 100644 --- a/fragments/notifications.js +++ b/fragments/notifications.js @@ -133,6 +133,15 @@ export const NOTIFICATIONS = gql` lud18Data } } + ... on WithdrawlPaid { + id + sortTime + earnedSats + withdrawl { + id + satsPaid + } + } } } } ` diff --git a/fragments/users.js b/fragments/users.js index f9225eca..8f94c10e 100644 --- a/fragments/users.js +++ b/fragments/users.js @@ -30,6 +30,7 @@ export const ME = gql` noteAllDescendants noteCowboyHat noteDeposits + noteWithdrawals noteEarning noteForwardedSats noteInvites @@ -72,6 +73,7 @@ export const SETTINGS_FIELDS = gql` noteAllDescendants noteMentions noteDeposits + noteWithdrawals noteInvites noteJobIndicator noteCowboyHat diff --git a/lib/webPush.js b/lib/webPush.js index c4daa44a..f6313e5c 100644 --- a/lib/webPush.js +++ b/lib/webPush.js @@ -43,6 +43,7 @@ const createUserFilter = (tag) => { INVITE: 'noteInvites', EARN: 'noteEarning', DEPOSIT: 'noteDeposits', + WITHDRAWAL: 'noteWithdrawals', STREAK: 'noteCowboyHat' } const key = tagMap[tag.split('-')[0]] diff --git a/pages/settings/index.js b/pages/settings/index.js index e67c6326..7de221e3 100644 --- a/pages/settings/index.js +++ b/pages/settings/index.js @@ -75,6 +75,7 @@ export default function Settings ({ ssrData }) { noteAllDescendants: settings?.noteAllDescendants, noteMentions: settings?.noteMentions, noteDeposits: settings?.noteDeposits, + noteWithdrawals: settings?.noteWithdrawals, noteInvites: settings?.noteInvites, noteJobIndicator: settings?.noteJobIndicator, noteCowboyHat: settings?.noteCowboyHat, @@ -223,6 +224,11 @@ export default function Settings ({ ssrData }) { name='noteDeposits' groupClassName='mb-0' /> +