From 26695bc4a67ee8ddf235543a03dd2656436e53dc Mon Sep 17 00:00:00 2001 From: nichenqin Date: Fri, 16 Aug 2024 15:47:23 +0800 Subject: [PATCH] feat: duplicate table --- .../blocks/create-field/create-field.svelte | 5 +- .../duplicate-table/duplicate-table.svelte | 65 +++++++++++++ .../blocks/table-header/table-header.svelte | 14 ++- apps/frontend/src/lib/store/modal.store.ts | 2 + .../duplicate-table.command-handler.ts | 20 ++++ .../command-handlers/src/handlers/index.ts | 2 + .../commands/src/duplicate-table.command.ts | 16 ++++ packages/commands/src/index.ts | 1 + .../src/table/table.filter-visitor.ts | 4 + .../src/table/table.mutation-visitor.ts | 2 + .../src/table/table.reference-visitor.ts | 2 + .../src/underlying/reference/join-table.ts | 2 +- .../underlying-table-field.visitor.ts | 29 +++--- .../underlying-table-spec.visitor.ts | 96 +++++++++++++------ .../underlying/underlying-table.service.ts | 4 +- packages/table/src/dto/duplicate-table.dto.ts | 8 ++ packages/table/src/dto/index.ts | 1 + .../src/methods/duplicate-table.method.ts | 20 ++++ .../methods/duplicate-table.method.ts | 36 +++++++ packages/table/src/services/table.service.ts | 4 + packages/table/src/specifications/index.ts | 1 + .../specifications/table-visitor.interface.ts | 2 + .../src/specifications/table.specification.ts | 23 +++++ packages/table/src/table.do.ts | 2 + packages/trpc/src/router.ts | 7 ++ 25 files changed, 315 insertions(+), 53 deletions(-) create mode 100644 apps/frontend/src/lib/components/blocks/duplicate-table/duplicate-table.svelte create mode 100644 packages/command-handlers/src/handlers/duplicate-table.command-handler.ts create mode 100644 packages/commands/src/duplicate-table.command.ts create mode 100644 packages/table/src/dto/duplicate-table.dto.ts create mode 100644 packages/table/src/methods/duplicate-table.method.ts create mode 100644 packages/table/src/services/methods/duplicate-table.method.ts create mode 100644 packages/table/src/specifications/table.specification.ts diff --git a/apps/frontend/src/lib/components/blocks/create-field/create-field.svelte b/apps/frontend/src/lib/components/blocks/create-field/create-field.svelte index 1776847ad..b25c0e930 100644 --- a/apps/frontend/src/lib/components/blocks/create-field/create-field.svelte +++ b/apps/frontend/src/lib/components/blocks/create-field/create-field.svelte @@ -3,14 +3,13 @@ import { Button } from "$lib/components/ui/button" import * as Form from "$lib/components/ui/form" import { Input } from "$lib/components/ui/input" - import { closeModal } from "$lib/store/modal.store" import { getTable } from "$lib/store/table.store" import { trpc } from "$lib/trpc/client" import { createMutation, useQueryClient } from "@tanstack/svelte-query" - import { createFieldDTO, FieldIdVo, type FieldType } from "@undb/table" + import { createFieldDTO, type FieldType } from "@undb/table" import { toast } from "svelte-sonner" import { derived } from "svelte/store" - import SuperDebug, { defaults, superForm } from "sveltekit-superforms" + import { defaults, superForm } from "sveltekit-superforms" import { zodClient } from "sveltekit-superforms/adapters" import FieldOptions from "../field-options/field-options.svelte" import FieldTypePicker from "../field-picker/field-type-picker.svelte" diff --git a/apps/frontend/src/lib/components/blocks/duplicate-table/duplicate-table.svelte b/apps/frontend/src/lib/components/blocks/duplicate-table/duplicate-table.svelte new file mode 100644 index 000000000..21c4380e0 --- /dev/null +++ b/apps/frontend/src/lib/components/blocks/duplicate-table/duplicate-table.svelte @@ -0,0 +1,65 @@ + + + { + if (!open) { + closeModal(DUPLICATE_TABLE_MODAL) + } + }} +> + + + Duplicate Table {table.name.value} + + Create a new table with the same structure as {table.name.value} + + + +
+ + +
+
+
diff --git a/apps/frontend/src/lib/components/blocks/table-header/table-header.svelte b/apps/frontend/src/lib/components/blocks/table-header/table-header.svelte index 37f5b1701..424d4aec8 100644 --- a/apps/frontend/src/lib/components/blocks/table-header/table-header.svelte +++ b/apps/frontend/src/lib/components/blocks/table-header/table-header.svelte @@ -20,6 +20,8 @@ FileJsonIcon, FileTextIcon, FileSpreadsheet, + TrashIcon, + CopyIcon, } from "lucide-svelte" import * as Breadcrumb from "$lib/components/ui/breadcrumb/index.js" import { getTable } from "$lib/store/table.store" @@ -32,6 +34,7 @@ import { DELETE_TABLE_MODAL, DELETE_VIEW, + DUPLICATE_TABLE_MODAL, DUPLICATE_VIEW, UPDATE_TABLE_MODAL, UPDATE_VIEW, @@ -39,6 +42,7 @@ } from "$lib/store/modal.store" import { getBaseById } from "$lib/store/base.store" import { hasPermission } from "$lib/store/space-member.store" + import DuplicateTable from "../duplicate-table/duplicate-table.svelte" const table = getTable() $: base = $getBaseById($table.baseId) @@ -94,12 +98,18 @@ Update table name {/if} + {#if $hasPermission("table:create")} + toggleModal(DUPLICATE_TABLE_MODAL)}> + + Duplicate Table + + {/if} {#if $hasPermission("table:delete")} toggleModal(DELETE_TABLE_MODAL)} class="text-xs text-red-500 hover:bg-red-50 hover:text-red-500" > - + Delete table {/if} @@ -268,3 +278,5 @@ + + diff --git a/apps/frontend/src/lib/store/modal.store.ts b/apps/frontend/src/lib/store/modal.store.ts index 08f7a7a75..070cd944e 100644 --- a/apps/frontend/src/lib/store/modal.store.ts +++ b/apps/frontend/src/lib/store/modal.store.ts @@ -6,6 +6,7 @@ export const modal = queryParam("modal", ssp.array()) export const CREATE_TABLE_MODAL = "createTable" as const export const UPDATE_TABLE_MODAL = "updateTable" as const +export const DUPLICATE_TABLE_MODAL = "duplicateTable" as const export const IMPORT_TABLE_MODAL = "importTable" as const export const CREATE_RECORD_MODAL = "createRecord" as const export const DELETE_RECORD_MODAL = "deleteRecord" as const @@ -23,6 +24,7 @@ type ModalType = | typeof CREATE_TABLE_MODAL | typeof UPDATE_TABLE_MODAL | typeof IMPORT_TABLE_MODAL + | typeof DUPLICATE_TABLE_MODAL | typeof DELETE_RECORD_MODAL | typeof DUPLICATE_RECORD_MODAL | typeof CREATE_RECORD_MODAL diff --git a/packages/command-handlers/src/handlers/duplicate-table.command-handler.ts b/packages/command-handlers/src/handlers/duplicate-table.command-handler.ts new file mode 100644 index 000000000..6de6d8b72 --- /dev/null +++ b/packages/command-handlers/src/handlers/duplicate-table.command-handler.ts @@ -0,0 +1,20 @@ +import { DuplicateTableCommand } from "@undb/commands" +import { commandHandler } from "@undb/cqrs" +import { singleton } from "@undb/di" +import type { ICommandHandler } from "@undb/domain" +import { injectTableService, type ITableService } from "@undb/table" + +@commandHandler(DuplicateTableCommand) +@singleton() +export class DuplicateTableCommandHandler implements ICommandHandler { + constructor( + @injectTableService() + private readonly service: ITableService, + ) {} + + async execute(command: DuplicateTableCommand): Promise { + const table = await this.service.duplicateTable(command.input) + + return table.id.value + } +} diff --git a/packages/command-handlers/src/handlers/index.ts b/packages/command-handlers/src/handlers/index.ts index cda767ce4..b47af0b0d 100644 --- a/packages/command-handlers/src/handlers/index.ts +++ b/packages/command-handlers/src/handlers/index.ts @@ -22,6 +22,7 @@ import { DeleteWebhookCommandHandler } from "./delete-webhook.command-handler" import { DisableShareCommandHandler } from "./disable-share.command-handler" import { DuplicateRecordCommandHandler } from "./duplicate-record.command-handler" import { DuplicateTableFieldCommandHandler } from "./duplicate-table-field.command-handler" +import { DuplicateTableCommandHandler } from "./duplicate-table.command-handler" import { DuplicateViewCommandHandler } from "./duplicate-view.command-handler" import { EnableShareCommandHandler } from "./enable-share.command-handler" import { ExportViewCommandHandler } from "./export-view.command-handler" @@ -88,4 +89,5 @@ export const commandHandlers = [ UpdateSpaceCommandHandler, DeleteSpaceCommandHandler, DeleteWebhookCommandHandler, + DuplicateTableCommandHandler, ] diff --git a/packages/commands/src/duplicate-table.command.ts b/packages/commands/src/duplicate-table.command.ts new file mode 100644 index 000000000..690fdceba --- /dev/null +++ b/packages/commands/src/duplicate-table.command.ts @@ -0,0 +1,16 @@ +import { Command, type CommandProps } from "@undb/domain" +import { duplicateTableDTO, type IDuplicateTableDTO } from "@undb/table" +import { z } from "@undb/zod" + +export const duplicateTableCommand = duplicateTableDTO + +export type IDuplicateTableCommand = z.infer + +export class DuplicateTableCommand extends Command { + public readonly input: IDuplicateTableDTO + + constructor(props: CommandProps) { + super(props) + this.input = props + } +} diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index a8bfa64fd..5a973766e 100644 --- a/packages/commands/src/index.ts +++ b/packages/commands/src/index.ts @@ -22,6 +22,7 @@ export * from "./delete-webhook.command" export * from "./disable-share.command" export * from "./duplicate-record.command" export * from "./duplicate-table-field.command" +export * from "./duplicate-table.command" export * from "./duplicate-view.command" export * from "./enable-share.command" export * from "./export-view.command" diff --git a/packages/persistence/src/table/table.filter-visitor.ts b/packages/persistence/src/table/table.filter-visitor.ts index 123dddad2..f270c1d3b 100644 --- a/packages/persistence/src/table/table.filter-visitor.ts +++ b/packages/persistence/src/table/table.filter-visitor.ts @@ -1,5 +1,6 @@ import { mustGetCurrentSpaceId } from "@undb/context/server" import type { + DuplicatedTableSpecification, ITableSpecVisitor, TableBaseIdSpecification, TableDo, @@ -66,6 +67,9 @@ export class TableFilterVisitor extends AbstractQBVisitor implements IT ), ) } + withDuplicatedTable(spec: DuplicatedTableSpecification): void { + throw new Error("Method not implemented.") + } withName(name: TableNameSpecification): void { this.addCond(this.eb.eb("name", "=", name.name.value)) } diff --git a/packages/persistence/src/table/table.mutation-visitor.ts b/packages/persistence/src/table/table.mutation-visitor.ts index 306dbe044..efce220b4 100644 --- a/packages/persistence/src/table/table.mutation-visitor.ts +++ b/packages/persistence/src/table/table.mutation-visitor.ts @@ -1,4 +1,5 @@ import type { + DuplicatedTableSpecification, ITableSpecVisitor, TableBaseIdSpecification, TableDo, @@ -108,6 +109,7 @@ export class TableMutationVisitor extends AbstractQBMutationVisitor implements I withDuplicateField(schema: WithDuplicatedFieldSpecification): void { // throw new Error("Method not implemented.") } + withDuplicatedTable(spec: DuplicatedTableSpecification): void {} withoutField(schema: WithoutFieldSpecification): void { this.setData(tables.schema.name, json(this.table.schema.toJSON())) diff --git a/packages/persistence/src/table/table.reference-visitor.ts b/packages/persistence/src/table/table.reference-visitor.ts index 1e7113b3d..305810832 100644 --- a/packages/persistence/src/table/table.reference-visitor.ts +++ b/packages/persistence/src/table/table.reference-visitor.ts @@ -1,5 +1,6 @@ import type { ISpecification } from "@undb/domain" import type { + DuplicatedTableSpecification, ITableSpecVisitor, TableBaseIdSpecification, TableComositeSpecification, @@ -82,6 +83,7 @@ export class TableReferenceVisitor implements ITableSpecVisitor { eb.and([eb.eb("undb_base.name", "=", spec.baseName), eb.eb("undb_table.name", "=", spec.tableName)]), ) } + withDuplicatedTable(spec: DuplicatedTableSpecification): void {} and(left: ISpecification, right: ISpecification): this { left.accept(this) right.accept(this) diff --git a/packages/persistence/src/underlying/reference/join-table.ts b/packages/persistence/src/underlying/reference/join-table.ts index f33fb8aee..632da91cb 100644 --- a/packages/persistence/src/underlying/reference/join-table.ts +++ b/packages/persistence/src/underlying/reference/join-table.ts @@ -17,6 +17,6 @@ export class JoinTable { getTableName() { const { field } = this const { isOwner, foreignTableId } = field - return `$${isOwner ? foreignTableId : this.table.id.value}_${isOwner ? field.id.value : field.symmetricFieldId!}_join_table` + return `$${isOwner ? foreignTableId : this.table.id.value}_${isOwner ? this.table.id.value + "_" + field.id.value : field.foreignTableId + "_" + field.symmetricFieldId!}_join_table` } } diff --git a/packages/persistence/src/underlying/underlying-table-field.visitor.ts b/packages/persistence/src/underlying/underlying-table-field.visitor.ts index 2f778e84e..826962a23 100644 --- a/packages/persistence/src/underlying/underlying-table-field.visitor.ts +++ b/packages/persistence/src/underlying/underlying-table-field.visitor.ts @@ -133,24 +133,19 @@ export class UnderlyingTableFieldVisitor } reference(field: ReferenceField): void { const joinTable = new JoinTable(this.t.table, field) - const option = field.option.expect("expect reference option") - const isOwner = option.isOwner - - if (isOwner) { - const sql = this.qb.schema - .createTable(joinTable.getTableName()) - .ifNotExists() - .addColumn(joinTable.getSymmetricValueFieldId(), "varchar(10)", (b) => - b.references(`${field.foreignTableId}.${ID_TYPE}`).notNull().onDelete("cascade"), - ) - .addColumn(joinTable.getValueFieldId(), "varchar(10)", (b) => - b.references(`${this.t.table.id.value}.${ID_TYPE}`).notNull().onDelete("cascade"), - ) - .addPrimaryKeyConstraint("primary_key", [joinTable.getSymmetricValueFieldId(), joinTable.getValueFieldId()]) - .compile() - this.addSql(sql) - } + const sql = this.qb.schema + .createTable(joinTable.getTableName()) + .ifNotExists() + .addColumn(joinTable.getSymmetricValueFieldId(), "varchar(10)", (b) => + b.references(`${field.foreignTableId}.${ID_TYPE}`).notNull().onDelete("cascade"), + ) + .addColumn(joinTable.getValueFieldId(), "varchar(10)", (b) => + b.references(`${this.t.table.id.value}.${ID_TYPE}`).notNull().onDelete("cascade"), + ) + .addPrimaryKeyConstraint("primary_key", [joinTable.getSymmetricValueFieldId(), joinTable.getValueFieldId()]) + .compile() + this.addSql(sql) } rollup(field: RollupField): void {} checkbox(field: CheckboxField): void { diff --git a/packages/persistence/src/underlying/underlying-table-spec.visitor.ts b/packages/persistence/src/underlying/underlying-table-spec.visitor.ts index 81633b21a..11abd00e9 100644 --- a/packages/persistence/src/underlying/underlying-table-spec.visitor.ts +++ b/packages/persistence/src/underlying/underlying-table-spec.visitor.ts @@ -1,32 +1,35 @@ import { WontImplementException, type ISpecification, type ISpecVisitor } from "@undb/domain" -import type { - ITableSpecVisitor, - SelectField, - TableBaseIdSpecification, - TableIdSpecification, - TableIdsSpecification, - TableNameSpecification, - TableSchemaSpecification, - TableSpaceIdSpecification, - TableUniqueNameSpecification, - TableViewsSpecification, - UserField, - WithDuplicatedFieldSpecification, - WithForeignRollupFieldSpec, - WithNewFieldSpecification, - WithNewView, - WithoutFieldSpecification, - WithoutView, - WithTableForeignTablesSpec, - WithUpdatedFieldSpecification, - WithView, - WithViewAggregate, - WithViewColor, - WithViewFields, - WithViewFilter, - WithViewIdSpecification, - WithViewOption, - WithViewSort, +import { + ReferenceField, + type DuplicatedTableSpecification, + type ITableSpecVisitor, + type SelectField, + type TableBaseIdSpecification, + type TableDo, + type TableIdSpecification, + type TableIdsSpecification, + type TableNameSpecification, + type TableSchemaSpecification, + type TableSpaceIdSpecification, + type TableUniqueNameSpecification, + type TableViewsSpecification, + type UserField, + type WithDuplicatedFieldSpecification, + type WithForeignRollupFieldSpec, + type WithNewFieldSpecification, + type WithNewView, + type WithoutFieldSpecification, + type WithoutView, + type WithTableForeignTablesSpec, + type WithUpdatedFieldSpecification, + type WithView, + type WithViewAggregate, + type WithViewColor, + type WithViewFields, + type WithViewFilter, + type WithViewIdSpecification, + type WithViewOption, + type WithViewSort, } from "@undb/table" import type { TableFormsSpecification, @@ -40,7 +43,7 @@ import type { IRecordQueryBuilder } from "../qb" import { ConversionContext } from "./conversion/conversion.context" import { ConversionFactory } from "./conversion/conversion.factory" import { JoinTable } from "./reference/join-table" -import type { UnderlyingTable } from "./underlying-table" +import { UnderlyingTable } from "./underlying-table" import { UnderlyingTableFieldVisitor } from "./underlying-table-field.visitor" export class UnderlyingTableSpecVisitor implements ITableSpecVisitor { @@ -119,6 +122,41 @@ export class UnderlyingTableSpecVisitor implements ITableSpecVisitor { } } } + withDuplicatedTable(spec: DuplicatedTableSpecification): void { + const { originalTable, duplicatedTable } = spec + // TODO: virtual fields common util + const getColumns = (table: TableDo) => + table.schema.fields.filter((f) => f.type !== "reference" && f.type !== "rollup").map((f) => f.id.value) + + const duplicateDataSql = this.qb + .insertInto(duplicatedTable.id.value) + .columns(getColumns(duplicatedTable)) + .expression((eb) => eb.selectFrom(originalTable.id.value).select(getColumns(originalTable))) + .compile() + + this.addSql(duplicateDataSql) + + const referenceFields = duplicatedTable.schema.fields.filter((f) => f.type === "reference") + + for (const field of referenceFields) { + const joinTable = new JoinTable(duplicatedTable, field) + const originalField = originalTable.schema.fields.find((f) => f.id.value === field.id.value) + if (!(originalField instanceof ReferenceField)) continue + + const originalJoinTable = new JoinTable(originalTable, originalField) + const duplicateReferenceSql = this.qb + .insertInto(joinTable.getTableName()) + .columns([joinTable.getValueFieldId(), joinTable.getSymmetricValueFieldId()]) + .expression((eb) => + eb + .selectFrom(originalJoinTable.getTableName()) + .select([originalJoinTable.getValueFieldId(), originalJoinTable.getSymmetricValueFieldId()]), + ) + .compile() + + this.addSql(duplicateReferenceSql) + } + } withViewFields(fields: WithViewFields): void {} withForm(views: WithFormSpecification): void {} withForms(views: TableFormsSpecification): void {} diff --git a/packages/persistence/src/underlying/underlying-table.service.ts b/packages/persistence/src/underlying/underlying-table.service.ts index 8e1dc2070..3504da735 100644 --- a/packages/persistence/src/underlying/underlying-table.service.ts +++ b/packages/persistence/src/underlying/underlying-table.service.ts @@ -2,7 +2,7 @@ import { singleton } from "@undb/di" import { createLogger } from "@undb/logger" import type { TableComositeSpecification, TableDo } from "@undb/table" import type { CompiledQuery } from "kysely" -import { getCurrentTransaction } from "../ctx" +import { getAnonymousTransaction, getCurrentTransaction } from "../ctx" import { UnderlyingTable } from "./underlying-table" import { UnderlyingTableFieldVisitor } from "./underlying-table-field.visitor" import { UnderlyingTableSpecVisitor } from "./underlying-table-spec.visitor" @@ -34,7 +34,7 @@ export class UnderlyingTableService { async update(table: TableDo, spec: TableComositeSpecification) { const t = new UnderlyingTable(table) - const trx = getCurrentTransaction() + const trx = getAnonymousTransaction() const visitor = new UnderlyingTableSpecVisitor(t, trx) spec.accept(visitor) diff --git a/packages/table/src/dto/duplicate-table.dto.ts b/packages/table/src/dto/duplicate-table.dto.ts new file mode 100644 index 000000000..13428d6b3 --- /dev/null +++ b/packages/table/src/dto/duplicate-table.dto.ts @@ -0,0 +1,8 @@ +import { z } from "@undb/zod" +import { tableId } from "../table-id.vo" + +export const duplicateTableDTO = z.object({ + tableId, +}) + +export type IDuplicateTableDTO = z.infer diff --git a/packages/table/src/dto/index.ts b/packages/table/src/dto/index.ts index ceed9a808..da37d4f84 100644 --- a/packages/table/src/dto/index.ts +++ b/packages/table/src/dto/index.ts @@ -4,6 +4,7 @@ export * from "./delete-table-field.dto" export * from "./delete-table.dto" export * from "./delete-view.dto" export * from "./duplicate-table-field.dto" +export * from "./duplicate-table.dto" export * from "./duplicate-view.dto" export * from "./set-table-form.dto" export * from "./set-table-rls.dto" diff --git a/packages/table/src/methods/duplicate-table.method.ts b/packages/table/src/methods/duplicate-table.method.ts new file mode 100644 index 000000000..b8153a82e --- /dev/null +++ b/packages/table/src/methods/duplicate-table.method.ts @@ -0,0 +1,20 @@ +import type { IDuplicateTableDTO } from "../dto/duplicate-table.dto" +import { getNextName } from "../modules" +import { DuplicatedTableSpecification } from "../specifications" +import { TableIdVo } from "../table-id.vo" +import { TableCreator } from "../table.builder" +import { TableDo } from "../table.do" + +export function duplicateTableMethod( + this: TableDo, + dto: IDuplicateTableDTO, + tableNames: string[], +): DuplicatedTableSpecification { + const duplicated = new TableCreator().fromJSON({ + ...this.toJSON(), + id: TableIdVo.create().value, + name: getNextName(tableNames, this.name.value), + }) + + return new DuplicatedTableSpecification(this, duplicated) +} diff --git a/packages/table/src/services/methods/duplicate-table.method.ts b/packages/table/src/services/methods/duplicate-table.method.ts new file mode 100644 index 000000000..140635510 --- /dev/null +++ b/packages/table/src/services/methods/duplicate-table.method.ts @@ -0,0 +1,36 @@ +import { Some } from "@undb/domain" +import type { IDuplicateTableDTO } from "../../dto" +import { ReferenceField } from "../../modules" +import { TableBaseIdSpecification } from "../../specifications/table-base-id.specification" +import { WithUpdatedFieldSpecification } from "../../specifications/table-schema.specification" +import { TableIdVo } from "../../table-id.vo" +import type { TableDo } from "../../table.do" +import type { TableService } from "../table.service" + +export async function duplicateTableMethod(this: TableService, dto: IDuplicateTableDTO): Promise { + const table = (await this.repository.findOneById(new TableIdVo(dto.tableId))).expect("Not found table") + const tables = await this.repository.find(Some(new TableBaseIdSpecification(table.baseId))) + + const tableNames = tables.map((t) => t.name.value) + const spec = table.$duplicate(dto, tableNames) + + const { duplicatedTable } = spec + await this.repository.insert(spec.duplicatedTable) + await this.repository.updateOneById(spec.duplicatedTable, Some(spec)) + + const referenceFields = duplicatedTable.schema.fields.filter((f) => f.type === "reference") + for (const referenceField of referenceFields) { + const foreignTable = (await this.repository.findOneById(new TableIdVo(referenceField.foreignTableId))).expect( + "Not found foreign table", + ) + + const symmetricField = ReferenceField.createSymmetricField(duplicatedTable, foreignTable, referenceField) + const foreignSpec = foreignTable.$createFieldSpec(symmetricField) + const spec = new WithUpdatedFieldSpecification(referenceField, referenceField) + + await this.repository.updateOneById(duplicatedTable, Some(spec)) + await this.repository.updateOneById(foreignTable, foreignSpec) + } + + return table +} diff --git a/packages/table/src/services/table.service.ts b/packages/table/src/services/table.service.ts index 5d0fb3b71..7d6870ca6 100644 --- a/packages/table/src/services/table.service.ts +++ b/packages/table/src/services/table.service.ts @@ -5,6 +5,7 @@ import type { ICreateTableFieldDTO, IDeleteTableDTO, IDeleteTableFieldDTO, + IDuplicateTableDTO, IDuplicateTableFieldDTO, IUpdateTableDTO, IUpdateTableFieldDTO, @@ -30,6 +31,7 @@ import { createTableMethod } from "./methods/create-table.method" import { deleteTableFieldMethod } from "./methods/delete-table-field.method" import { deleteTableMethod } from "./methods/delete-table.method" import { duplicateTableFieldMethod } from "./methods/duplicate-table-field.method" +import { duplicateTableMethod } from "./methods/duplicate-table.method" import { exportViewMethod } from "./methods/export-view.method" import { updateTableFieldMethod } from "./methods/update-table-field.method" import { updateTableMethod } from "./methods/update-table.method" @@ -38,6 +40,7 @@ export interface ITableService { createTable(dto: ICreateTableDTO): Promise updateTable(dto: IUpdateTableDTO): Promise deleteTable(dto: IDeleteTableDTO): Promise + duplicateTable(dto: IDuplicateTableDTO): Promise createTableField(dto: ICreateTableFieldDTO): Promise updateTableField(dto: IUpdateTableFieldDTO): Promise @@ -70,6 +73,7 @@ export class TableService implements ITableService { createTable = createTableMethod updateTable = updateTableMethod deleteTable = deleteTableMethod + duplicateTable = duplicateTableMethod createTableField = createTableFieldMethod deleteTableField = deleteTableFieldMethod diff --git a/packages/table/src/specifications/index.ts b/packages/table/src/specifications/index.ts index f38067d27..64d080cc5 100644 --- a/packages/table/src/specifications/index.ts +++ b/packages/table/src/specifications/index.ts @@ -9,3 +9,4 @@ export * from "./table-view.specification" export * from "./table-views.specification" export * from "./table-visitor.interface" export { TableComositeSpecification } from "./table.composite-specification" +export * from "./table.specification" diff --git a/packages/table/src/specifications/table-visitor.interface.ts b/packages/table/src/specifications/table-visitor.interface.ts index 600a867fb..ed9d9336e 100644 --- a/packages/table/src/specifications/table-visitor.interface.ts +++ b/packages/table/src/specifications/table-visitor.interface.ts @@ -32,8 +32,10 @@ import type { WithViewSort, } from "./table-view.specification" import type { TableViewsSpecification } from "./table-views.specification" +import type { DuplicatedTableSpecification } from "./table.specification" export interface ITableSpecVisitor extends ISpecVisitor { + withDuplicatedTable(spec: DuplicatedTableSpecification): void withId(id: TableIdSpecification): void withBaseId(id: TableBaseIdSpecification): void withSpaceId(id: TableSpaceIdSpecification): void diff --git a/packages/table/src/specifications/table.specification.ts b/packages/table/src/specifications/table.specification.ts new file mode 100644 index 000000000..92e66d01d --- /dev/null +++ b/packages/table/src/specifications/table.specification.ts @@ -0,0 +1,23 @@ +import { Ok, type Result } from "@undb/domain" +import type { TableDo } from "../table.do" +import type { ITableSpecVisitor } from "./table-visitor.interface" +import { TableComositeSpecification } from "./table.composite-specification" + +export class DuplicatedTableSpecification extends TableComositeSpecification { + constructor( + public readonly originalTable: TableDo, + public readonly duplicatedTable: TableDo, + ) { + super() + } + isSatisfiedBy(t: TableDo): boolean { + throw new Error("Method not implemented.") + } + mutate(t: TableDo): Result { + throw new Error("Method not implemented.") + } + accept(v: ITableSpecVisitor): Result { + v.withDuplicatedTable(this) + return Ok(undefined) + } +} diff --git a/packages/table/src/table.do.ts b/packages/table/src/table.do.ts index 016c5ed7f..08c4b56cf 100644 --- a/packages/table/src/table.do.ts +++ b/packages/table/src/table.do.ts @@ -8,6 +8,7 @@ import { createViewMethod } from "./methods/create-view.method" import { deleteFieldMethod } from "./methods/delete-field.method" import { deleteViewMethod } from "./methods/delete-view.method" import { duplicateFieldMethod } from "./methods/duplicate-field.method" +import { duplicateTableMethod } from "./methods/duplicate-table.method" import { duplicateViewMethod } from "./methods/duplicate-view.method" import { setTableForm } from "./methods/set-table-form.method" import { setTableRLS } from "./methods/set-table-rls.method" @@ -49,6 +50,7 @@ export class TableDo extends AggregateRoot { public rls: Option = None $update = updateTable + $duplicate = duplicateTableMethod $updateView = updateView $setViewFilter = setViewFilter $setViewOption = setViewOption diff --git a/packages/trpc/src/router.ts b/packages/trpc/src/router.ts index c617f9b24..e57dbeb44 100644 --- a/packages/trpc/src/router.ts +++ b/packages/trpc/src/router.ts @@ -20,6 +20,7 @@ import { DeleteWebhookCommand, DisableShareCommand, DuplicateRecordCommand, + DuplicateTableCommand, DuplicateTableFieldCommand, DuplicateViewCommand, EnableShareCommand, @@ -62,6 +63,7 @@ import { deleteWebhookCommand, disableShareCommand, duplicateRecordCommand, + duplicateTableCommand, duplicateTableFieldCommand, duplicateViewCommand, enableShareCommand, @@ -206,6 +208,11 @@ const tableRouter = t.router({ .use(authz("table:update")) .input(updateTableCommand) .mutation(({ input }) => commandBus.execute(new UpdateTableCommand(input))), + duplicate: privateProcedure + .use(authz("table:create")) + .input(duplicateTableCommand) + .output(z.string()) + .mutation(({ input }) => commandBus.execute(new DuplicateTableCommand(input))), delete: privateProcedure .use(authz("table:delete")) .input(deleteTableCommand)