Skip to content

Commit

Permalink
More push notification types (#530)
Browse files Browse the repository at this point in the history
* Add push notifications for referrals

* Add push notifications for daily rewards

* Add push notifications for deposits

* Add push notifications for earning cowboy hats

* Use streak id to synchronize blurb

* Fix usage of magic number for blurbs

* Fix missing catch

* Add push notification for losing cowboy hats

* Fix null in deposit push notification

* Add push notification for invites

* Don't replace streak push notifications

* Fix missing unit in daily reward push notification title

* Attach sats to payload options instead of parsing title

---------

Co-authored-by: ekzyis <[email protected]>
Co-authored-by: Keyan <[email protected]>
  • Loading branch information
3 people authored Oct 4, 2023
1 parent b3e8280 commit 425220d
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 32 deletions.
7 changes: 6 additions & 1 deletion api/webPush/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ const createUserFilter = (tag) => {
REPLY: 'noteAllDescendants',
MENTION: 'noteMentions',
TIP: 'noteItemSats',
FORWARDEDTIP: 'noteForwardedSats'
FORWARDEDTIP: 'noteForwardedSats',
REFERRAL: 'noteInvites',
INVITE: 'noteInvites',
EARN: 'noteEarning',
DEPOSIT: 'noteDeposits',
STREAK: 'noteCowboyHat'
}
const key = tagMap[tag.split('-')[0]]
return key ? { user: { [key]: true } } : undefined
Expand Down
22 changes: 2 additions & 20 deletions components/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { dayMonthYear, timeSince } from '../lib/time'
import Link from 'next/link'
import Check from '../svgs/check-double-line.svg'
import HandCoin from '../svgs/hand-coin-fill.svg'
import { COMMENT_DEPTH_LIMIT } from '../lib/constants'
import { COMMENT_DEPTH_LIMIT, LOST_BLURBS, FOUND_BLURBS } from '../lib/constants'
import CowboyHatIcon from '../svgs/cowboy.svg'
import BaldIcon from '../svgs/bald.svg'
import { RootProvider } from './root'
Expand Down Expand Up @@ -122,25 +122,7 @@ const defaultOnClick = n => {

function Streak ({ n }) {
function blurb (n) {
const index = Number(n.id) % 6
const FOUND_BLURBS = [
'The harsh frontier is no place for the unprepared. This hat will protect you from the sun, dust, and other elements Mother Nature throws your way.',
'A cowboy is nothing without a cowboy hat. Take good care of it, and it will protect you from the sun, dust, and other elements on your journey.',
"This is not just a hat, it's a matter of survival. Take care of this essential tool, and it will shield you from the scorching sun and the elements.",
"A cowboy hat isn't just a fashion statement. It's your last defense against the unforgiving elements of the Wild West. Hang onto it tight.",
"A good cowboy hat is worth its weight in gold, shielding you from the sun, wind, and dust of the western frontier. Don't lose it.",
'Your cowboy hat is the key to your survival in the wild west. Treat it with respect and it will protect you from the elements.'
]

const LOST_BLURBS = [
'your cowboy hat was taken by the wind storm that blew in from the west. No worries, a true cowboy always finds another hat.',
"you left your trusty cowboy hat in the saloon before leaving town. You'll need a replacement for the long journey west.",
'you lost your cowboy hat in a wild shoot-out on the outskirts of town. Tough luck, tIme to start searching for another one.',
'you ran out of food and had to trade your hat for supplies. Better start looking for another hat.',
"your hat was stolen by a mischievous prairie dog. You won't catch the dog, but you can always find another hat.",
'you lost your hat while crossing the river on your journey west. Maybe you can find a replacement hat in the next town.'
]

const index = Number(n.id) % Math.min(FOUND_BLURBS.length, LOST_BLURBS.length)
if (n.days) {
return `After ${numWithUnits(n.days, {
abbreviate: false,
Expand Down
17 changes: 17 additions & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,20 @@ export const ANON_COMMENT_FEE = 100
export const SSR = typeof window === 'undefined'
export const MAX_FORWARDS = 5
export const LNURLP_COMMENT_MAX_LENGTH = 1000

export const FOUND_BLURBS = [
'The harsh frontier is no place for the unprepared. This hat will protect you from the sun, dust, and other elements Mother Nature throws your way.',
'A cowboy is nothing without a cowboy hat. Take good care of it, and it will protect you from the sun, dust, and other elements on your journey.',
"This is not just a hat, it's a matter of survival. Take care of this essential tool, and it will shield you from the scorching sun and the elements.",
"A cowboy hat isn't just a fashion statement. It's your last defense against the unforgiving elements of the Wild West. Hang onto it tight.",
"A good cowboy hat is worth its weight in gold, shielding you from the sun, wind, and dust of the western frontier. Don't lose it.",
'Your cowboy hat is the key to your survival in the wild west. Treat it with respect and it will protect you from the elements.'
]
export const LOST_BLURBS = [
'your cowboy hat was taken by the wind storm that blew in from the west. No worries, a true cowboy always finds another hat.',
"you left your trusty cowboy hat in the saloon before leaving town. You'll need a replacement for the long journey west.",
'you lost your cowboy hat in a wild shoot-out on the outskirts of town. Tough luck, tIme to start searching for another one.',
'you ran out of food and had to trade your hat for supplies. Better start looking for another hat.',
"your hat was stolen by a mischievous prairie dog. You won't catch the dog, but you can always find another hat.",
'you lost your hat while crossing the river on your journey west. Maybe you can find a replacement hat in the next town.'
]
2 changes: 2 additions & 0 deletions pages/api/auth/[...nextauth].js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { decode, getToken } from 'next-auth/jwt'
import { NodeNextRequest } from 'next/dist/server/base-http/node'
import jose1 from 'jose1'
import { schnorr } from '@noble/curves/secp256k1'
import { sendUserNotification } from '../../../api/webPush'

function getCallbacks (req) {
return {
Expand Down Expand Up @@ -42,6 +43,7 @@ function getCallbacks (req) {
const referrer = await prisma.user.findUnique({ where: { name: req.cookies.sn_referrer } })
if (referrer) {
await prisma.user.update({ where: { id: user.id }, data: { referrerId: referrer.id } })
sendUserNotification(referrer.id, { title: 'someone joined via one of your referral links', tag: 'REFERRAL' }).catch(console.error)
}
}

Expand Down
3 changes: 3 additions & 0 deletions pages/invites/[id].js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import getSSRApolloClient from '../../api/ssrApollo'
import Link from 'next/link'
import { CenterLayout } from '../../components/layout'
import { getAuthOptions } from '../api/auth/[...nextauth]'
import { sendUserNotification } from '../../api/webPush'

export async function getServerSideProps ({ req, res, query: { id, error = null } }) {
const session = await getServerSession(req, res, getAuthOptions(req))
Expand Down Expand Up @@ -36,6 +37,8 @@ export async function getServerSideProps ({ req, res, query: { id, error = null
// catch any errors and just ignore them for now
await serialize(models,
models.$queryRawUnsafe('SELECT invite_drain($1::INTEGER, $2::INTEGER)', session.user.id, id))
const invite = await models.invite.findUnique({ where: { id } })
sendUserNotification(invite.userId, { title: 'your invite has been redeemed', tag: 'INVITE' }).catch(console.error)
} catch (e) {
console.log(e)
}
Expand Down
23 changes: 18 additions & 5 deletions sw/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { NetworkOnly } from 'workbox-strategies'
import { enable } from 'workbox-navigation-preload'
import manifest from './precache-manifest.json'
import ServiceWorkerStorage from 'serviceworker-storage'
import { numWithUnits } from '../lib/format'

// comment out to enable workbox console logs
self.__WB_DISABLE_DEV_LOGS = true
Expand Down Expand Up @@ -49,7 +50,8 @@ self.addEventListener('push', async function (event) {
if (!payload) return
const { tag } = payload.options
event.waitUntil((async () => {
if (!['REPLY', 'MENTION'].includes(tag)) {
// TIP and EARN notifications simply replace the previous notifications
if (!tag || ['TIP', 'EARN'].includes(tag.split('-')[0])) {
return self.registration.showNotification(payload.title, payload.options)
}

Expand All @@ -66,15 +68,26 @@ self.addEventListener('push', async function (event) {
}
const currentNotification = notifications[0]
const amount = currentNotification.data?.amount ? currentNotification.data.amount + 1 : 2
let title = ''
let newTitle = ''
const data = {}
if (tag === 'REPLY') {
title = `You have ${amount} new replies`
newTitle = `You have ${amount} new replies`
} else if (tag === 'MENTION') {
title = `You were mentioned ${amount} times`
newTitle = `You were mentioned ${amount} times`
} else if (tag === 'REFERRAL') {
newTitle = `${amount} stackers joined via your referral links`
} else if (tag === 'INVITE') {
newTitle = `your invite has been redeemed by ${amount} stackers`
} else if (tag === 'DEPOSIT') {
const currentSats = currentNotification.data.sats
const incomingSats = payload.options.data.sats
const newSats = currentSats + incomingSats
data.sats = newSats
newTitle = `${numWithUnits(newSats, { abbreviate: false })} were deposited in your account`
}
currentNotification.close()
const { icon } = currentNotification
return self.registration.showNotification(title, { icon, tag, data: { url: '/notifications', amount } })
return self.registration.showNotification(newTitle, { icon, tag, data: { url: '/notifications', amount, ...data } })
})())
})

Expand Down
6 changes: 6 additions & 0 deletions worker/earn.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import serialize from '../api/resolvers/serial.js'
import { sendUserNotification } from '../api/webPush/index.js'
import { ANON_USER_ID } from '../lib/constants.js'
import { msatsToSats, numWithUnits } from '../lib/format.js'

const ITEM_EACH_REWARD = 4.0
const UPVOTE_EACH_REWARD = 4.0
Expand Down Expand Up @@ -160,6 +162,10 @@ export function earn ({ models }) {
await serialize(models,
models.$executeRaw`SELECT earn(${earner.userId}::INTEGER, ${earnings},
${now}::timestamp without time zone, ${earner.type}::"EarnType", ${earner.id}::INTEGER, ${earner.rank}::INTEGER)`)
sendUserNotification(earner.userId, {
title: `you stacked ${numWithUnits(msatsToSats(earnings), { abbreviate: false })} in rewards`,
tag: 'EARN'
}).catch(console.error)
}
})

Expand Down
40 changes: 34 additions & 6 deletions worker/streak.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { sendUserNotification } from '../api/webPush'
import { FOUND_BLURBS, LOST_BLURBS } from '../lib/constants'

const STREAK_THRESHOLD = 100

export function computeStreaks ({ models }) {
Expand All @@ -7,8 +10,8 @@ export function computeStreaks ({ models }) {
// get all eligible users in the last day
// if the user doesn't have an active streak, add one
// if they have an active streak but didn't maintain it, end it
await models.$executeRawUnsafe(
`WITH day_streaks (id) AS (
const endingStreaks = await models.$queryRaw`
WITH day_streaks (id) AS (
SELECT "userId"
FROM
((SELECT "userId", floor(sum("ItemAct".msats)/1000) as sats_spent
Expand Down Expand Up @@ -58,7 +61,20 @@ export function computeStreaks ({ models }) {
UPDATE "Streak"
SET "endedAt" = (now() AT TIME ZONE 'America/Chicago' - interval '1 day')::date, updated_at = now_utc()
FROM ending_streaks
WHERE ending_streaks.id = "Streak"."userId" AND "endedAt" IS NULL`)
WHERE ending_streaks.id = "Streak"."userId" AND "endedAt" IS NULL
RETURNING "Streak".id, ending_streaks."id" AS "userId"`

Promise.allSettled(
endingStreaks.map(({ id, userId }) => {
const index = id % LOST_BLURBS.length
const blurb = LOST_BLURBS[index]
return sendUserNotification(userId, {
title: 'you lost your cowboy hat',
body: blurb,
tag: 'STREAK-LOST'
}).catch(console.error)
})
)

console.log('done computing streaks')
}
Expand All @@ -69,7 +85,7 @@ export function checkStreak ({ models }) {
console.log('checking streak', id)

// if user is actively streaking skip
const streak = await models.streak.findFirst({
let streak = await models.streak.findFirst({
where: {
userId: Number(id),
endedAt: null
Expand All @@ -81,7 +97,7 @@ export function checkStreak ({ models }) {
return
}

await models.$executeRaw`
[streak] = await models.$queryRaw`
WITH streak_started (id) AS (
SELECT "userId"
FROM
Expand All @@ -103,8 +119,20 @@ export function checkStreak ({ models }) {
)
INSERT INTO "Streak" ("userId", "startedAt", created_at, updated_at)
SELECT id, (now() AT TIME ZONE 'America/Chicago')::date, now_utc(), now_utc()
FROM streak_started`
FROM streak_started
RETURNING "Streak".id`

console.log('done checking streak', id)

if (!streak) return

// new streak started for user
const index = streak.id % FOUND_BLURBS.length
const blurb = FOUND_BLURBS[index]
sendUserNotification(id, {
title: 'you found a cowboy hat',
body: blurb,
tag: 'STREAK-FOUND'
}).catch(console.error)
}
}
8 changes: 8 additions & 0 deletions worker/wallet.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import serialize from '../api/resolvers/serial.js'
import { getInvoice, getPayment, cancelHodlInvoice } from 'ln-service'
import { datePivot } from '../lib/time.js'
import { sendUserNotification } from '../api/webPush/index.js'
import { msatsToSats, numWithUnits } from '../lib/format'

const walletOptions = { startAfter: 5, retryLimit: 21, retryBackoff: true }

Expand Down Expand Up @@ -29,6 +31,12 @@ export function checkInvoice ({ boss, models, lnd }) {
// we manually confirm them when we settle them
await serialize(models,
models.$executeRaw`SELECT confirm_invoice(${inv.id}, ${Number(inv.received_mtokens)})`)
sendUserNotification(dbInv.userId, {
title: `${numWithUnits(msatsToSats(inv.received_mtokens), { abbreviate: false })} were deposited in your account`,
body: dbInv.comment || undefined,
tag: 'DEPOSIT',
data: { sats: msatsToSats(inv.received_mtokens) }
}).catch(console.error)
return boss.send('nip57', { hash })
}

Expand Down

0 comments on commit 425220d

Please sign in to comment.