feat: more work on the commenting system

This commit is contained in:
MTG2000
2022-07-20 18:36:51 +03:00
parent 9d8f591596
commit 263e8c410d
33 changed files with 591 additions and 299 deletions

View File

@@ -139,7 +139,7 @@ export interface NexusGenObjects {
PostComment: { // root type
author: NexusGenRootTypes['Author']; // Author!
body: string; // String!
createdAt: NexusGenScalars['Date']; // Date!
created_at: NexusGenScalars['Date']; // Date!
id: number; // Int!
parentId?: number | null; // Int
votes_count: number; // Int!
@@ -158,7 +158,6 @@ export interface NexusGenObjects {
}
Query: {};
Question: { // root type
answers_count: number; // Int!
body: string; // String!
createdAt: NexusGenScalars['Date']; // Date!
excerpt: string; // String!
@@ -316,7 +315,7 @@ export interface NexusGenFieldTypes {
PostComment: { // field return type
author: NexusGenRootTypes['Author']; // Author!
body: string; // String!
createdAt: NexusGenScalars['Date']; // Date!
created_at: NexusGenScalars['Date']; // Date!
id: number; // Int!
parentId: number | null; // Int
votes_count: number; // Int!
@@ -358,10 +357,8 @@ export interface NexusGenFieldTypes {
searchProjects: NexusGenRootTypes['Project'][]; // [Project!]!
}
Question: { // field return type
answers_count: number; // Int!
author: NexusGenRootTypes['Author']; // Author!
body: string; // String!
comments: NexusGenRootTypes['PostComment'][]; // [PostComment!]!
createdAt: NexusGenScalars['Date']; // Date!
excerpt: string; // String!
id: number; // Int!
@@ -375,8 +372,6 @@ export interface NexusGenFieldTypes {
Story: { // field return type
author: NexusGenRootTypes['Author']; // Author!
body: string; // String!
comments: NexusGenRootTypes['PostComment'][]; // [PostComment!]!
comments_count: number; // Int!
cover_image: string | null; // String
createdAt: NexusGenScalars['Date']; // Date!
excerpt: string; // String!
@@ -524,7 +519,7 @@ export interface NexusGenFieldTypeNames {
PostComment: { // field return type name
author: 'Author'
body: 'String'
createdAt: 'Date'
created_at: 'Date'
id: 'Int'
parentId: 'Int'
votes_count: 'Int'
@@ -566,10 +561,8 @@ export interface NexusGenFieldTypeNames {
searchProjects: 'Project'
}
Question: { // field return type name
answers_count: 'Int'
author: 'Author'
body: 'String'
comments: 'PostComment'
createdAt: 'Date'
excerpt: 'String'
id: 'Int'
@@ -583,8 +576,6 @@ export interface NexusGenFieldTypeNames {
Story: { // field return type name
author: 'Author'
body: 'String'
comments: 'PostComment'
comments_count: 'Int'
cover_image: 'String'
createdAt: 'Date'
excerpt: 'String'

View File

@@ -124,7 +124,7 @@ interface PostBase {
type PostComment {
author: Author!
body: String!
createdAt: Date!
created_at: Date!
id: Int!
parentId: Int
votes_count: Int!
@@ -169,10 +169,8 @@ type Query {
}
type Question implements PostBase {
answers_count: Int!
author: Author!
body: String!
comments: [PostComment!]!
createdAt: Date!
excerpt: String!
id: Int!
@@ -187,8 +185,6 @@ type Question implements PostBase {
type Story implements PostBase {
author: Author!
body: String!
comments: [PostComment!]!
comments_count: Int!
cover_image: String
createdAt: Date!
excerpt: String!

View File

@@ -70,29 +70,29 @@ const Story = objectType({
resolve: () => t.typeName
});
t.string('cover_image');
t.nonNull.list.nonNull.field('comments', {
type: "PostComment",
resolve: (parent) => prisma.story.findUnique({ where: { id: parent.id } }).comments()
});
// t.nonNull.list.nonNull.field('comments', {
// type: "PostComment",
// resolve: (parent) => prisma.story.findUnique({ where: { id: parent.id } }).comments()
// });
t.nonNull.list.nonNull.field('tags', {
type: "Tag",
resolve: (parent) => prisma.story.findUnique({ where: { id: parent.id } }).tags()
});
t.nonNull.int('comments_count', {
resolve: async (parent) => {
const post = await prisma.story.findUnique({
where: { id: parent.id },
include: {
_count: {
select: {
comments: true
}
}
}
})
return post._count.comments;
}
});
// t.nonNull.int('comments_count', {
// resolve: async (parent) => {
// const post = await prisma.story.findUnique({
// where: { id: parent.id },
// include: {
// _count: {
// select: {
// comments: true
// }
// }
// }
// })
// return post._count.comments;
// }
// });
t.nonNull.field('author', {
type: "Author",
resolve: (parent) =>
@@ -170,13 +170,13 @@ const Question = objectType({
resolve: (parent) => prisma.question.findUnique({ where: { id: parent.id } }).tags()
});
t.nonNull.int('answers_count');
t.nonNull.list.nonNull.field('comments', {
type: "PostComment",
resolve: (parent) => {
return prisma.question.findUnique({ where: { id: parent.id } }).comments();
}
});
// t.nonNull.int('answers_count');
// t.nonNull.list.nonNull.field('comments', {
// type: "PostComment",
// resolve: (parent) => {
// return prisma.question.findUnique({ where: { id: parent.id } }).comments();
// }
// });
t.nonNull.field('author', {
type: "Author",
@@ -191,7 +191,7 @@ const PostComment = objectType({
name: 'PostComment',
definition(t) {
t.nonNull.int('id');
t.nonNull.date('createdAt');
t.nonNull.date('created_at');
t.nonNull.string('body');
t.nonNull.field('author', {
type: "Author"

View File

@@ -4,11 +4,12 @@ const { createExpressApp } = require('../../modules');
const express = require('express');
const extractKeyFromCookie = require('../../utils/extractKeyFromCookie');
const { getUserByPubKey } = require('../../auth/utils/helperFuncs');
const { generatePrivateKey, getPublicKey, signEvent: signNostrEvent } = require('nostr-tools');
const { generatePrivateKey, getPublicKey, signEvent: signNostrEvent } = require('../../utils/nostr-tools');
const { prisma } = require('../../prisma');
const signEvent = async (req, res) => {
console.log(req.body);
try {
const userPubKey = await extractKeyFromCookie(req.headers.cookie ?? req.headers.Cookie)
const user = await getUserByPubKey(userPubKey);
@@ -33,16 +34,18 @@ const signEvent = async (req, res) => {
})
}
const { content, tags } = req.body
const { content, tags, kind = 1 } = req.body.event
const event = {
kind: 1,
kind,
pubkey,
content,
tags,
created_at: Date.now(),
}
event.sig = await signNostrEvent(event, prvkey)
event.sig = await signNostrEvent(event, prvkey);
console.log(event);
return res
.status(200)
@@ -59,11 +62,11 @@ let app;
if (process.env.LOCAL) {
app = createExpressApp()
app.get('/sign-event', signEvent);
app.post('/sign-event', signEvent);
}
else {
const router = express.Router();
router.get('/sign-event', signEvent)
router.post('/sign-event', signEvent)
app = createExpressApp(router)
}

View File

@@ -1,13 +1,21 @@
const express = require('express');
var cors = require('cors');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser')
const createExpressApp = (router) => {
const app = express();
const routerBasePath = process.env.LOCAL ? `/dev` : `/.netlify/functions`
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
app.use(cookieParser());
app.use(cors({
origin: ['http://localhost:3000', 'https://studio.apollographql.com'],

View File

@@ -1,4 +1,5 @@
module.exports = {
CONSTS: require('./consts')
CONSTS: require('./consts'),
nostr_tools: require('./nostr-tools')
}

45
api/utils/nostr-tools.js Normal file
View File

@@ -0,0 +1,45 @@
// import * as secp256k1 from '@noble/secp256k1'
// import { Buffer } from 'buffer'
const secp256k1 = require('@noble/secp256k1');
const crypto = require('crypto')
function generatePrivateKey() {
return Buffer.from(secp256k1.utils.randomPrivateKey()).toString('hex')
}
function getPublicKey(privateKey) {
return Buffer.from(secp256k1.schnorr.getPublicKey(privateKey)).toString('hex')
}
function serializeEvent(evt) {
return JSON.stringify([
0,
evt.pubkey,
evt.created_at,
evt.kind,
evt.tags,
evt.content
])
}
function getEventHash(event) {
let eventHash = crypto.createHash('sha256')
.update(Buffer.from(serializeEvent(event)))
.digest()
return Buffer.from(eventHash).toString('hex')
}
async function signEvent(event, key) {
return Buffer.from(
await secp256k1.schnorr.sign(getEventHash(event), key)
).toString('hex')
}
module.exports = {
generatePrivateKey,
getPublicKey,
signEvent,
}

241
package-lock.json generated
View File

@@ -10,6 +10,7 @@
"dependencies": {
"@apollo/client": "^3.5.10",
"@hookform/resolvers": "^2.8.8",
"@noble/secp256k1": "^1.6.3",
"@prisma/client": "^3.12.0",
"@react-hookz/web": "^13.2.1",
"@react-spring/web": "^9.4.4",
@@ -84,8 +85,8 @@
"react-tooltip": "^4.2.21",
"react-topbar-progress-indicator": "^4.1.1",
"remirror": "^1.0.77",
"secp256k1": "^4.0.3",
"serverless-http": "^3.0.1",
"stream": "npm:stream-browserify",
"turndown": "^7.1.1",
"typescript": "^4.6.3",
"web-vitals": "^2.1.4",
@@ -123,6 +124,7 @@
"msw": "^0.39.2",
"netlify-cli": "^10.0.0",
"postcss": "^8.4.12",
"readable-stream": "^4.1.0",
"serverless-offline": "^8.7.0",
"tailwindcss": "^3.0.24",
"webpack": "^5.72.0"
@@ -17048,6 +17050,19 @@
"node": ">=10"
}
},
"node_modules/are-we-there-yet/node_modules/readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/arg": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.1.tgz",
@@ -18233,6 +18248,19 @@
"ieee754": "^1.1.13"
}
},
"node_modules/bl/node_modules/readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@@ -18578,6 +18606,20 @@
"safe-buffer": "^5.2.0"
}
},
"node_modules/browserify-sign/node_modules/readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dev": true,
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/browserify-sign/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -25222,6 +25264,19 @@
"node": ">=4"
}
},
"node_modules/hash-base/node_modules/readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/hash-base/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -55868,6 +55923,19 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
"node_modules/node-gyp/node_modules/readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/node-int64": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@@ -60797,16 +60865,15 @@
}
},
"node_modules/readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.1.0.tgz",
"integrity": "sha512-sVisi3+P2lJ2t0BPbpK629j8wRW06yKGJUcaLAGXPAUhyUxVJm7VsCTit1PFgT4JHUDMrGNR+ZjSKpzGaRF3zw==",
"dev": true,
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
"abort-controller": "^3.0.0"
},
"engines": {
"node": ">= 6"
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/readdirp": {
@@ -63591,6 +63658,19 @@
"wbuf": "^1.7.3"
}
},
"node_modules/spdy-transport/node_modules/readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/split-string": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
@@ -63907,6 +63987,16 @@
"integrity": "sha512-CMtO2Uneg3SAz/d6fZ/6qbqqQHi2ynq6/KzMD/26gTkiEShCcpqFfTHgOxsE0egAq6SX3FmN4CeSqn8BzXQkJg==",
"dev": true
},
"node_modules/stream": {
"name": "stream-browserify",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
"integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==",
"dependencies": {
"inherits": "~2.0.4",
"readable-stream": "^3.5.0"
}
},
"node_modules/stream-browserify": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
@@ -63994,6 +64084,19 @@
"integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
"dev": true
},
"node_modules/stream/node_modules/readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/strict-event-emitter": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.4.tgz",
@@ -64666,6 +64769,19 @@
"node": ">=6"
}
},
"node_modules/tar-stream/node_modules/readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/tar/node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
@@ -80925,6 +81041,18 @@
"requires": {
"delegates": "^1.0.0",
"readable-stream": "^3.6.0"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"arg": {
@@ -81851,6 +81979,16 @@
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
@@ -82153,6 +82291,17 @@
"safe-buffer": "^5.2.0"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -87282,6 +87431,16 @@
"safe-buffer": "^5.2.0"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -110601,6 +110760,16 @@
"gauge": "^4.0.3",
"set-blocking": "^2.0.0"
}
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
@@ -114164,13 +114333,12 @@
}
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.1.0.tgz",
"integrity": "sha512-sVisi3+P2lJ2t0BPbpK629j8wRW06yKGJUcaLAGXPAUhyUxVJm7VsCTit1PFgT4JHUDMrGNR+ZjSKpzGaRF3zw==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
"abort-controller": "^3.0.0"
}
},
"readdirp": {
@@ -116356,6 +116524,18 @@
"obuf": "^1.1.2",
"readable-stream": "^3.0.6",
"wbuf": "^1.7.3"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"split-string": {
@@ -116620,6 +116800,27 @@
"integrity": "sha512-CMtO2Uneg3SAz/d6fZ/6qbqqQHi2ynq6/KzMD/26gTkiEShCcpqFfTHgOxsE0egAq6SX3FmN4CeSqn8BzXQkJg==",
"dev": true
},
"stream": {
"version": "npm:stream-browserify@3.0.0",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
"integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==",
"requires": {
"inherits": "~2.0.4",
"readable-stream": "^3.5.0"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"stream-browserify": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
@@ -117231,6 +117432,18 @@
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"telejson": {

View File

@@ -5,6 +5,7 @@
"dependencies": {
"@apollo/client": "^3.5.10",
"@hookform/resolvers": "^2.8.8",
"@noble/secp256k1": "^1.6.3",
"@prisma/client": "^3.12.0",
"@react-hookz/web": "^13.2.1",
"@react-spring/web": "^9.4.4",
@@ -79,8 +80,8 @@
"react-tooltip": "^4.2.21",
"react-topbar-progress-indicator": "^4.1.1",
"remirror": "^1.0.77",
"secp256k1": "^4.0.3",
"serverless-http": "^3.0.1",
"stream": "npm:stream-browserify",
"turndown": "^7.1.1",
"typescript": "^4.6.3",
"web-vitals": "^2.1.4",
@@ -174,6 +175,7 @@
"msw": "^0.39.2",
"netlify-cli": "^10.0.0",
"postcss": "^8.4.12",
"readable-stream": "^4.1.0",
"serverless-offline": "^8.7.0",
"tailwindcss": "^3.0.24",
"webpack": "^5.72.0"

View File

@@ -0,0 +1,9 @@
/*
Warnings:
- You are about to drop the column `createdAt` on the `PostComment` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "PostComment" DROP COLUMN "createdAt",
ADD COLUMN "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;

View File

@@ -156,7 +156,7 @@ model Question {
model PostComment {
id Int @id @default(autoincrement())
body String
createdAt DateTime @default(now())
created_at DateTime @default(now())
votes_count Int @default(0)
replies PostComment[] @relation("PostComment_Replies")

View File

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

View File

@@ -26,10 +26,11 @@ interface Props {
placeholder?: string;
name?: string;
autoFocus?: boolean
onSubmit?: (comment: string) => void;
}
export default function AddComment({ initialContent, placeholder, name, autoFocus }: Props) {
export default function AddComment({ initialContent, placeholder, name, autoFocus, onSubmit }: Props) {
const containerRef = useRef<HTMLDivElement>(null)
const linkExtension = useMemo(() => {
@@ -85,8 +86,8 @@ export default function AddComment({ initialContent, placeholder, name, autoFocu
const submitComment = () => {
console.log(valueRef.current);
manager.view.updateState(manager.createState({ content: manager.createEmptyDoc() }))
onSubmit?.(valueRef.current);
// manager.view.updateState(manager.createState({ content: manager.createEmptyDoc() }))
}
@@ -96,8 +97,8 @@ export default function AddComment({ initialContent, placeholder, name, autoFocu
manager={manager}
state={state}
onChange={e => {
const html = e.helpers.getHTML(e.state)
valueRef.current = html;
const md = e.helpers.getMarkdown(e.state)
valueRef.current = md;
onChange(e);
}}
autoFocus={autoFocus}

View File

@@ -18,9 +18,10 @@ export const Default = Template.bind({});
Default.args = {
comment: {
...MOCK_DATA.generatePostComments(1)[0],
created_at: Date.now(),
replies: [
{ ...MOCK_DATA.generatePostComments(1)[0], replies: [] },
{ ...MOCK_DATA.generatePostComments(1)[0], replies: [] }
{ ...MOCK_DATA.generatePostComments(1)[0], replies: [], created_at: Date.now() },
{ ...MOCK_DATA.generatePostComments(1)[0], replies: [], created_at: Date.now() }
]
}
}

View File

@@ -7,17 +7,17 @@ import { CommentWithReplies } from "../types";
interface Props {
comment: CommentWithReplies
isRoot?: boolean;
onClickedReply?: () => void
}
export default function Comment({ comment, onClickedReply }: Props) {
export default function Comment({ comment, isRoot, onClickedReply }: Props) {
const [replyOpen, setReplyOpen] = useState(false)
const isRootComment = !comment.parentId;
const clickReply = () => {
if (isRootComment)
if (isRoot)
setReplyOpen(true);
else
onClickedReply?.()

View File

@@ -16,9 +16,8 @@ const Template: ComponentStory<typeof CommentCard> = (args) => <div className="m
export const Default = Template.bind({});
Default.args = {
comment: {
...MOCK_DATA.posts.stories[0].comments[0],
}
comment: MOCK_DATA.generatePostComments(1)[0]
}

View File

@@ -12,12 +12,12 @@ interface Props {
export default function CommentCard({ comment, onReply }: Props) {
return (
<div className="border rounded-12 p-24">
<Header author={comment.author} date={comment.createdAt} />
<Header author={comment.author} date={new Date(comment.created_at).toISOString()} />
<p className="text-body4 mt-16">
{comment.body}
</p>
<div className="flex gap-24 mt-16 items-center">
<VotesCount count={comment.votes_count} />
<VotesCount count={0} />
<button
className="text-gray-600 font-medium hover:bg-gray-100 py-8 px-12 rounded-8"
onClick={onReply}

View File

@@ -16,7 +16,6 @@ const Template: ComponentStory<typeof CommentsSection> = (args) => <div classNam
export const Default = Template.bind({});
Default.args = {
comments: MOCK_DATA.generatePostComments(15)
}

View File

@@ -1,44 +1,55 @@
import React, { useEffect, useMemo } from 'react'
import Comment from '../Comment/Comment'
import React, { useEffect, useMemo, useState } from 'react'
import CommentRoot from '../Comment/Comment'
import AddComment from '../AddComment/AddComment'
import { convertCommentsToTree } from '../helpers'
import { Comment as IComment } from '../types'
import { } from '../helpers'
import { Comment, } from '../types'
import { createWorkerFactory, useWorker } from '@shopify/react-web-worker'
import * as CommentsWorker from './comments.worker'
import { Post_Type } from 'src/graphql'
const createWorker = createWorkerFactory(() => import('./comments.worker'));
// const createWorker = createWorkerFactory(() => import('./comments.worker'));
interface Props {
comments: IComment[]
}
type: Post_Type,
id: number | string
};
export default function CommentsSection({ comments }: Props) {
const worker = useWorker(createWorker);
export default function CommentsSection({ type, id }: Props) {
// const worker = useWorker(createWorker);
// const commentsTree = useMemo(() => convertCommentsToTree(comments), [comments])
useEffect(() => {
(async () => {
// Note: in your actual app code, make sure to check if Home
// is still mounted before setting state asynchronously!
const webWorkerMessage = await worker.now('prefix');
// worker
// alert(webWorkerMessage);
const [commentsTree, setCommentsTree] = useState<Comment[]>([])
// setMessage(webWorkerMessage);
})();
}, [worker])
const filter = useMemo(() => `boltfun ${type}_comment ${id}` + (process.env.NODE_ENV === 'development' ? 'dev' : ""), [id, type])
useEffect(() => {
CommentsWorker.connect();
const unsub = CommentsWorker.sub(filter, (newComments) => {
setCommentsTree(newComments)
})
return () => {
unsub();
}
}, [filter]);
const handleNewComment = (newComment: string) => {
CommentsWorker.post(newComment, filter);
}
return (
<div className="border border-gray-200 rounded-10 p-32 bg-white">
<h6 className="text-body2 font-bolder">Discussion ({comments.length})</h6>
<h6 className="text-body2 font-bolder">Discussion</h6>
<div className="mt-24">
<AddComment placeholder='Leave a comment...' />
<AddComment placeholder='Leave a comment...' onSubmit={handleNewComment} />
</div>
<div className='flex flex-col gap-16 mt-32'>
{commentsTree.map(comment => <CommentRoot key={comment.id} comment={comment} />)}
</div>
{/* <div className='flex flex-col gap-16 mt-32'>
{commentsTree.map(comment => <Comment key={comment.id} comment={comment} />)}
</div> */}
</div>
)
}

View File

@@ -1,12 +1,27 @@
import dayjs from 'dayjs'
import { relayPool } from 'nostr-tools'
import { generatePrivateKey, getPublicKey, relayPool } from 'nostr-tools'
import { Nullable } from 'remirror';
import { CONSTS } from 'src/utils';
import { Comment } from '../types';
import { mapPubkeysToUsers, } from './comment.server';
const pool = relayPool()
type Author = {
id: number;
name: string;
avatar: string;
}
const pool = relayPool();
const globalKeys = {
prvkey: '',
pubkey: ''
}
export function now(prefix: string) {
const hell = window.localStorage.getItem('test');
@@ -16,31 +31,38 @@ export function now(prefix: string) {
export function connect() {
const RELAYS = [
'wss://rsslay.fiatjaf.com',
'wss://nostr-pub.wellorder.net',
'wss://expensive-relay.fiatjaf.com',
'wss://nostr.bitcoiner.social',
'wss://relayer.fiatjaf.com',
'wss://nostr.rocks'
'wss://nostr.drss.io',
'wss://nostr-relay.freeberty.net',
'wss://nostr.unknown.place',
'wss://nostr-relay.untethr.me',
'wss://relay.damus.io'
];
RELAYS.forEach(url => {
pool.addRelay(url, { read: true, write: true })
})
pool.onNotice((notice: string, relay: any) => {
console.log(`${relay.url} says: ${notice}`)
})
};
const events: Record<string, Required<NostrEvent>> = {};
export function sub(filter: any) {
export function sub(filter: string, cb: (data: Comment[]) => void) {
let sub = pool.sub({
filter,
cb: (event: Required<NostrEvent>) => {
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
eventsUpdated();
const newComments = await constructTree();
cb(newComments)
}
});
@@ -53,28 +75,55 @@ export function sub(filter: any) {
const getSignedEvents = async (event: any) => {
const res = await fetch(CONSTS.apiEndpoint + '/sign-event', {
method: "post",
body: JSON.stringify({ event }),
credentials: 'include'
})
const data = await res.json()
return data.event;
}
export async function post(data: string, filter: any) {
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;
let event: NostrEvent = {
created_at: Math.round(Date.now() / 1000),
}
export async function post(data: string, filter: string) {
// setKeys();
let event: NostrEvent;
try {
event = await getSignedEvents({
// pubkey: globalKeys.pubkey,
// created_at: Math.round(Date.now() / 1000),
kind: 1,
tags: filter,
tags: [['r', filter]],
content: data
};
}) as NostrEvent;
console.log(event);
return;
} catch (error) {
alert("Couldn't sign the object successfully...")
return;
}
event = await getSignedEvents(event);
const publishTimeout = setTimeout(() => {
alert(
`failed to publish event ${event.id?.slice(0, 5)} to any relay.`
)
}, 4000)
`failed to publish comment to any relay.`
);
}, 5000)
pool.publish(event, (status: number, relay: string) => {
switch (status) {
@@ -102,27 +151,14 @@ function extractParentId(event: NostrEvent): Nullable<string> {
return null;
}
export async function eventsUpdated() {
export async function constructTree() {
// This function is responsible for transforming the object shaped events into a tree of comments
// ----------------------------------------------------------------------------------------------
// Sort them chronologically from oldest to newest
let sortedEvenets = Object.values(events).sort((a, b) => a.created_at - b.created_at);
type Author = {
id: number;
name: string;
avatar: string;
}
type Comment = {
id: string,
pubkey: string;
author?: Author;
content: any;
created_at: number;
replies: Comment[]
}
// Extract the pubkeys used
const pubkeysSet = new Set();
@@ -138,13 +174,19 @@ export async function eventsUpdated() {
const parentId = extractParentId(e);
if (parentId) {
eventsTree[parentId]?.replies.push({
...e,
id: e.id,
body: e.content,
created_at: e.created_at,
pubkey: e.pubkey,
author: pubkeyToUser[e.pubkey],
replies: [],
});
} else {
eventsTree[e.id] = ({
...e,
id: e.id,
body: e.content,
created_at: e.created_at,
pubkey: e.pubkey,
author: pubkeyToUser[e.pubkey],
replies: [],
});

View File

@@ -1,19 +1,19 @@
import { Comment, CommentWithReplies } from "./types";
export function convertCommentsToTree(comments: Comment[]) {
let tree: Record<Comment['id'], CommentWithReplies> = {};
// export function convertCommentsToTree(comments: Comment[]) {
// let tree: Record<Comment['id'], CommentWithReplies> = {};
for (const comment of comments)
tree[comment.id] = { ...comment, replies: [] }
// for (const comment of comments)
// tree[comment.id] = { ...comment, replies: [] }
for (const comment of Object.values(tree)) {
if (comment.parentId)
tree[comment.parentId].replies = [...tree[comment.parentId].replies, comment]
}
// for (const comment of Object.values(tree)) {
// if (comment.parentId)
// tree[comment.parentId].replies = [...tree[comment.parentId].replies, comment]
// }
// TODO
// Sort the comments according to date
// // TODO
// // Sort the comments according to date
return Object.values(tree).filter(node => !node.parentId);
}
// return Object.values(tree).filter(node => !node.parentId);
// }

View File

@@ -2,12 +2,12 @@
import { Author } from "src/features/Posts/types";
export interface Comment {
id: number
author: Author
createdAt: string
body: string
votes_count: number
parentId: number | null
id: string | number,
pubkey: string;
author?: Pick<Author, 'id' | 'name' | 'avatar'>;
body: any;
created_at: number;
replies: Comment[]
}
export interface CommentWithReplies extends Comment {

View File

@@ -6,7 +6,7 @@ import { Link } from 'react-router-dom';
import { createRoute } from 'src/utils/routing';
interface Props {
author: {
author?: {
id: number,
name: string,
avatar: string
@@ -45,11 +45,15 @@ export default function Header({
return (
<div className='flex gap-8'>
{props.author ?
<Link to={createRoute({ type: 'profile', id: props.author.id, username: props.author.name })}>
<Avatar width={avatarSize[size]} src={props.author.avatar} />
</Link>
:
<></>
}
<div className='overflow-hidden'>
<p className={`${nameSize[size]} text-black font-medium overflow-hidden text-ellipsis`}>{trimText(props.author.name, 30)}</p>
<p className={`${nameSize[size]} text-black font-medium overflow-hidden text-ellipsis`}>{props.author ? trimText(props.author.name, 30) : "Anonymouse"}</p>
<p className={`text-body6 text-gray-600`}>{dateToShow()}</p>
</div>
{/* {showTimeAgo && <p className={`${nameSize[size]} text-gray-500 ml-auto `}>

View File

@@ -16,14 +16,13 @@ export type QuestionCardType = Pick<Question,
| 'author'
| 'excerpt'
| 'votes_count'
| "answers_count"
> & {
comments: Array<Pick<Question['comments'][number],
| 'id'
| 'author'
| 'body'
| 'createdAt'
>>
// comments: Array<Pick<Question['comments'][number],
// | 'id'
// | 'author'
// | 'body'
// | 'createdAt'
// >>
tags: Array<Pick<Tag, 'id' | "title">>
};
interface Props {
@@ -54,24 +53,24 @@ export default function QuestionCard({ question }: Props) {
<hr className="my-16 bg-gray-200" />
<div className="flex gap-24 items-center">
<VoteButton votes={question.votes_count} dense />
<div className="text-gray-600">
{/* <div className="text-gray-600">
<FiUsers /> <span className="align-middle text-body5">{question.answers_count} Answers</span>
</div>
</div> */}
</div>
<div className="flex p-16 mt-16 flex-col gap-10 bg-gray-50">
<div className="flex flex-col gap-10">
{question.comments.slice(0, 2).map(comment => <div key={comment.id} className="border-b last-of-type:border-b-0 pb-8 " >
{/* {question.comments.slice(0, 2).map(comment => <div key={comment.id} className="border-b last-of-type:border-b-0 pb-8 " >
<Header author={comment.author} size='sm' date={comment.createdAt} />
<p className="text-body5 text-gray-600 mt-8">{trimText(comment.body, 80)}</p>
</div>)}
</div>)} */}
</div>
<div className="flex">
{/* <div className="flex">
<Link to={`/blog/post/Question/${question.id}`} className="text-black font-medium p-8 hover:bg-gray-100 rounded">
See all {question.answers_count} comments
</Link>
</div>
</div> */}
</div>
</div>
</div>

View File

@@ -18,7 +18,6 @@ export type StoryCardType = Pick<Story,
| 'author'
| 'excerpt'
| 'votes_count'
| 'comments_count'
> & {
tags: Array<Pick<Tag, 'id' | "title">>
};
@@ -51,9 +50,9 @@ export default function StoryCard({ story }: Props) {
<hr className="my-16 bg-gray-200" />
<div className="flex gap-24 items-center">
<VoteButton votes={story.votes_count} dense onVote={vote} />
<div className="text-gray-600">
{/* <div className="text-gray-600">
<BiComment /> <span className="align-middle text-body5">{story.comments_count} Comments</span>
</div>
</div> */}
</div>
</div>
</div>

View File

@@ -12,7 +12,7 @@ mutation createStory($data: StoryInputType) {
is_published
type
cover_image
comments_count
# comments_count
}
}

View File

@@ -18,7 +18,7 @@ query Feed($take: Int, $skip: Int, $sortBy: String, $tag: Int) {
votes_count
type
cover_image
comments_count
# comments_count
}
... on Bounty {
id
@@ -59,18 +59,18 @@ query Feed($take: Int, $skip: Int, $sortBy: String, $tag: Int) {
}
votes_count
type
answers_count
comments {
id
createdAt
body
author {
id
name
avatar
join_date
}
}
# answers_count
# comments {
# id
# created_at
# body
# author {
# id
# name
# avatar
# join_date
# }
# }
}
}
}

View File

@@ -3,7 +3,7 @@ import { Helmet } from 'react-helmet'
import { useParams } from 'react-router-dom'
import LoadingPage from 'src/Components/LoadingPage/LoadingPage'
import NotFoundPage from 'src/features/Shared/pages/NotFoundPage/NotFoundPage'
import { usePostDetailsQuery } from 'src/graphql'
import { Post_Type, usePostDetailsQuery } from 'src/graphql'
import { capitalize } from 'src/utils/helperFunctions'
import { useAppSelector, } from 'src/utils/hooks'
import { CommentsSection } from '../../Components/Comments'
@@ -72,7 +72,7 @@ export default function PostDetailsPage() {
</div>
</aside>
<div id="comments">
<CommentsSection comments={[]} />
<CommentsSection id={post.id} type={type as Post_Type} />
</div>
</div>
</>

View File

@@ -19,19 +19,19 @@ query PostDetails($id: Int!, $type: POST_TYPE!) {
type
cover_image
is_published
comments_count
comments {
id
createdAt
body
votes_count
parentId
author {
id
name
avatar
}
}
# comments_count
# comments {
# id
# created_at
# body
# votes_count
# parentId
# author {
# id
# name
# avatar
# }
# }
}
... on Bounty {
id
@@ -82,19 +82,19 @@ query PostDetails($id: Int!, $type: POST_TYPE!) {
}
votes_count
type
answers_count
comments {
id
createdAt
body
votes_count
parentId
author {
id
name
avatar
}
}
# answers_count
# comments {
# id
# created_at
# body
# votes_count
# parentId
# author {
# id
# name
# avatar
# }
# }
}
}
}

View File

@@ -186,7 +186,7 @@ export type PostComment = {
__typename?: 'PostComment';
author: Author;
body: Scalars['String'];
createdAt: Scalars['Date'];
created_at: Scalars['Date'];
id: Scalars['Int'];
parentId: Maybe<Scalars['Int']>;
votes_count: Scalars['Int'];
@@ -311,10 +311,8 @@ export type QuerySearchProjectsArgs = {
export type Question = PostBase & {
__typename?: 'Question';
answers_count: Scalars['Int'];
author: Author;
body: Scalars['String'];
comments: Array<PostComment>;
createdAt: Scalars['Date'];
excerpt: Scalars['String'];
id: Scalars['Int'];
@@ -330,8 +328,6 @@ export type Story = PostBase & {
__typename?: 'Story';
author: Author;
body: Scalars['String'];
comments: Array<PostComment>;
comments_count: Scalars['Int'];
cover_image: Maybe<Scalars['String']>;
createdAt: Scalars['Date'];
excerpt: Scalars['String'];
@@ -482,7 +478,7 @@ export type CreateStoryMutationVariables = Exact<{
}>;
export type CreateStoryMutation = { __typename?: 'Mutation', createStory: { __typename?: 'Story', id: number, title: string, createdAt: any, body: string, votes_count: number, is_published: boolean | null, type: string, cover_image: string | null, comments_count: number, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } | null };
export type CreateStoryMutation = { __typename?: 'Mutation', createStory: { __typename?: 'Story', id: number, title: string, createdAt: any, body: string, votes_count: number, is_published: boolean | null, type: string, cover_image: string | null, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } | null };
export type DeleteStoryMutationVariables = Exact<{
deleteStoryId: Scalars['Int'];
@@ -504,7 +500,7 @@ export type FeedQueryVariables = Exact<{
}>;
export type FeedQuery = { __typename?: 'Query', getFeed: Array<{ __typename?: 'Bounty', id: number, title: string, createdAt: any, excerpt: string, votes_count: number, type: string, cover_image: string | null, deadline: string, reward_amount: number, applicants_count: number, author: { __typename?: 'Author', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } | { __typename?: 'Question', id: number, title: string, createdAt: any, excerpt: string, votes_count: number, type: string, answers_count: number, author: { __typename?: 'Author', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }>, comments: Array<{ __typename?: 'PostComment', id: number, createdAt: any, body: string, author: { __typename?: 'Author', id: number, name: string, avatar: string, join_date: any } }> } | { __typename?: 'Story', id: number, title: string, createdAt: any, excerpt: string, votes_count: number, type: string, cover_image: string | null, comments_count: number, author: { __typename?: 'Author', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }> }> };
export type FeedQuery = { __typename?: 'Query', getFeed: Array<{ __typename?: 'Bounty', id: number, title: string, createdAt: any, excerpt: string, votes_count: number, type: string, cover_image: string | null, deadline: string, reward_amount: number, applicants_count: number, author: { __typename?: 'Author', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } | { __typename?: 'Question', id: number, title: string, createdAt: any, excerpt: string, votes_count: number, type: string, author: { __typename?: 'Author', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } | { __typename?: 'Story', id: number, title: string, createdAt: any, excerpt: string, votes_count: number, type: string, cover_image: string | null, author: { __typename?: 'Author', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }> }> };
export type PostDetailsQueryVariables = Exact<{
id: Scalars['Int'];
@@ -512,14 +508,14 @@ export type PostDetailsQueryVariables = Exact<{
}>;
export type PostDetailsQuery = { __typename?: 'Query', getPostById: { __typename?: 'Bounty', id: number, title: string, createdAt: any, body: string, votes_count: number, type: string, cover_image: string | null, deadline: string, reward_amount: number, applicants_count: number, author: { __typename?: 'Author', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }>, applications: Array<{ __typename?: 'BountyApplication', id: number, date: string, workplan: string, author: { __typename?: 'Author', id: number, name: string, avatar: string } }> } | { __typename?: 'Question', id: number, title: string, createdAt: any, body: string, votes_count: number, type: string, answers_count: number, author: { __typename?: 'Author', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }>, comments: Array<{ __typename?: 'PostComment', id: number, createdAt: any, body: string, votes_count: number, parentId: number | null, author: { __typename?: 'Author', id: number, name: string, avatar: string } }> } | { __typename?: 'Story', id: number, title: string, createdAt: any, body: string, votes_count: number, type: string, cover_image: string | null, is_published: boolean | null, comments_count: number, author: { __typename?: 'Author', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }>, comments: Array<{ __typename?: 'PostComment', id: number, createdAt: any, body: string, votes_count: number, parentId: number | null, author: { __typename?: 'Author', id: number, name: string, avatar: string } }> } };
export type PostDetailsQuery = { __typename?: 'Query', getPostById: { __typename?: 'Bounty', id: number, title: string, createdAt: any, body: string, votes_count: number, type: string, cover_image: string | null, deadline: string, reward_amount: number, applicants_count: number, author: { __typename?: 'Author', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }>, applications: Array<{ __typename?: 'BountyApplication', id: number, date: string, workplan: string, author: { __typename?: 'Author', id: number, name: string, avatar: string } }> } | { __typename?: 'Question', id: number, title: string, createdAt: any, body: string, votes_count: number, type: string, author: { __typename?: 'Author', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } | { __typename?: 'Story', id: number, title: string, createdAt: any, body: string, votes_count: number, type: string, cover_image: string | null, is_published: boolean | null, author: { __typename?: 'Author', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } };
export type ProfileQueryVariables = Exact<{
profileId: Scalars['Int'];
}>;
export type ProfileQuery = { __typename?: 'Query', profile: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any, role: string | null, email: string | null, jobTitle: string | null, lightning_address: string | null, website: string | null, twitter: string | null, github: string | null, linkedin: string | null, bio: string | null, location: string | null, stories: Array<{ __typename?: 'Story', id: number, title: string, createdAt: any, tags: Array<{ __typename?: 'Tag', title: string, icon: string | null, id: number }> }> } | null };
export type ProfileQuery = { __typename?: 'Query', profile: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any, role: string | null, email: string | null, jobTitle: string | null, lightning_address: string | null, website: string | null, twitter: string | null, github: string | null, linkedin: string | null, bio: string | null, location: string | null, stories: Array<{ __typename?: 'Story', id: number, title: string, createdAt: any, tags: Array<{ __typename?: 'Tag', id: number, title: string, icon: string | null }> }> } | null };
export type UpdateProfileAboutMutationVariables = Exact<{
data: InputMaybe<UpdateProfileInput>;
@@ -1005,7 +1001,6 @@ export const CreateStoryDocument = gql`
is_published
type
cover_image
comments_count
}
}
`;
@@ -1125,7 +1120,6 @@ export const FeedDocument = gql`
votes_count
type
cover_image
comments_count
}
... on Bounty {
id
@@ -1166,18 +1160,6 @@ export const FeedDocument = gql`
}
votes_count
type
answers_count
comments {
id
createdAt
body
author {
id
name
avatar
join_date
}
}
}
}
}
@@ -1235,19 +1217,6 @@ export const PostDetailsDocument = gql`
type
cover_image
is_published
comments_count
comments {
id
createdAt
body
votes_count
parentId
author {
id
name
avatar
}
}
}
... on Bounty {
id
@@ -1298,19 +1267,6 @@ export const PostDetailsDocument = gql`
}
votes_count
type
answers_count
comments {
id
createdAt
body
votes_count
parentId
author {
id
name
avatar
}
}
}
}
}
@@ -1366,9 +1322,9 @@ export const ProfileDocument = gql`
title
createdAt
tags {
id
title
icon
id
}
}
}

View File

@@ -15,7 +15,7 @@ const getAuthor = () => ({
join_date: getDate()
})
export const generatePostComments = (cnt: number = 1): Story['comments'] => {
export const generatePostComments = (cnt: number = 1) => {
let comments = [];
const rootCommentsIds: any[] = []
@@ -24,10 +24,12 @@ export const generatePostComments = (cnt: number = 1): Story['comments'] => {
const comment = {
id: i + 1,
body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nisi, at ut sit id. Vulputate aliquet aliquam penatibus ac, et dictum est etiam. Sagittis odio dui sed viverra donec rutrum iaculis vitae morbi.",
createdAt: getDate(),
created_at: Date.now(),
author: getAuthor(),
votes_count: 123,
parentId
parentId,
pubkey: '123',
replies: [],
}
comments.push(comment);
if (!parentId)
@@ -85,14 +87,17 @@ export let posts = {
title: 'Digital Editor, Mars Review of Books',
body: postBody,
cover_image: getCoverImage(),
comments_count: 3,
// comments_count: 3,
createdAt: getDate(),
updatedAt: getDate(),
votes_count: 120,
excerpt: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. In odio libero accumsan...',
type: "Story",
tags: randomItems(3, ...tags),
author: getAuthor(),
comments: generatePostComments(3),
// comments: generatePostComments(3),
is_published: true,
},
{
@@ -100,15 +105,16 @@ export let posts = {
title: 'The End Is Nigh',
body: postBody,
cover_image: getCoverImage(),
comments_count: 3,
// comments_count: 3,
createdAt: getDate(),
updatedAt: getDate(),
votes_count: 120,
excerpt: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. In odio libero accumsan...',
type: "Story",
tags: randomItems(3, ...tags),
author: getAuthor(),
comments: generatePostComments(3),
// comments: generatePostComments(3),
is_published: true,
},
] as Story[],
bounties: [
@@ -153,32 +159,36 @@ export let posts = {
id: 33,
title: 'Digital Editor, Mars Review of Books',
body: postBody,
answers_count: 3,
// answers_count: 3,
createdAt: getDate(),
updatedAt: getDate(),
votes_count: 70,
excerpt: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. In odio libero accumsan...',
tags: [
{ id: 1, title: "lnurl" },
{ id: 2, title: "webln" },
{ id: 1, title: "lnurl", description: '', isOfficial: false, icon: '' },
{ id: 2, title: "webln", description: '', isOfficial: false, icon: '' },
],
author: getAuthor(),
comments: generatePostComments(3)
// comments: generatePostComments(3),
is_published: true,
},
{
type: "Question",
id: 33,
title: 'What is a man but miserable pile of secrets?',
body: postBody,
answers_count: 3,
// answers_count: 3,
createdAt: getDate(),
updatedAt: getDate(),
votes_count: 70,
excerpt: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. In odio libero accumsan...',
tags: [
{ id: 1, title: "lnurl" },
{ id: 2, title: "webln" },
{ id: 1, title: "lnurl", description: '', isOfficial: false, icon: '' },
{ id: 2, title: "webln", description: '', isOfficial: false, icon: '' },
],
author: getAuthor(),
comments: generatePostComments(3)
// comments: generatePostComments(3),
is_published: true,
},
] as Question[]
}

View File

@@ -11,16 +11,16 @@ interface NostrEvent {
declare module 'nostr-tools' {
declare function generatePrivateKey(): void
declare function generatePrivateKey(): string
declare function relayConnect(): void
declare function relayPool(): any
declare function signEvent(event: NostrEvent, key: sting): string
declare function validateEvent(): void
declare function verifySignature(event: NostrEvent): bool
declare function serializeEvent(): void
declare function serializeEvent(): string
declare function getEventHash(event: NostrEvent): string
declare function getPublicKey(event: NostrEvent): boolean
declare function getBlankEvent(): void
declare function getPublicKey(prvKey: string): string
declare function getBlankEvent(): NostrEvent
declare function matchFilter(): void
declare function matchFilters(): void
}