More push notification types (#530)
* 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 <ek@stacker.news> Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
This commit is contained in:
parent
b3e8280d76
commit
425220d8cb
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
@ -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,
|
||||
|
|
|
@ -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.'
|
||||
]
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
@ -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)
|
||||
}
|
||||
|
|
23
sw/index.js
23
sw/index.js
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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 } })
|
||||
})())
|
||||
})
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -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 }) {
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -81,7 +97,7 @@ export function checkStreak ({ models }) {
|
|||
return
|
||||
}
|
||||
|
||||
await models.$executeRaw`
|
||||
[streak] = await models.$queryRaw`
|
||||
WITH streak_started (id) AS (
|
||||
SELECT "userId"
|
||||
FROM
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
@ -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 })
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue