From 84b69fc4818c269c9d5be10b0189cf55b6bb7c65 Mon Sep 17 00:00:00 2001 From: keyan Date: Tue, 27 Apr 2021 16:30:58 -0500 Subject: [PATCH] ranking mostly --- api/client.js | 1 - api/resolvers/item.js | 65 ++++++++++++++++--- api/resolvers/user.js | 10 ++- api/typeDefs/item.js | 1 + components/comment.js | 5 ++ components/item.js | 5 ++ components/reply.js | 3 + components/upvote.js | 12 ++-- fragments/comments.js | 1 + fragments/items.js | 1 + pages/items/[id].js | 12 ++-- pages/login.js | 2 +- .../20210426184717_vote/migration.sql | 26 -------- .../20210426193558_stimi/migration.sql | 3 - .../migration.sql | 26 ++++++++ prisma/scatch.sql | 39 +++++++++++ prisma/schema.prisma | 2 +- 17 files changed, 159 insertions(+), 55 deletions(-) delete mode 100644 prisma/migrations/20210426184717_vote/migration.sql delete mode 100644 prisma/migrations/20210426193558_stimi/migration.sql rename prisma/migrations/{20210325212402_init => 20210427152429_init}/migration.sql (84%) create mode 100644 prisma/scatch.sql diff --git a/api/client.js b/api/client.js index 90fd8f25..8630e81f 100644 --- a/api/client.js +++ b/api/client.js @@ -8,7 +8,6 @@ import models from './models' export default async function serverSideClient (req) { const session = await getSession({ req }) - console.log(session) return new ApolloClient({ ssrMode: true, // Instead of "createHttpLink" use SchemaLink here diff --git a/api/resolvers/item.js b/api/resolvers/item.js index b96c93cb..25c40073 100644 --- a/api/resolvers/item.js +++ b/api/resolvers/item.js @@ -6,14 +6,16 @@ export default { return await models.$queryRaw(` ${SELECT} FROM "Item" - WHERE "parentId" IS NULL`) + ${LEFT_JOIN_SATS} + WHERE "parentId" IS NULL + ${ORDER_BY_SATS}`) }, recent: async (parent, args, { models }) => { return await models.$queryRaw(` ${SELECT} FROM "Item" WHERE "parentId" IS NULL - ORDER BY created_at`) + ORDER BY created_at DESC`) }, item: async (parent, { id }, { models }) => { return (await models.$queryRaw(` @@ -82,6 +84,18 @@ export default { throw new AuthenticationError('You must be logged in') } + if (sats <= 0) { + throw new UserInputError('Sats must be positive', { argumentName: 'sats' }) + } + + // check if we've already voted for the item + const boosted = await models.vote.findFirst({ + where: { + itemId: parseInt(id), + userId: me.id + } + }) + const data = { sats, item: { @@ -93,7 +107,8 @@ export default { connect: { name: me.name } - } + }, + boost: !!boosted } await models.vote.create({ data }) @@ -113,10 +128,17 @@ export default { }, comments: async (item, args, { models }) => { const flat = await models.$queryRaw(` - ${SELECT} - FROM "Item" - WHERE path <@ (SELECT path FROM "Item" where id = ${item.id}) AND id != ${item.id} - ORDER BY "path"`) + WITH RECURSIVE base AS ( + ${SELECT}, ARRAY[row_number() OVER (${ORDER_BY_SATS}, "Item".path)] AS sort_path + FROM "Item" + ${LEFT_JOIN_SATS} + WHERE "parentId" = ${item.id} + UNION ALL + ${SELECT}, p.sort_path || row_number() OVER (${ORDER_BY_SATS}, "Item".path) + FROM base p + JOIN "Item" ON ltree2text(subpath("Item"."path", 0, -1)) = p."path" + ${LEFT_JOIN_SATS}) + SELECT * FROM base ORDER BY sort_path`) return nestComments(flat, item.id)[0] }, sats: async (item, args, { models }) => { @@ -125,7 +147,21 @@ export default { sats: true }, where: { - itemId: item.id + itemId: item.id, + boost: false + } + }) + + return sats + }, + boost: async (item, args, { models }) => { + const { sum: { sats } } = await models.vote.aggregate({ + sum: { + sats: true + }, + where: { + itemId: item.id, + boost: true } }) @@ -205,5 +241,14 @@ function nestComments (flat, parentId) { // we have to do our own query because ltree is unsupported const SELECT = - `SELECT id, created_at as "createdAt", updated_at as "updatedAt", title, - text, url, "userId", "parentId", ltree2text("path") AS "path"` + `SELECT "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 LEFT_JOIN_SATS = + `LEFT JOIN (SELECT i.id, SUM("Vote".sats) as sats + FROM "Item" i + JOIN "Vote" ON i.id = "Vote"."itemId" + GROUP BY i.id) x ON "Item".id = x.id` + +const ORDER_BY_SATS = + 'ORDER BY (x.sats-1)/POWER(EXTRACT(EPOCH FROM ((NOW() AT TIME ZONE \'UTC\') - "Item".created_at))/3600+2, 1.5) DESC NULLS LAST' diff --git a/api/resolvers/user.js b/api/resolvers/user.js index c9e041bc..99ec74e4 100644 --- a/api/resolvers/user.js +++ b/api/resolvers/user.js @@ -3,7 +3,6 @@ export default { me: async (parent, args, { models, me }) => me ? await models.user.findUnique({ where: { id: me.id } }) : null, user: async (parent, { name }, { models }) => { - console.log(name) return await models.user.findUnique({ where: { name } }) }, users: async (parent, args, { models }) => @@ -17,7 +16,14 @@ export default { ncomments: async (user, args, { models }) => { return await models.item.count({ where: { userId: user.id, parentId: { not: null } } }) }, - stacked: () => 0, + stacked: async (user, args, { models }) => { + const [{ sum }] = await models.$queryRaw` + SELECT sum("Vote".sats) + FROM "Item" + LEFT JOIN "Vote" on "Vote"."itemId" = "Item".id + WHERE "Item"."userId" = ${user.id}` + return sum + }, sats: () => 0 } } diff --git a/api/typeDefs/item.js b/api/typeDefs/item.js index c2b44725..9015b062 100644 --- a/api/typeDefs/item.js +++ b/api/typeDefs/item.js @@ -27,6 +27,7 @@ export default gql` user: User! depth: Int! sats: Int! + boost: Int! meSats: Int! ncomments: Int! comments: [Item!]! diff --git a/components/comment.js b/components/comment.js index 870082f9..6bc8aead 100644 --- a/components/comment.js +++ b/components/comment.js @@ -60,6 +60,11 @@ export default function Comment ({ item, children, replyOpen, includeParent, cac {timeSince(new Date(item.createdAt))} \ {item.sats} sats + {!!item.boost && + <> + \ + {item.boost} boost + } \ {item.ncomments} replies diff --git a/components/item.js b/components/item.js index 8f37dabc..1c806c46 100644 --- a/components/item.js +++ b/components/item.js @@ -23,6 +23,11 @@ export default function Item ({ item, rank, children }) {
{item.sats} sats + {!!item.boost && + <> + \ + {item.boost} boost + } \ {item.ncomments} comments diff --git a/components/reply.js b/components/reply.js index b6923ac6..7cbb5688 100644 --- a/components/reply.js +++ b/components/reply.js @@ -31,6 +31,9 @@ export default function Reply ({ parentId, onSuccess }) { fragmentName: 'CommentsRecursive' }) return [newCommentRef, ...existingCommentRefs] + }, + ncomments (existingNComments = 0) { + return existingNComments + 1 } } }) diff --git a/components/upvote.js b/components/upvote.js index 304da2d4..73f1632a 100644 --- a/components/upvote.js +++ b/components/upvote.js @@ -3,23 +3,21 @@ import UpArrow from '../svgs/lightning-arrow.svg' import styles from './upvote.module.css' import { gql, useMutation } from '@apollo/client' -export default function UpVote ({ itemId, meSats, className, item }) { +export default function UpVote ({ itemId, meSats, className }) { const [vote] = useMutation( gql` mutation vote($id: ID!, $sats: Int!) { vote(id: $id, sats: $sats) }`, { update (cache, { data: { vote } }) { - if (item) { - item.sats += vote - item.meSats += vote - return - } cache.modify({ id: `Item:${itemId}`, fields: { sats (existingSats = 0) { - return existingSats + vote + return existingSats || vote + }, + boost (existingBoost = 0) { + return meSats >= 1 ? existingBoost + vote : existingBoost }, meSats (existingMeSats = 0) { return existingMeSats + vote diff --git a/fragments/comments.js b/fragments/comments.js index 22b8e938..2ecc357d 100644 --- a/fragments/comments.js +++ b/fragments/comments.js @@ -10,6 +10,7 @@ export const COMMENT_FIELDS = gql` name } sats + boost meSats ncomments } diff --git a/fragments/items.js b/fragments/items.js index b33d1103..773bb039 100644 --- a/fragments/items.js +++ b/fragments/items.js @@ -11,6 +11,7 @@ export const ITEM_FIELDS = gql` name } sats + boost meSats ncomments }` diff --git a/pages/items/[id].js b/pages/items/[id].js index 62c5c6d6..79840e8a 100644 --- a/pages/items/[id].js +++ b/pages/items/[id].js @@ -7,12 +7,16 @@ import Comments, { CommentsSkeleton } from '../../components/comments' import { COMMENTS } from '../../fragments/comments' import { ITEM_FIELDS } from '../../fragments/items' import { gql, useQuery } from '@apollo/client' -import { useRouter } from 'next/router' -export default function FullItem ({ item }) { - const router = useRouter() - const { id } = router.query +export async function getServerSideProps ({ params: { id } }) { + return { + props: { + id + } + } +} +export default function FullItem ({ id }) { const query = gql` ${ITEM_FIELDS} ${COMMENTS} diff --git a/pages/login.js b/pages/login.js index ec9e78a4..192935a5 100644 --- a/pages/login.js +++ b/pages/login.js @@ -17,7 +17,7 @@ export async function getServerSideProps ({ req, res, query: { callbackUrl, erro Location: callbackUrl }) res.end() - return + return { props: {} } } return { diff --git a/prisma/migrations/20210426184717_vote/migration.sql b/prisma/migrations/20210426184717_vote/migration.sql deleted file mode 100644 index 79dc03cd..00000000 --- a/prisma/migrations/20210426184717_vote/migration.sql +++ /dev/null @@ -1,26 +0,0 @@ --- DropIndex -DROP INDEX "item_gist_path_index"; - --- CreateTable -CREATE TABLE "Vote" ( - "id" SERIAL NOT NULL, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL, - "sats" INTEGER NOT NULL, - "itemId" INTEGER NOT NULL, - "userId" INTEGER NOT NULL, - - PRIMARY KEY ("id") -); - --- CreateIndex -CREATE INDEX "Vote.itemId_index" ON "Vote"("itemId"); - --- CreateIndex -CREATE INDEX "Vote.userId_index" ON "Vote"("userId"); - --- AddForeignKey -ALTER TABLE "Vote" ADD FOREIGN KEY ("itemId") REFERENCES "Item"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Vote" ADD FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20210426193558_stimi/migration.sql b/prisma/migrations/20210426193558_stimi/migration.sql deleted file mode 100644 index b3f81e1d..00000000 --- a/prisma/migrations/20210426193558_stimi/migration.sql +++ /dev/null @@ -1,3 +0,0 @@ --- AlterTable -ALTER TABLE "Vote" ADD COLUMN "stimi" BOOLEAN NOT NULL DEFAULT false, -ALTER COLUMN "sats" SET DEFAULT 1; diff --git a/prisma/migrations/20210325212402_init/migration.sql b/prisma/migrations/20210427152429_init/migration.sql similarity index 84% rename from prisma/migrations/20210325212402_init/migration.sql rename to prisma/migrations/20210427152429_init/migration.sql index e3742fdb..46a5b807 100644 --- a/prisma/migrations/20210325212402_init/migration.sql +++ b/prisma/migrations/20210427152429_init/migration.sql @@ -20,6 +20,7 @@ CREATE TABLE "Message" ( PRIMARY KEY ("id") ); + -- Edit: create extension for path CREATE EXTENSION ltree; @@ -63,6 +64,19 @@ CREATE TRIGGER path_tgr BEFORE INSERT OR UPDATE ON "Item" FOR EACH ROW EXECUTE PROCEDURE update_item_path(); +-- CreateTable +CREATE TABLE "Vote" ( + "id" SERIAL NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + "sats" INTEGER NOT NULL DEFAULT 1, + "boost" BOOLEAN NOT NULL DEFAULT false, + "itemId" INTEGER NOT NULL, + "userId" INTEGER NOT NULL, + + PRIMARY KEY ("id") +); + -- CreateTable CREATE TABLE "accounts" ( "id" SERIAL NOT NULL, @@ -117,6 +131,12 @@ CREATE INDEX "Item.userId_index" ON "Item"("userId"); -- CreateIndex CREATE INDEX "Item.parentId_index" ON "Item"("parentId"); +-- CreateIndex +CREATE INDEX "Vote.itemId_index" ON "Vote"("itemId"); + +-- CreateIndex +CREATE INDEX "Vote.userId_index" ON "Vote"("userId"); + -- CreateIndex CREATE UNIQUE INDEX "accounts.compound_id_unique" ON "accounts"("compound_id"); @@ -146,3 +166,9 @@ ALTER TABLE "Item" ADD FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE -- AddForeignKey ALTER TABLE "Item" ADD FOREIGN KEY ("parentId") REFERENCES "Item"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Vote" ADD FOREIGN KEY ("itemId") REFERENCES "Item"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Vote" ADD FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/scatch.sql b/prisma/scatch.sql new file mode 100644 index 00000000..9c064f59 --- /dev/null +++ b/prisma/scatch.sql @@ -0,0 +1,39 @@ +WITH RECURSIVE base AS ( + SELECT "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", + ARRAY[row_number() OVER (ORDER BY (x.sats-1)/POWER(EXTRACT(EPOCH FROM ((NOW() AT TIME ZONE 'UTC') - "Item".created_at))/3600+2, 1.5) DESC NULLS LAST, "Item"."path")] AS sort_path + FROM "Item" + LEFT JOIN (SELECT i.id, SUM("Vote".sats) as sats + FROM "Item" i + JOIN "Vote" ON i.id = "Vote"."itemId" + WHERE i."parentId" IS NULL + GROUP BY i.id) x ON "Item".id = x.id + WHERE "parentId" IS NULL + UNION ALL + SELECT "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", + p.sort_path || row_number() OVER (ORDER BY (x.sats-1)/POWER(EXTRACT(EPOCH FROM ((NOW() AT TIME ZONE 'UTC') - "Item".created_at))/3600+2, 1.5) DESC NULLS LAST, "Item"."path") + FROM base p + JOIN "Item" ON ltree2text(subpath("Item"."path", 0, -1)) = p."path" + LEFT JOIN (SELECT i.id, SUM("Vote".sats) as sats + FROM "Item" i + JOIN "Vote" ON i.id = "Vote"."itemId" + WHERE i."parentId" IS NULL + GROUP BY i.id) x ON "Item".id = x.id +) +select * from base order by sort_path; + +WITH RECURSIVE base AS ( + SELECT "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", + ARRAY[row_number() OVER (ORDER BY "Item".created_at, "Item"."path")] AS sort_path + FROM "Item" + WHERE "parentId" IS NULL + UNION ALL + SELECT "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", + p.sort_path || row_number() OVER (ORDER BY "Item".created_at, "Item"."path") + FROM base p + JOIN "Item" ON ltree2text(subpath("Item"."path", 0, -1)) = p."path" +) +select * from base order by sort_path; \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2e82cf0e..1a02eea7 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -56,7 +56,7 @@ model Vote { createdAt DateTime @default(now()) @map(name: "created_at") updatedAt DateTime @updatedAt @map(name: "updated_at") sats Int @default(1) - stimi Boolean @default(false) + boost Boolean @default(false) item Item @relation(fields: [itemId], references: [id]) itemId Int user User @relation(fields: [userId], references: [id])