From e7787e3e67ccd05d466156f91a2c26597894ae3c Mon Sep 17 00:00:00 2001 From: keyan Date: Thu, 23 Sep 2021 12:42:00 -0500 Subject: [PATCH] WIP bios --- api/models/index.js | 2 +- api/resolvers/item.js | 27 +++-- api/resolvers/user.js | 24 +++- api/typeDefs/user.js | 2 + components/comment.js | 5 +- components/discussion-form.js | 19 +-- components/footer.js | 10 +- components/item-full.js | 76 ++++++++++++ components/layout.js | 4 +- components/user-header.js | 109 ++++++++++-------- components/user-header.module.css | 13 +++ pages/[username].js | 72 +++++++++++- pages/[username]/posts.js | 45 ++++++++ pages/items/[id].js | 70 +---------- .../20210921232401_user_bio/migration.sql | 5 + .../20210922214428_create_bio/migration.sql | 16 +++ prisma/schema.prisma | 3 + styles/user.module.css | 17 +++ styles/username.module.css | 0 19 files changed, 367 insertions(+), 152 deletions(-) create mode 100644 components/item-full.js create mode 100644 pages/[username]/posts.js create mode 100644 prisma/migrations/20210921232401_user_bio/migration.sql create mode 100644 prisma/migrations/20210922214428_create_bio/migration.sql create mode 100644 styles/user.module.css delete mode 100644 styles/username.module.css diff --git a/api/models/index.js b/api/models/index.js index e79851f2..25300ec6 100644 --- a/api/models/index.js +++ b/api/models/index.js @@ -4,6 +4,6 @@ const prisma = global.prisma || new PrismaClient({ log: ['warn', 'error'] }) -if (process.env.NODE_ENV === 'development') global.prisma = prisma +global.prisma = prisma export default prisma diff --git a/api/resolvers/item.js b/api/resolvers/item.js index 3bb364c7..812ddd59 100644 --- a/api/resolvers/item.js +++ b/api/resolvers/item.js @@ -21,6 +21,18 @@ async function comments (models, id) { return nestComments(flat, id)[0] } +export async function getItem (parent, { id }, { models }) { + console.log(id) + const [item] = await models.$queryRaw(` + ${SELECT} + FROM "Item" + WHERE id = $1`, Number(id)) + if (item) { + item.comments = comments(models, id) + } + return item +} + export default { Query: { moreItems: async (parent, { sort, cursor, userId }, { me, models }) => { @@ -82,16 +94,7 @@ export default { comments } }, - item: async (parent, { id }, { models }) => { - const [item] = await models.$queryRaw(` - ${SELECT} - FROM "Item" - WHERE id = $1`, Number(id)) - if (item) { - item.comments = comments(models, id) - } - return item - }, + item: getItem, userComments: async (parent, { userId }, { models }) => { return await models.$queryRaw(` ${SELECT} @@ -363,7 +366,7 @@ export default { const namePattern = /\B@[\w_]+/gi -const createMentions = async (item, models) => { +export const createMentions = async (item, models) => { // if we miss a mention, in the rare circumstance there's some kind of // failure, it's not a big deal so we don't do it transactionally // ideally, we probably would @@ -456,7 +459,7 @@ function nestComments (flat, parentId) { } // we have to do our own query because ltree is unsupported -const SELECT = +export const SELECT = `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"` diff --git a/api/resolvers/user.js b/api/resolvers/user.js index 6c98dba6..3e23a164 100644 --- a/api/resolvers/user.js +++ b/api/resolvers/user.js @@ -1,4 +1,21 @@ import { AuthenticationError, UserInputError } from 'apollo-server-errors' +import { createMentions, getItem, SELECT } from './item' +import serialize from './serial' + +export const createBio = async (parent, { title, text }, { me, models }) => { + if (!me) { + throw new AuthenticationError('you must be logged in') + } + + const [item] = await serialize(models, + models.$queryRaw(`${SELECT} FROM create_bio($1, $2, $3) AS "Item"`, + title, text, Number(me.id))) + + await createMentions(item, models) + + item.comments = [] + return item +} export default { Query: { @@ -32,7 +49,8 @@ export default { } throw error } - } + }, + createBio: createBio }, User: { @@ -54,6 +72,10 @@ export default { sats: async (user, args, { models }) => { return Math.floor(user.msats / 1000) }, + bio: async (user, args, { models }) => { + console.log(user) + return getItem(user, { id: user.bioId }, { models }) + }, hasNewNotes: async (user, args, { models }) => { // check if any votes have been cast for them since checkedNotesAt const votes = await models.$queryRaw(` diff --git a/api/typeDefs/user.js b/api/typeDefs/user.js index b42bb04e..eb941793 100644 --- a/api/typeDefs/user.js +++ b/api/typeDefs/user.js @@ -10,6 +10,7 @@ export default gql` extend type Mutation { setName(name: String!): Boolean + createBio(title: String!, text: String): Item! } type User { @@ -22,6 +23,7 @@ export default gql` freeComments: Int! hasNewNotes: Boolean! tipDefault: Int! + bio: Item sats: Int! msats: Int! } diff --git a/components/comment.js b/components/comment.js index ddcba996..e998a15e 100644 --- a/components/comment.js +++ b/components/comment.js @@ -39,7 +39,10 @@ function Parent ({ item, rootText }) { ) } -export default function Comment ({ item, children, replyOpen, includeParent, rootText, noComments, noReply }) { +export default function Comment ({ + item, children, replyOpen, includeParent, + rootText, noComments, noReply +}) { const [reply, setReply] = useState(replyOpen) const [edit, setEdit] = useState() const [collapse, setCollapse] = useState(false) diff --git a/components/discussion-form.js b/components/discussion-form.js index 94539e7c..cc4faca3 100644 --- a/components/discussion-form.js +++ b/components/discussion-form.js @@ -12,7 +12,11 @@ export const DiscussionSchema = Yup.object({ ...AdvPostSchema }) -export function DiscussionForm ({ item, editThreshold }) { +export function DiscussionForm ({ + item, editThreshold, titleLabel = 'title', + textLabel = 'text', buttonText = 'post', + adv, handleSubmit +}) { const router = useRouter() const [createDiscussion] = useMutation( gql` @@ -53,7 +57,7 @@ export function DiscussionForm ({ item, editThreshold }) { ...AdvPostInitial }} schema={DiscussionSchema} - onSubmit={async ({ boost, ...values }) => { + onSubmit={handleSubmit || (async ({ boost, ...values }) => { let id, error if (item) { ({ data: { updateDiscussion: { id } }, error } = await updateDiscussion({ variables: { ...values, id: item.id } })) @@ -63,17 +67,18 @@ export function DiscussionForm ({ item, editThreshold }) { if (error) { throw new Error({ message: error.toString() }) } + router.push(`/items/${id}`) - }} + })} > text optional} + label={<>{textLabel} optional} name='text' as={TextareaAutosize} minRows={4} @@ -81,9 +86,9 @@ export function DiscussionForm ({ item, editThreshold }) { ? : null} /> - {!item && } + {!item && adv && } - {item ? 'save' : 'post'} + {item ? 'save' : buttonText} ) diff --git a/components/footer.js b/components/footer.js index 4b32d1ce..1a98cf02 100644 --- a/components/footer.js +++ b/components/footer.js @@ -84,12 +84,12 @@ export default function Footer ({ noLinks }) { /> } - - This is free open source software + + This is free open source software - - made with sound love in Austin - by @k00bideh + + made with sound love in Austin + by@k00bideh diff --git a/components/item-full.js b/components/item-full.js new file mode 100644 index 00000000..a6cd87bf --- /dev/null +++ b/components/item-full.js @@ -0,0 +1,76 @@ +import Item, { ItemSkeleton } from './item' +import Reply, { ReplySkeleton } from './reply' +import Comment from './comment' +import Text from './text' +import Comments, { CommentsSkeleton } from './comments' +import { COMMENTS } from '../fragments/comments' +import { ITEM_FIELDS } from '../fragments/items' +import { gql, useQuery } from '@apollo/client' +import styles from '../styles/item.module.css' +import { NOFOLLOW_LIMIT } from '../lib/constants' +import { useRouter } from 'next/router' + +export default function ItemFull ({ item: qItem, minimal }) { + const query = gql` + ${ITEM_FIELDS} + ${COMMENTS} + { + item(id: ${qItem.id}) { + ...ItemFields + text + comments { + ...CommentsRecursive + } + } + }` + + const router = useRouter() + const { error, data } = useQuery(query, { + fetchPolicy: router.query.cache ? 'cache-first' : undefined + }) + if (error) return
Failed to load!
+ + if (!data) { + return ( +
+ + + +
+ +
+
+ ) + } + + const { item } = data + + return ( + <> + {item.parentId + ? + : (minimal + ? ( + <> + {item.text && +
+ {item.text} +
} + ) + : ( + <> + + {item.text && +
+ {item.text} +
} + +
+ ) + )} +
+ +
+ + ) +} diff --git a/components/layout.js b/components/layout.js index 189fc6f6..3b7f36cd 100644 --- a/components/layout.js +++ b/components/layout.js @@ -5,7 +5,7 @@ import { LightningProvider } from './lightning' import Footer from './footer' import Seo from './seo' -export default function Layout ({ noContain, noFooter, noFooterLinks, noSeo, children }) { +export default function Layout ({ noContain, noFooter, noFooterLinks, containClassName, noSeo, children }) { return ( <> {!noSeo && } @@ -17,7 +17,7 @@ export default function Layout ({ noContain, noFooter, noFooterLinks, noSeo, chi {noContain ? children : ( - + {children} )} diff --git a/components/user-header.js b/components/user-header.js index 5c657a97..2b086c66 100644 --- a/components/user-header.js +++ b/components/user-header.js @@ -31,7 +31,7 @@ export default function UserHeader ({ user }) { const client = useApolloClient() const [setName] = useMutation(NAME_MUTATION) - const Satistics = () =>

{user.sats} sats \ {user.stacked} stacked

+ const Satistics = () =>

{user.sats} sats \ {user.stacked} stacked

const UserSchema = Yup.object({ name: Yup.string() @@ -51,66 +51,73 @@ export default function UserHeader ({ user }) { return ( <> - {editting - ? ( -
{ - if (name === user.name) { - setEditting(false) - return - } - const { error } = await setName({ variables: { name } }) - if (error) { - throw new Error({ message: error.toString() }) - } - router.replace(`/${name}`) - session.user.name = name +
+ {editting + ? ( + { + if (name === user.name) { + setEditting(false) + return + } + const { error } = await setName({ variables: { name } }) + if (error) { + throw new Error({ message: error.toString() }) + } + router.replace(`/${name}`) + session.user.name = name - client.writeFragment({ - id: `User:${user.id}`, - fragment: gql` + client.writeFragment({ + id: `User:${user.id}`, + fragment: gql` fragment CurUser on User { name } `, - data: { - name - } - }) + data: { + name + } + }) - setEditting(false) - }} - > - @ - name='name' - autoFocus - groupClassName={`mb-0 ${styles.username}`} - showValid - /> - - setEditting(true)}>save - - ) - : ( -
-

@{user.name}

- - {session && session.user && session.user.name === user.name && - } -
- )} + setEditting(false) + }} + > + @ + name='name' + autoFocus + groupClassName={`mb-0 ${styles.username}`} + showValid + /> + setEditting(true)}>save + + ) + : ( +
+

@{user.name}

+ {session?.user?.name === user.name && + } +
+ )} + +
diff --git a/components/user-header.module.css b/components/user-header.module.css index 1694276f..6540294c 100644 --- a/components/user-header.module.css +++ b/components/user-header.module.css @@ -1,3 +1,16 @@ .username { width: 300px; +} + +.nav { + margin-top: 1rem; + justify-content: space-between; +} + +.nav div:first-child a { + padding-left: 0; +} + +.nav div:last-child a { + padding-right: 0; } \ No newline at end of file diff --git a/pages/[username].js b/pages/[username].js index e7e8db9c..bf221943 100644 --- a/pages/[username].js +++ b/pages/[username].js @@ -1,14 +1,22 @@ import Layout from '../components/layout' -import Items from '../components/items' -import { gql } from '@apollo/client' +import { gql, useMutation } from '@apollo/client' import ApolloClient from '../api/client' import UserHeader from '../components/user-header' import Seo from '../components/seo' +import { Button } from 'react-bootstrap' +import styles from '../styles/user.module.css' +import { useState } from 'react' +import { DiscussionForm } from '../components/discussion-form' +import { useSession } from 'next-auth/client' +import { ITEM_FIELDS } from '../fragments/items' +import ItemFull from '../components/item-full' export async function getServerSideProps ({ req, params }) { const { error, data: { user } } = await (await ApolloClient(req)).query({ query: - gql`{ + gql` + ${ITEM_FIELDS} + { user(name: "${params.username}") { id createdAt @@ -17,6 +25,9 @@ export async function getServerSideProps ({ req, params }) { ncomments stacked sats + bio { + ...ItemFields + } } }` }) @@ -34,12 +45,61 @@ export async function getServerSideProps ({ req, params }) { } } -export default function User ({ user }) { +function BioForm () { + const [createBio] = useMutation( + gql` + mutation createBio($title: String!, $text: String) { + createBio(title: $title, text: $text) { + id + } + }`, { + update (cache, { data: { createBio } }) { + cache.modify({ + id: `User:${createBio.userId}`, + fields: { + bio () { + return createBio + } + } + }) + } + } + ) + return ( - +
+ { + const { error } = await createBio({ variables: values }) + if (error) { + throw new Error({ message: error.toString() }) + } + }} + /> +
+ ) +} + +export default function User ({ user }) { + const [create, setCreate] = useState(false) + const [session] = useSession() + + return ( + - + {user.bio + ? + : ( +
+ {create + ? + : ( + session?.user?.name === user.name && + + )} +
)}
) } diff --git a/pages/[username]/posts.js b/pages/[username]/posts.js new file mode 100644 index 00000000..76d7030e --- /dev/null +++ b/pages/[username]/posts.js @@ -0,0 +1,45 @@ +import Layout from '../../components/layout' +import { gql } from '@apollo/client' +import ApolloClient from '../../api/client' +import UserHeader from '../../components/user-header' +import Seo from '../../components/seo' +import Items from '../../components/items' + +export async function getServerSideProps ({ req, params }) { + const { error, data: { user } } = await (await ApolloClient(req)).query({ + query: + gql`{ + user(name: "${params.username}") { + id + createdAt + name + nitems + ncomments + stacked + sats + } + }` + }) + + if (!user || error) { + return { + notFound: true + } + } + + return { + props: { + user + } + } +} + +export default function UserPosts ({ user }) { + return ( + + + + + + ) +} diff --git a/pages/items/[id].js b/pages/items/[id].js index 6f06be05..ec35e9f6 100644 --- a/pages/items/[id].js +++ b/pages/items/[id].js @@ -1,17 +1,9 @@ -import Item, { ItemSkeleton } from '../../components/item' import Layout from '../../components/layout' -import Reply, { ReplySkeleton } from '../../components/reply' -import Comment from '../../components/comment' -import Text from '../../components/text' -import Comments, { CommentsSkeleton } from '../../components/comments' -import { COMMENTS } from '../../fragments/comments' import { ITEM_FIELDS } from '../../fragments/items' -import { gql, useQuery } from '@apollo/client' -import styles from '../../styles/item.module.css' +import { gql } from '@apollo/client' import Seo from '../../components/seo' import ApolloClient from '../../api/client' -import { NOFOLLOW_LIMIT } from '../../lib/constants' -import { useRouter } from 'next/router' +import ItemFull from '../../components/item-full' // ssr the item without comments so that we can populate metatags export async function getServerSideProps ({ req, params: { id } }) { @@ -46,65 +38,11 @@ export async function getServerSideProps ({ req, params: { id } }) { } } -export default function FullItem ({ item }) { - const query = gql` - ${ITEM_FIELDS} - ${COMMENTS} - { - item(id: ${item.id}) { - ...ItemFields - text - comments { - ...CommentsRecursive - } - } - }` - +export default function AnItem ({ item }) { return ( - + ) } - -function LoadItem ({ query }) { - const router = useRouter() - const { error, data } = useQuery(query, { - fetchPolicy: router.query.cache ? 'cache-first' : undefined - }) - if (error) return
Failed to load!
- - if (!data) { - return ( -
- - - -
- -
-
- ) - } - - const { item } = data - - return ( - <> - {item.parentId - ? - : ( - <> - - {item.text &&
{item.text}
} - -
- - )} -
- -
- - ) -} diff --git a/prisma/migrations/20210921232401_user_bio/migration.sql b/prisma/migrations/20210921232401_user_bio/migration.sql new file mode 100644 index 00000000..bd5ea9a2 --- /dev/null +++ b/prisma/migrations/20210921232401_user_bio/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "users" ADD COLUMN "bioId" INTEGER; + +-- AddForeignKey +ALTER TABLE "users" ADD FOREIGN KEY ("bioId") REFERENCES "Item"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/migrations/20210922214428_create_bio/migration.sql b/prisma/migrations/20210922214428_create_bio/migration.sql new file mode 100644 index 00000000..e2bb3d41 --- /dev/null +++ b/prisma/migrations/20210922214428_create_bio/migration.sql @@ -0,0 +1,16 @@ +CREATE OR REPLACE FUNCTION create_bio(title TEXT, text TEXT, user_id INTEGER) +RETURNS "Item" +LANGUAGE plpgsql +AS $$ +DECLARE + item "Item"; +BEGIN + PERFORM ASSERT_SERIALIZED(); + + SELECT * INTO item FROM create_item(title, NULL, text, 0, NULL, user_id); + + UPDATE users SET "bioId" = item.id WHERE id = user_id; + + RETURN item; +END; +$$; \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4aee1dfe..d9d6bdcb 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -24,6 +24,8 @@ model User { actions ItemAct[] invoices Invoice[] withdrawls Withdrawl[] + bio Item? @relation(name: "Item", fields: [bioId], references: [id]) + bioId Int? msats Int @default(0) freeComments Int @default(5) freePosts Int @default(2) @@ -65,6 +67,7 @@ model Item { mentions Mention[] path Unsupported("LTREE")? + User User[] @relation("Item") @@index([userId]) @@index([parentId]) } diff --git a/styles/user.module.css b/styles/user.module.css new file mode 100644 index 00000000..59945ab0 --- /dev/null +++ b/styles/user.module.css @@ -0,0 +1,17 @@ +.contain { + display: flex; + flex-direction: column; + flex-grow: 1; +} + +.create { + flex-grow: 1; + align-items: center; + justify-content: center; + display: flex; +} + +.createFormContainer { + width: 100%; + max-width: 740px; +} \ No newline at end of file diff --git a/styles/username.module.css b/styles/username.module.css deleted file mode 100644 index e69de29b..00000000