attempts at voting before running into cache issues
This commit is contained in:
parent
900b70da77
commit
c626998952
|
@ -6,23 +6,22 @@ import resolvers from './resolvers'
|
|||
import typeDefs from './typeDefs'
|
||||
import models from './models'
|
||||
|
||||
const client = new ApolloClient({
|
||||
ssrMode: true,
|
||||
// Instead of "createHttpLink" use SchemaLink here
|
||||
link: new SchemaLink({
|
||||
schema: mergeSchemas({
|
||||
schemas: typeDefs,
|
||||
resolvers: resolvers
|
||||
}),
|
||||
context: async ({ req }) => {
|
||||
const session = await getSession({ req })
|
||||
return {
|
||||
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
|
||||
link: new SchemaLink({
|
||||
schema: mergeSchemas({
|
||||
schemas: typeDefs,
|
||||
resolvers: resolvers
|
||||
}),
|
||||
context: {
|
||||
models,
|
||||
me: session ? session.user : null
|
||||
}
|
||||
}
|
||||
}),
|
||||
cache: new InMemoryCache()
|
||||
})
|
||||
|
||||
export default client
|
||||
}),
|
||||
cache: new InMemoryCache()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,64 +1,5 @@
|
|||
import { UserInputError, AuthenticationError } from 'apollo-server-micro'
|
||||
|
||||
const createItem = async (parent, { title, text, url, parentId }, { me, models }) => {
|
||||
if (!me) {
|
||||
throw new AuthenticationError('You must be logged in')
|
||||
}
|
||||
|
||||
const data = {
|
||||
title,
|
||||
url,
|
||||
text,
|
||||
user: {
|
||||
connect: {
|
||||
name: me.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (parentId) {
|
||||
data.parent = {
|
||||
connect: {
|
||||
id: parseInt(parentId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const item = await models.item.create({ data })
|
||||
item.comments = []
|
||||
return item
|
||||
}
|
||||
|
||||
function nestComments (flat, parentId) {
|
||||
const result = []
|
||||
let added = 0
|
||||
for (let i = 0; i < flat.length;) {
|
||||
if (!flat[i].comments) flat[i].comments = []
|
||||
if (Number(flat[i].parentId) === Number(parentId)) {
|
||||
result.push(flat[i])
|
||||
added++
|
||||
i++
|
||||
} else if (result.length > 0) {
|
||||
const item = result[result.length - 1]
|
||||
const [nested, newAdded] = nestComments(flat.slice(i), item.id)
|
||||
if (newAdded === 0) {
|
||||
break
|
||||
}
|
||||
item.comments.push(...nested)
|
||||
i += newAdded
|
||||
added += newAdded
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return [result, added]
|
||||
}
|
||||
|
||||
// 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"`
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
items: async (parent, args, { models }) => {
|
||||
|
@ -142,6 +83,29 @@ export default {
|
|||
}
|
||||
|
||||
return await createItem(parent, { text, parentId }, { me, models })
|
||||
},
|
||||
vote: async (parent, { id, sats = 1 }, { me, models }) => {
|
||||
// need to make sure we are logged in
|
||||
if (!me) {
|
||||
throw new AuthenticationError('You must be logged in')
|
||||
}
|
||||
|
||||
const data = {
|
||||
sats,
|
||||
item: {
|
||||
connect: {
|
||||
id: parseInt(id)
|
||||
}
|
||||
},
|
||||
user: {
|
||||
connect: {
|
||||
name: me.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await models.vote.create({ data })
|
||||
return sats
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -155,6 +119,96 @@ export default {
|
|||
WHERE path <@ text2ltree(${item.path}) AND id != ${item.id}`
|
||||
return count
|
||||
},
|
||||
sats: () => 0
|
||||
sats: async (item, args, { models }) => {
|
||||
const { sum: { sats } } = await models.vote.aggregate({
|
||||
sum: {
|
||||
sats: true
|
||||
},
|
||||
where: {
|
||||
itemId: item.id
|
||||
}
|
||||
})
|
||||
|
||||
return sats
|
||||
},
|
||||
meSats: async (item, args, { me, models }) => {
|
||||
if (!me) return 0
|
||||
|
||||
const { sum: { sats } } = await models.vote.aggregate({
|
||||
sum: {
|
||||
sats: true
|
||||
},
|
||||
where: {
|
||||
itemId: item.id,
|
||||
userId: me.id
|
||||
}
|
||||
})
|
||||
|
||||
return sats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const createItem = async (parent, { title, text, url, parentId }, { me, models }) => {
|
||||
if (!me) {
|
||||
throw new AuthenticationError('You must be logged in')
|
||||
}
|
||||
|
||||
const data = {
|
||||
title,
|
||||
url,
|
||||
text,
|
||||
item: {
|
||||
connect: {
|
||||
|
||||
}
|
||||
},
|
||||
user: {
|
||||
connect: {
|
||||
name: me.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (parentId) {
|
||||
data.parent = {
|
||||
connect: {
|
||||
id: parseInt(parentId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const item = await models.item.create({ data })
|
||||
item.comments = []
|
||||
return item
|
||||
}
|
||||
|
||||
function nestComments (flat, parentId) {
|
||||
const result = []
|
||||
let added = 0
|
||||
for (let i = 0; i < flat.length;) {
|
||||
if (!flat[i].comments) flat[i].comments = []
|
||||
if (Number(flat[i].parentId) === Number(parentId)) {
|
||||
result.push(flat[i])
|
||||
added++
|
||||
i++
|
||||
} else if (result.length > 0) {
|
||||
const item = result[result.length - 1]
|
||||
const [nested, newAdded] = nestComments(flat.slice(i), item.id)
|
||||
if (newAdded === 0) {
|
||||
break
|
||||
}
|
||||
item.comments.push(...nested)
|
||||
i += newAdded
|
||||
added += newAdded
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return [result, added]
|
||||
}
|
||||
|
||||
// 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"`
|
||||
|
|
|
@ -15,7 +15,7 @@ export default gql`
|
|||
createLink(title: String!, url: String): Item!
|
||||
createDiscussion(title: String!, text: String): Item!
|
||||
createComment(text: String!, parentId: ID!): Item!
|
||||
vote(sats: Int): Int!
|
||||
vote(id: ID!, sats: Int): Int!
|
||||
}
|
||||
|
||||
type Item {
|
||||
|
@ -28,6 +28,7 @@ export default gql`
|
|||
user: User!
|
||||
depth: Int!
|
||||
sats: Int!
|
||||
meSats: Int!
|
||||
ncomments: Int!
|
||||
comments: [Item!]!
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ export default function Comment ({ item, children, replyOpen, includeParent, cac
|
|||
<div />
|
||||
<div>
|
||||
<div className={`${itemStyles.item} ${styles.item}`}>
|
||||
<UpVote className={styles.upvote} />
|
||||
<UpVote itemId={item.id} meSats={item.meSats} className={styles.upvote} />
|
||||
<div className={itemStyles.hunk}>
|
||||
<div className={itemStyles.other}>
|
||||
<Link href={`/${item.user.name}`} passHref>
|
||||
|
|
|
@ -20,7 +20,7 @@ export default function Header () {
|
|||
<>
|
||||
<Nav.Item>
|
||||
<Link href={'/' + session.user.name} passHref>
|
||||
<Nav.Link className='text-reset'>@{session.user.name}</Nav.Link>
|
||||
<Nav.Link>@{session.user.name}</Nav.Link>
|
||||
</Link>
|
||||
</Nav.Item>
|
||||
<Nav.Item>
|
||||
|
@ -51,6 +51,9 @@ export default function Header () {
|
|||
<Nav.Link>post</Nav.Link>
|
||||
</Link>
|
||||
</Nav.Item>
|
||||
<Nav.Item>
|
||||
<Nav.Link href='https://bitcoinerjobs.co' target='_blank'>jobs</Nav.Link>
|
||||
</Nav.Item>
|
||||
</Nav>
|
||||
<Nav className='ml-auto align-items-center' activeKey={router.asPath.split('?')[0]}>
|
||||
<Corner />
|
||||
|
|
|
@ -13,7 +13,7 @@ export default function Item ({ item, rank, children }) {
|
|||
</div>)
|
||||
: <div />}
|
||||
<div className={styles.item}>
|
||||
<UpVote />
|
||||
<UpVote itemId={item.id} meSats={item.meSats} />
|
||||
<div className={styles.hunk}>
|
||||
<div className={`${styles.main} flex-wrap flex-md-nowrap`}>
|
||||
<Link href={`/items/${item.id}`} passHref>
|
||||
|
|
|
@ -24,7 +24,7 @@ export default function Reply ({ parentId, onSuccess, cacheId }) {
|
|||
cache.modify({
|
||||
id: cacheId || `Item:${parentId}`,
|
||||
fields: {
|
||||
comments (existingCommentRefs = [], { readField }) {
|
||||
comments (existingCommentRefs = []) {
|
||||
const newCommentRef = cache.writeFragment({
|
||||
data: createComment,
|
||||
fragment: COMMENTS,
|
||||
|
|
|
@ -1,16 +1,55 @@
|
|||
import { LightningConsumer } from './lightning'
|
||||
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 }) {
|
||||
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
|
||||
},
|
||||
meSats (existingMeSats = 0) {
|
||||
return existingMeSats + vote
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export default function UpVote ({ className }) {
|
||||
return (
|
||||
<LightningConsumer>
|
||||
{({ strike }) =>
|
||||
<UpArrow
|
||||
width={24}
|
||||
height={24}
|
||||
className={`${styles.upvote} ${className || ''}`}
|
||||
onClick={strike}
|
||||
className={
|
||||
`${styles.upvote}
|
||||
${className || ''}
|
||||
${meSats ? (meSats > 1 ? styles.stimi : styles.voted) : ''}`
|
||||
}
|
||||
onClick={async () => {
|
||||
if (!itemId) return
|
||||
const { error } = await vote({ variables: { id: itemId, sats: 1 } })
|
||||
if (error) {
|
||||
throw new Error({ message: error.toString() })
|
||||
}
|
||||
|
||||
strike()
|
||||
}}
|
||||
/>}
|
||||
</LightningConsumer>
|
||||
)
|
||||
|
|
|
@ -7,4 +7,14 @@
|
|||
.upvote:hover {
|
||||
fill: darkgray;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.upvote.voted {
|
||||
fill: #F6911D;
|
||||
filter: drop-shadow(0 0 7px #F6911D);
|
||||
}
|
||||
|
||||
.upvote.stimi {
|
||||
fill: #993DF5;
|
||||
filter: drop-shadow(0 0 7px #C28BF9);
|
||||
}
|
|
@ -10,6 +10,7 @@ export const COMMENT_FIELDS = gql`
|
|||
name
|
||||
}
|
||||
sats
|
||||
meSats
|
||||
ncomments
|
||||
}
|
||||
`
|
||||
|
|
|
@ -11,6 +11,7 @@ export const ITEM_FIELDS = gql`
|
|||
name
|
||||
}
|
||||
sats
|
||||
meSats
|
||||
ncomments
|
||||
}`
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ import { gql } from '@apollo/client'
|
|||
import ApolloClient from '../api/client'
|
||||
import UserHeader from '../components/user-header'
|
||||
|
||||
export async function getServerSideProps ({ params }) {
|
||||
const { error, data: { user } } = await ApolloClient.query({
|
||||
export async function getServerSideProps ({ req, params }) {
|
||||
const { error, data: { user } } = await (await ApolloClient(req)).query({
|
||||
query:
|
||||
gql`{
|
||||
user(name: "${params.username}") {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import gql from 'graphql-tag'
|
||||
import Item from '../../components/item'
|
||||
import Layout from '../../components/layout'
|
||||
import ApolloClient from '../../api/client'
|
||||
|
@ -7,23 +6,18 @@ import Comment from '../../components/comment'
|
|||
import Text from '../../components/text'
|
||||
import Comments from '../../components/comments'
|
||||
import { COMMENTS } from '../../fragments/comments'
|
||||
import { ITEM_FIELDS } from '../../fragments/items'
|
||||
import { gql } from '@apollo/client'
|
||||
|
||||
export async function getServerSideProps ({ params }) {
|
||||
const { error, data: { item } } = await ApolloClient.query({
|
||||
export async function getServerSideProps ({ req, params }) {
|
||||
const { error, data: { item } } = await (await ApolloClient(req)).query({
|
||||
query:
|
||||
gql`{
|
||||
gql`
|
||||
${ITEM_FIELDS}
|
||||
{
|
||||
item(id: ${params.id}) {
|
||||
id
|
||||
createdAt
|
||||
title
|
||||
url
|
||||
...ItemFields
|
||||
text
|
||||
parentId
|
||||
user {
|
||||
name
|
||||
}
|
||||
sats
|
||||
ncomments
|
||||
}
|
||||
}`
|
||||
})
|
||||
|
|
|
@ -66,7 +66,7 @@ export default function login ({ providers, csrfToken, error }) {
|
|||
|
||||
return (
|
||||
<Button
|
||||
className={`d-block mt-2 ${styles.providerButton}`}
|
||||
className={`mt-2 ${styles.providerButton}`}
|
||||
key={provider.name}
|
||||
variant={variant}
|
||||
onClick={() => signIn(provider.id)}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
-- 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;
|
|
@ -0,0 +1,3 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "Vote" ADD COLUMN "stimi" BOOLEAN NOT NULL DEFAULT false,
|
||||
ALTER COLUMN "sats" SET DEFAULT 1;
|
|
@ -55,7 +55,8 @@ model Vote {
|
|||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
sats Int
|
||||
sats Int @default(1)
|
||||
stimi Boolean @default(false)
|
||||
item Item @relation(fields: [itemId], references: [id])
|
||||
itemId Int
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
|
|
@ -5,6 +5,7 @@ $theme-colors: (
|
|||
"info" : #007cbe,
|
||||
"success" : #5c8001,
|
||||
"twitter" : #1da1f2,
|
||||
"boost" : #7A0CE9,
|
||||
);
|
||||
|
||||
$body-bg: #fafafa;
|
||||
|
@ -50,6 +51,14 @@ $container-max-widths: (
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.alert-dismissible .close {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: #fada5e;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
.login .providerButton {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.page {
|
||||
|
|
Loading…
Reference in New Issue