feat: pubkeys-to-users api, finished the addComment component functionality, show/hide reply

This commit is contained in:
MTG2000
2022-07-21 19:03:29 +03:00
parent 41b8ff40c6
commit 8c5eac030b
8 changed files with 126 additions and 58 deletions

View File

@@ -0,0 +1,62 @@
const serverless = require('serverless-http');
const { createExpressApp } = require('../../modules');
const express = require('express');
const extractKeyFromCookie = require('../../utils/extractKeyFromCookie');
const { getUserByPubKey } = require('../../auth/utils/helperFuncs');
const { prisma } = require('../../prisma');
const mapPubkeysToUsers = async (req, res) => {
try {
const pubkeys = req.body.pubkeys ?? [];
const usersArr = await prisma.user.findMany({
where: {
nostr_pub_key: {
in: pubkeys
}
},
select: {
id: true,
name: true,
avatar: true,
nostr_pub_key: true,
lightning_address: true
}
})
let pubkeysToUsers = {}
usersArr.forEach(user => {
pubkeysToUsers[user.nostr_pub_key] = user;
});
return res
.status(200)
.json({ pubkeysToUsers });
} catch (error) {
console.log(error);
res.status(500).send("Unexpected error happened, please try again")
}
}
let app;
if (process.env.LOCAL) {
app = createExpressApp()
app.post('/pubkeys-to-users', mapPubkeysToUsers);
}
else {
const router = express.Router();
router.post('/pubkeys-to-users', mapPubkeysToUsers)
app = createExpressApp(router)
}
const handler = serverless(app);
exports.handler = async (event, context) => {
return await handler(event, context);
};

View File

@@ -51,9 +51,12 @@ functions:
sign-event:
handler: api/functions/sign-event/sign-event.handler
events:
- http:
path: sign-event
method: get
- http:
path: sign-event
method: post
pubkeys-to-users:
handler: api/functions/pubkeys-to-users/pubkeys-to-users.handler
events:
- http:
path: pubkeys-to-users
method: post

View File

@@ -15,7 +15,8 @@ const Template: ComponentStory<typeof AddComment> = (args) => <div className="ma
export const Default = Template.bind({});
Default.args = {
placeholder: "Leave a comment..."
placeholder: "Leave a comment...",
avatar: "https://i.pravatar.cc/150?img=8"
}

View File

@@ -24,13 +24,13 @@ import { InvalidContentHandler } from 'remirror';
interface Props {
initialContent?: string;
placeholder?: string;
name?: string;
avatar: string;
autoFocus?: boolean
onSubmit?: (comment: string) => void;
}
export default function AddComment({ initialContent, placeholder, name, autoFocus, onSubmit }: Props) {
export default function AddComment({ initialContent, placeholder, avatar, autoFocus, onSubmit }: Props) {
const containerRef = useRef<HTMLDivElement>(null)
const linkExtension = useMemo(() => {
@@ -104,7 +104,7 @@ export default function AddComment({ initialContent, placeholder, name, autoFocu
autoFocus={autoFocus}
>
<div className="flex gap-16 items-start pb-24 border-b border-gray-200 focus-within:border-primary-500">
<div className="hidden sm:block mt-16 shrink-0"><Avatar width={48} src='https://i.pravatar.cc/150?img=1' /></div>
<div className="hidden sm:block mt-16 shrink-0"><Avatar width={48} src={avatar} /></div>
<div className="flex-grow">
<EditorComponent
/>

View File

@@ -1,5 +1,6 @@
import { useState } from "react";
import { useAppSelector } from "src/utils/hooks";
import AddComment from "../AddComment/AddComment";
import CommentCard from "../CommentCard/CommentCard";
import { CommentWithReplies } from "../types";
@@ -8,13 +9,14 @@ import { CommentWithReplies } from "../types";
interface Props {
comment: CommentWithReplies
isRoot?: boolean;
canReply: boolean;
onClickedReply?: () => void
}
export default function Comment({ comment, isRoot, onClickedReply }: Props) {
export default function Comment({ comment, canReply, isRoot, onClickedReply }: Props) {
const [replyOpen, setReplyOpen] = useState(false)
const user = useAppSelector(s => s.user.me)
const clickReply = () => {
if (isRoot)
@@ -25,7 +27,7 @@ export default function Comment({ comment, isRoot, onClickedReply }: Props) {
return (
<div >
<CommentCard comment={comment} onReply={clickReply} />
<CommentCard canReply={canReply} comment={comment} onReply={clickReply} />
{(comment.replies.length > 0 || replyOpen) && <div className="flex mt-16 gap-8 md:gap-20 pl-8">
<div className="border-l border-b border-gray-200 w-16 md:w-24 h-40 rounded-bl-8 flex-shrink-0"></div>
<div className="flex flex-col w-full gap-16">
@@ -33,8 +35,9 @@ export default function Comment({ comment, isRoot, onClickedReply }: Props) {
key={reply.id}
comment={reply}
onClickedReply={clickReply}
canReply={false}
/>)}
{replyOpen && <AddComment autoFocus placeholder="Leave a reply..." />}
{replyOpen && <AddComment avatar={user?.avatar!} autoFocus placeholder="Leave a reply..." />}
</div>
</div>}
</div>

View File

@@ -7,10 +7,11 @@ import { Comment } from "../types";
interface Props {
comment: Comment
canReply?: boolean;
onReply?: () => void
}
export default function CommentCard({ comment, onReply }: Props) {
export default function CommentCard({ comment, canReply, onReply }: Props) {
return (
<div className="border rounded-12 p-24">
<Header author={comment.author} date={new Date(comment.created_at).toISOString()} />
@@ -19,12 +20,12 @@ export default function CommentCard({ comment, onReply }: Props) {
</p>
<div className="flex gap-24 mt-16 items-center">
<VotesCount count={0} />
<button
{canReply && <button
className="text-gray-600 font-medium hover:bg-gray-100 py-8 px-12 rounded-8"
onClick={onReply}
>
<BiComment /> <span className="align-middle text-body5">Reply</span>
</button>
</button>}
</div>
</div>
)

View File

@@ -1,9 +1,9 @@
import React, { useEffect, useMemo, useState } from 'react'
import CommentRoot from '../Comment/Comment'
import AddComment from '../AddComment/AddComment'
import { } from '../helpers'
import { Comment, } from '../types'
import { createWorkerFactory, useWorker } from '@shopify/react-web-worker'
import { useAppSelector } from "src/utils/hooks";
import * as CommentsWorker from './comments.worker'
import { Post_Type } from 'src/graphql'
@@ -22,7 +22,7 @@ export default function CommentsSection({ type, id }: Props) {
// const commentsTree = useMemo(() => convertCommentsToTree(comments), [comments])
const [commentsTree, setCommentsTree] = useState<Comment[]>([])
const user = useAppSelector(state => state.user.me)
const filter = useMemo(() => `boltfun ${type}_comment ${id}` + (process.env.NODE_ENV === 'development' ? 'dev' : ""), [id, type])
useEffect(() => {
@@ -44,11 +44,21 @@ export default function CommentsSection({ type, id }: Props) {
return (
<div className="border border-gray-200 rounded-10 p-32 bg-white">
<h6 className="text-body2 font-bolder">Discussion</h6>
<div className="mt-24">
<AddComment placeholder='Leave a comment...' onSubmit={handleNewComment} />
</div>
{!!user && <div className="mt-24">
<AddComment
placeholder='Leave a comment...'
onSubmit={handleNewComment}
avatar={user.avatar}
/>
</div>}
<div className='flex flex-col gap-16 mt-32'>
{commentsTree.map(comment => <CommentRoot key={comment.id} comment={comment} />)}
{commentsTree.map(comment =>
<CommentRoot
key={comment.id}
comment={comment}
isRoot
canReply={!!user}
/>)}
</div>
</div>
)

View File

@@ -1,33 +1,20 @@
import dayjs from 'dayjs'
import { generatePrivateKey, getPublicKey, relayPool } from 'nostr-tools'
import debounce from 'lodash.debounce';
import { relayPool } from 'nostr-tools'
import { Nullable } from 'remirror';
import { CONSTS } from 'src/utils';
import { Comment } from '../types';
import { mapPubkeysToUsers, } from './comment.server';
type Author = {
id: number;
name: string;
avatar: string;
lightning_address?: string
}
const pool = relayPool();
const globalKeys = {
prvkey: '',
pubkey: ''
}
export function now(prefix: string) {
const hell = window.localStorage.getItem('test');
if (!hell) window.localStorage.setItem('test', 'test');
return hell + prefix + dayjs()
};
export function connect() {
const RELAYS = [
@@ -45,35 +32,37 @@ export function connect() {
})
};
const events: Record<string, Required<NostrEvent>> = {};
let events: Record<string, Required<NostrEvent>> = {};
export function sub(filter: string, cb: (data: Comment[]) => void) {
const reconstructTree = debounce(async () => {
const newComments = await constructTree();
cb(newComments)
}, 1000)
let sub = pool.sub({
filter: {
"#r": [filter]
},
cb: async (event: Required<NostrEvent>) => {
//Got a new event
console.log(event);
if (!event.id) return;
if (event.id in events) return
events[event.id] = event
const newComments = await constructTree();
cb(newComments)
reconstructTree()
}
});
return () => {
sub.unsub();
events = {};
};
}
const getSignedEvents = async (event: any) => {
async function getSignedEvents(event: any) {
const res = await fetch(CONSTS.apiEndpoint + '/sign-event', {
method: "post",
body: JSON.stringify({ event }),
@@ -86,21 +75,20 @@ const getSignedEvents = async (event: any) => {
return data.event;
}
function setKeys() {
if (globalKeys.prvkey) return;
let privateKey = localStorage.getItem('nostrkey')
if (!privateKey) {
privateKey = generatePrivateKey()
localStorage.setItem('nostrkey', privateKey)
}
pool.setPrivateKey(privateKey)
const pubkey = getPublicKey(privateKey)
globalKeys.prvkey = privateKey
globalKeys.pubkey = pubkey;
async function mapPubkeysToUsers(pubkeys: string[]) {
const res = await fetch(CONSTS.apiEndpoint + '/pubkeys-to-users', {
method: "post",
body: JSON.stringify({ pubkeys }),
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
});
const data = await res.json()
return data.pubkeysToUsers as Record<string, Author>;
}
export async function post(data: string, filter: string) {
// setKeys();
@@ -162,7 +150,7 @@ export async function constructTree() {
// Extract the pubkeys used
const pubkeysSet = new Set();
const pubkeysSet = new Set<string>();
sortedEvenets.forEach(e => pubkeysSet.add(e.pubkey));