From e0d76764a85fffeb8985d34a63f02c5f5962988e Mon Sep 17 00:00:00 2001 From: Lucas Vieira Date: Mon, 13 Jan 2025 15:20:08 -0300 Subject: [PATCH] improve/remove transactions --- apps/api/src/document-tree.ts | 32 +++- .../src/v1/workspaces/workspace/components.ts | 72 ++++---- .../workspace/data-sources/data-source.ts | 123 +++++++------- .../workspace/documents/document/index.ts | 159 ++++++------------ .../workspace/documents/document/publish.ts | 54 ++---- .../workspaces/workspace/documents/index.ts | 42 ++--- .../workspace/environment-variables.ts | 58 +++---- apps/api/src/workspace/index.ts | 1 - apps/api/src/yjs/v2/documents.ts | 126 +++++++------- packages/database/src/documents.ts | 96 +++-------- packages/database/src/schedule.ts | 125 +++++++------- 11 files changed, 369 insertions(+), 519 deletions(-) diff --git a/apps/api/src/document-tree.ts b/apps/api/src/document-tree.ts index ff407c54..cb95f3b4 100644 --- a/apps/api/src/document-tree.ts +++ b/apps/api/src/document-tree.ts @@ -1,6 +1,6 @@ import * as yjsDocsV2 from './yjs/v2/documents.js' import * as dfns from 'date-fns' -import { PrismaTransaction, Document } from '@briefer/database' +import { prisma, PrismaTransaction, Document } from '@briefer/database' import PQueue from 'p-queue' import { IOServer } from './websocket/index.js' @@ -261,6 +261,21 @@ async function softDeleteChildren( } export async function deleteDocument( + id: string, + workspaceId: string, + softDelete: boolean, + tx?: PrismaTransaction +): Promise { + if (tx) { + return deleteDocumentInTransaction(id, workspaceId, softDelete, tx) + } + + return prisma().$transaction((tx) => + deleteDocumentInTransaction(id, workspaceId, softDelete, tx) + ) +} + +async function deleteDocumentInTransaction( id: string, workspaceId: string, softDelete: boolean, @@ -323,6 +338,21 @@ function getDuplicatedTitle(prevTitle: string) { } export async function duplicateDocument( + id: string, + workspaceId: string, + socketServer: IOServer, + tx?: PrismaTransaction +): Promise { + if (tx) { + return duplicateDocumentInTransaction(id, workspaceId, socketServer, tx) + } + + return prisma().$transaction((tx) => + duplicateDocumentInTransaction(id, workspaceId, socketServer, tx) + ) +} + +export async function duplicateDocumentInTransaction( id: string, workspaceId: string, socketServer: IOServer, diff --git a/apps/api/src/v1/workspaces/workspace/components.ts b/apps/api/src/v1/workspaces/workspace/components.ts index 3f3b45ff..88466ab3 100644 --- a/apps/api/src/v1/workspaces/workspace/components.ts +++ b/apps/api/src/v1/workspaces/workspace/components.ts @@ -47,50 +47,40 @@ export default function componentsRouter(socketServer: IOServer) { return } - await prisma().$transaction( - async (tx) => { - const component = await createReusableComponent(payload.data, tx) - await getYDocForUpdate( - getDocId(payload.data.documentId, null), - socketServer, - payload.data.documentId, - workspaceId, - async (ydoc) => { - const block = ydoc.blocks.get(payload.data.blockId) - if (!block) { - throw new Error( - `Could not find block ${payload.data.blockId} in document ${payload.data.documentId}` - ) - } - - switchBlockType(block, { - onSQL: (block) => - block.setAttribute('componentId', component.id), - onPython: (block) => - block.setAttribute('componentId', component.id), - onRichText: () => {}, - onVisualization: () => {}, - onInput: () => {}, - onDropdownInput: () => {}, - onDateInput: () => {}, - onFileUpload: () => {}, - onDashboardHeader: () => {}, - onWriteback: () => {}, - onPivotTable: () => {}, - }) - }, - new DocumentPersistor(payload.data.documentId), - tx - ) - await broadcastComponent(socketServer, component) + const component = await createReusableComponent(payload.data) + await getYDocForUpdate( + getDocId(payload.data.documentId, null), + socketServer, + payload.data.documentId, + workspaceId, + async (ydoc) => { + const block = ydoc.blocks.get(payload.data.blockId) + if (!block) { + throw new Error( + `Could not find block ${payload.data.blockId} in document ${payload.data.documentId}` + ) + } - res.json(component) + switchBlockType(block, { + onSQL: (block) => block.setAttribute('componentId', component.id), + onPython: (block) => + block.setAttribute('componentId', component.id), + onRichText: () => {}, + onVisualization: () => {}, + onInput: () => {}, + onDropdownInput: () => {}, + onDateInput: () => {}, + onFileUpload: () => {}, + onDashboardHeader: () => {}, + onWriteback: () => {}, + onPivotTable: () => {}, + }) }, - { - maxWait: 31000, - timeout: 30000, - } + new DocumentPersistor(payload.data.documentId) ) + await broadcastComponent(socketServer, component) + + res.json(component) } catch (err) { req.log.error({ workspaceId, err }, 'Error creating reusable component') res.sendStatus(500) diff --git a/apps/api/src/v1/workspaces/workspace/data-sources/data-source.ts b/apps/api/src/v1/workspaces/workspace/data-sources/data-source.ts index 914334ec..ed29931d 100644 --- a/apps/api/src/v1/workspaces/workspace/data-sources/data-source.ts +++ b/apps/api/src/v1/workspaces/workspace/data-sources/data-source.ts @@ -588,70 +588,65 @@ const dataSourceRouter = (socketServer: IOServer) => { { type: dataSource.type, id: dataSource.data.id, isDefault: true }, ]) - await prisma().$transaction( - (tx) => - Promise.all( - actions.map((ds) => { - switch (ds.type) { - case 'psql': - return tx.postgreSQLDataSource.update({ - where: { id: ds.id }, - data: { isDefault: ds.isDefault }, - }) - case 'mysql': - return tx.mySQLDataSource.update({ - where: { id: ds.id }, - data: { isDefault: ds.isDefault }, - }) - case 'trino': - return tx.trinoDataSource.update({ - where: { id: ds.id }, - data: { isDefault: ds.isDefault }, - }) - case 'athena': - return tx.athenaDataSource.update({ - where: { id: ds.id }, - data: { isDefault: ds.isDefault }, - }) - case 'oracle': - return tx.oracleDataSource.update({ - where: { id: ds.id }, - data: { isDefault: ds.isDefault }, - }) - case 'bigquery': - return tx.bigQueryDataSource.update({ - where: { id: ds.id }, - data: { isDefault: ds.isDefault }, - }) - case 'redshift': - return tx.redshiftDataSource.update({ - where: { id: ds.id }, - data: { isDefault: ds.isDefault }, - }) - case 'sqlserver': - return tx.sQLServerDataSource.update({ - where: { id: ds.id }, - data: { isDefault: ds.isDefault }, - }) - case 'snowflake': - return tx.snowflakeDataSource.update({ - where: { id: ds.id }, - data: { isDefault: ds.isDefault }, - }) - case 'databrickssql': - return tx.databricksSQLDataSource.update({ - where: { id: ds.id }, - data: { isDefault: ds.isDefault }, - }) - default: - exhaustiveCheck(ds.type) - } - }) - ), - { - maxWait: 31000, - timeout: 30000, - } + await prisma().$transaction((tx) => + Promise.all( + actions.map((ds) => { + switch (ds.type) { + case 'psql': + return tx.postgreSQLDataSource.update({ + where: { id: ds.id }, + data: { isDefault: ds.isDefault }, + }) + case 'mysql': + return tx.mySQLDataSource.update({ + where: { id: ds.id }, + data: { isDefault: ds.isDefault }, + }) + case 'trino': + return tx.trinoDataSource.update({ + where: { id: ds.id }, + data: { isDefault: ds.isDefault }, + }) + case 'athena': + return tx.athenaDataSource.update({ + where: { id: ds.id }, + data: { isDefault: ds.isDefault }, + }) + case 'oracle': + return tx.oracleDataSource.update({ + where: { id: ds.id }, + data: { isDefault: ds.isDefault }, + }) + case 'bigquery': + return tx.bigQueryDataSource.update({ + where: { id: ds.id }, + data: { isDefault: ds.isDefault }, + }) + case 'redshift': + return tx.redshiftDataSource.update({ + where: { id: ds.id }, + data: { isDefault: ds.isDefault }, + }) + case 'sqlserver': + return tx.sQLServerDataSource.update({ + where: { id: ds.id }, + data: { isDefault: ds.isDefault }, + }) + case 'snowflake': + return tx.snowflakeDataSource.update({ + where: { id: ds.id }, + data: { isDefault: ds.isDefault }, + }) + case 'databrickssql': + return tx.databricksSQLDataSource.update({ + where: { id: ds.id }, + data: { isDefault: ds.isDefault }, + }) + default: + exhaustiveCheck(ds.type) + } + }) + ) ) await broadcastDataSources(socketServer, workspaceId) res.sendStatus(204) diff --git a/apps/api/src/v1/workspaces/workspace/documents/document/index.ts b/apps/api/src/v1/workspaces/workspace/documents/document/index.ts index f8a1f2e4..a817234a 100644 --- a/apps/api/src/v1/workspaces/workspace/documents/document/index.ts +++ b/apps/api/src/v1/workspaces/workspace/documents/document/index.ts @@ -69,39 +69,22 @@ export default function documentRouter(socketServer: IOServer) { const payload = bodyResult.data let status = 500 try { - const doc = await prisma().$transaction( - async (tx) => { - const previousDoc = await getDocument(documentId, tx) - if (!previousDoc) { - status = 404 - throw new Error('Document not found') - } - - if (payload.relations) { - await moveDocument( - previousDoc.id, - workspaceId, - payload.relations.parentId, - payload.relations.orderIndex, - tx - ) - } + const previousDoc = await getDocument(documentId) + if (!previousDoc) { + status = 404 + throw new Error('Document not found') + } - const updatedDoc = await updateDocument( - documentId, - { - title: bodyResult.data.title, - }, - tx - ) + if (payload.relations) { + const { parentId, orderIndex } = payload.relations + await prisma().$transaction(async (tx) => + moveDocument(previousDoc.id, workspaceId, parentId, orderIndex, tx) + ) + } - return updatedDoc - }, - { - maxWait: 31000, - timeout: 30000, - } - ) + const doc = await updateDocument(documentId, { + title: bodyResult.data.title, + }) res.json(await toApiDocument(doc)) } catch (err) { @@ -115,7 +98,7 @@ export default function documentRouter(socketServer: IOServer) { } req.log.error({ err, documentId }, 'Failed to update document') - res.status(500).end() + res.status(status).end() } } ) @@ -128,38 +111,21 @@ export default function documentRouter(socketServer: IOServer) { const documentId = getParam(req, 'documentId') const isPermanent = req.query['isPermanent'] === 'true' - let status: number = 500 try { - const document = await prisma().$transaction( - async (tx) => { - const document = await getDocument(documentId, tx) - if (!document || document.workspaceId !== workspaceId) { - status = 404 - throw new Error('Document not found') - } - - const deletedDoc = await deleteDocument( - document.id, - workspaceId, - !isPermanent, - tx - ) + const document = await getDocument(documentId) + if (!document) { + res.status(404).end() + return + } - return { ...document, deletedAt: deletedDoc.deletedAt } - }, - { - maxWait: 31000, - timeout: 30000, - } + const deletedDoc = await deleteDocument( + documentId, + workspaceId, + !isPermanent ) - res.json(document) + res.json({ ...document, deletedAt: deletedDoc.deletedAt }) } catch (err) { - if (status !== 500) { - res.status(status).end() - return - } - req.log.error( { err, documentId, isPermanent }, 'Failed to delete document' @@ -176,34 +142,26 @@ export default function documentRouter(socketServer: IOServer) { const documentId = getParam(req, 'documentId') const workspaceId = getParam(req, 'workspaceId') - let status = 500 try { - const restoredDocument = await prisma().$transaction( - async (tx) => { - let document = await tx.document.findUnique({ - where: { - id: documentId, - workspaceId, - deletedAt: { not: null }, - }, - }) - if (!document) { - status = 400 - throw new Error('Document not found or not deleted') - } - - return restoreDocument(document.id, workspaceId, tx) + let doc = await prisma().document.findUnique({ + where: { + id: documentId, + workspaceId, + deletedAt: { not: null }, }, - { maxWait: 31000, timeout: 30000 } + select: { id: true }, + }) + if (!doc) { + res.status(400).end() + return + } + + const restoredDocument = await prisma().$transaction(async (tx) => + restoreDocument(doc.id, workspaceId, tx) ) res.json(await toApiDocument(restoredDocument)) } catch (err) { - if (status !== 500) { - res.status(status).end() - return - } - req.log.error({ err, documentId }, 'Failed to restore document') res.status(500).end() } @@ -218,33 +176,14 @@ export default function documentRouter(socketServer: IOServer) { const workspaceId = getParam(req, 'workspaceId') try { - const duplicatedDocument = await prisma().$transaction( - async (tx) => { - const prevDoc = await tx.document.findUnique({ - where: { - id: originalDocumentId, - workspaceId, - deletedAt: null, - }, - }) - if (!prevDoc) { - return null - } - - return await duplicateDocument( - prevDoc.id, - workspaceId, - socketServer, - tx - ) + const prevDoc = await prisma().document.findUnique({ + where: { + id: originalDocumentId, + workspaceId, + deletedAt: null, }, - { - maxWait: 31000, - timeout: 30000, - } - ) - - if (!duplicatedDocument) { + }) + if (!prevDoc) { req.log.error( { originalDocumentId, workspaceId }, 'Failed to duplicate document, document not found' @@ -253,6 +192,12 @@ export default function documentRouter(socketServer: IOServer) { return } + const duplicatedDocument = await duplicateDocument( + prevDoc.id, + workspaceId, + socketServer + ) + res.status(201).json(await toApiDocument(duplicatedDocument)) } catch (err) { req.log.error( diff --git a/apps/api/src/v1/workspaces/workspace/documents/document/publish.ts b/apps/api/src/v1/workspaces/workspace/documents/document/publish.ts index ddaa0699..ad92d498 100644 --- a/apps/api/src/v1/workspaces/workspace/documents/document/publish.ts +++ b/apps/api/src/v1/workspaces/workspace/documents/document/publish.ts @@ -27,44 +27,26 @@ export default function publishRouter(socketServer: IOServer) { let hasDashboard = false try { - await prisma().$transaction( - async (tx) => { - const doc = await tx.document.findUnique({ - where: { id: documentId }, - }) - - if (!doc) { - res.status(404).end() - return - } - - const id = getDocId(documentId, null) - await getYDocForUpdate( - id, - socketServer, - doc.id, - doc.workspaceId, - async (yDoc) => { - hasDashboard = yDoc.dashboard.size > 0 - await tx.yjsAppDocument.create({ - data: { - documentId, - state: Buffer.from( - Y.encodeStateAsUpdate(getYDocWithoutHistory(yDoc)) - ), - hasDashboard, - }, - }) - setPristine(yDoc.ydoc) + const id = getDocId(documentId, null) + await getYDocForUpdate( + id, + socketServer, + documentId, + workspaceId, + async (yDoc) => { + hasDashboard = yDoc.dashboard.size > 0 + await prisma().yjsAppDocument.create({ + data: { + documentId, + state: Buffer.from( + Y.encodeStateAsUpdate(getYDocWithoutHistory(yDoc)) + ), + hasDashboard, }, - new DocumentPersistor(documentId), - tx - ) + }) + setPristine(yDoc.ydoc) }, - { - maxWait: 31000, - timeout: 30000, - } + new DocumentPersistor(documentId) ) await broadcastDocument(socketServer, workspaceId, documentId) diff --git a/apps/api/src/v1/workspaces/workspace/documents/index.ts b/apps/api/src/v1/workspaces/workspace/documents/index.ts index 246c8e1c..a61b73df 100644 --- a/apps/api/src/v1/workspaces/workspace/documents/index.ts +++ b/apps/api/src/v1/workspaces/workspace/documents/index.ts @@ -42,33 +42,23 @@ export default function documentsRouter(socketServer: IOServer) { let status = 500 try { - const document = await prisma().$transaction( - async (tx) => { - const result = await upsertDocument( - data.id ?? uuidv4(), - '', - workspaceId, - data.parentId, - -1, - data.version ?? 1, - tx - ) - - if (!result) { - throw new Error('Failed to create document') - } - - if (result.created) { - res.status(201) - } - - return result.document - }, - { - maxWait: 31000, - timeout: 30000, + const document = await prisma().$transaction(async (tx) => { + const result = await upsertDocument( + data.id ?? uuidv4(), + '', + workspaceId, + data.parentId, + -1, + data.version ?? 1, + tx + ) + + if (result.created) { + res.status(201) } - ) + + return result.document + }) await broadcastDocument(socketServer, workspaceId, document.id) res.json(await toApiDocument(document)) diff --git a/apps/api/src/v1/workspaces/workspace/environment-variables.ts b/apps/api/src/v1/workspaces/workspace/environment-variables.ts index 91d59491..203c3f29 100644 --- a/apps/api/src/v1/workspaces/workspace/environment-variables.ts +++ b/apps/api/src/v1/workspaces/workspace/environment-variables.ts @@ -45,42 +45,34 @@ environmentVariablesRouter.post('/', async (req, res) => { const workspaceId = getParam(req, 'workspaceId') try { - await prisma().$transaction( - async (tx) => { - const removeNames = await tx.environmentVariable.findMany({ - where: { id: { in: body.data.remove }, workspaceId }, - select: { name: true }, - }) - await tx.environmentVariable.deleteMany({ - where: { id: { in: body.data.remove }, workspaceId }, - }) + const removeNames = await prisma().environmentVariable.findMany({ + where: { id: { in: body.data.remove }, workspaceId }, + select: { name: true }, + }) - await tx.environmentVariable.createMany({ - data: body.data.add.map((v) => ({ - name: encrypt( - v.name, - config().ENVIRONMENT_VARIABLES_ENCRYPTION_KEY - ), - value: encrypt( - v.value, - config().ENVIRONMENT_VARIABLES_ENCRYPTION_KEY - ), - workspaceId, - })), - }) + await prisma().$transaction(async (tx) => { + await tx.environmentVariable.deleteMany({ + where: { id: { in: body.data.remove }, workspaceId }, + }) - await getJupyterManager().setEnvironmentVariables(workspaceId, { - add: body.data.add, - remove: removeNames.map((v) => - decrypt(v.name, config().ENVIRONMENT_VARIABLES_ENCRYPTION_KEY) + await tx.environmentVariable.createMany({ + data: body.data.add.map((v) => ({ + name: encrypt(v.name, config().ENVIRONMENT_VARIABLES_ENCRYPTION_KEY), + value: encrypt( + v.value, + config().ENVIRONMENT_VARIABLES_ENCRYPTION_KEY ), - }) - }, - { - maxWait: 31000, - timeout: 30000, - } - ) + workspaceId, + })), + }) + }) + + await getJupyterManager().setEnvironmentVariables(workspaceId, { + add: body.data.add, + remove: removeNames.map((v) => + decrypt(v.name, config().ENVIRONMENT_VARIABLES_ENCRYPTION_KEY) + ), + }) const envVars = await prisma().environmentVariable.findMany({ where: { workspaceId }, diff --git a/apps/api/src/workspace/index.ts b/apps/api/src/workspace/index.ts index e9854df3..1b80ebfb 100644 --- a/apps/api/src/workspace/index.ts +++ b/apps/api/src/workspace/index.ts @@ -5,7 +5,6 @@ import { UserWorkspaceRole, ApiWorkspace, createWorkspace as prismaCreateWorkspace, - createDocument, } from '@briefer/database' import { IOServer } from '../websocket/index.js' import { WorkspaceCreateInput } from '@briefer/types' diff --git a/apps/api/src/yjs/v2/documents.ts b/apps/api/src/yjs/v2/documents.ts index 383cee7b..7d7ab027 100644 --- a/apps/api/src/yjs/v2/documents.ts +++ b/apps/api/src/yjs/v2/documents.ts @@ -140,78 +140,68 @@ export async function duplicateDocument( tx: PrismaTransaction, datasourceMap?: Map ) { - function transaction(cb: (tx: PrismaTransaction) => Promise) { - if (tx) { - return cb(tx) - } - - return prisma().$transaction(cb, { maxWait: 31000, timeout: 30000 }) - } - - await transaction(async (tx) => { - const prevId = getDocId(prevDoc.id, null) - await getYDocForUpdate( - prevId, - socketServer, - prevDoc.id, - prevDoc.workspaceId, - async (existingYDoc) => { - const newId = getDocId(newDoc.id, null) - await getYDocForUpdate( - newId, - socketServer, - newDoc.id, - newDoc.workspaceId, - async (newYDoc) => { - duplicateYDoc(existingYDoc, newYDoc.ydoc, getDuplicatedTitle, { - keepIds: false, - datasourceMap, + const prevId = getDocId(prevDoc.id, null) + await getYDocForUpdate( + prevId, + socketServer, + prevDoc.id, + prevDoc.workspaceId, + async (existingYDoc) => { + const newId = getDocId(newDoc.id, null) + await getYDocForUpdate( + newId, + socketServer, + newDoc.id, + newDoc.workspaceId, + async (newYDoc) => { + duplicateYDoc(existingYDoc, newYDoc.ydoc, getDuplicatedTitle, { + keepIds: false, + datasourceMap, + }) + + const blocks = newYDoc.blocks + const componentInstances: { + blockId: string + componentId: string + }[] = [] + for (const [blockId, block] of blocks) { + const componentId = switchBlockType(block, { + onSQL: (block) => block.getAttribute('componentId'), + onPython: (block) => block.getAttribute('componentId'), + onRichText: () => null, + onVisualization: () => null, + onInput: () => null, + onDropdownInput: () => null, + onDateInput: () => null, + onFileUpload: () => null, + onDashboardHeader: () => null, + onWriteback: () => null, + onPivotTable: () => null, }) - const blocks = newYDoc.blocks - const componentInstances: { - blockId: string - componentId: string - }[] = [] - for (const [blockId, block] of blocks) { - const componentId = switchBlockType(block, { - onSQL: (block) => block.getAttribute('componentId'), - onPython: (block) => block.getAttribute('componentId'), - onRichText: () => null, - onVisualization: () => null, - onInput: () => null, - onDropdownInput: () => null, - onDateInput: () => null, - onFileUpload: () => null, - onDashboardHeader: () => null, - onWriteback: () => null, - onPivotTable: () => null, - }) - - if (componentId) { - componentInstances.push({ blockId, componentId }) - } + if (componentId) { + componentInstances.push({ blockId, componentId }) } + } - if (componentInstances.length > 0) { - await tx.reusableComponentInstance.createMany({ - data: componentInstances.map((ci) => ({ - blockId: ci.blockId, - reusableComponentId: ci.componentId, - documentId: newDoc.id, - })), - skipDuplicates: true, - }) - } - }, - new DocumentPersistor(newDoc.id), - tx - ) - }, - new DocumentPersistor(prevDoc.id), - tx - ) - }) + if (componentInstances.length > 0) { + await tx.reusableComponentInstance.createMany({ + data: componentInstances.map((ci) => ({ + blockId: ci.blockId, + reusableComponentId: ci.componentId, + documentId: newDoc.id, + })), + skipDuplicates: true, + }) + } + }, + new DocumentPersistor(newDoc.id), + tx + ) + }, + new DocumentPersistor(prevDoc.id), + tx + ) } export async function updateAppState( diff --git a/packages/database/src/documents.ts b/packages/database/src/documents.ts index bc3f9b4c..3bc9f0b6 100644 --- a/packages/database/src/documents.ts +++ b/packages/database/src/documents.ts @@ -146,19 +146,22 @@ export async function restoreDocument( }) } -export async function restoreDocumentAndChildren( +// TODO: we could use a recursive CTE to get all descendants. +// Since prisma does not nativelly support it, we'll avoid it while we can, I +// don't think people will have huge nesting levels anyways. +export async function listAllDescendants( documentId: string, - workspaceId: string, tx?: PrismaTransaction -) { - const allChildren: string[] = [] - const queue = allChildren.slice() - let current = queue.shift() +): Promise { + const result: string[] = [] + + const queue: string[] = [] + let current: string | undefined = documentId let first = true while (current || first) { first = false const children = ( - await prisma().document.findMany({ + await (tx ?? prisma()).document.findMany({ where: { parentId: current, }, @@ -171,6 +174,15 @@ export async function restoreDocumentAndChildren( current = queue.shift() } + return result +} + +export async function restoreDocumentAndChildren( + documentId: string, + workspaceId: string, + tx?: PrismaTransaction +) { + const allChildren = await listAllDescendants(documentId, tx) const allDocs = [documentId, ...allChildren] const recover = async (tx: PrismaTransaction) => { @@ -231,76 +243,6 @@ export async function createDocument( }) } -export async function deleteDocument( - id: string, - isPermanent: boolean, - tx?: PrismaTransaction -): Promise { - async function softDelete(tx: PrismaTransaction) { - const document = await tx.document.update({ - where: { id }, - data: { deletedAt: new Date() }, - }) - - await tx.favorite.deleteMany({ - where: { - documentId: id, - }, - }) - - return document - } - - if (isPermanent) { - return (tx ?? prisma()).document.delete({ - where: { id }, - }) - } - - if (tx) { - return softDelete(tx) - } - - return prisma().$transaction(softDelete) -} - -// we can do better using a recursive CTE but then we cannot use -// prisma query builder, this is good enough for now -export async function deleteDocumentAndChildren( - documentId: string, - isPermanent: boolean, - tx?: PrismaTransaction -) { - async function run(tx: PrismaTransaction) { - const doc = await deleteDocument(documentId, isPermanent, tx) - if (!doc) { - return null - } - - // if we are deleting permanently, children get's deleted - // automatically by the database - if (isPermanent) { - return doc - } - - const children = await tx.document.findMany({ - where: { - parentId: documentId, - }, - }) - - for (const child of children) { - await deleteDocumentAndChildren(child.id, isPermanent, tx) - } - - return doc - } - - return tx - ? run(tx) - : prisma().$transaction(run, { maxWait: 31000, timeout: 30000 }) -} - export const createFavorite = async (userId: string, documentId: string) => { return prisma().favorite.create({ data: { diff --git a/packages/database/src/schedule.ts b/packages/database/src/schedule.ts index edc8dd8f..97e55bfb 100644 --- a/packages/database/src/schedule.ts +++ b/packages/database/src/schedule.ts @@ -53,74 +53,69 @@ export type ExecutionSchedule = { id: string } & ScheduleParams -export function createSchedule( +export async function createSchedule( scheduleParams: ScheduleParams ): Promise { - return prisma().$transaction( - async (prisma) => { - let schedule: PrismaExecutionSchedule - switch (scheduleParams.type) { - case 'hourly': - schedule = await prisma.executionSchedule.create({ - data: { - type: 'hourly', - documentId: scheduleParams.documentId, - minute: scheduleParams.minute!, - timezone: scheduleParams.timezone, - }, - }) - break - case 'daily': - schedule = await prisma.executionSchedule.create({ - data: { - type: 'daily', - documentId: scheduleParams.documentId, - hour: scheduleParams.hour, - minute: scheduleParams.minute, - timezone: scheduleParams.timezone, - }, - }) - break - case 'weekly': - schedule = await prisma.executionSchedule.create({ - data: { - type: 'weekly', - documentId: scheduleParams.documentId, - hour: scheduleParams.hour, - minute: scheduleParams.minute, - weekdays: JSON.stringify(scheduleParams.weekdays), - timezone: scheduleParams.timezone, - }, - }) - break - case 'monthly': - schedule = await prisma.executionSchedule.create({ - data: { - type: 'monthly', - documentId: scheduleParams.documentId, - hour: scheduleParams.hour, - minute: scheduleParams.minute, - days: JSON.stringify(scheduleParams.days), - timezone: scheduleParams.timezone, - }, - }) - break - case 'cron': - schedule = await prisma.executionSchedule.create({ - data: { - type: 'cron', - documentId: scheduleParams.documentId, - cron: scheduleParams.cron, - timezone: scheduleParams.timezone, - }, - }) - break - } + let schedule: PrismaExecutionSchedule + switch (scheduleParams.type) { + case 'hourly': + schedule = await prisma().executionSchedule.create({ + data: { + type: 'hourly', + documentId: scheduleParams.documentId, + minute: scheduleParams.minute!, + timezone: scheduleParams.timezone, + }, + }) + break + case 'daily': + schedule = await prisma().executionSchedule.create({ + data: { + type: 'daily', + documentId: scheduleParams.documentId, + hour: scheduleParams.hour, + minute: scheduleParams.minute, + timezone: scheduleParams.timezone, + }, + }) + break + case 'weekly': + schedule = await prisma().executionSchedule.create({ + data: { + type: 'weekly', + documentId: scheduleParams.documentId, + hour: scheduleParams.hour, + minute: scheduleParams.minute, + weekdays: JSON.stringify(scheduleParams.weekdays), + timezone: scheduleParams.timezone, + }, + }) + break + case 'monthly': + schedule = await prisma().executionSchedule.create({ + data: { + type: 'monthly', + documentId: scheduleParams.documentId, + hour: scheduleParams.hour, + minute: scheduleParams.minute, + days: JSON.stringify(scheduleParams.days), + timezone: scheduleParams.timezone, + }, + }) + break + case 'cron': + schedule = await prisma().executionSchedule.create({ + data: { + type: 'cron', + documentId: scheduleParams.documentId, + cron: scheduleParams.cron, + timezone: scheduleParams.timezone, + }, + }) + break + } - return convertSchedule(schedule) - }, - { maxWait: 31000, timeout: 30000 } - ) + return convertSchedule(schedule) } export function convertSchedule(