fix clickToContext issue for comments, fix non-inner joins, make notification query work
This commit is contained in:
		
							parent
							
								
									96a18e6c9d
								
							
						
					
					
						commit
						c8df41bfa5
					
				| @ -1,7 +1,7 @@ | |||||||
| import { PrismaClient } from '@prisma/client' | import { PrismaClient } from '@prisma/client' | ||||||
| 
 | 
 | ||||||
| const prisma = global.prisma || new PrismaClient({ | const prisma = global.prisma || new PrismaClient({ | ||||||
|   log: ['query', 'warn', 'error'] |   log: ['warn', 'error'] | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| if (process.env.NODE_ENV === 'development') global.prisma = prisma | if (process.env.NODE_ENV === 'development') global.prisma = prisma | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { AuthenticationError } from 'apollo-server-micro' | ||||||
| import { decodeCursor, LIMIT, nextCursorEncoded } from './cursor' | import { decodeCursor, LIMIT, nextCursorEncoded } from './cursor' | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
| @ -44,7 +45,8 @@ export default { | |||||||
|       let notifications = await models.$queryRaw(` |       let notifications = await models.$queryRaw(` | ||||||
|         SELECT ${ITEM_FIELDS}, "Item".created_at as sort_time, NULL as "earnedSats" |         SELECT ${ITEM_FIELDS}, "Item".created_at as sort_time, NULL as "earnedSats" | ||||||
|         From "Item" |         From "Item" | ||||||
|         JOIN "Item" p ON "Item"."parentId" = p.id AND p."userId" = $1 |         JOIN "Item" p ON "Item"."parentId" = p.id | ||||||
|  |         WHERE p."userId" = $1 | ||||||
|         AND "Item"."userId" <> $1 AND "Item".created_at <= $2 |         AND "Item"."userId" <> $1 AND "Item".created_at <= $2 | ||||||
|         UNION ALL |         UNION ALL | ||||||
|         (SELECT ${ITEM_SUBQUERY_FIELDS}, max(subquery.voted_at) as sort_time, sum(subquery.sats) as "earnedSats" |         (SELECT ${ITEM_SUBQUERY_FIELDS}, max(subquery.voted_at) as sort_time, sum(subquery.sats) as "earnedSats" | ||||||
| @ -52,16 +54,16 @@ export default { | |||||||
|         (SELECT ${ITEM_FIELDS}, "Vote".created_at as voted_at, "Vote".sats, |         (SELECT ${ITEM_FIELDS}, "Vote".created_at as voted_at, "Vote".sats, | ||||||
|         ROW_NUMBER() OVER(ORDER BY "Vote".created_at) - |         ROW_NUMBER() OVER(ORDER BY "Vote".created_at) - | ||||||
|         ROW_NUMBER() OVER(PARTITION BY "Item".id ORDER BY "Vote".created_at) as island |         ROW_NUMBER() OVER(PARTITION BY "Item".id ORDER BY "Vote".created_at) as island | ||||||
|         FROM "Item" |         FROM "Vote" | ||||||
|         LEFT JOIN "Vote" on "Vote"."itemId" = "Item".id |         JOIN "Item" on "Vote"."itemId" = "Item".id | ||||||
|         AND "Vote"."userId" <> $1 |         WHERE "Vote"."userId" <> $1 | ||||||
|         AND "Item".created_at <= $2 |         AND "Item".created_at <= $2 | ||||||
|         AND "Vote".boost = false |         AND "Vote".boost = false | ||||||
|         WHERE "Item"."userId" = $1) subquery |         AND "Item"."userId" = $1) subquery | ||||||
|         GROUP BY ${ITEM_SUBQUERY_FIELDS}, subquery.island ORDER BY max(subquery.voted_at) desc) |         GROUP BY ${ITEM_SUBQUERY_FIELDS}, subquery.island ORDER BY max(subquery.voted_at) desc) | ||||||
|         ORDER BY sort_time DESC |         ORDER BY sort_time DESC | ||||||
|         OFFSET $3 |         OFFSET $3 | ||||||
|         LIMIT ${LIMIT}`, me ? me.id : 622, decodedCursor.time, decodedCursor.offset)
 |         LIMIT ${LIMIT}`, 622, decodedCursor.time, decodedCursor.offset)
 | ||||||
| 
 | 
 | ||||||
|       notifications = notifications.map(n => { |       notifications = notifications.map(n => { | ||||||
|         n.item = { ...n } |         n.item = { ...n } | ||||||
| @ -85,4 +87,4 @@ const ITEM_SUBQUERY_FIELDS = | |||||||
| 
 | 
 | ||||||
| const ITEM_FIELDS = | const ITEM_FIELDS = | ||||||
|   `"Item".id, "Item".created_at as "createdAt", "Item".updated_at as "updatedAt", "Item".title,
 |   `"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"` |   "Item".text, "Item".url, "Item"."userId", "Item"."parentId", ltree2text("Item"."path") AS path` | ||||||
|  | |||||||
| @ -25,12 +25,12 @@ export default { | |||||||
| 
 | 
 | ||||||
|       const [{ sum }] = await models.$queryRaw(` |       const [{ sum }] = await models.$queryRaw(` | ||||||
|         SELECT sum("Vote".sats) |         SELECT sum("Vote".sats) | ||||||
|         FROM "Item" |         FROM "Vote" | ||||||
|         LEFT JOIN "Vote" on "Vote"."itemId" = "Item".id |         JOIN "Item" on "Vote"."itemId" = "Item".id | ||||||
|         AND "Vote"."userId" <> $1 |         WHERE "Vote"."userId" <> $1 | ||||||
|         AND ("Vote".created_at > $2 OR $2 IS NULL) |         AND ("Vote".created_at > $2 OR $2 IS NULL) | ||||||
|         AND "Vote".boost = false |         AND "Vote".boost = false | ||||||
|         WHERE "Item"."userId" = $1`, user.id, user.checkedNotesAt)
 |         AND "Item"."userId" = $1`, user.id, user.checkedNotesAt)
 | ||||||
| 
 | 
 | ||||||
|       await models.user.update({ where: { id: me.id }, data: { checkedNotesAt: new Date() } }) |       await models.user.update({ where: { id: me.id }, data: { checkedNotesAt: new Date() } }) | ||||||
|       return sum || 0 |       return sum || 0 | ||||||
| @ -64,9 +64,10 @@ export default { | |||||||
|     stacked: async (user, args, { models }) => { |     stacked: async (user, args, { models }) => { | ||||||
|       const [{ sum }] = await models.$queryRaw` |       const [{ sum }] = await models.$queryRaw` | ||||||
|         SELECT sum("Vote".sats) |         SELECT sum("Vote".sats) | ||||||
|         FROM "Item" |         FROM "Vote" | ||||||
|         LEFT JOIN "Vote" on "Vote"."itemId" = "Item".id AND "Vote"."userId" <> ${user.id} AND boost = false |         JOIN "Item" on "Vote"."itemId" = "Item".id | ||||||
|         WHERE "Item"."userId" = ${user.id}` |         WHERE "Vote"."userId" <> ${user.id} AND boost = false | ||||||
|  |         AND "Item"."userId" = ${user.id}` | ||||||
|       return sum || 0 |       return sum || 0 | ||||||
|     }, |     }, | ||||||
|     sats: async (user, args, { models }) => { |     sats: async (user, args, { models }) => { | ||||||
| @ -77,11 +78,11 @@ export default { | |||||||
|       const votes = await models.$queryRaw(` |       const votes = await models.$queryRaw(` | ||||||
|         SELECT "Vote".id, "Vote".created_at |         SELECT "Vote".id, "Vote".created_at | ||||||
|         FROM "Vote" |         FROM "Vote" | ||||||
|         LEFT JOIN "Item" on "Vote"."itemId" = "Item".id |         JOIN "Item" on "Vote"."itemId" = "Item".id | ||||||
|         AND "Vote"."userId" <> $1 |         WHERE "Vote"."userId" <> $1 | ||||||
|         AND ("Vote".created_at > $2 OR $2 IS NULL) |         AND ("Vote".created_at > $2 OR $2 IS NULL) | ||||||
|         AND "Vote".boost = false |         AND "Vote".boost = false | ||||||
|         WHERE "Item"."userId" = $1 |         AND "Item"."userId" = $1 | ||||||
|         LIMIT 1`, user.id, user.checkedNotesAt)
 |         LIMIT 1`, user.id, user.checkedNotesAt)
 | ||||||
|       if (votes.length > 0) { |       if (votes.length > 0) { | ||||||
|         return true |         return true | ||||||
| @ -91,7 +92,8 @@ export default { | |||||||
|       const newReplies = await models.$queryRaw(` |       const newReplies = await models.$queryRaw(` | ||||||
|         SELECT "Item".id, "Item".created_at |         SELECT "Item".id, "Item".created_at | ||||||
|         From "Item" |         From "Item" | ||||||
|         JOIN "Item" p ON "Item"."parentId" = p.id AND p."userId" = $1 |         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 |         AND ("Item".created_at > $2 OR $2 IS NULL)  AND "Item"."userId" <> $1 | ||||||
|         LIMIT 1`, user.id, user.checkedNotesAt)
 |         LIMIT 1`, user.id, user.checkedNotesAt)
 | ||||||
|       return !!newReplies.length |       return !!newReplies.length | ||||||
|  | |||||||
| @ -44,5 +44,6 @@ export default gql` | |||||||
|     meSats: Int! |     meSats: Int! | ||||||
|     ncomments: Int! |     ncomments: Int! | ||||||
|     comments: [Item!]! |     comments: [Item!]! | ||||||
|  |     path: String | ||||||
|   } |   } | ||||||
| ` | ` | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ import { useMe } from './me' | |||||||
| import CommentEdit from './comment-edit' | import CommentEdit from './comment-edit' | ||||||
| import Countdown from './countdown' | import Countdown from './countdown' | ||||||
| 
 | 
 | ||||||
| function Parent ({ item }) { | function Parent ({ item, rootText }) { | ||||||
|   const ParentFrag = () => ( |   const ParentFrag = () => ( | ||||||
|     <> |     <> | ||||||
|       <span> \ </span> |       <span> \ </span> | ||||||
| @ -32,13 +32,13 @@ function Parent ({ item }) { | |||||||
|       {Number(item.root.id) !== Number(item.parentId) && <ParentFrag />} |       {Number(item.root.id) !== Number(item.parentId) && <ParentFrag />} | ||||||
|       <span> \ </span> |       <span> \ </span> | ||||||
|       <Link href={`/items/${item.root.id}`} passHref> |       <Link href={`/items/${item.root.id}`} passHref> | ||||||
|         <a onClick={e => e.stopPropagation()} className='text-reset'>root: {item.root.title}</a> |         <a onClick={e => e.stopPropagation()} className='text-reset'>{rootText || 'on:'} {item.root.title}</a> | ||||||
|       </Link> |       </Link> | ||||||
|     </> |     </> | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default function Comment ({ item, children, replyOpen, includeParent, cacheId, noComments, noReply, clickToContext }) { | export default function Comment ({ item, children, replyOpen, includeParent, rootText, noComments, noReply, clickToContext }) { | ||||||
|   const [reply, setReply] = useState(replyOpen) |   const [reply, setReply] = useState(replyOpen) | ||||||
|   const [edit, setEdit] = useState() |   const [edit, setEdit] = useState() | ||||||
|   const [collapse, setCollapse] = useState(false) |   const [collapse, setCollapse] = useState(false) | ||||||
| @ -50,8 +50,11 @@ export default function Comment ({ item, children, replyOpen, includeParent, cac | |||||||
|   const [canEdit, setCanEdit] = |   const [canEdit, setCanEdit] = | ||||||
|     useState(mine && (Date.now() < editThreshold)) |     useState(mine && (Date.now() < editThreshold)) | ||||||
| 
 | 
 | ||||||
|  |   console.log('wtf router', router, item.id, ref.current) | ||||||
|  | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (Number(router.query.commentId) === Number(item.id)) { |     if (Number(router.query.commentId) === Number(item.id)) { | ||||||
|  |       console.log(ref.current.scrollTop) | ||||||
|       ref.current.scrollIntoView() |       ref.current.scrollIntoView() | ||||||
|       // ref.current.classList.add('flash-it')
 |       // ref.current.classList.add('flash-it')
 | ||||||
|     } |     } | ||||||
| @ -61,7 +64,12 @@ export default function Comment ({ item, children, replyOpen, includeParent, cac | |||||||
|     <div |     <div | ||||||
|       ref={ref} onClick={() => { |       ref={ref} onClick={() => { | ||||||
|         if (clickToContext) { |         if (clickToContext) { | ||||||
|           router.push(`/items/${item.parentId}?commentId=${item.id}`, `/items/${item.parentId}`) |           console.log('pushing') | ||||||
|  |           // router.push(`/items/${item.parentId}?commentId=${item.id}`, `/items/${item.parentId}`, { scroll: false })
 | ||||||
|  |           router.push({ | ||||||
|  |             pathname: '/items/[id]', | ||||||
|  |             query: { id: item.parentId, commentId: item.id } | ||||||
|  |           }, `/items/${item.parentId}`) | ||||||
|         } |         } | ||||||
|       }} className={includeParent ? `${clickToContext ? styles.clickToContext : ''}` : `${styles.comment} ${collapse ? styles.collapsed : ''}`} |       }} className={includeParent ? `${clickToContext ? styles.clickToContext : ''}` : `${styles.comment} ${collapse ? styles.collapsed : ''}`} | ||||||
|     > |     > | ||||||
| @ -83,7 +91,7 @@ export default function Comment ({ item, children, replyOpen, includeParent, cac | |||||||
|               </Link> |               </Link> | ||||||
|               <span> </span> |               <span> </span> | ||||||
|               <span>{timeSince(new Date(item.createdAt))}</span> |               <span>{timeSince(new Date(item.createdAt))}</span> | ||||||
|               {includeParent && <Parent item={item} />} |               {includeParent && <Parent item={item} rootText={rootText} />} | ||||||
|             </div> |             </div> | ||||||
|             {!includeParent && (collapse |             {!includeParent && (collapse | ||||||
|               ? <Eye className={styles.collapser} height={10} width={10} onClick={() => setCollapse(false)} /> |               ? <Eye className={styles.collapser} height={10} width={10} onClick={() => setCollapse(false)} /> | ||||||
|  | |||||||
| @ -2,20 +2,30 @@ import { useQuery } from '@apollo/client' | |||||||
| import Button from 'react-bootstrap/Button' | import Button from 'react-bootstrap/Button' | ||||||
| import { useState } from 'react' | import { useState } from 'react' | ||||||
| import Comment, { CommentSkeleton } from './comment' | import Comment, { CommentSkeleton } from './comment' | ||||||
|  | import Item from './item' | ||||||
| import { NOTIFICATIONS } from '../fragments/notifications' | import { NOTIFICATIONS } from '../fragments/notifications' | ||||||
| 
 | 
 | ||||||
| export default function CommentsFlat ({ variables, ...props }) { | export default function Notifications ({ variables, ...props }) { | ||||||
|   const { loading, error, data, fetchMore } = useQuery(NOTIFICATIONS) |   const { loading, error, data, fetchMore } = useQuery(NOTIFICATIONS, { | ||||||
|  |     variables | ||||||
|  |   }) | ||||||
|   if (error) return <div>Failed to load!</div> |   if (error) return <div>Failed to load!</div> | ||||||
|   if (loading) { |   if (loading) { | ||||||
|     return <CommentsFlatSkeleton /> |     return <CommentsFlatSkeleton /> | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
|   const { notifications: { notifications, cursor } } = data |   const { notifications: { notifications, cursor } } = data | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|       {notifications.map(item => ( |       {/* XXX we shouldn't use the index but we don't have a unique id in this union yet */} | ||||||
|         <Comment key={item.id} item={item} {...props} /> |       {notifications.map((n, i) => ( | ||||||
|  |         <div key={i}> | ||||||
|  |           {n.__typename === 'Votification' && <small className='font-weight-bold text-success'>your {n.item.title ? 'post' : 'reply'} stacked {n.earnedSats} sats</small>} | ||||||
|  |           <div className={n.__typename === 'Votification' ? 'ml-sm-4 ml-2' : ''}> | ||||||
|  |             {n.item.title | ||||||
|  |               ? <Item item={n.item} /> | ||||||
|  |               : <Comment item={n.item} noReply includeParent rootText={n.__typename === 'Reply' ? 'replying to you on:' : undefined} clickToContext {...props} />} | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|       ))} |       ))} | ||||||
|       <MoreFooter cursor={cursor} fetchMore={fetchMore} /> |       <MoreFooter cursor={cursor} fetchMore={fetchMore} /> | ||||||
|     </> |     </> | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import { gql } from 'apollo-server-micro' | import { gql } from '@apollo/client' | ||||||
| import { ITEM_FIELDS } from './items' | import { ITEM_FIELDS } from './items' | ||||||
| 
 | 
 | ||||||
| export const NOTIFICATIONS = gql` | export const NOTIFICATIONS = gql` | ||||||
|  | |||||||
| @ -49,6 +49,24 @@ const client = new ApolloClient({ | |||||||
|                 } |                 } | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
|  |           }, | ||||||
|  |           notifications: { | ||||||
|  |             merge (existing, incoming, { readField }) { | ||||||
|  |               const notifications = existing ? existing.notifications : [] | ||||||
|  |               return { | ||||||
|  |                 cursor: incoming.cursor, | ||||||
|  |                 notifications: [...notifications, ...incoming.notifications] | ||||||
|  |               } | ||||||
|  |             }, | ||||||
|  | 
 | ||||||
|  |             read (existing) { | ||||||
|  |               if (existing) { | ||||||
|  |                 return { | ||||||
|  |                   cursor: existing.cursor, | ||||||
|  |                   notifications: existing.notifications | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  | |||||||
| @ -1,28 +1,10 @@ | |||||||
| import { gql, useQuery } from '@apollo/client' |  | ||||||
| import CommentsFlat from '../components/comments-flat' |  | ||||||
| import Layout from '../components/layout' | import Layout from '../components/layout' | ||||||
|  | import Notifications from '../components/notifications' | ||||||
| 
 | 
 | ||||||
| export function RecentlyStacked () { | export default function NotificationPage () { | ||||||
|   const query = gql` |  | ||||||
|   { |  | ||||||
|     recentlyStacked |  | ||||||
|   }` |  | ||||||
|   const { data } = useQuery(query) |  | ||||||
|   if (!data || !data.recentlyStacked) return null |  | ||||||
| 
 |  | ||||||
|   return ( |  | ||||||
|     <h2 className='visible text-success text-center py-3'> |  | ||||||
|       you stacked <span className='text-monospace'>{data.recentlyStacked}</span> sats |  | ||||||
|     </h2> |  | ||||||
|   ) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export default function Notifications ({ user }) { |  | ||||||
|   return ( |   return ( | ||||||
|     <Layout> |     <Layout> | ||||||
|       <RecentlyStacked /> |       <Notifications /> | ||||||
|       <h6 className='text-muted'>replies</h6> |  | ||||||
|       <CommentsFlat noReply includeParent clickToContext /> |  | ||||||
|     </Layout> |     </Layout> | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user