Territory notifications for everyone (#870)
* Territory notifications * Migrate old setting to new table * Auto subscribe founders to their territories on creation * Fix (un)subscribe not shown to founder * Rename to toggleSubSubscription * Fix inconsistency between toggleSubSubscription and toggleMuteSub * Add dedicated button in header for following territories * Don't drop noteTerritoryPosts column * Fix db dip in Sub.meSubscription resolver * Move territory subscribe to new territory context menu * Decrease space between share icon and mute button * Fix eslint
This commit is contained in:
parent
96ff26f26e
commit
fa4f09ddca
|
@ -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, notifyFounders } from '../../lib/push-notifications'
|
import { notifyItemParents, notifyUserSubscribers, notifyZapped, notifyTerritorySubscribers } 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'
|
||||||
|
@ -125,7 +125,8 @@ export async function itemQueryWithMeta ({ me, models, query, orderBy = '' }, ..
|
||||||
COALESCE("ItemAct"."meMsats", 0) as "meMsats",
|
COALESCE("ItemAct"."meMsats", 0) as "meMsats",
|
||||||
COALESCE("ItemAct"."meDontLikeMsats", 0) as "meDontLikeMsats", b."itemId" IS NOT NULL AS "meBookmark",
|
COALESCE("ItemAct"."meDontLikeMsats", 0) as "meDontLikeMsats", b."itemId" IS NOT NULL AS "meBookmark",
|
||||||
"ThreadSubscription"."itemId" IS NOT NULL AS "meSubscription", "ItemForward"."itemId" IS NOT NULL AS "meForward",
|
"ThreadSubscription"."itemId" IS NOT NULL AS "meSubscription", "ItemForward"."itemId" IS NOT NULL AS "meForward",
|
||||||
to_jsonb("Sub".*) || jsonb_build_object('meMuteSub', "MuteSub"."userId" IS NOT NULL) as sub
|
to_jsonb("Sub".*) || jsonb_build_object('meMuteSub', "MuteSub"."userId" IS NOT NULL)
|
||||||
|
|| jsonb_build_object('meSubscription', "SubSubscription"."userId" IS NOT NULL) as sub
|
||||||
FROM (
|
FROM (
|
||||||
${query}
|
${query}
|
||||||
) "Item"
|
) "Item"
|
||||||
|
@ -136,6 +137,7 @@ export async function itemQueryWithMeta ({ me, models, query, orderBy = '' }, ..
|
||||||
LEFT JOIN "ItemForward" ON "ItemForward"."itemId" = "Item".id AND "ItemForward"."userId" = ${me.id}
|
LEFT JOIN "ItemForward" ON "ItemForward"."itemId" = "Item".id AND "ItemForward"."userId" = ${me.id}
|
||||||
LEFT JOIN "Sub" ON "Sub"."name" = "Item"."subName"
|
LEFT JOIN "Sub" ON "Sub"."name" = "Item"."subName"
|
||||||
LEFT JOIN "MuteSub" ON "Sub"."name" = "MuteSub"."subName" AND "MuteSub"."userId" = ${me.id}
|
LEFT JOIN "MuteSub" ON "Sub"."name" = "MuteSub"."subName" AND "MuteSub"."userId" = ${me.id}
|
||||||
|
LEFT JOIN "SubSubscription" ON "Sub"."name" = "SubSubscription"."subName" AND "SubSubscription"."userId" = ${me.id}
|
||||||
LEFT JOIN LATERAL (
|
LEFT JOIN LATERAL (
|
||||||
SELECT "itemId", sum("ItemAct".msats) FILTER (WHERE act = 'FEE' OR act = 'TIP') AS "meMsats",
|
SELECT "itemId", sum("ItemAct".msats) FILTER (WHERE act = 'FEE' OR act = 'TIP') AS "meMsats",
|
||||||
sum("ItemAct".msats) FILTER (WHERE act = 'DONT_LIKE_THIS') AS "meDontLikeMsats"
|
sum("ItemAct".msats) FILTER (WHERE act = 'DONT_LIKE_THIS') AS "meDontLikeMsats"
|
||||||
|
@ -1340,7 +1342,7 @@ export const createItem = async (parent, { forward, options, ...item }, { me, mo
|
||||||
|
|
||||||
notifyUserSubscribers({ models, item })
|
notifyUserSubscribers({ models, item })
|
||||||
|
|
||||||
notifyFounders({ models, item })
|
notifyTerritorySubscribers({ models, item })
|
||||||
|
|
||||||
item.comments = []
|
item.comments = []
|
||||||
return item
|
return item
|
||||||
|
|
|
@ -107,16 +107,20 @@ export default {
|
||||||
LIMIT ${LIMIT}+$3`
|
LIMIT ${LIMIT}+$3`
|
||||||
)
|
)
|
||||||
|
|
||||||
if (meFull.noteTerritoryPosts) {
|
// Territory subscriptions
|
||||||
itemDrivenQueries.push(
|
itemDrivenQueries.push(
|
||||||
`SELECT "Item".*, "Item".created_at AS "sortTime", 'TerritoryPost' AS type
|
`SELECT "Item".*, "Item".created_at AS "sortTime", 'TerritoryPost' AS type
|
||||||
FROM "Item"
|
FROM "Item"
|
||||||
JOIN "Sub" ON "Item"."subName" = "Sub".name
|
JOIN "SubSubscription" ON "Item"."subName" = "SubSubscription"."subName"
|
||||||
WHERE "Sub"."userId" = $1 AND "Item"."userId" <> $1
|
${whereClause(
|
||||||
|
'"SubSubscription"."userId" = $1',
|
||||||
|
'"Item"."userId" <> $1',
|
||||||
|
'"Item"."parentId" IS NULL',
|
||||||
|
'"Item".created_at >= "SubSubscription".created_at'
|
||||||
|
)}
|
||||||
ORDER BY "sortTime" DESC
|
ORDER BY "sortTime" DESC
|
||||||
LIMIT ${LIMIT}+$3`
|
LIMIT ${LIMIT}+$3`
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// mentions
|
// mentions
|
||||||
if (meFull.noteMentions) {
|
if (meFull.noteMentions) {
|
||||||
|
|
|
@ -259,6 +259,22 @@ export default {
|
||||||
await models.muteSub.create({ data: { ...lookupData } })
|
await models.muteSub.create({ data: { ...lookupData } })
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
toggleSubSubscription: async (sub, { name }, { me, models }) => {
|
||||||
|
if (!me) {
|
||||||
|
throw new GraphQLError('you must be logged in', { extensions: { code: 'UNAUTHENTICATED' } })
|
||||||
|
}
|
||||||
|
|
||||||
|
const lookupData = { userId: me.id, subName: name }
|
||||||
|
const where = { userId_subName: lookupData }
|
||||||
|
const existing = await models.subSubscription.findUnique({ where })
|
||||||
|
if (existing) {
|
||||||
|
await models.subSubscription.delete({ where })
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
await models.subSubscription.create({ data: lookupData })
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Sub: {
|
Sub: {
|
||||||
|
@ -281,6 +297,9 @@ export default {
|
||||||
if (typeof sub.ncomments !== 'undefined') {
|
if (typeof sub.ncomments !== 'undefined') {
|
||||||
return sub.ncomments
|
return sub.ncomments
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
meSubscription: async (sub, args, { me, models }) => {
|
||||||
|
return sub.meSubscription || sub.SubSubscription?.length > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -331,6 +350,13 @@ async function createSub (parent, data, { me, models, lnd, hash, hmac }) {
|
||||||
msats: cost,
|
msats: cost,
|
||||||
type: 'BILLING'
|
type: 'BILLING'
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
// notify 'em (in the future)
|
||||||
|
models.subSubscription.create({
|
||||||
|
data: {
|
||||||
|
userId: me.id,
|
||||||
|
subName: data.name
|
||||||
|
}
|
||||||
})
|
})
|
||||||
], { models, lnd, hash, hmac, me, enforceFee: billingCost })
|
], { models, lnd, hash, hmac, me, enforceFee: billingCost })
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ export default gql`
|
||||||
moderated: Boolean!, hash: String, hmac: String, nsfw: Boolean!): Sub
|
moderated: Boolean!, hash: String, hmac: String, nsfw: Boolean!): Sub
|
||||||
paySub(name: String!, hash: String, hmac: String): Sub
|
paySub(name: String!, hash: String, hmac: String): Sub
|
||||||
toggleMuteSub(name: String!): Boolean!
|
toggleMuteSub(name: String!): Boolean!
|
||||||
|
toggleSubSubscription(name: String!): Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Sub {
|
type Sub {
|
||||||
|
@ -46,6 +47,7 @@ export default gql`
|
||||||
nsfw: Boolean!
|
nsfw: Boolean!
|
||||||
nposts(when: String, from: String, to: String): Int!
|
nposts(when: String, from: String, to: String): Int!
|
||||||
ncomments(when: String, from: String, to: String): Int!
|
ncomments(when: String, from: String, to: String): Int!
|
||||||
|
meSubscription: Boolean!
|
||||||
|
|
||||||
optional: SubOptional!
|
optional: SubOptional!
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,6 @@ 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!
|
||||||
|
@ -136,7 +135,6 @@ 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,7 +35,6 @@ 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',
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { useMe } from './me'
|
||||||
import Share from './share'
|
import Share from './share'
|
||||||
import { gql, useMutation } from '@apollo/client'
|
import { gql, useMutation } from '@apollo/client'
|
||||||
import { useToast } from './toast'
|
import { useToast } from './toast'
|
||||||
|
import ActionDropdown from './action-dropdown'
|
||||||
|
|
||||||
export function TerritoryDetails ({ sub }) {
|
export function TerritoryDetails ({ sub }) {
|
||||||
return (
|
return (
|
||||||
|
@ -79,7 +80,7 @@ export default function TerritoryHeader ({ sub }) {
|
||||||
<TerritoryDetails sub={sub} />
|
<TerritoryDetails sub={sub} />
|
||||||
</div>
|
</div>
|
||||||
<div className='d-flex my-2 justify-content-end'>
|
<div className='d-flex my-2 justify-content-end'>
|
||||||
<Share path={`/~${sub.name}`} title={`~${sub.name} stacker news territory`} className='mx-3' />
|
<Share path={`/~${sub.name}`} title={`~${sub.name} stacker news territory`} className='mx-1' />
|
||||||
{me &&
|
{me &&
|
||||||
(Number(sub.userId) === Number(me?.id)
|
(Number(sub.userId) === Number(me?.id)
|
||||||
? (
|
? (
|
||||||
|
@ -101,6 +102,9 @@ export default function TerritoryHeader ({ sub }) {
|
||||||
}}
|
}}
|
||||||
>{sub.meMuteSub ? 'join' : 'mute'} territory
|
>{sub.meMuteSub ? 'join' : 'mute'} territory
|
||||||
</Button>))}
|
</Button>))}
|
||||||
|
<ActionDropdown>
|
||||||
|
<ToggleSubSubscriptionDropdownItem sub={sub} />
|
||||||
|
</ActionDropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -170,3 +174,37 @@ export function PinSubDropdownItem ({ item: { id, position } }) {
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ToggleSubSubscriptionDropdownItem ({ sub: { name, meSubscription } }) {
|
||||||
|
const toaster = useToast()
|
||||||
|
const [toggleSubSubscription] = useMutation(
|
||||||
|
gql`
|
||||||
|
mutation toggleSubSubscription($name: String!) {
|
||||||
|
toggleSubSubscription(name: $name)
|
||||||
|
}`, {
|
||||||
|
update (cache, { data: { toggleSubSubscription } }) {
|
||||||
|
cache.modify({
|
||||||
|
id: `Sub:{"name":"${name}"}`,
|
||||||
|
fields: {
|
||||||
|
meSubscription: () => toggleSubSubscription
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<Dropdown.Item
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
await toggleSubSubscription({ variables: { name } })
|
||||||
|
toaster.success(meSubscription ? 'unsubscribed' : 'subscribed')
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
toaster.danger(meSubscription ? 'failed to unsubscribe' : 'failed to subscribe')
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{meSubscription ? `unsubscribe from ~${name}` : `subscribe to ~${name}`}
|
||||||
|
</Dropdown.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ export const ITEM_FIELDS = gql`
|
||||||
userId
|
userId
|
||||||
moderated
|
moderated
|
||||||
meMuteSub
|
meMuteSub
|
||||||
|
meSubscription
|
||||||
nsfw
|
nsfw
|
||||||
}
|
}
|
||||||
otsHash
|
otsHash
|
||||||
|
@ -82,6 +83,7 @@ export const ITEM_FULL_FIELDS = gql`
|
||||||
userId
|
userId
|
||||||
moderated
|
moderated
|
||||||
meMuteSub
|
meMuteSub
|
||||||
|
meSubscription
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
forwards {
|
forwards {
|
||||||
|
|
|
@ -20,6 +20,7 @@ export const SUB_FIELDS = gql`
|
||||||
moderated
|
moderated
|
||||||
moderatedCount
|
moderatedCount
|
||||||
meMuteSub
|
meMuteSub
|
||||||
|
meSubscription
|
||||||
nsfw
|
nsfw
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ export const ME = gql`
|
||||||
lastCheckedJobs
|
lastCheckedJobs
|
||||||
nostrCrossposting
|
nostrCrossposting
|
||||||
noteAllDescendants
|
noteAllDescendants
|
||||||
noteTerritoryPosts
|
|
||||||
noteCowboyHat
|
noteCowboyHat
|
||||||
noteDeposits
|
noteDeposits
|
||||||
noteEarning
|
noteEarning
|
||||||
|
@ -70,7 +69,6 @@ export const SETTINGS_FIELDS = gql`
|
||||||
noteItemSats
|
noteItemSats
|
||||||
noteEarning
|
noteEarning
|
||||||
noteAllDescendants
|
noteAllDescendants
|
||||||
noteTerritoryPosts
|
|
||||||
noteMentions
|
noteMentions
|
||||||
noteDeposits
|
noteDeposits
|
||||||
noteInvites
|
noteInvites
|
||||||
|
|
|
@ -28,6 +28,40 @@ export const notifyUserSubscribers = async ({ models, item }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const notifyTerritorySubscribers = async ({ models, item }) => {
|
||||||
|
try {
|
||||||
|
const isPost = !!item.title
|
||||||
|
const { subName } = item
|
||||||
|
|
||||||
|
// only notify on posts in subs
|
||||||
|
if (!isPost || !subName) return
|
||||||
|
|
||||||
|
const territorySubs = await models.subSubscription.findMany({
|
||||||
|
where: {
|
||||||
|
subName
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const author = await models.user.findUnique({ where: { id: item.userId } })
|
||||||
|
|
||||||
|
const tag = `TERRITORY_POST-${subName}`
|
||||||
|
await Promise.allSettled(
|
||||||
|
territorySubs
|
||||||
|
// don't send push notification to author itself
|
||||||
|
.filter(({ userId }) => userId !== author.id)
|
||||||
|
.map(({ userId }) =>
|
||||||
|
sendUserNotification(userId, {
|
||||||
|
title: `@${author.name} created a post in ~${subName}`,
|
||||||
|
body: item.title,
|
||||||
|
item,
|
||||||
|
data: { subName },
|
||||||
|
tag
|
||||||
|
})))
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const notifyItemParents = async ({ models, item, me }) => {
|
export const notifyItemParents = async ({ models, item, me }) => {
|
||||||
try {
|
try {
|
||||||
const user = await models.user.findUnique({ where: { id: me?.id || ANON_USER_ID } })
|
const user = await models.user.findUnique({ where: { id: me?.id || ANON_USER_ID } })
|
||||||
|
@ -96,29 +130,3 @@ 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -69,7 +69,6 @@ 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,
|
||||||
|
@ -220,11 +219,6 @@ 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,17 @@
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "SubSubscription" (
|
||||||
|
"userId" INTEGER NOT NULL,
|
||||||
|
"subName" CITEXT NOT NULL,
|
||||||
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "SubSubscription_pkey" PRIMARY KEY ("userId","subName")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "SubSubscription.created_at_index" ON "SubSubscription"("created_at");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "SubSubscription" ADD CONSTRAINT "SubSubscription_subName_fkey" FOREIGN KEY ("subName") REFERENCES "Sub"("name") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "SubSubscription" ADD CONSTRAINT "SubSubscription_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- migrate values from founders which had notifications for territory posts enabled to new table
|
||||||
|
INSERT INTO "SubSubscription"("userId", "subName")
|
||||||
|
SELECT u.id, s.name
|
||||||
|
FROM users u JOIN "Sub" s ON u.id = s."userId"
|
||||||
|
WHERE "noteTerritoryPosts";
|
||||||
|
|
||||||
|
-- we don't drop the users.noteTerritoryPosts column in this migration since it's a backwards incompatible change
|
|
@ -35,7 +35,6 @@ 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)
|
||||||
|
@ -81,6 +80,7 @@ model User {
|
||||||
ReferralAct ReferralAct[]
|
ReferralAct ReferralAct[]
|
||||||
Streak Streak[]
|
Streak Streak[]
|
||||||
ThreadSubscriptions ThreadSubscription[]
|
ThreadSubscriptions ThreadSubscription[]
|
||||||
|
SubSubscriptions SubSubscription[]
|
||||||
Upload Upload[] @relation("Uploads")
|
Upload Upload[] @relation("Uploads")
|
||||||
nostrRelays UserNostrRelay[]
|
nostrRelays UserNostrRelay[]
|
||||||
withdrawls Withdrawl[]
|
withdrawls Withdrawl[]
|
||||||
|
@ -489,6 +489,7 @@ model Sub {
|
||||||
Item Item[]
|
Item Item[]
|
||||||
SubAct SubAct[]
|
SubAct SubAct[]
|
||||||
MuteSub MuteSub[]
|
MuteSub MuteSub[]
|
||||||
|
SubSubscription SubSubscription[]
|
||||||
|
|
||||||
@@index([parentName])
|
@@index([parentName])
|
||||||
@@index([createdAt])
|
@@index([createdAt])
|
||||||
|
@ -736,6 +737,17 @@ model UserSubscription {
|
||||||
@@index([followeeId], map: "UserSubscription.followee_index")
|
@@index([followeeId], map: "UserSubscription.followee_index")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model SubSubscription {
|
||||||
|
userId Int
|
||||||
|
subName String @db.Citext
|
||||||
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
|
sub Sub @relation(fields: [subName], references: [name], onDelete: Cascade, onUpdate: Cascade)
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||||
|
|
||||||
|
@@id([userId, subName])
|
||||||
|
@@index([createdAt], map: "SubSubscription.created_at_index")
|
||||||
|
}
|
||||||
|
|
||||||
model PushSubscription {
|
model PushSubscription {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
userId Int
|
userId Int
|
||||||
|
|
Loading…
Reference in New Issue