From 95aaf480af4cd67f3786ca393c3508dacc194902 Mon Sep 17 00:00:00 2001 From: Hakan Shehu Date: Thu, 8 Aug 2024 12:25:02 +0200 Subject: [PATCH] Refactor transactions and use kafka --- desktop/src/components/accounts/login.tsx | 10 +- .../src/electron/database/workspace/index.ts | 117 +++++++----- desktop/src/electron/event-loop.ts | 6 +- desktop/src/types/nodes.ts | 6 +- desktop/src/types/transactions.ts | 33 +++- server/package-lock.json | 10 + server/package.json | 1 + server/src/api.ts | 26 +++ server/src/consumers/transactions.ts | 175 ++++++++++++++++++ server/src/data/kafka.ts | 38 ++++ server/src/data/{db.ts => prisma.ts} | 0 server/src/index.ts | 27 +-- server/src/routes/accounts.ts | 2 +- server/src/routes/transactions.ts | 95 ++-------- server/src/routes/workspaces.ts | 8 +- server/src/types/nodes.ts | 2 +- server/src/types/transactions.ts | 33 +++- 17 files changed, 423 insertions(+), 166 deletions(-) create mode 100644 server/src/api.ts create mode 100644 server/src/consumers/transactions.ts create mode 100644 server/src/data/kafka.ts rename server/src/data/{db.ts => prisma.ts} (100%) diff --git a/desktop/src/components/accounts/login.tsx b/desktop/src/components/accounts/login.tsx index 5fb9d1df..5f697ecd 100644 --- a/desktop/src/components/accounts/login.tsx +++ b/desktop/src/components/accounts/login.tsx @@ -2,26 +2,24 @@ import React from 'react'; import { EmailLogin } from '@/components/accounts/email-login'; import { LoginOutput } from '@/types/accounts'; import { EmailRegister } from '@/components/accounts/email-register'; -import { useStore } from "@/contexts/store"; -import { observer } from "mobx-react-lite"; +import { observer } from 'mobx-react-lite'; const serverUrl = 'http://localhost:3000'; export const Login = observer(() => { - const store = useStore(); const [showRegister, setShowRegister] = React.useState(false); const handleLogin = async (output: LoginOutput) => { - store.addAccount(output.account); await window.globalDb.addAccount(output.account); if (output.workspaces.length > 0) { for (const workspace of output.workspaces) { - store.addWorkspace(workspace); await window.globalDb.addWorkspace(workspace); } } - } + + window.location.href = '/'; + }; return (
diff --git a/desktop/src/electron/database/workspace/index.ts b/desktop/src/electron/database/workspace/index.ts index ad65faae..78b86a77 100644 --- a/desktop/src/electron/database/workspace/index.ts +++ b/desktop/src/electron/database/workspace/index.ts @@ -10,6 +10,10 @@ import { CreateNodeInput, Node, UpdateNodeInput } from '@/types/nodes'; import { NeuronId } from '@/lib/id'; import { LeafNodeTypes, NodeTypes, RootNodeTypes } from '@/lib/constants'; import { eventBus } from '@/lib/event-bus'; +import { + CreateNodeTransactionInput, + UpdateNodeTransactionInput, +} from '@/types/transactions'; export class WorkspaceDatabase { accountId: string; @@ -76,33 +80,18 @@ export class WorkspaceDatabase { .returningAll() .executeTakeFirst(); - const node: Node = { - id: insertedRow.id, - type: insertedRow.type, - index: insertedRow.index, - parentId: insertedRow.parent_id, - workspaceId: insertedRow.workspace_id, - attrs: insertedRow.attrs && JSON.parse(insertedRow.attrs), - content: insertedRow.content && JSON.parse(insertedRow.content), - createdAt: new Date(insertedRow.created_at), - createdBy: insertedRow.created_by, - updatedAt: insertedRow.updated_at - ? new Date(insertedRow.updated_at) - : null, - updatedBy: insertedRow.updated_by, - versionId: insertedRow.version_id, - }; - + const transactionInput = this.mapToCreateNodeTransactionInput(insertedRow); await this.globalDatabase.addTransaction({ id: NeuronId.generate(NeuronId.Type.Transaction), type: 'create_node', workspaceId: this.workspaceId, userId: this.userId, accountId: this.accountId, - input: JSON.stringify(node), + input: JSON.stringify(transactionInput), createdAt: new Date(), }); + const node = this.mapToNode(insertedRow); eventBus.publish({ event: 'node_created', payload: node, @@ -129,32 +118,21 @@ export class WorkspaceDatabase { .returningAll() .execute(); - const nodes = insertedRows.map((node) => ({ - id: node.id, - type: node.type, - index: node.index, - parentId: node.parent_id, - workspaceId: node.workspace_id, - attrs: node.attrs && JSON.parse(node.attrs), - content: node.content && JSON.parse(node.content), - createdAt: new Date(node.created_at), - createdBy: node.created_by, - updatedAt: node.updated_at ? new Date(node.updated_at) : null, - updatedBy: node.updated_by, - versionId: node.version_id, - })); - + const transactionInputs = insertedRows.map((node) => + this.mapToCreateNodeTransactionInput(node), + ); await this.globalDatabase.addTransaction({ id: NeuronId.generate(NeuronId.Type.Transaction), type: 'create_nodes', workspaceId: this.workspaceId, userId: this.userId, accountId: this.accountId, - input: JSON.stringify(nodes), + input: JSON.stringify(transactionInputs), createdAt: new Date(), }); - nodes.forEach((node) => { + insertedRows.forEach((row) => { + const node = this.mapToNode(row); eventBus.publish({ event: 'node_created', payload: node, @@ -163,6 +141,16 @@ export class WorkspaceDatabase { }; updateNode = async (input: UpdateNodeInput) => { + const row = await this.database + .selectFrom('nodes') + .selectAll() + .where('id', '=', input.id) + .executeTakeFirst(); + + if (!row) { + return; + } + let updateDefinition = this.database.updateTable('nodes').set({ updated_at: new Date().toISOString(), updated_by: this.userId, @@ -170,17 +158,22 @@ export class WorkspaceDatabase { }); if (input.attrs !== undefined) { + const existingAttrs = row.attrs && JSON.parse(row.attrs); + const updatedAttrs = { + ...existingAttrs, + ...input.attrs, + }; + updateDefinition = updateDefinition.set( 'attrs', - JSON.stringify(input.attrs), + JSON.stringify(updatedAttrs), ); } if (input.content !== undefined) { - updateDefinition = updateDefinition.set( - 'content', - JSON.stringify(input.content), - ); + const content = + input.content === null ? null : JSON.stringify(input.content); + updateDefinition = updateDefinition.set('content', content); } if (input.index !== undefined) { @@ -196,18 +189,18 @@ export class WorkspaceDatabase { .returningAll() .executeTakeFirst(); - const node: Node = this.mapToNode(updatedRow); - + const transactionInput = this.mapToUpdateNodeTransactionInput(updatedRow); await this.globalDatabase.addTransaction({ id: NeuronId.generate(NeuronId.Type.Transaction), type: 'update_node', - workspaceId: node.workspaceId, + workspaceId: updatedRow.workspace_id, accountId: this.accountId, userId: this.userId, - input: JSON.stringify(input), + input: JSON.stringify(transactionInput), createdAt: new Date(), }); + const node: Node = this.mapToNode(updatedRow); eventBus.publish({ event: 'node_updated', payload: node, @@ -221,6 +214,10 @@ export class WorkspaceDatabase { .returningAll() .executeTakeFirst(); + if (!deletedRow) { + return; + } + await this.globalDatabase.addTransaction({ id: NeuronId.generate(NeuronId.Type.Transaction), type: 'delete_node', @@ -488,4 +485,36 @@ export class WorkspaceDatabase { versionId: node.version_id, }; }; + + mapToCreateNodeTransactionInput = ( + node: NodesTableSchema, + ): CreateNodeTransactionInput => { + return { + id: node.id, + workspaceId: node.workspace_id, + parentId: node.parent_id, + type: node.type, + index: node.index, + attrs: node.attrs && JSON.parse(node.attrs), + content: node.content && JSON.parse(node.content), + createdAt: new Date(node.created_at), + createdBy: node.created_by, + versionId: node.version_id, + }; + }; + + mapToUpdateNodeTransactionInput = ( + node: NodesTableSchema, + ): UpdateNodeTransactionInput => { + return { + id: node.id, + parentId: node.parent_id, + index: node.index, + attrs: node.attrs && JSON.parse(node.attrs), + content: node.content && JSON.parse(node.content), + updatedAt: new Date(node.updated_at), + updatedBy: node.updated_by, + versionId: node.version_id, + }; + }; } diff --git a/desktop/src/electron/event-loop.ts b/desktop/src/electron/event-loop.ts index f29cf3fa..0d4847b1 100644 --- a/desktop/src/electron/event-loop.ts +++ b/desktop/src/electron/event-loop.ts @@ -5,9 +5,9 @@ import { Node } from '@/types/nodes'; export const initEventLoop = () => { setInterval(async () => { - // const accounts = await globalDatabase.getAccounts(); - // await sendTransactions(accounts); - // await syncWorkspaces(accounts); + const accounts = await globalDatabase.getAccounts(); + await sendTransactions(accounts); + await syncWorkspaces(accounts); }, 10000); }; diff --git a/desktop/src/types/nodes.ts b/desktop/src/types/nodes.ts index d6bc29f1..0c5bb2db 100644 --- a/desktop/src/types/nodes.ts +++ b/desktop/src/types/nodes.ts @@ -4,7 +4,7 @@ export type Node = { parentId?: string | null; type: string; index?: string | null; - attrs: any; + attrs?: Record | null; content?: NodeBlock[] | null; createdAt: Date; createdBy: string; @@ -34,7 +34,7 @@ export type CreateNodeInput = { type: string; parentId?: string | null; index?: string | null; - attrs: any; + attrs?: Record | null; content?: NodeBlock[] | null; }; @@ -42,6 +42,6 @@ export type UpdateNodeInput = { id: string; parentId?: string | null; index?: string | null; - attrs?: any | null; + attrs?: Record | null; content?: NodeBlock[] | null; }; diff --git a/desktop/src/types/transactions.ts b/desktop/src/types/transactions.ts index 21a444dd..80264a46 100644 --- a/desktop/src/types/transactions.ts +++ b/desktop/src/types/transactions.ts @@ -1,4 +1,11 @@ -export type TransactionType = 'create_node' | 'create_nodes' | 'update_node' | 'delete_node' | 'delete_nodes'; +import { NodeBlock } from '@/types/nodes'; + +export type TransactionType = + | 'create_node' + | 'create_nodes' + | 'update_node' + | 'delete_node' + | 'delete_nodes'; export type Transaction = { id: string; @@ -9,3 +16,27 @@ export type Transaction = { input: string; createdAt: Date; }; + +export type CreateNodeTransactionInput = { + id: string; + workspaceId: string; + parentId?: string | null; + type: string; + index?: string | null; + attrs?: Record | null; + content?: NodeBlock[] | null; + createdAt: Date; + createdBy: string; + versionId: string; +}; + +export type UpdateNodeTransactionInput = { + id: string; + parentId?: string | null; + index?: string | null; + attrs?: Record | null; + content?: NodeBlock[] | null; + updatedAt: Date; + updatedBy: string; + versionId: string; +}; diff --git a/server/package-lock.json b/server/package-lock.json index c5e3aa3a..3c35bb55 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -16,6 +16,7 @@ "cors": "^2.8.5", "express": "^4.19.2", "jsonwebtoken": "^9.0.2", + "kafkajs": "^2.2.4", "postgres": "^3.4.4", "ulid": "^2.3.0" }, @@ -1599,6 +1600,15 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/kafkajs": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/kafkajs/-/kafkajs-2.2.4.tgz", + "integrity": "sha512-j/YeapB1vfPT2iOIUn/vxdyKEuhuY2PxMBvf5JWux6iSaukAccrMtXEY/Lb7OvavDhOWME589bpLrEdnVHjfjA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", diff --git a/server/package.json b/server/package.json index 4c7e9caa..08ce725f 100644 --- a/server/package.json +++ b/server/package.json @@ -33,6 +33,7 @@ "cors": "^2.8.5", "express": "^4.19.2", "jsonwebtoken": "^9.0.2", + "kafkajs": "^2.2.4", "postgres": "^3.4.4", "ulid": "^2.3.0" } diff --git a/server/src/api.ts b/server/src/api.ts new file mode 100644 index 00000000..8192ea9a --- /dev/null +++ b/server/src/api.ts @@ -0,0 +1,26 @@ +import express, { Request, Response } from 'express'; +import cors from 'cors'; +import { accountsRouter } from '@/routes/accounts'; +import { workspacesRouter } from '@/routes/workspaces'; +import { transactionsRouter } from '@/routes/transactions'; +import { authMiddleware } from '@/middlewares/auth'; + +export const initApi = () => { + const app = express(); + const port = 3000; + + app.use(express.json()); + app.use(cors()); + + app.get('/', (req: Request, res: Response) => { + res.send('Neuron'); + }); + + app.use('/v1/accounts', accountsRouter); + app.use('/v1/workspaces', authMiddleware, workspacesRouter); + app.use('/v1/transactions', authMiddleware, transactionsRouter); + + app.listen(port, () => { + console.log(`Server is running at http://localhost:${port}`); + }); +}; diff --git a/server/src/consumers/transactions.ts b/server/src/consumers/transactions.ts new file mode 100644 index 00000000..3b0cdd16 --- /dev/null +++ b/server/src/consumers/transactions.ts @@ -0,0 +1,175 @@ +import { kafka, TOPIC_NAMES, KAFKA_CONSUMER_GROUP } from '@/data/kafka'; +import { + CreateNodeTransactionInput, + Transaction, + UpdateNodeTransactionInput, +} from '@/types/transactions'; +import { prisma } from '@/data/prisma'; +import { Prisma } from '@prisma/client'; + +export const initTransactionsConsumer = async () => { + const consumer = kafka.consumer({ groupId: KAFKA_CONSUMER_GROUP }); + await consumer.connect(); + + await consumer.subscribe({ topic: TOPIC_NAMES.TRANSACTIONS }); + + await consumer.run({ + eachMessage: async ({ message }) => { + if (message.value === null) return; + + const transaction: Transaction = JSON.parse(message.value.toString()); + await handleTransaction(transaction); + }, + }); +}; + +const handleTransaction = async (transaction: Transaction) => { + switch (transaction.type) { + case 'create_node': + await handleCreateNodeTransaction(transaction); + break; + case 'create_nodes': + await handleCreateNodesTransaction(transaction); + break; + case 'update_node': + await handleUpdateNodeTransaction(transaction); + break; + case 'delete_node': + await handleDeleteNodeTransaction(transaction); + break; + case 'delete_nodes': + await handleDeleteNodesTransaction(transaction); + break; + } +}; + +const handleCreateNodeTransaction = async (transaction: Transaction) => { + const input = JSON.parse(transaction.input) as CreateNodeTransactionInput; + + const data: Prisma.nodesUncheckedCreateInput = { + id: input.id, + parentId: input.parentId, + workspaceId: input.workspaceId, + type: input.type, + index: input.index, + createdAt: input.createdAt, + createdBy: input.createdBy, + versionId: input.versionId, + }; + + if (input.attrs !== undefined && input.attrs !== null) { + data.attrs = input.attrs; + } + + if (input.content !== undefined && input.content !== null) { + data.content = input.content; + } + + await prisma.nodes.create({ + data: data, + }); +}; + +const handleCreateNodesTransaction = async (transaction: Transaction) => { + const nodes = JSON.parse(transaction.input) as CreateNodeTransactionInput[]; + const data: Prisma.nodesUncheckedCreateInput[] = nodes.map((node) => { + const data: Prisma.nodesUncheckedCreateInput = { + id: node.id, + parentId: node.parentId, + workspaceId: node.workspaceId, + type: node.type, + index: node.index, + createdAt: node.createdAt, + createdBy: node.createdBy, + versionId: node.versionId, + }; + + if (node.attrs !== undefined && node.attrs !== null) { + data.attrs = node.attrs; + } + + if (node.content !== undefined && node.content !== null) { + data.content = node.content; + } + + return data; + }); + + await prisma.nodes.createMany({ + data: data, + }); +}; + +const handleUpdateNodeTransaction = async (transaction: Transaction) => { + const input = JSON.parse(transaction.input) as UpdateNodeTransactionInput; + + const existingNode = await prisma.nodes.findUnique({ + where: { + id: input.id, + }, + }); + + if (!existingNode) { + return; + } + + const data: Prisma.nodesUncheckedUpdateInput = { + parentId: input.parentId, + updatedAt: input.updatedAt, + updatedBy: input.updatedBy, + versionId: input.versionId, + }; + + if (input.attrs !== undefined) { + if (input.attrs === null) { + data.attrs = Prisma.JsonNull; + } else { + const existingAttrs = + existingNode.attrs != null + ? (existingNode.attrs as Record) + : {}; + + const newAttrs = { + ...existingAttrs, + ...input.attrs, + }; + + const hasAttrs = Object.keys(newAttrs).length > 0; + data.attrs = hasAttrs ? newAttrs : Prisma.JsonNull; + } + } + + if (input.content !== undefined) { + if (input.content === null) { + data.content = Prisma.JsonNull; + } else { + data.content = input.content; + } + } + + await prisma.nodes.update({ + where: { + id: existingNode.id, + }, + data: data, + }); +}; + +const handleDeleteNodeTransaction = async (transaction: Transaction) => { + await prisma.nodes.delete({ + where: { + id: transaction.input, + }, + }); +}; + +const handleDeleteNodesTransaction = async (transaction: Transaction) => { + const input = JSON.parse(transaction.input) as string[]; + await prisma.nodes.deleteMany({ + where: { + id: { + in: input, + }, + }, + }); +}; diff --git a/server/src/data/kafka.ts b/server/src/data/kafka.ts new file mode 100644 index 00000000..b2308f73 --- /dev/null +++ b/server/src/data/kafka.ts @@ -0,0 +1,38 @@ +import { Kafka } from 'kafkajs'; + +const KAFKA_CLIENT_ID = process.env.KAFKA_CLIENT_ID ?? 'neuron'; +const KAFKA_BROKERS = process.env.KAFKA_BROKERS ?? ''; +const KAFKA_USERNAME = process.env.KAFKA_USERNAME; +const KAFKA_PASSWORD = process.env.KAFKA_PASSWORD; +const KAFKA_TRANSACTIONS_TOPIC_NAME = + process.env.KAFKA_TRANSACTIONS_TOPIC_NAME ?? 'neuron_transactions'; +export const KAFKA_CONSUMER_GROUP = + process.env.KAFKA_CONSUMER_GROUP ?? 'neuron'; + +export const kafka = new Kafka({ + clientId: KAFKA_CLIENT_ID, + brokers: KAFKA_BROKERS.split(','), + sasl: + KAFKA_USERNAME && KAFKA_PASSWORD + ? { + username: KAFKA_USERNAME, + password: KAFKA_PASSWORD, + mechanism: 'plain', + } + : undefined, +}); + +export const producer = kafka.producer(); + +export const TOPIC_NAMES = { + TRANSACTIONS: KAFKA_TRANSACTIONS_TOPIC_NAME, +}; + +const connectProducer = async () => { + await producer.connect(); + console.log('Kafka Producer connected'); +}; + +connectProducer().catch((err) => { + console.error('Failed to connect Kafka Producer', err); +}); diff --git a/server/src/data/db.ts b/server/src/data/prisma.ts similarity index 100% rename from server/src/data/db.ts rename to server/src/data/prisma.ts diff --git a/server/src/index.ts b/server/src/index.ts index 28574627..d8599bf2 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,24 +1,7 @@ -import express, { Request, Response } from 'express'; -import cors from 'cors'; -import { accountsRouter } from '@/routes/accounts'; -import { workspacesRouter } from '@/routes/workspaces'; -import { transactionsRouter } from '@/routes/transactions'; -import { authMiddleware } from '@/middlewares/auth'; +import { initApi } from '@/api'; +import { initTransactionsConsumer } from '@/consumers/transactions'; -const app = express(); -const port = 3000; - -app.use(express.json()); -app.use(cors()); - -app.get('/', (req: Request, res: Response) => { - res.send('Neuron'); -}); - -app.use('/v1/accounts', accountsRouter); -app.use('/v1/workspaces', authMiddleware, workspacesRouter); -app.use('/v1/transactions', authMiddleware, transactionsRouter); - -app.listen(port, () => { - console.log(`Server is running at http://localhost:${port}`); +initApi(); +initTransactionsConsumer().then(() => { + console.log('Transactions consumer started'); }); diff --git a/server/src/routes/accounts.ts b/server/src/routes/accounts.ts index 7f3a2901..ea9d3948 100644 --- a/server/src/routes/accounts.ts +++ b/server/src/routes/accounts.ts @@ -11,7 +11,7 @@ import axios from 'axios'; import { ApiError } from '@/types/api'; import { NeuronId } from '@/lib/id'; import jwt from 'jsonwebtoken'; -import { prisma } from '@/data/db'; +import { prisma } from '@/data/prisma'; import bcrypt from 'bcrypt'; import { WorkspaceOutput } from '@/types/workspaces'; diff --git a/server/src/routes/transactions.ts b/server/src/routes/transactions.ts index 1b594900..aa4a7ceb 100644 --- a/server/src/routes/transactions.ts +++ b/server/src/routes/transactions.ts @@ -1,92 +1,27 @@ import { Request, Response, Router } from 'express'; import { Transaction } from '@/types/transactions'; -import { Node } from '@/types/nodes'; -import { prisma } from '@/data/db'; +import { producer, TOPIC_NAMES } from '@/data/kafka'; export const transactionsRouter = Router(); transactionsRouter.post('/', async (req: Request, res: Response) => { const transactions: Transaction[] = req.body.transactions; - const appliedTransactionIds: string[] = []; - for (const transaction of transactions) { - if (transaction.type === 'create_node') { - const node = JSON.parse(transaction.input) as Node; - await prisma.nodes.create({ - data: { - id: node.id, - parentId: node.parentId, - workspaceId: node.workspaceId, - type: node.type, - index: node.index, - attrs: node.attrs, - createdAt: node.createdAt, - createdBy: node.createdBy, - versionId: node.versionId, - content: JSON.stringify(node.content), - state: node.state, - }, - }); + const messages = transactions.map((transaction) => ({ + key: transaction.id, + value: JSON.stringify(transaction), + })); - appliedTransactionIds.push(transaction.id); - } else if (transaction.type === 'create_nodes') { - const nodes = JSON.parse(transaction.input) as Node[]; - await prisma.nodes.createMany({ - data: nodes.map((node) => ({ - id: node.id, - parentId: node.parentId, - workspaceId: node.workspaceId, - type: node.type, - index: node.index, - attrs: node.attrs, - createdAt: node.createdAt, - createdBy: node.createdBy, - versionId: node.versionId, - content: JSON.stringify(node.content), - state: node.state, - })), - }); + await producer.sendBatch({ + topicMessages: [ + { + topic: TOPIC_NAMES.TRANSACTIONS, + messages: messages, + }, + ], + }); - appliedTransactionIds.push(transaction.id); - } else if (transaction.type === 'update_node') { - const node = JSON.parse(transaction.input) as Node; - await prisma.nodes.update({ - where: { - id: node.id, - }, - data: { - parentId: node.parentId, - attrs: node.attrs, - content: JSON.stringify(node.content), - updatedAt: node.updatedAt, - updatedBy: node.updatedBy, - }, - }); - - appliedTransactionIds.push(transaction.id); - } else if (transaction.type === 'delete_node') { - await prisma.nodes.delete({ - where: { - id: transaction.input, - }, - }); - - appliedTransactionIds.push(transaction.id); - } else if (transaction.type === 'delete_nodes') { - const input = JSON.parse(transaction.input) as string[]; - await prisma.nodes.deleteMany({ - where: { - id: { - in: input, - }, - }, - }); - - appliedTransactionIds.push(transaction.id); - } - } - - res.json({ - appliedTransactionIds, + res.status(200).json({ + appliedTransactionIds: transactions.map((transaction) => transaction.id), }); }); diff --git a/server/src/routes/workspaces.ts b/server/src/routes/workspaces.ts index eb81ef1e..95f4e952 100644 --- a/server/src/routes/workspaces.ts +++ b/server/src/routes/workspaces.ts @@ -9,9 +9,9 @@ import { } from '@/types/workspaces'; import { ApiError, NeuronRequest, NeuronResponse } from '@/types/api'; import { NeuronId } from '@/lib/id'; -import { prisma } from '@/data/db'; +import { prisma } from '@/data/prisma'; import { Request, Response, Router } from 'express'; -import { Node } from '@/types/nodes'; +import { Node, NodeBlock } from '@/types/nodes'; export const workspacesRouter = Router(); @@ -343,11 +343,11 @@ workspacesRouter.get('/:id/nodes', async (req: Request, res: Response) => { workspaceId: node.workspaceId, type: node.type, index: node.index, - attrs: node.attrs, + attrs: node.attrs as Record, createdAt: node.createdAt, createdBy: node.createdBy, versionId: node.versionId, - content: node.content ? JSON.parse(node.content as string) : null, + content: node.content as NodeBlock[], updatedAt: node.updatedAt, updatedBy: node.updatedBy, state: node.state, diff --git a/server/src/types/nodes.ts b/server/src/types/nodes.ts index 4d872dd0..ae55c6f7 100644 --- a/server/src/types/nodes.ts +++ b/server/src/types/nodes.ts @@ -4,7 +4,7 @@ export type Node = { parentId?: string | null; type: string; index: string | null; - attrs: any; + attrs?: Record | null; content?: NodeBlock[] | null; createdAt: Date; createdBy: string; diff --git a/server/src/types/transactions.ts b/server/src/types/transactions.ts index 21a444dd..80264a46 100644 --- a/server/src/types/transactions.ts +++ b/server/src/types/transactions.ts @@ -1,4 +1,11 @@ -export type TransactionType = 'create_node' | 'create_nodes' | 'update_node' | 'delete_node' | 'delete_nodes'; +import { NodeBlock } from '@/types/nodes'; + +export type TransactionType = + | 'create_node' + | 'create_nodes' + | 'update_node' + | 'delete_node' + | 'delete_nodes'; export type Transaction = { id: string; @@ -9,3 +16,27 @@ export type Transaction = { input: string; createdAt: Date; }; + +export type CreateNodeTransactionInput = { + id: string; + workspaceId: string; + parentId?: string | null; + type: string; + index?: string | null; + attrs?: Record | null; + content?: NodeBlock[] | null; + createdAt: Date; + createdBy: string; + versionId: string; +}; + +export type UpdateNodeTransactionInput = { + id: string; + parentId?: string | null; + index?: string | null; + attrs?: Record | null; + content?: NodeBlock[] | null; + updatedAt: Date; + updatedBy: string; + versionId: string; +};