diff --git a/api/resolvers/notifications.js b/api/resolvers/notifications.js index 65084409..e1a88237 100644 --- a/api/resolvers/notifications.js +++ b/api/resolvers/notifications.js @@ -1,5 +1,6 @@ import { AuthenticationError } from 'apollo-server-micro' import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor' +import { getItem } from './item' export default { Query: { @@ -62,48 +63,50 @@ export default { // HACK to make notifications faster, we only return a limited sub set of the unioned // queries ... we only ever need at most LIMIT+current offset in the child queries to // have enough items to return in the union - let notifications = await models.$queryRaw(` - (SELECT ${ITEM_FIELDS}, "Item".created_at as "sortTime", NULL as "earnedSats", - false as mention + const notifications = await models.$queryRaw(` + (SELECT "Item".id::TEXT, "Item".created_at AS "sortTime", NULL as "earnedSats", + 'Reply' AS type FROM "Item" JOIN "Item" p ON "Item"."parentId" = p.id WHERE p."userId" = $1 AND "Item"."userId" <> $1 AND "Item".created_at <= $2 - ORDER BY "Item".created_at desc + ORDER BY "Item".created_at DESC LIMIT ${LIMIT}+$3) UNION ALL - (SELECT ${ITEM_FIELDS}, max("ItemAct".created_at) as "sortTime", - sum("ItemAct".sats) as "earnedSats", false as mention + (SELECT "Item".id::TEXT, MAX("ItemAct".created_at) AS "sortTime", + sum("ItemAct".sats) as "earnedSats", 'Votification' AS type FROM "Item" - JOIN "ItemAct" on "ItemAct"."itemId" = "Item".id + JOIN "ItemAct" ON "ItemAct"."itemId" = "Item".id WHERE "ItemAct"."userId" <> $1 AND "ItemAct".created_at <= $2 AND "ItemAct".act <> 'BOOST' AND "Item"."userId" = $1 GROUP BY ${ITEM_GROUP_FIELDS} - ORDER BY max("ItemAct".created_at) desc + ORDER BY MAX("ItemAct".created_at) DESC LIMIT ${LIMIT}+$3) UNION ALL - (SELECT ${ITEM_FIELDS}, "Mention".created_at as "sortTime", NULL as "earnedSats", - true as mention + (SELECT "Item".id::TEXT, "Mention".created_at AS "sortTime", NULL as "earnedSats", + 'Mention' AS type FROM "Mention" - JOIN "Item" on "Mention"."itemId" = "Item".id - LEFT JOIN "Item" p on "Item"."parentId" = p.id + JOIN "Item" ON "Mention"."itemId" = "Item".id + LEFT JOIN "Item" p ON "Item"."parentId" = p.id WHERE "Mention"."userId" = $1 AND "Mention".created_at <= $2 AND "Item"."userId" <> $1 AND (p."userId" IS NULL OR p."userId" <> $1) - ORDER BY "Mention".created_at desc + ORDER BY "Mention".created_at DESC LIMIT ${LIMIT}+$3) + UNION ALL + (SELECT "Invite".id, MAX(users.created_at) AS "sortTime", NULL as "earnedSats", + 'Invitification' AS type + FROM users JOIN "Invite" on users."inviteId" = "Invite".id + WHERE "Invite"."userId" = $1 + AND users.created_at <= $2 + GROUP BY "Invite".id) ORDER BY "sortTime" DESC OFFSET $3 LIMIT ${LIMIT}`, me.id, decodedCursor.time, decodedCursor.offset) - notifications = notifications.map(n => { - n.item = { ...n } - return n - }) - const { checkedNotesAt } = await models.user.findUnique({ where: { id: me.id } }) if (decodedCursor.offset === 0) { await models.user.update({ where: { id: me.id }, data: { checkedNotesAt: new Date() } }) @@ -117,8 +120,26 @@ export default { } }, Notification: { - __resolveType: async (notification, args, { models }) => - notification.earnedSats ? 'Votification' : (notification.mention ? 'Mention' : 'Reply') + __resolveType: async (n, args, { models }) => n.type + }, + Votification: { + item: async (n, args, { models }) => getItem(n, { id: n.id }, { models }) + }, + Reply: { + item: async (n, args, { models }) => getItem(n, { id: n.id }, { models }) + }, + Mention: { + mention: async (n, args, { models }) => true, + item: async (n, args, { models }) => getItem(n, { id: n.id }, { models }) + }, + Invitification: { + invite: async (n, args, { models }) => { + return await models.invite.findUnique({ + where: { + id: n.id + } + }) + } } } @@ -130,6 +151,6 @@ const ITEM_GROUP_FIELDS = `"Item".id, "Item".created_at, "Item".updated_at, "Item".title, "Item".text, "Item".url, "Item"."userId", "Item"."parentId", ltree2text("Item"."path")` -const ITEM_FIELDS = - `"Item".id, "Item".created_at as "createdAt", "Item".updated_at as "updatedAt", "Item".title, - "Item".text, "Item".url, "Item"."userId", "Item"."parentId", ltree2text("Item"."path") AS path` +// const ITEM_FIELDS = +// `"Item".id, "Item".created_at as "createdAt", "Item".updated_at as "updatedAt", "Item".title, +// "Item".text, "Item".url, "Item"."userId", "Item"."parentId", ltree2text("Item"."path") AS path` diff --git a/api/resolvers/user.js b/api/resolvers/user.js index 11cb90cd..1d01f7d8 100644 --- a/api/resolvers/user.js +++ b/api/resolvers/user.js @@ -160,13 +160,13 @@ export default { // check if any votes have been cast for them since checkedNotesAt const votes = await models.$queryRaw(` SELECT "ItemAct".id, "ItemAct".created_at - FROM "ItemAct" - JOIN "Item" on "ItemAct"."itemId" = "Item".id - WHERE "ItemAct"."userId" <> $1 - AND ("ItemAct".created_at > $2 OR $2 IS NULL) - AND "ItemAct".act <> 'BOOST' - AND "Item"."userId" = $1 - LIMIT 1`, user.id, user.checkedNotesAt) + FROM "ItemAct" + JOIN "Item" on "ItemAct"."itemId" = "Item".id + WHERE "ItemAct"."userId" <> $1 + AND ("ItemAct".created_at > $2 OR $2 IS NULL) + AND "ItemAct".act <> 'BOOST' + AND "Item"."userId" = $1 + LIMIT 1`, user.id, user.checkedNotesAt) if (votes.length > 0) { return true } @@ -174,11 +174,11 @@ export default { // check if they have any replies since checkedNotesAt const newReplies = await models.$queryRaw(` SELECT "Item".id, "Item".created_at - From "Item" - JOIN "Item" p ON "Item"."parentId" = p.id - WHERE p."userId" = $1 - AND ("Item".created_at > $2 OR $2 IS NULL) AND "Item"."userId" <> $1 - LIMIT 1`, user.id, user.checkedNotesAt) + FROM "Item" + JOIN "Item" p ON "Item"."parentId" = p.id + WHERE p."userId" = $1 + AND ("Item".created_at > $2 OR $2 IS NULL) AND "Item"."userId" <> $1 + LIMIT 1`, user.id, user.checkedNotesAt) if (newReplies.length > 0) { return true } @@ -186,13 +186,24 @@ export default { // check if they have any mentions since checkedNotesAt const newMentions = await models.$queryRaw(` SELECT "Item".id, "Item".created_at - From "Mention" - JOIN "Item" ON "Mention"."itemId" = "Item".id - WHERE "Mention"."userId" = $1 - AND ("Mention".created_at > $2 OR $2 IS NULL) - AND "Item"."userId" <> $1 - LIMIT 1`, user.id, user.checkedNotesAt) - return newMentions.length > 0 + FROM "Mention" + JOIN "Item" ON "Mention"."itemId" = "Item".id + WHERE "Mention"."userId" = $1 + AND ("Mention".created_at > $2 OR $2 IS NULL) + AND "Item"."userId" <> $1 + LIMIT 1`, user.id, user.checkedNotesAt) + if (newMentions.length > 0) { + return true + } + + // check if new invites have been redeemed + const newInvitees = await models.$queryRaw(` + SELECT "Invite".id + FROM users JOIN "Invite" on users."inviteId" = "Invite".id + WHERE "Invite"."userId" = $1 + AND (users.created_at > $2 or $2 IS NULL) + LIMIT 1`, user.id, user.checkedNotesAt) + return newInvitees.length > 0 } } } diff --git a/api/typeDefs/notifications.js b/api/typeDefs/notifications.js index 3071db4e..704ec4fb 100644 --- a/api/typeDefs/notifications.js +++ b/api/typeDefs/notifications.js @@ -22,7 +22,12 @@ export default gql` sortTime: String! } - union Notification = Reply | Votification | Mention + type Invitification { + invite: Invite! + sortTime: String! + } + + union Notification = Reply | Votification | Mention | Invitification type Notifications { lastChecked: String diff --git a/components/invite.js b/components/invite.js new file mode 100644 index 00000000..af9e5212 --- /dev/null +++ b/components/invite.js @@ -0,0 +1,52 @@ +import { CopyInput } from './form' +import { gql, useMutation } from '@apollo/client' +import { INVITE_FIELDS } from '../fragments/invites' +import styles from '../styles/invites.module.css' + +export default function Invite ({ invite, active }) { + const [revokeInvite] = useMutation( + gql` + ${INVITE_FIELDS} + mutation revokeInvite($id: ID!) { + revokeInvite(id: $id) { + ...InviteFields + } + }` + ) + + return ( +