From 9600296dba61eb427798042b586f9e336fd0efa3 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Fri, 16 Aug 2024 21:32:38 +0800 Subject: [PATCH] feat: duplicate include data --- .../blocks/base/duplicate-base.svelte | 96 +++++++++++++++---- .../duplicate-table/duplicate-table.svelte | 90 +++++++++++++---- packages/base/src/base.ts | 6 +- packages/base/src/dto/duplicate-base.dto.ts | 6 +- .../rules/base-name-should-be-unique.rule.ts | 6 +- .../duplicate-base.command-handler.ts | 2 +- .../commands/src/duplicate-base.command.ts | 4 + .../underlying-table-spec.visitor.ts | 4 + packages/table/src/dto/duplicate-table.dto.ts | 3 + .../src/methods/duplicate-table.method.ts | 4 +- .../services/methods/duplicate-base.method.ts | 13 ++- .../methods/duplicate-table.method.ts | 8 +- .../methods/duplicate-tables.method.ts | 3 + packages/table/src/services/table.service.ts | 4 +- .../src/specifications/table.specification.ts | 1 + 15 files changed, 192 insertions(+), 58 deletions(-) diff --git a/apps/frontend/src/lib/components/blocks/base/duplicate-base.svelte b/apps/frontend/src/lib/components/blocks/base/duplicate-base.svelte index ad0b71fd5..e1ff72110 100644 --- a/apps/frontend/src/lib/components/blocks/base/duplicate-base.svelte +++ b/apps/frontend/src/lib/components/blocks/base/duplicate-base.svelte @@ -3,14 +3,45 @@ import Button from "$lib/components/ui/button/button.svelte" import * as Dialog from "$lib/components/ui/dialog" import { closeModal, DUPLICATE_BASE_MODAL, isModalOpen } from "$lib/store/modal.store" + import * as Form from "$lib/components/ui/form" + import { Input } from "$lib/components/ui/input" import { trpc } from "$lib/trpc/client" import { createMutation } from "@tanstack/svelte-query" import type { Base, IBaseDTO } from "@undb/base" + import { duplicateBaseCommand } from "@undb/commands" import { LoaderCircleIcon } from "lucide-svelte" import { toast } from "svelte-sonner" + import { defaults, superForm } from "sveltekit-superforms" + import { zodClient } from "sveltekit-superforms/adapters" + import { Checkbox } from "$lib/components/ui/checkbox" export let base: Omit + const form = superForm( + defaults( + { + id: base.id, + name: "", + includeData: true, + }, + zodClient(duplicateBaseCommand), + ), + { + SPA: true, + dataType: "json", + validators: zodClient(duplicateBaseCommand), + resetForm: false, + invalidateAll: false, + onUpdate(event) { + if (!event.form.valid) { + return + } + + $duplicateBaseMutation.mutate(event.form.data) + }, + }, + ) + const duplicateBaseMutation = createMutation({ mutationFn: trpc.base.duplicate.mutate, onSuccess: async (data) => { @@ -22,6 +53,8 @@ toast.error(error.message) }, }) + + const { form: formData, enhance } = form Create a new base include all tables in base. -
- - -
+
+ + + Name + + + This is base display name. + + + + + +
+ Include data + Include data in the new base. +
+ +
+
+ +
+ + { + $duplicateBaseMutation.mutate({ id: base.id }) + }} + > + {#if $duplicateBaseMutation.isPending} + + {/if} + Duplicate + +
+
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 index 21c4380e0..a7b491814 100644 --- a/apps/frontend/src/lib/components/blocks/duplicate-table/duplicate-table.svelte +++ b/apps/frontend/src/lib/components/blocks/duplicate-table/duplicate-table.svelte @@ -5,12 +5,45 @@ import { closeModal, DUPLICATE_TABLE_MODAL, isModalOpen } from "$lib/store/modal.store" import { trpc } from "$lib/trpc/client" import { createMutation } from "@tanstack/svelte-query" + import { duplicateTableCommand } from "@undb/commands" import type { TableDo } from "@undb/table" import { LoaderCircleIcon } from "lucide-svelte" import { toast } from "svelte-sonner" + import { defaults, superForm } from "sveltekit-superforms" + import { zodClient } from "sveltekit-superforms/adapters" + import * as Form from "$lib/components/ui/form" + import { Input } from "$lib/components/ui/input" + import { Checkbox } from "$lib/components/ui/checkbox" export let table: TableDo + const form = superForm( + defaults( + { + tableId: table.id.value, + name: "", + includeData: true, + }, + zodClient(duplicateTableCommand), + ), + { + SPA: true, + dataType: "json", + validators: zodClient(duplicateTableCommand), + resetForm: false, + invalidateAll: false, + onUpdate(event) { + if (!event.form.valid) { + return + } + + $duplicateTableMutation.mutate(event.form.data) + }, + }, + ) + + const { form: formData, enhance } = form + const duplicateTableMutation = createMutation({ mutationFn: trpc.table.duplicate.mutate, onSuccess: async (data) => { @@ -40,26 +73,41 @@ -
- - -
+
+ + + Name + + + This is new table display name. + + + + + +
+ Include data + Include data in the new table. +
+ +
+
+
+ + + {#if $duplicateTableMutation.isPending} + + {/if} + Duplicate + +
+
diff --git a/packages/base/src/base.ts b/packages/base/src/base.ts index 0eda7e40b..115555413 100644 --- a/packages/base/src/base.ts +++ b/packages/base/src/base.ts @@ -4,6 +4,7 @@ import { getNextName } from "@undb/utils" import type { Option } from "oxide.ts" import { BaseFactory } from "./base.factory.js" import type { IBaseDTO } from "./dto/base.dto.js" +import type { IDuplicateBaseDTO } from "./dto/duplicate-base.dto.js" import type { IUpdateBaseDTO } from "./dto/update-base.dto.js" import { BaseUpdatedEvent } from "./events/base-updated.event.js" import type { IBaseSpecification } from "./interface.js" @@ -39,11 +40,12 @@ export class Base extends AggregateRoot { return spec } - public $duplicate(baseNames: string[]): DuplicatedBaseSpecification { + public $duplicate(dto: IDuplicateBaseDTO, baseNames: string[]): DuplicatedBaseSpecification { const duplicatedBase = BaseFactory.fromJSON({ ...this.toJSON(), id: BaseId.create().value, - name: getNextName(baseNames, this.name.value), + spaceId: dto.spaceId ?? this.spaceId, + name: dto.name ?? getNextName(baseNames, this.name.value), }) return new DuplicatedBaseSpecification(this, duplicatedBase) diff --git a/packages/base/src/dto/duplicate-base.dto.ts b/packages/base/src/dto/duplicate-base.dto.ts index 44e1e4729..61c4f1af1 100644 --- a/packages/base/src/dto/duplicate-base.dto.ts +++ b/packages/base/src/dto/duplicate-base.dto.ts @@ -1,8 +1,12 @@ +import { spaceIdSchema } from "@undb/space" import { z } from "@undb/zod" -import { baseIdSchema } from "../value-objects" +import { baseIdSchema, baseNameSchema } from "../value-objects" export const duplicateBaseDTO = z.object({ id: baseIdSchema, + spaceId: spaceIdSchema.optional(), + name: baseNameSchema.optional(), + includeData: z.boolean().optional(), }) export type IDuplicateBaseDTO = z.infer diff --git a/packages/base/src/rules/base-name-should-be-unique.rule.ts b/packages/base/src/rules/base-name-should-be-unique.rule.ts index 34609fa85..d110de50e 100644 --- a/packages/base/src/rules/base-name-should-be-unique.rule.ts +++ b/packages/base/src/rules/base-name-should-be-unique.rule.ts @@ -9,13 +9,17 @@ class BaseNameShouldBeUniqueError extends ExceptionBase { } export class BaseNameShouldBeUnique extends DomainRules { - constructor(private readonly hasBase: boolean) { + constructor(private readonly hasBase: boolean | string[]) { super() } override err = new BaseNameShouldBeUniqueError() override isBroken(): boolean { + if (Array.isArray(this.hasBase)) { + return this.hasBase.length !== new Set(this.hasBase).size + } + return !!this.hasBase } } diff --git a/packages/command-handlers/src/handlers/duplicate-base.command-handler.ts b/packages/command-handlers/src/handlers/duplicate-base.command-handler.ts index d97197610..069d8290b 100644 --- a/packages/command-handlers/src/handlers/duplicate-base.command-handler.ts +++ b/packages/command-handlers/src/handlers/duplicate-base.command-handler.ts @@ -22,7 +22,7 @@ export class DuplicateBaseCommandHandler implements ICommandHandler { const base = (await this.baseRepository.findOneById(command.id)).expect("Base not found") - const duplicatedBase = await this.tableService.duplicateBase(base, mustGetCurrentSpaceId()) + const duplicatedBase = await this.tableService.duplicateBase(base, mustGetCurrentSpaceId(), command) return duplicatedBase.id.value } diff --git a/packages/commands/src/duplicate-base.command.ts b/packages/commands/src/duplicate-base.command.ts index 6be6bfab0..e9aa7b365 100644 --- a/packages/commands/src/duplicate-base.command.ts +++ b/packages/commands/src/duplicate-base.command.ts @@ -8,9 +8,13 @@ export type IDuplicateBaseCommand = z.infer export class DuplicateBaseCommand extends Command implements IDuplicateBaseCommand { public readonly id: string + public readonly name?: string + public readonly includeData?: boolean constructor(props: CommandProps) { super(props) this.id = props.id + this.name = props.name + this.includeData = props.includeData } } diff --git a/packages/persistence/src/underlying/underlying-table-spec.visitor.ts b/packages/persistence/src/underlying/underlying-table-spec.visitor.ts index ee5faf0b2..619f19286 100644 --- a/packages/persistence/src/underlying/underlying-table-spec.visitor.ts +++ b/packages/persistence/src/underlying/underlying-table-spec.visitor.ts @@ -123,6 +123,10 @@ export class UnderlyingTableSpecVisitor implements ITableSpecVisitor { } } withDuplicatedTable(spec: DuplicatedTableSpecification): void { + if (!spec.includeData) { + return + } + const { originalTable, duplicatedTable } = spec // TODO: virtual fields common util const getColumns = (table: TableDo) => diff --git a/packages/table/src/dto/duplicate-table.dto.ts b/packages/table/src/dto/duplicate-table.dto.ts index 24594fed9..31443bd22 100644 --- a/packages/table/src/dto/duplicate-table.dto.ts +++ b/packages/table/src/dto/duplicate-table.dto.ts @@ -2,11 +2,14 @@ import { baseIdSchema } from "@undb/base" import { spaceIdSchema } from "@undb/space" import { z } from "@undb/zod" import { tableId } from "../table-id.vo" +import { tableName } from "../table-name.vo" export const duplicateTableDTO = z.object({ tableId, + name: tableName.optional(), baseId: baseIdSchema.optional(), spaceId: spaceIdSchema.optional(), + includeData: z.boolean().optional(), }) export type IDuplicateTableDTO = z.infer diff --git a/packages/table/src/methods/duplicate-table.method.ts b/packages/table/src/methods/duplicate-table.method.ts index f4ab59db6..8302c7ba1 100644 --- a/packages/table/src/methods/duplicate-table.method.ts +++ b/packages/table/src/methods/duplicate-table.method.ts @@ -15,8 +15,8 @@ export function duplicateTableMethod( id: dto.tableId ?? TableIdVo.create().value, baseId: dto.baseId ?? this.baseId, spaceId: dto.spaceId ?? this.spaceId, - name: getNextName(tableNames, this.name.value), + name: dto.name ?? getNextName(tableNames, this.name.value), }) - return new DuplicatedTableSpecification(this, duplicated) + return new DuplicatedTableSpecification(this, duplicated, dto.includeData ?? false) } diff --git a/packages/table/src/services/methods/duplicate-base.method.ts b/packages/table/src/services/methods/duplicate-base.method.ts index f96087a97..10b6dd34f 100644 --- a/packages/table/src/services/methods/duplicate-base.method.ts +++ b/packages/table/src/services/methods/duplicate-base.method.ts @@ -1,20 +1,23 @@ -import { WithBaseSpaceId, type Base } from "@undb/base" -import { Some } from "@undb/domain" +import { BaseNameShouldBeUnique, WithBaseSpaceId, type Base, type IDuplicateBaseDTO } from "@undb/base" +import { applyRules, Some } from "@undb/domain" import type { ISpaceId } from "@undb/space" import { TableBaseIdSpecification } from "../../specifications" import type { TableService } from "../table.service" -export async function duplicateBaseMethod(this: TableService, base: Base, spaceId: ISpaceId) { +export async function duplicateBaseMethod(this: TableService, base: Base, spaceId: ISpaceId, dto: IDuplicateBaseDTO) { const bases = await this.baseRepository.find(new WithBaseSpaceId(spaceId)) const baseNames = bases.map((b) => b.name.value) - const spec = base.$duplicate(baseNames) + const spec = base.$duplicate(dto, baseNames) const { duplicatedBase } = spec + + applyRules(new BaseNameShouldBeUnique(baseNames.concat(duplicatedBase.name.value))) + await this.baseRepository.insert(duplicatedBase) const tables = await this.repository.find(Some(new TableBaseIdSpecification(base.id.value))) - await this.duplicateTables(spaceId, duplicatedBase, tables) + await this.duplicateTables(spaceId, duplicatedBase, tables, dto.includeData) return duplicatedBase } diff --git a/packages/table/src/services/methods/duplicate-table.method.ts b/packages/table/src/services/methods/duplicate-table.method.ts index 140635510..2f8fdd5b0 100644 --- a/packages/table/src/services/methods/duplicate-table.method.ts +++ b/packages/table/src/services/methods/duplicate-table.method.ts @@ -1,6 +1,7 @@ -import { Some } from "@undb/domain" +import { applyRules, Some } from "@undb/domain" import type { IDuplicateTableDTO } from "../../dto" import { ReferenceField } from "../../modules" +import { TableNameShouldBeUnique } from "../../rules/table-name-should-be-unique.rule" import { TableBaseIdSpecification } from "../../specifications/table-base-id.specification" import { WithUpdatedFieldSpecification } from "../../specifications/table-schema.specification" import { TableIdVo } from "../../table-id.vo" @@ -12,9 +13,12 @@ export async function duplicateTableMethod(this: TableService, dto: IDuplicateTa 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 spec = table.$duplicate({ ...dto, tableId: TableIdVo.create().value }, tableNames) const { duplicatedTable } = spec + + applyRules(new TableNameShouldBeUnique(tableNames.concat(duplicatedTable.name.value))) + await this.repository.insert(spec.duplicatedTable) await this.repository.updateOneById(spec.duplicatedTable, Some(spec)) diff --git a/packages/table/src/services/methods/duplicate-tables.method.ts b/packages/table/src/services/methods/duplicate-tables.method.ts index f935e2be9..c4e14ac43 100644 --- a/packages/table/src/services/methods/duplicate-tables.method.ts +++ b/packages/table/src/services/methods/duplicate-tables.method.ts @@ -11,6 +11,7 @@ export async function duplicateTablesMethod( spaceId: ISpaceId, base: Base, tables: TableDo[], + includeData: boolean = false, ): Promise { const idsMap = new Map() const specs: DuplicatedTableSpecification[] = [] @@ -26,10 +27,12 @@ export async function duplicateTablesMethod( tableId: idsMap.get(table.id.value)!, baseId: base.id.value, spaceId, + includeData, }, [], ) specs.push(spec) + const { duplicatedTable } = spec duplicatedTables.push(duplicatedTable) diff --git a/packages/table/src/services/table.service.ts b/packages/table/src/services/table.service.ts index 83d792345..511123495 100644 --- a/packages/table/src/services/table.service.ts +++ b/packages/table/src/services/table.service.ts @@ -1,4 +1,4 @@ -import { injectBaseRepository, type Base, type IBaseRepository } from "@undb/base" +import { injectBaseRepository, type Base, type IBaseRepository, type IDuplicateBaseDTO } from "@undb/base" import { singleton } from "@undb/di" import { createLogger } from "@undb/logger" import type { ISpaceId } from "@undb/space" @@ -55,7 +55,7 @@ export interface ITableService { createTableView(dto: ICreateTableViewDTO): Promise exportView(tableId: string, dto: IExportViewDTO): Promise<{ table: TableDo; records: IReadableRecordDTO[] }> - duplicateBase(base: Base, spaceId: ISpaceId): Promise + duplicateBase(base: Base, spaceId: ISpaceId, dto: IDuplicateBaseDTO): Promise duplicateTables(spaceId: ISpaceId, base: Base, tables: TableDo[]): Promise } diff --git a/packages/table/src/specifications/table.specification.ts b/packages/table/src/specifications/table.specification.ts index 92e66d01d..f73ce86fe 100644 --- a/packages/table/src/specifications/table.specification.ts +++ b/packages/table/src/specifications/table.specification.ts @@ -7,6 +7,7 @@ export class DuplicatedTableSpecification extends TableComositeSpecification { constructor( public readonly originalTable: TableDo, public readonly duplicatedTable: TableDo, + public readonly includeData: boolean, ) { super() }