diff --git a/apps/discord-bot/.gitignore b/apps/discord-bot/.gitignore index 11ddd8db..5802ddea 100644 --- a/apps/discord-bot/.gitignore +++ b/apps/discord-bot/.gitignore @@ -1,3 +1,6 @@ node_modules # Keep environment variables out of version control .env + +# log files +*.log diff --git a/apps/discord-bot/src/config/config.ts b/apps/discord-bot/src/config/config.ts index de823442..f7c20ff5 100644 --- a/apps/discord-bot/src/config/config.ts +++ b/apps/discord-bot/src/config/config.ts @@ -3,6 +3,7 @@ import {DIDSession} from "did-session"; export const config = { server: { port: process.env.AGGREGATOR_PORT || 4000, + apiKey: process.env.AGGREGATOR_API_KEY || "sample-api-key", }, compose: { nodeUrl: process.env.CERAMIC_NODE || "", diff --git a/apps/discord-bot/src/core/comments/handler.ts b/apps/discord-bot/src/core/comments/handler.ts index e11f0471..3e69cbe9 100644 --- a/apps/discord-bot/src/core/comments/handler.ts +++ b/apps/discord-bot/src/core/comments/handler.ts @@ -5,13 +5,12 @@ import {config, constants} from "../../config"; import {Resp} from "../utils/response"; import {Client} from "discord.js"; import {commentHandler as discordCommentHandler} from "../../bots/discord"; -import {composeQueryHandler} from "@devnode/composedb"; import {logger} from "../utils/logger"; import {communityHasSocial, getSocialCommunityId} from "../utils/data"; export const postComment = async (clients: Clients, req: Request, res: Response) => { const {commentId} = req.body; - const comment: Node = await composeQueryHandler().fetchCommentDetails(commentId); + const comment: Node = await clients.composeQuery().fetchCommentDetails(commentId); const socials = _.get(comment, "node.thread.community.socialPlatforms.edges"); if (communityHasSocial(socials, constants.PLATFORM_DISCORD_NAME)) { diff --git a/apps/discord-bot/src/core/middleware/auth.ts b/apps/discord-bot/src/core/middleware/auth.ts new file mode 100644 index 00000000..e0bbd698 --- /dev/null +++ b/apps/discord-bot/src/core/middleware/auth.ts @@ -0,0 +1,12 @@ +import {NextFunction, Request, Response} from "express"; +import {Resp} from "../utils/response"; + +export const apiKeyAuth = (apiKey: string) => { + return (req: Request, res: Response, next: NextFunction) => { + const apiKeyHeader = req.headers['x-api-key']; + if (apiKeyHeader !== apiKey) { + return Resp.unAuth(res, "unauthorized"); + } + next(); + }; +} diff --git a/apps/discord-bot/src/core/middleware/validator.ts b/apps/discord-bot/src/core/middleware/validator.ts index 809fbf95..9d21ba24 100644 --- a/apps/discord-bot/src/core/middleware/validator.ts +++ b/apps/discord-bot/src/core/middleware/validator.ts @@ -1,5 +1,6 @@ import joi from "joi"; import {NextFunction, Request, Response} from "express"; +import {Resp} from "../utils/response"; export const commentSchema = joi.object({ commentId: joi.string().required(), @@ -15,7 +16,7 @@ export const validator = (schema: joi.Schema) => { req.body = await schema.validateAsync(req.body); return next(); } catch (e) { - return res.status(400).json(e); + return Resp.notOk(res, "invalid payload"); } } } diff --git a/apps/discord-bot/src/core/server.ts b/apps/discord-bot/src/core/server.ts index 0cab7ea2..445ec04b 100644 --- a/apps/discord-bot/src/core/server.ts +++ b/apps/discord-bot/src/core/server.ts @@ -7,6 +7,7 @@ import {Client, GatewayIntentBits, Partials} from "discord.js"; import {commentSchema, threadSchema, validator} from "./middleware/validator"; import {Clients} from "./types"; import {logger} from "./utils/logger"; +import {apiKeyAuth} from "./middleware/auth"; process.on("uncaughtException", (error, origin) => { console.log(error); @@ -23,6 +24,7 @@ export const initServer = (clients: Clients): Express => { server.use(cors()); server.use(express.urlencoded({extended: true})); server.use(express.json()); + server.use(apiKeyAuth(config.server.apiKey)); const router = express.Router(); router.get("/ping", (_, res) => res.send("pong")); diff --git a/apps/discord-bot/src/core/threads/handler.ts b/apps/discord-bot/src/core/threads/handler.ts index 44085a18..036b9aa8 100644 --- a/apps/discord-bot/src/core/threads/handler.ts +++ b/apps/discord-bot/src/core/threads/handler.ts @@ -1,22 +1,27 @@ import {Request, Response} from "express"; -import {composeQueryHandler} from "@devnode/composedb"; import {Clients, Node, Thread} from "../types"; import _ from "lodash"; import {communityHasSocial, getSocialCommunityId} from "../utils/data"; import {config, constants} from "../../config"; import {Resp} from "../utils/response"; import {threadHandler as discordThreadHandler} from "../../bots/discord"; +import {logger} from "../utils/logger"; export const postThread = async (clients: Clients, req: Request, res: Response) => { - const {threadId} = req.body; - const thread: Node = await composeQueryHandler().fetchThreadDetails(threadId); - const socials = _.get(thread, "node.community.socialPlatforms.edges"); + try { + const {threadId} = req.body; + const thread: Node = await clients.composeQuery().fetchThreadDetails(threadId); + const socials = _.get(thread, "node.community.socialPlatforms.edges"); - if (communityHasSocial(socials, constants.PLATFORM_DISCORD_NAME)) { - const threadId = await postThreadToDiscord(clients, thread); - return Resp.okD(res, {threadId}, "Created thread on socials"); - } else { - return Resp.notOk(res, "No discord for this community, bailing out!"); + if (communityHasSocial(socials, constants.PLATFORM_DISCORD_NAME)) { + const threadId = await postThreadToDiscord(clients, thread); + return Resp.okD(res, {threadId}, "Created thread on socials"); + } else { + return Resp.notOk(res, "No discord for this community, bailing out!"); + } + } catch (e) { + logger.error('core', {e, body: req.body}); + return Resp.error(res, "Server error occurred"); } }; diff --git a/apps/discord-bot/src/core/types.ts b/apps/discord-bot/src/core/types.ts index 7fff4e95..757ea91c 100644 --- a/apps/discord-bot/src/core/types.ts +++ b/apps/discord-bot/src/core/types.ts @@ -1,8 +1,10 @@ import {ComposeClient} from "@composedb/client"; import {Client as DiscordClient} from "discord.js"; +import {composeQueryHandler} from "@devnode/composedb"; export type Clients = { compose: ComposeClient, + composeQuery: typeof composeQueryHandler, discord: DiscordClient, }; diff --git a/apps/discord-bot/src/core/utils/response.ts b/apps/discord-bot/src/core/utils/response.ts index 7ec95074..198e0f24 100644 --- a/apps/discord-bot/src/core/utils/response.ts +++ b/apps/discord-bot/src/core/utils/response.ts @@ -3,6 +3,7 @@ import {Response} from "express"; export const Resp = { ok: (res: Response, msg: string) => res.status(200).json({msg}).end(), okD: (res: Response, data: object, msg: string) => res.status(200).json({msg, data}).end(), - notOk: (res: Response, msg: string) => res.status(401).json({msg}).end(), + notOk: (res: Response, msg: string) => res.status(400).json({msg}).end(), + unAuth: (res: Response, msg: string) => res.status(401).json({msg}).end(), error: (res: Response, msg: string) => res.status(500).json({msg}).end(), } diff --git a/apps/discord-bot/src/index.ts b/apps/discord-bot/src/index.ts index 48672047..0242e484 100644 --- a/apps/discord-bot/src/index.ts +++ b/apps/discord-bot/src/index.ts @@ -1,7 +1,7 @@ import {initDiscord, initServer} from "./core"; import {config} from "./config"; import {ComposeClient} from "@composedb/client"; -import {definition} from "@devnode/composedb"; +import {composeQueryHandler, definition} from "@devnode/composedb"; import {attachListeners} from "./bots/discord"; import {Clients} from "./core/types"; @@ -15,6 +15,7 @@ const start = async () => { const clients: Clients = { discord: discordClient, compose: composeClient, + composeQuery: composeQueryHandler, }; const server = initServer(clients); diff --git a/apps/discord-bot/tests/data.ts b/apps/discord-bot/tests/data.ts new file mode 100644 index 00000000..0c5e1fe1 --- /dev/null +++ b/apps/discord-bot/tests/data.ts @@ -0,0 +1,141 @@ +export const sampleComment = { + "node": { + "id": "kjzl6kcym7w8y6384dl2gsx51b6vvn780h61qcwtil7klp5s5y3u3zo0uekc0oo", + "text": "I am aware of the fact that storing anything sensitive on the client side is a no-do. ", + "userId": "k2t6wzhkhabz2wixqd45q1wxpoy9fg7wd2dbfp2vzgq0wd2dq9sh5fkzywjxye", + "threadId": "kjzl6kcym7w8y772zht4omem25y3plfp929wsgumb158oi8uca2fnlhcdri2jqx", + "createdAt": "2023-03-16T09:59:02.979Z", + "createdFrom": "devnode", + "user": { + "id": "k2t6wzhkhabz2wixqd45q1wxpoy9fg7wd2dbfp2vzgq0wd2dq9sh5fkzywjxye", + "walletAddress": "0x7c98C2DEc5038f00A2cbe8b7A64089f9c0b51991", + "author": { + "id": "did:pkh:eip155:1:0x7c98C2DEc5038f00A2cbe8b7A64089f9c0b51991" + }, + "userPlatforms": [ + { + "platformId": "devnode", + "platformName": "devnode", + "platformAvatar": "https://avatars.githubusercontent.com/u/35269424", + "platformUsername": "atul" + }, + { + "platformId": "922429029544525866", + "platformName": "discord", + "platformAvatar": "https://cdn.discordapp.com/avatars/922429029544525866/5d55754141899036e54df7df8ae8e7c5.jpg", + "platformUsername": "atul#0555" + } + ], + "createdAt": "2023-03-16T06:56:22.443Z" + }, + "thread": { + "id": "kjzl6kcym7w8y772zht4omem25y3plfp929wsgumb158oi8uca2fnlhcdri2jqx", + "title": "Is storing secrets in redux store secure?", + "userId": "k2t6wzhkhabz2wixqd45q1wxpoy9fg7wd2dbfp2vzgq0wd2dq9sh5fkzywjxye", + "threadId": "na", + "createdAt": "2023-03-15T09:22:45.468Z", + "community": { + "socialPlatforms": { + "edges": [ + { + "node": { + "platformId": "785055113126871091", + "platform": "discord" + } + } + ] + } + }, + "communityId": "kjzl6kcym7w8y8dgfi093n3o6gby2gwq4fs3nltxjl8gutwvr2ol1sf9vpdmn09", + "createdFrom": "devnode", + "author": { + "id": "did:key:" + }, + "user": { + "id": "k2t6wzhkhabz2wixqd45q1wxpoy9fg7wd2dbfp2vzgq0wd2dq9sh5fkzywjxye", + "walletAddress": "0x7c98C2DEc5038f00A2cbe8b7A64089f9c0b51991", + "author": { + "id": "did:pkh:eip155:1:0x7c98C2DEc5038f00A2cbe8b7A64089f9c0b51991" + }, + "userPlatforms": [ + { + "platformId": "devnode", + "platformName": "devnode", + "platformAvatar": "https://avatars.githubusercontent.com/u/35269424", + "platformUsername": "atul" + }, + { + "platformId": "922429029544525866", + "platformName": "discord", + "platformAvatar": "https://cdn.discordapp.com/avatars/922429029544525866/5d55754141899036e54df7df8ae8e7c5.jpg", + "platformUsername": "atul#0555" + } + ], + "createdAt": "2023-03-16T06:56:22.443Z" + } + }, + "author": { + "id": "did:pkh:eip155:1:0x7c98C2DEc5038f00A2cbe8b7A64089f9c0b51991" + } + } +}; + +export const sampleThread = { + "node": { + "id": "kjzl6kcym7w8y772zht4omem25y3plfp929wsgumb158oi8uca2fnlhcdri2jqx", + "title": "Is storing secrets in redux store secure?", + "body": "From the docs, I understand that Redux store variables in memory so it should be readable to the browser, but is it secure enough to store sensitive information?", + "userId": "k2t6wzhkhabz2wixqd45q1wxpoy9fg7wd2dbfp2vzgq0wd2dq9sh5fkzywjxye", + "threadId": "na", + "createdAt": "2023-03-15T09:22:45.468Z", + "communityId": "kjzl6kcym7w8y8dgfi093n3o6gby2gwq4fs3nltxjl8gutwvr2ol1sf9vpdmn09", + "createdFrom": "devnode", + "author": { + "id": "did:key:z" + }, + "user": { + "id": "k2t6wzhkhabz2wixqd45q1wxpoy9fg7wd2dbfp2vzgq0wd2dq9sh5fkzywjxye", + "walletAddress": "0x7c98C2DEc5038f00A2cbe8b7A64089f9c0b51991", + "author": { + "id": "did:pkh:eip155:1:0x7c98C2DEc5038f00A2cbe8b7A64089f9c0b51991" + }, + "userPlatforms": [ + { + "platformId": "devnode", + "platformName": "devnode", + "platformAvatar": "https://avatars.githubusercontent.com/u/35269424", + "platformUsername": "atul" + }, + { + "platformId": "922429029544525866", + "platformName": "discord", + "platformAvatar": "https://cdn.discordapp.com/avatars/922429029544525866/5d55754141899036e54df7df8ae8e7c5.jpg", + "platformUsername": "atul#0555" + } + ], + "createdAt": "2023-03-16T06:56:22.443Z" + }, + "community": { + "id": "kjzl6kcym7w8y8dgfi093n3o6gby2gwq4fs3nltxjl8gutwvr2ol1sf9vpdmn09", + "createdAt": "2023-03-15T11:55:36.358Z", + "communityName": "rushiserver", + "socialPlatforms": { + "edges": [ + { + "node": { + "platformId": "785055113126871091", + "platform": "discord" + } + } + ] + }, + "author": { + "id": "did:pkh:eip155:1:0x8b2a6a22ec055225C4c4b5815e7d9F566b8be68F" + } + }, + "comments": { + "edges": [sampleComment] + } + } +}; + diff --git a/apps/discord-bot/tests/functional/comments.spec.ts b/apps/discord-bot/tests/functional/comments.spec.ts index 91d80791..38054656 100644 --- a/apps/discord-bot/tests/functional/comments.spec.ts +++ b/apps/discord-bot/tests/functional/comments.spec.ts @@ -1,16 +1,36 @@ import chai, {expect} from "../setup"; import {initServer} from "../../src/core"; import {Express} from "express"; +import {fakeComposeClient, fakeComposeQueryClient, fakeDiscordClient} from "../mock/fakes"; +import {config} from "../../src/config"; describe("comment api", () => { let server: Express; const url = "/api/web-comment"; - before(() => server = initServer({} as any)); + const header = {'x-api-key': config.server.apiKey}; - it("should validate the schema for request", async () => { + before(() => { + server = initServer({ + discord: fakeDiscordClient, + compose: fakeComposeClient, + composeQuery: fakeComposeQueryClient, + }); + }); + + it("should authenticate the api call", async () => { const res = await chai.request(server).post(url); + expect(res.status).to.eql(401); + }); + + it("should validate the schema for request", async () => { + const res = await chai.request(server).post(url).set(header); expect(res.status).to.eql(400); - const another = await chai.request(server).post(url).send({commentId: 123}); + const another = await chai.request(server).post(url).set(header).send({commentId: 123}); expect(another.status).to.eql(400); }); + + it("should response with 200 on sent message", async () => { + const res = await chai.request(server).post(url).set(header).send({commentId: "123"}); + expect(res.status).to.eql(200); + }); }); diff --git a/apps/discord-bot/tests/functional/ping.spec.ts b/apps/discord-bot/tests/functional/ping.spec.ts index 067f7e48..8020e27b 100644 --- a/apps/discord-bot/tests/functional/ping.spec.ts +++ b/apps/discord-bot/tests/functional/ping.spec.ts @@ -1,12 +1,21 @@ import {Express} from "express"; import {initServer} from "../../src/core"; import chai, {expect} from "../setup"; +import {config} from "../../src/config"; describe("ping api", () => { let server: Express; + const header = {'x-api-key': config.server.apiKey}; + before(() => server = initServer({} as any)); - it("should respond with pong on call", async () => { + + it("should authenticate the call", async () => { const res = await chai.request(server).get("/api/ping"); + expect(res).to.have.status(401); + }); + + it("should respond with pong on call", async () => { + const res = await chai.request(server).get("/api/ping").set(header); expect(res).to.have.status(200); expect(res.text).to.eql("pong"); }); diff --git a/apps/discord-bot/tests/functional/threads.spec.ts b/apps/discord-bot/tests/functional/threads.spec.ts index cb1fc767..767e8be8 100644 --- a/apps/discord-bot/tests/functional/threads.spec.ts +++ b/apps/discord-bot/tests/functional/threads.spec.ts @@ -1,16 +1,31 @@ import chai, {expect} from "../setup"; import {initServer} from "../../src/core"; import {Express} from "express"; +import {fakeComposeClient, fakeComposeQueryClient, fakeDiscordClient} from "../mock/fakes"; +import {config} from "../../src/config"; describe("thread api", () => { let server: Express; const url = "/api/web-thread"; - before(() => server = initServer({} as any)); + const header = {'x-api-key': config.server.apiKey}; - it("should validate the schema for request", async () => { + before(() => { + server = initServer({ + discord: fakeDiscordClient, + compose: fakeComposeClient, + composeQuery: fakeComposeQueryClient, + }); + }); + + it("should authenticate the api call", async () => { const res = await chai.request(server).post(url); + expect(res.status).to.eql(401); + }); + + it("should validate the schema for request", async () => { + const res = await chai.request(server).post(url).set(header); expect(res.status).to.eql(400); - const another = await chai.request(server).post(url).send({threadId: 123}); + const another = await chai.request(server).post(url).set(header).send({threadId: 123}); expect(another.status).to.eql(400); }); }); diff --git a/apps/discord-bot/tests/mock/fakes.ts b/apps/discord-bot/tests/mock/fakes.ts new file mode 100644 index 00000000..f999a649 --- /dev/null +++ b/apps/discord-bot/tests/mock/fakes.ts @@ -0,0 +1,26 @@ +import {Client} from "discord.js"; +import {ComposeClient} from "@composedb/client"; +import {sampleComment, sampleThread} from "../data"; +import * as sinon from 'sinon'; + +export const fakeDiscordClient = { + guilds: { + cache: { + get: () => this, + }, + channels: { + cache: [], + create: sinon.stub().returns( + new Promise((res) => res({ + id: 1, + send: sinon.stub(), + })) + ), + } + } +} as unknown as Client; +export const fakeComposeClient = {} as ComposeClient; +export const fakeComposeQueryClient = () => ({ + fetchCommentDetails: async (id: string) => sampleComment, + fetchThreadDetails: async (id: string) => sampleThread, +}) as any; diff --git a/apps/discord-bot/tests/unit/utils/data.spec.ts b/apps/discord-bot/tests/unit/utils/data.spec.ts index febaf301..f2c90238 100644 --- a/apps/discord-bot/tests/unit/utils/data.spec.ts +++ b/apps/discord-bot/tests/unit/utils/data.spec.ts @@ -2,7 +2,7 @@ import {SocialPlatform, Node} from "../../../src/core/types"; import {expect} from "../../setup"; import {communityHasSocial, getSocialCommunityId} from "../../../src/core/utils/data"; -describe('socialFunctions', () => { +describe('utils.data', () => { const socialPlatforms: Node[] = [ { node: { diff --git a/apps/discord-bot/tests/unit/utils/response.spec.ts b/apps/discord-bot/tests/unit/utils/response.spec.ts index 5ff0f810..a7e48020 100644 --- a/apps/discord-bot/tests/unit/utils/response.spec.ts +++ b/apps/discord-bot/tests/unit/utils/response.spec.ts @@ -23,10 +23,19 @@ describe('Resp', () => { expect(res.end).to.have.been.calledOnce; }); - it('should send a 401 response with the given message', () => { + it('should send a 400 response with the given message', () => { const message = 'test message'; Resp.notOk(res, message); + expect(res.status).to.have.been.calledWith(400); + expect(res.json).to.have.been.calledWith({msg: message}); + expect(res.end).to.have.been.calledOnce; + }); + + it('should send a 401 response with the given message', () => { + const message = 'test message'; + Resp.unAuth(res, message); + expect(res.status).to.have.been.calledWith(401); expect(res.json).to.have.been.calledWith({msg: message}); expect(res.end).to.have.been.calledOnce; diff --git a/apps/web/src/components/Thread/CreateThread.tsx b/apps/web/src/components/Thread/CreateThread.tsx index 142a0831..085a40eb 100644 --- a/apps/web/src/components/Thread/CreateThread.tsx +++ b/apps/web/src/components/Thread/CreateThread.tsx @@ -1,14 +1,13 @@ -import { useState } from "react"; -import { SecondaryButton } from "../Button/SecondaryButton"; +import {useState} from "react"; +import {SecondaryButton} from "../Button/SecondaryButton"; import Question from "../Modal/Question/Question"; import * as utils from "../../utils"; -import { get, has, isEmpty } from "lodash"; -import { toast } from "react-toastify"; -import { trpc } from "../../utils/trpc"; -import { constants } from "../../config"; -import { isRight } from "../../utils/fp"; -import { config } from "../../config"; -import { CreateThreadProps } from "./type"; +import {has, isEmpty} from "lodash"; +import {toast} from "react-toastify"; +import {trpc} from "../../utils/trpc"; +import {config, constants} from "../../config"; +import {isRight} from "../../utils/fp"; +import {CreateThreadProps} from "./type"; const CreateThread = (props: CreateThreadProps) => { const [question, setQuestion] = useState(""); @@ -17,14 +16,12 @@ const CreateThread = (props: CreateThreadProps) => { const [questionError, setQuestionError] = useState(false); const [descriptionError, setDescriptionError] = useState(false); - const { user, community, did, didSession, refetch } = props; + const {user, community, did, didSession, refetch} = props; const userName = user.userPlatforms && user.userPlatforms[0].platformUsername; const communityName = community.communityName; const communityId = community.selectedCommunity; const createThread = trpc.thread.createThread.useMutation(); - const updateThreadWithSocilaId = - trpc.thread.updateThreadWithSocialId.useMutation(); const handleQuestionInput = (e) => { questionError && !isEmpty(e.target.value.trim()) @@ -79,47 +76,16 @@ const CreateThread = (props: CreateThreadProps) => { createdFrom: constants.CREATED_FROM_DEVNODE, createdAt: new Date().toISOString(), }) - if (isRight(result)) { - const threadId = get(result, "value.createThread.document.id"); - await handleWebToAggregator(threadId) - .then(async (response) => { - const data = await response.json(); - const result = await updateThreadWithSocilaId.mutateAsync({ - session: didSession, - streamId: threadId, - threadId: get(data,"data.threadId"), - }); - - if (isRight(result)) { - setQuestion(""); - setDescription(""); - toast.success("Thread created successfully!"); - refetch(); - } else { - toast.error("Failed to create thread. Try again in a while!"); - } - }) - .catch(() => { - toast.error("Failed to create thread. Try again in a while!"); - }).finally(() => setCreatingThread(false)); + setQuestion(""); + setDescription(""); + toast.success("Thread created successfully!"); + refetch(); } else { toast.error("Failed to create thread. Try again in a while!"); } - }; - - const handleWebToAggregator = async (threadId: string) => { - const endpoint = `${config.aggregator.endpoint}/web-thread`; - return await fetch(endpoint, { - body: JSON.stringify({ - threadId: threadId, - }), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - }); + setCreatingThread(false) }; return ( @@ -146,7 +112,8 @@ const CreateThread = (props: CreateThreadProps) => { onChange={handleDescriptionInput} required /> -
+
User name diff --git a/apps/web/src/config/config.ts b/apps/web/src/config/config.ts index f88ba9c9..a0f5cd47 100644 --- a/apps/web/src/config/config.ts +++ b/apps/web/src/config/config.ts @@ -14,6 +14,7 @@ export const config = { }, aggregator: { endpoint: process.env.NEXT_PUBLIC_DISCORD_BOT_URL || "http://localhost:4000/api", + apiKey: process.env.AGGREGATOR_API_KEY || "sample-api-key", }, ceramic: { nodeUrl: process.env.CERAMIC_NODE || "", diff --git a/apps/web/src/pages/[id].tsx b/apps/web/src/pages/[id].tsx index 44d0d9b8..dbb19d95 100644 --- a/apps/web/src/pages/[id].tsx +++ b/apps/web/src/pages/[id].tsx @@ -13,7 +13,6 @@ import {toast} from "react-toastify"; import {constants} from "../config"; import {isRight} from "../utils/fp"; import {DIDSession} from "did-session"; -import {config} from "../config"; import { useAppSelector } from "../store"; const QuestionPage = () => { @@ -83,30 +82,14 @@ const QuestionPage = () => { }).finally(() => setIsCommenting(false)); if (isRight(result)) { - const commentId = get(result, "value.createComment.document.id"); setComment(""); toast.success("Comment posted successfully!"); await currentThread.refetch(); - handleWebToAggregator(commentId).catch(console.log); } else { toast.error("Failed to post message. Try again in a while!"); } } - const handleWebToAggregator = async (commentId: string) => { - const endpoint = `${config.aggregator.endpoint}/web-comment`; - await fetch(endpoint, { - body: JSON.stringify({ - commentId: commentId, - }), - method: "POST", - headers: { - "Content-Type": "application/json", - }, - } - ); - } - return ( {}} diff --git a/apps/web/src/server/trpc/router/comment.ts b/apps/web/src/server/trpc/router/comment.ts index 03d4b3c7..6a590c03 100644 --- a/apps/web/src/server/trpc/router/comment.ts +++ b/apps/web/src/server/trpc/router/comment.ts @@ -4,7 +4,7 @@ import {composeMutationHandler, definition} from "@devnode/composedb"; import {ComposeClient} from "@composedb/client"; import {config} from "../../../config"; import {left, right} from "../../../utils/fp"; -import {omit} from "lodash"; +import {omit, get} from "lodash"; import {DIDSession} from "did-session"; export const compose = new ComposeClient({ @@ -35,6 +35,10 @@ export const commentRouter = router({ const handler = await getHandler(input.session); const payload = omit(input, ["session"]); const response = await handler.createComment(payload as any); + if(response.data) { + const commentId = get(response.data, "createComment.document.id"); + handleWebToAggregator(commentId); + } return (response.errors && response.errors.length > 0) ? left(response.errors) : right(response.data); @@ -43,3 +47,18 @@ export const commentRouter = router({ } }), }); + +const handleWebToAggregator = async (commentId: string) => { + const endpoint = `${config.aggregator.endpoint}/web-comment`; + await fetch(endpoint, { + body: JSON.stringify({ + commentId: commentId, + }), + method: "POST", + headers: { + "Content-Type": "application/json", + "x-api-key": config.aggregator.apiKey, + }, + } + ); +} diff --git a/apps/web/src/server/trpc/router/thread.ts b/apps/web/src/server/trpc/router/thread.ts index de1dda76..32b07b3e 100644 --- a/apps/web/src/server/trpc/router/thread.ts +++ b/apps/web/src/server/trpc/router/thread.ts @@ -7,8 +7,8 @@ import { } from "@devnode/composedb"; import { ComposeClient } from "@composedb/client"; import { config } from "../../../config"; -import { left, right } from "../../../utils/fp"; -import { omit } from "lodash"; +import {isRight, left, right} from "../../../utils/fp"; +import {get, has, omit} from "lodash"; import { DIDSession } from "did-session"; export const compose = new ComposeClient({ @@ -47,9 +47,23 @@ export const threadRouter = router({ const handler = await getHandler(input.session); const payload = omit(input, ["session"]); const response = await handler.createThread(payload as ThreadInput); - return response.errors && response.errors.length > 0 - ? left(response.errors) - : right(response.data); + if(response.data) { + const threadId = get(response.data, "createThread.document.id"); + const apiResponse = await handleWebToAggregator(threadId); + const data = await apiResponse.json(); + if (has(data, "data.threadId")) { + const updated = await updateThread(handler, threadId, data.data.threadId); + if (isRight(updated)) { + return right({createThread: response.data, updateThread: updated.value}); + } else { + return updated; + } + } else { + return left(data); + } + } else { + return left(response.errors); + } } catch (e) { return left(e); } @@ -59,7 +73,6 @@ export const threadRouter = router({ .mutation(async ({ input }) => { try { const handler = await getHandler(input.session); - const payload = omit(input, ["session"]); const response = await handler.updateThreadWithSocialThreadId( input.streamId, input.threadId @@ -72,3 +85,31 @@ export const threadRouter = router({ } }), }); + +const updateThread = async (handler, streamId, threadId) => { + try { + const response = await handler.updateThreadWithSocialThreadId( + streamId, + threadId + ); + return response.errors && response.errors.length > 0 + ? left(response.errors) + : right(response.data); + } catch (e) { + return left(e); + } +} + +const handleWebToAggregator = async (threadId: string) => { + const endpoint = `${config.aggregator.endpoint}/web-thread`; + return await fetch(endpoint, { + body: JSON.stringify({ + threadId: threadId, + }), + method: "POST", + headers: { + "Content-Type": "application/json", + "x-api-key": config.aggregator.apiKey, + }, + }); +};