use keyset pagination for notifications (#899)

This commit is contained in:
Keyan 2024-03-06 13:53:13 -06:00 committed by GitHub
parent 8d49c034c6
commit 48aef15a07
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 48 additions and 31 deletions

View File

@ -1,5 +1,5 @@
import { GraphQLError } from 'graphql' import { GraphQLError } from 'graphql'
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor' import { decodeCursor, LIMIT, nextNoteCursorEncoded } from '../../lib/cursor'
import { getItem, filterClause, whereClause, muteClause } from './item' import { getItem, filterClause, whereClause, muteClause } from './item'
import { getInvoice } from './wallet' import { getInvoice } from './wallet'
import { pushSubscriptionSchema, ssValidate } from '../../lib/validate' import { pushSubscriptionSchema, ssValidate } from '../../lib/validate'
@ -84,10 +84,11 @@ export default {
'"ThreadSubscription"."userId" = $1', '"ThreadSubscription"."userId" = $1',
'"Item"."userId" <> $1', '"Item"."userId" <> $1',
'"Item".created_at >= "ThreadSubscription".created_at', '"Item".created_at >= "ThreadSubscription".created_at',
'"Item".created_at < $2',
'"Item"."parentId" IS NOT NULL' '"Item"."parentId" IS NOT NULL'
)} )}
ORDER BY "sortTime" DESC ORDER BY "sortTime" DESC
LIMIT ${LIMIT}+$3` LIMIT ${LIMIT}`
) )
// User subscriptions // User subscriptions
@ -97,6 +98,7 @@ export default {
FROM "Item" FROM "Item"
JOIN "UserSubscription" ON "Item"."userId" = "UserSubscription"."followeeId" JOIN "UserSubscription" ON "Item"."userId" = "UserSubscription"."followeeId"
${whereClause( ${whereClause(
'"Item".created_at < $2',
'"UserSubscription"."followerId" = $1', '"UserSubscription"."followerId" = $1',
`( `(
("Item"."parentId" IS NULL AND "UserSubscription"."postsSubscribedAt" IS NOT NULL AND "Item".created_at >= "UserSubscription"."postsSubscribedAt") ("Item"."parentId" IS NULL AND "UserSubscription"."postsSubscribedAt" IS NOT NULL AND "Item".created_at >= "UserSubscription"."postsSubscribedAt")
@ -104,7 +106,7 @@ export default {
)` )`
)} )}
ORDER BY "sortTime" DESC ORDER BY "sortTime" DESC
LIMIT ${LIMIT}+$3` LIMIT ${LIMIT}`
) )
// Territory subscriptions // Territory subscriptions
@ -113,13 +115,14 @@ export default {
FROM "Item" FROM "Item"
JOIN "SubSubscription" ON "Item"."subName" = "SubSubscription"."subName" JOIN "SubSubscription" ON "Item"."subName" = "SubSubscription"."subName"
${whereClause( ${whereClause(
'"Item".created_at < $2',
'"SubSubscription"."userId" = $1', '"SubSubscription"."userId" = $1',
'"Item"."userId" <> $1', '"Item"."userId" <> $1',
'"Item"."parentId" IS NULL', '"Item"."parentId" IS NULL',
'"Item".created_at >= "SubSubscription".created_at' '"Item".created_at >= "SubSubscription".created_at'
)} )}
ORDER BY "sortTime" DESC ORDER BY "sortTime" DESC
LIMIT ${LIMIT}+$3` LIMIT ${LIMIT}`
) )
// mentions // mentions
@ -129,11 +132,12 @@ export default {
FROM "Mention" FROM "Mention"
JOIN "Item" ON "Mention"."itemId" = "Item".id JOIN "Item" ON "Mention"."itemId" = "Item".id
${whereClause( ${whereClause(
'"Item".created_at < $2',
'"Mention"."userId" = $1', '"Mention"."userId" = $1',
'"Item"."userId" <> $1' '"Item"."userId" <> $1'
)} )}
ORDER BY "sortTime" DESC ORDER BY "sortTime" DESC
LIMIT ${LIMIT}+$3` LIMIT ${LIMIT}`
) )
} }
// Inner union to de-dupe item-driven notifications // Inner union to de-dupe item-driven notifications
@ -145,7 +149,7 @@ export default {
${itemDrivenQueries.map(q => `(${q})`).join(' UNION ALL ')} ${itemDrivenQueries.map(q => `(${q})`).join(' UNION ALL ')}
) as "Item" ) as "Item"
${whereClause( ${whereClause(
'"Item".created_at <= $2', '"Item".created_at < $2',
await filterClause(me, models), await filterClause(me, models),
muteClause(me))} muteClause(me))}
ORDER BY id ASC, CASE ORDER BY id ASC, CASE
@ -163,9 +167,9 @@ export default {
FROM "Item" FROM "Item"
WHERE "Item"."userId" = $1 WHERE "Item"."userId" = $1
AND "maxBid" IS NOT NULL AND "maxBid" IS NOT NULL
AND "statusUpdatedAt" <= $2 AND "statusUpdatedAt" <> created_at AND "statusUpdatedAt" < $2 AND "statusUpdatedAt" <> created_at
ORDER BY "sortTime" DESC ORDER BY "sortTime" DESC
LIMIT ${LIMIT}+$3)` LIMIT ${LIMIT})`
) )
// territory transfers // territory transfers
@ -186,12 +190,12 @@ export default {
FROM "Item" FROM "Item"
JOIN "ItemAct" ON "ItemAct"."itemId" = "Item".id JOIN "ItemAct" ON "ItemAct"."itemId" = "Item".id
WHERE "ItemAct"."userId" <> $1 WHERE "ItemAct"."userId" <> $1
AND "ItemAct".created_at <= $2 AND "ItemAct".created_at < $2
AND "ItemAct".act IN ('TIP', 'FEE') AND "ItemAct".act IN ('TIP', 'FEE')
AND "Item"."userId" = $1 AND "Item"."userId" = $1
GROUP BY "Item".id GROUP BY "Item".id
ORDER BY "sortTime" DESC ORDER BY "sortTime" DESC
LIMIT ${LIMIT}+$3)` LIMIT ${LIMIT})`
) )
} }
@ -204,11 +208,11 @@ export default {
JOIN "ItemForward" ON "ItemForward"."itemId" = "Item".id AND "ItemForward"."userId" = $1 JOIN "ItemForward" ON "ItemForward"."itemId" = "Item".id AND "ItemForward"."userId" = $1
WHERE "ItemAct"."userId" <> $1 WHERE "ItemAct"."userId" <> $1
AND "Item"."userId" <> $1 AND "Item"."userId" <> $1
AND "ItemAct".created_at <= $2 AND "ItemAct".created_at < $2
AND "ItemAct".act IN ('TIP') AND "ItemAct".act IN ('TIP')
GROUP BY "Item".id GROUP BY "Item".id
ORDER BY "sortTime" DESC ORDER BY "sortTime" DESC
LIMIT ${LIMIT}+$3)` LIMIT ${LIMIT})`
) )
} }
@ -220,9 +224,9 @@ export default {
WHERE "Invoice"."userId" = $1 WHERE "Invoice"."userId" = $1
AND "confirmedAt" IS NOT NULL AND "confirmedAt" IS NOT NULL
AND "isHeld" IS NULL AND "isHeld" IS NULL
AND created_at <= $2 AND created_at < $2
ORDER BY "sortTime" DESC ORDER BY "sortTime" DESC
LIMIT ${LIMIT}+$3)` LIMIT ${LIMIT})`
) )
} }
@ -232,10 +236,10 @@ export default {
'Invitification' AS type 'Invitification' AS type
FROM users JOIN "Invite" on users."inviteId" = "Invite".id FROM users JOIN "Invite" on users."inviteId" = "Invite".id
WHERE "Invite"."userId" = $1 WHERE "Invite"."userId" = $1
AND users.created_at <= $2 AND users.created_at < $2
GROUP BY "Invite".id GROUP BY "Invite".id
ORDER BY "sortTime" DESC ORDER BY "sortTime" DESC
LIMIT ${LIMIT}+$3)` LIMIT ${LIMIT})`
) )
queries.push( queries.push(
`(SELECT users.id::text, users.created_at AS "sortTime", NULL as "earnedSats", `(SELECT users.id::text, users.created_at AS "sortTime", NULL as "earnedSats",
@ -243,37 +247,44 @@ export default {
FROM users FROM users
WHERE "users"."referrerId" = $1 WHERE "users"."referrerId" = $1
AND "inviteId" IS NULL AND "inviteId" IS NULL
AND users.created_at <= $2 AND users.created_at < $2
LIMIT ${LIMIT}+$3)` ORDER BY "sortTime" DESC
LIMIT ${LIMIT})`
) )
} }
if (meFull.noteEarning) { if (meFull.noteEarning) {
queries.push( queries.push(
`SELECT min(id)::text, created_at AS "sortTime", FLOOR(sum(msats) / 1000) as "earnedSats", `(SELECT min(id)::text, created_at AS "sortTime", FLOOR(sum(msats) / 1000) as "earnedSats",
'Earn' AS type 'Earn' AS type
FROM "Earn" FROM "Earn"
WHERE "userId" = $1 WHERE "userId" = $1
AND created_at <= $2 AND created_at < $2
GROUP BY "userId", created_at` GROUP BY "userId", created_at
ORDER BY "sortTime" DESC
LIMIT ${LIMIT})`
) )
queries.push( queries.push(
`SELECT min(id)::text, created_at AS "sortTime", FLOOR(sum(msats) / 1000) as "earnedSats", `(SELECT min(id)::text, created_at AS "sortTime", FLOOR(sum(msats) / 1000) as "earnedSats",
'Revenue' AS type 'Revenue' AS type
FROM "SubAct" FROM "SubAct"
WHERE "userId" = $1 WHERE "userId" = $1
AND type = 'REVENUE' AND type = 'REVENUE'
AND created_at <= $2 AND created_at < $2
GROUP BY "userId", "subName", created_at` GROUP BY "userId", "subName", created_at
ORDER BY "sortTime" DESC
LIMIT ${LIMIT})`
) )
} }
if (meFull.noteCowboyHat) { if (meFull.noteCowboyHat) {
queries.push( queries.push(
`SELECT id::text, updated_at AS "sortTime", 0 as "earnedSats", 'Streak' AS type `(SELECT id::text, updated_at AS "sortTime", 0 as "earnedSats", 'Streak' AS type
FROM "Streak" FROM "Streak"
WHERE "userId" = $1 WHERE "userId" = $1
AND updated_at <= $2` AND updated_at < $2
ORDER BY "sortTime" DESC
LIMIT ${LIMIT})`
) )
} }
@ -283,9 +294,9 @@ export default {
FROM "Sub" FROM "Sub"
WHERE "Sub"."userId" = $1 WHERE "Sub"."userId" = $1
AND "status" <> 'ACTIVE' AND "status" <> 'ACTIVE'
AND "statusUpdatedAt" <= $2 AND "statusUpdatedAt" < $2
ORDER BY "sortTime" DESC ORDER BY "sortTime" DESC
LIMIT ${LIMIT}+$3)` LIMIT ${LIMIT})`
) )
// we do all this crazy subquery stuff to make 'reward' islands // we do all this crazy subquery stuff to make 'reward' islands
@ -306,8 +317,7 @@ export default {
) sub ) sub
GROUP BY type, island GROUP BY type, island
ORDER BY "sortTime" DESC ORDER BY "sortTime" DESC
OFFSET $3 LIMIT ${LIMIT}`, me.id, decodedCursor.time)
LIMIT ${LIMIT}`, me.id, decodedCursor.time, decodedCursor.offset)
if (decodedCursor.offset === 0) { if (decodedCursor.offset === 0) {
await models.user.update({ where: { id: me.id }, data: { checkedNotesAt: new Date() } }) await models.user.update({ where: { id: me.id }, data: { checkedNotesAt: new Date() } })
@ -315,7 +325,7 @@ export default {
return { return {
lastChecked: meFull.checkedNotesAt, lastChecked: meFull.checkedNotesAt,
cursor: notifications.length === LIMIT ? nextCursorEncoded(decodedCursor) : null, cursor: notifications.length === LIMIT ? nextNoteCursorEncoded(decodedCursor, notifications) : null,
notifications notifications
} }
} }

View File

@ -14,3 +14,10 @@ export function nextCursorEncoded (cursor, limit = LIMIT) {
cursor.offset += limit cursor.offset += limit
return Buffer.from(JSON.stringify(cursor)).toString('base64') return Buffer.from(JSON.stringify(cursor)).toString('base64')
} }
export function nextNoteCursorEncoded (cursor, notifications = [], limit = LIMIT) {
// what we are looking for this oldest sort time for every table we are looking at
cursor.time = new Date(notifications.slice(-1).pop()?.sortTime ?? cursor.time)
cursor.offset += limit
return Buffer.from(JSON.stringify(cursor)).toString('base64')
}