Territory post notifications (#745)
* Notify founders of new posts * Only merge notifications of same territory * Show territory posts in /notifications * Don't notify on own posts
This commit is contained in:
parent
39c9775c4c
commit
bdf9b1f0fd
@ -17,7 +17,7 @@ import uu from 'url-unshort'
|
|||||||
import { actSchema, advSchema, bountySchema, commentSchema, discussionSchema, jobSchema, linkSchema, pollSchema, ssValidate } from '../../lib/validate'
|
import { actSchema, advSchema, bountySchema, commentSchema, discussionSchema, jobSchema, linkSchema, pollSchema, ssValidate } from '../../lib/validate'
|
||||||
import { sendUserNotification } from '../webPush'
|
import { sendUserNotification } from '../webPush'
|
||||||
import { defaultCommentSort, isJob, deleteItemByAuthor, getDeleteCommand, hasDeleteCommand } from '../../lib/item'
|
import { defaultCommentSort, isJob, deleteItemByAuthor, getDeleteCommand, hasDeleteCommand } from '../../lib/item'
|
||||||
import { notifyItemParents, notifyUserSubscribers, notifyZapped } from '../../lib/push-notifications'
|
import { notifyItemParents, notifyUserSubscribers, notifyZapped, notifyFounders } from '../../lib/push-notifications'
|
||||||
import { datePivot, whenRange } from '../../lib/time'
|
import { datePivot, whenRange } from '../../lib/time'
|
||||||
import { imageFeesInfo, uploadIdsFromText } from './image'
|
import { imageFeesInfo, uploadIdsFromText } from './image'
|
||||||
import assertGofacYourself from './ofac'
|
import assertGofacYourself from './ofac'
|
||||||
@ -1228,6 +1228,8 @@ export const createItem = async (parent, { forward, options, ...item }, { me, mo
|
|||||||
|
|
||||||
notifyUserSubscribers({ models, item })
|
notifyUserSubscribers({ models, item })
|
||||||
|
|
||||||
|
notifyFounders({ models, item })
|
||||||
|
|
||||||
item.comments = []
|
item.comments = []
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
@ -107,6 +107,17 @@ export default {
|
|||||||
LIMIT ${LIMIT}+$3`
|
LIMIT ${LIMIT}+$3`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (meFull.noteTerritoryPosts) {
|
||||||
|
itemDrivenQueries.push(
|
||||||
|
`SELECT "Item".*, "Item".created_at AS "sortTime", 'TerritoryPost' AS type
|
||||||
|
FROM "Item"
|
||||||
|
JOIN "Sub" ON "Item"."subName" = "Sub".name
|
||||||
|
WHERE "Sub"."userId" = $1 AND "Item"."userId" <> $1
|
||||||
|
ORDER BY "sortTime" DESC
|
||||||
|
LIMIT ${LIMIT}+$3`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// mentions
|
// mentions
|
||||||
if (meFull.noteMentions) {
|
if (meFull.noteMentions) {
|
||||||
itemDrivenQueries.push(
|
itemDrivenQueries.push(
|
||||||
@ -137,6 +148,7 @@ export default {
|
|||||||
WHEN type = 'Mention' THEN 1
|
WHEN type = 'Mention' THEN 1
|
||||||
WHEN type = 'Reply' THEN 2
|
WHEN type = 'Reply' THEN 2
|
||||||
WHEN type = 'FollowActivity' THEN 3
|
WHEN type = 'FollowActivity' THEN 3
|
||||||
|
WHEN type = 'TerritoryPost' THEN 4
|
||||||
END ASC
|
END ASC
|
||||||
)`
|
)`
|
||||||
)
|
)
|
||||||
@ -348,6 +360,9 @@ export default {
|
|||||||
FollowActivity: {
|
FollowActivity: {
|
||||||
item: async (n, args, { models, me }) => getItem(n, { id: n.id }, { models, me })
|
item: async (n, args, { models, me }) => getItem(n, { id: n.id }, { models, me })
|
||||||
},
|
},
|
||||||
|
TerritoryPost: {
|
||||||
|
item: async (n, args, { models, me }) => getItem(n, { id: n.id }, { models, me })
|
||||||
|
},
|
||||||
JobChanged: {
|
JobChanged: {
|
||||||
item: async (n, args, { models, me }) => getItem(n, { id: n.id }, { models, me })
|
item: async (n, args, { models, me }) => getItem(n, { id: n.id }, { models, me })
|
||||||
},
|
},
|
||||||
|
@ -102,9 +102,15 @@ export default gql`
|
|||||||
sortTime: Date!
|
sortTime: Date!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TerritoryPost {
|
||||||
|
id: ID!
|
||||||
|
item: Item!
|
||||||
|
sortTime: Date!
|
||||||
|
}
|
||||||
|
|
||||||
union Notification = Reply | Votification | Mention
|
union Notification = Reply | Votification | Mention
|
||||||
| Invitification | Earn | JobChanged | InvoicePaid | Referral
|
| Invitification | Earn | JobChanged | InvoicePaid | Referral
|
||||||
| Streak | FollowActivity | ForwardedVotification | Revenue | SubStatus
|
| Streak | FollowActivity | ForwardedVotification | Revenue | SubStatus | TerritoryPost
|
||||||
|
|
||||||
type Notifications {
|
type Notifications {
|
||||||
lastChecked: Date
|
lastChecked: Date
|
||||||
|
@ -69,6 +69,7 @@ export default gql`
|
|||||||
nostrPubkey: String
|
nostrPubkey: String
|
||||||
nostrRelays: [String!]
|
nostrRelays: [String!]
|
||||||
noteAllDescendants: Boolean!
|
noteAllDescendants: Boolean!
|
||||||
|
noteTerritoryPosts: Boolean!
|
||||||
noteCowboyHat: Boolean!
|
noteCowboyHat: Boolean!
|
||||||
noteDeposits: Boolean!
|
noteDeposits: Boolean!
|
||||||
noteEarning: Boolean!
|
noteEarning: Boolean!
|
||||||
@ -125,6 +126,7 @@ export default gql`
|
|||||||
nostrPubkey: String
|
nostrPubkey: String
|
||||||
nostrRelays: [String!]
|
nostrRelays: [String!]
|
||||||
noteAllDescendants: Boolean!
|
noteAllDescendants: Boolean!
|
||||||
|
noteTerritoryPosts: Boolean!
|
||||||
noteCowboyHat: Boolean!
|
noteCowboyHat: Boolean!
|
||||||
noteDeposits: Boolean!
|
noteDeposits: Boolean!
|
||||||
noteEarning: Boolean!
|
noteEarning: Boolean!
|
||||||
|
@ -35,6 +35,7 @@ const createUserFilter = (tag) => {
|
|||||||
// filter users by notification settings
|
// filter users by notification settings
|
||||||
const tagMap = {
|
const tagMap = {
|
||||||
REPLY: 'noteAllDescendants',
|
REPLY: 'noteAllDescendants',
|
||||||
|
TERRITORY_POST: 'noteTerritoryPosts',
|
||||||
MENTION: 'noteMentions',
|
MENTION: 'noteMentions',
|
||||||
TIP: 'noteItemSats',
|
TIP: 'noteItemSats',
|
||||||
FORWARDEDTIP: 'noteForwardedSats',
|
FORWARDEDTIP: 'noteForwardedSats',
|
||||||
|
@ -47,7 +47,8 @@ function Notification ({ n, fresh }) {
|
|||||||
(type === 'JobChanged' && <JobChanged n={n} />) ||
|
(type === 'JobChanged' && <JobChanged n={n} />) ||
|
||||||
(type === 'Reply' && <Reply n={n} />) ||
|
(type === 'Reply' && <Reply n={n} />) ||
|
||||||
(type === 'SubStatus' && <SubStatus n={n} />) ||
|
(type === 'SubStatus' && <SubStatus n={n} />) ||
|
||||||
(type === 'FollowActivity' && <FollowActivity n={n} />)
|
(type === 'FollowActivity' && <FollowActivity n={n} />) ||
|
||||||
|
(type === 'TerritoryPost' && <TerritoryPost n={n} />)
|
||||||
}
|
}
|
||||||
</NotificationLayout>
|
</NotificationLayout>
|
||||||
)
|
)
|
||||||
@ -419,6 +420,19 @@ function FollowActivity ({ n }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TerritoryPost ({ n }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<small className='fw-bold text-info ms-2'>
|
||||||
|
new post in ~{n.item.sub.name}
|
||||||
|
</small>
|
||||||
|
<div>
|
||||||
|
<Item item={n.item} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function NotificationAlert () {
|
export function NotificationAlert () {
|
||||||
const [showAlert, setShowAlert] = useState(false)
|
const [showAlert, setShowAlert] = useState(false)
|
||||||
const [hasSubscription, setHasSubscription] = useState(false)
|
const [hasSubscription, setHasSubscription] = useState(false)
|
||||||
|
@ -86,6 +86,14 @@ export const NOTIFICATIONS = gql`
|
|||||||
text
|
text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
... on TerritoryPost {
|
||||||
|
id
|
||||||
|
sortTime
|
||||||
|
item {
|
||||||
|
...ItemFullFields
|
||||||
|
text
|
||||||
|
}
|
||||||
|
}
|
||||||
... on Invitification {
|
... on Invitification {
|
||||||
id
|
id
|
||||||
sortTime
|
sortTime
|
||||||
|
@ -23,6 +23,7 @@ export const ME = gql`
|
|||||||
lastCheckedJobs
|
lastCheckedJobs
|
||||||
nostrCrossposting
|
nostrCrossposting
|
||||||
noteAllDescendants
|
noteAllDescendants
|
||||||
|
noteTerritoryPosts
|
||||||
noteCowboyHat
|
noteCowboyHat
|
||||||
noteDeposits
|
noteDeposits
|
||||||
noteEarning
|
noteEarning
|
||||||
@ -57,6 +58,7 @@ export const SETTINGS_FIELDS = gql`
|
|||||||
noteItemSats
|
noteItemSats
|
||||||
noteEarning
|
noteEarning
|
||||||
noteAllDescendants
|
noteAllDescendants
|
||||||
|
noteTerritoryPosts
|
||||||
noteMentions
|
noteMentions
|
||||||
noteDeposits
|
noteDeposits
|
||||||
noteInvites
|
noteInvites
|
||||||
|
@ -96,3 +96,29 @@ export const notifyZapped = async ({ models, id }) => {
|
|||||||
console.error(err)
|
console.error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const notifyFounders = async ({ models, item }) => {
|
||||||
|
try {
|
||||||
|
const isPost = !!item.title
|
||||||
|
|
||||||
|
// only notify on posts in subs
|
||||||
|
if (!isPost || !item.subName) return
|
||||||
|
|
||||||
|
const author = await models.user.findUnique({ where: { id: item.userId } })
|
||||||
|
const sub = await models.sub.findUnique({ where: { name: item.subName } })
|
||||||
|
|
||||||
|
// don't send notifications on own posts to founders
|
||||||
|
if (sub.userId === author.id) return
|
||||||
|
|
||||||
|
const tag = `TERRITORY_POST-${sub.name}`
|
||||||
|
await sendUserNotification(sub.userId, {
|
||||||
|
title: `@${author.name} created a post in ~${sub.name}`,
|
||||||
|
body: item.title,
|
||||||
|
item,
|
||||||
|
data: { subName: sub.name },
|
||||||
|
tag
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -68,6 +68,7 @@ export default function Settings ({ ssrData }) {
|
|||||||
noteItemSats: settings?.noteItemSats,
|
noteItemSats: settings?.noteItemSats,
|
||||||
noteEarning: settings?.noteEarning,
|
noteEarning: settings?.noteEarning,
|
||||||
noteAllDescendants: settings?.noteAllDescendants,
|
noteAllDescendants: settings?.noteAllDescendants,
|
||||||
|
noteTerritoryPosts: settings?.noteTerritoryPosts,
|
||||||
noteMentions: settings?.noteMentions,
|
noteMentions: settings?.noteMentions,
|
||||||
noteDeposits: settings?.noteDeposits,
|
noteDeposits: settings?.noteDeposits,
|
||||||
noteInvites: settings?.noteInvites,
|
noteInvites: settings?.noteInvites,
|
||||||
@ -196,6 +197,11 @@ export default function Settings ({ ssrData }) {
|
|||||||
name='noteAllDescendants'
|
name='noteAllDescendants'
|
||||||
groupClassName='mb-0'
|
groupClassName='mb-0'
|
||||||
/>
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label='someone writes a post in a territory I founded'
|
||||||
|
name='noteTerritoryPosts'
|
||||||
|
groupClassName='mb-0'
|
||||||
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label='someone joins using my invite or referral links'
|
label='someone joins using my invite or referral links'
|
||||||
name='noteInvites'
|
name='noteInvites'
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "users" ADD COLUMN "noteTerritoryPosts" BOOLEAN NOT NULL DEFAULT true;
|
@ -35,6 +35,7 @@ model User {
|
|||||||
lastSeenAt DateTime?
|
lastSeenAt DateTime?
|
||||||
stackedMsats BigInt @default(0)
|
stackedMsats BigInt @default(0)
|
||||||
noteAllDescendants Boolean @default(true)
|
noteAllDescendants Boolean @default(true)
|
||||||
|
noteTerritoryPosts Boolean @default(true)
|
||||||
noteDeposits Boolean @default(true)
|
noteDeposits Boolean @default(true)
|
||||||
noteEarning Boolean @default(true)
|
noteEarning Boolean @default(true)
|
||||||
noteInvites Boolean @default(true)
|
noteInvites Boolean @default(true)
|
||||||
|
@ -113,7 +113,7 @@ const mergeAndShowNotification = async (sw, payload, currentNotifications, tag,
|
|||||||
// merge notifications into single notification payload
|
// merge notifications into single notification payload
|
||||||
// ---
|
// ---
|
||||||
// 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']
|
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']
|
||||||
// this should reflect the amount of notifications that were already merged before
|
// this should reflect the amount of notifications that were already merged before
|
||||||
@ -135,7 +135,7 @@ const mergeAndShowNotification = async (sw, payload, currentNotifications, tag,
|
|||||||
log(`[sw:push] ${nid} - merged payload: ${JSON.stringify(mergedPayload)}`)
|
log(`[sw:push] ${nid} - merged payload: ${JSON.stringify(mergedPayload)}`)
|
||||||
|
|
||||||
// calculate title from merged payload
|
// calculate title from merged payload
|
||||||
const { amount, followeeName, subType, sats } = mergedPayload
|
const { amount, followeeName, subName, subType, sats } = mergedPayload
|
||||||
let title = ''
|
let title = ''
|
||||||
if (AMOUNT_TAGS.includes(compareTag)) {
|
if (AMOUNT_TAGS.includes(compareTag)) {
|
||||||
if (compareTag === 'REPLY') {
|
if (compareTag === 'REPLY') {
|
||||||
@ -148,6 +148,8 @@ const mergeAndShowNotification = async (sw, payload, currentNotifications, tag,
|
|||||||
title = `your invite has been redeemed by ${amount} stackers`
|
title = `your invite has been redeemed by ${amount} stackers`
|
||||||
} else if (compareTag === 'FOLLOW') {
|
} else if (compareTag === 'FOLLOW') {
|
||||||
title = `@${followeeName} ${subType === 'POST' ? `created ${amount} posts` : `replied ${amount} times`}`
|
title = `@${followeeName} ${subType === 'POST' ? `created ${amount} posts` : `replied ${amount} times`}`
|
||||||
|
} else if (compareTag === 'TERRITORY_POST') {
|
||||||
|
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
|
// there is only DEPOSIT in this array
|
||||||
|
Loading…
x
Reference in New Issue
Block a user