Skip to content

Commit

Permalink
feat: duplicate include data
Browse files Browse the repository at this point in the history
  • Loading branch information
nichenqin committed Aug 16, 2024
1 parent b26ca76 commit 9600296
Show file tree
Hide file tree
Showing 15 changed files with 192 additions and 58 deletions.
96 changes: 75 additions & 21 deletions apps/frontend/src/lib/components/blocks/base/duplicate-base.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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<IBaseDTO, "spaceId">
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) => {
Expand All @@ -22,6 +53,8 @@
toast.error(error.message)
},
})
const { form: formData, enhance } = form
</script>

<Dialog.Root
Expand All @@ -38,26 +71,47 @@
<Dialog.Description>Create a new base include all tables in base.</Dialog.Description>
</Dialog.Header>

<div class="item-center flex justify-end gap-2">
<Button
on:click={() => {
closeModal(DUPLICATE_BASE_MODAL)
}}
variant="outline"
>
Cancel
</Button>
<Button
disabled={$duplicateBaseMutation.isPending}
on:click={() => {
$duplicateBaseMutation.mutate({ id: base.id })
}}
>
{#if $duplicateBaseMutation.isPending}
<LoaderCircleIcon class="mr-2 h-5 w-5 animate-spin" />
{/if}
Duplicate
</Button>
</div>
<form method="POST" use:enhance>
<Form.Field {form} name="name">
<Form.Control let:attrs>
<Form.Label>Name</Form.Label>
<Input {...attrs} bind:value={$formData.name} />
</Form.Control>
<Form.Description>This is base display name.</Form.Description>
<Form.FieldErrors />
</Form.Field>
<Form.Field {form} name="includeData" class="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
<Form.Control let:attrs>
<Checkbox {...attrs} bind:checked={$formData.includeData} />
<div class="space-y-1 leading-none">
<Form.Label>Include data</Form.Label>
<Form.Description>Include data in the new base.</Form.Description>
</div>
<input name={attrs.name} value={$formData.includeData} hidden />
</Form.Control>
</Form.Field>

<div class="mt-4 flex items-center justify-end gap-2">
<Button
on:click={() => {
closeModal(DUPLICATE_BASE_MODAL)
}}
variant="outline"
>
Cancel
</Button>
<Form.Button
disabled={$duplicateBaseMutation.isPending}
on:click={() => {
$duplicateBaseMutation.mutate({ id: base.id })
}}
>
{#if $duplicateBaseMutation.isPending}
<LoaderCircleIcon class="mr-2 h-5 w-5 animate-spin" />
{/if}
Duplicate
</Form.Button>
</div>
</form>
</Dialog.Content>
</Dialog.Root>
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -40,26 +73,41 @@
</Dialog.Description>
</Dialog.Header>

<div class="item-center flex justify-end gap-2">
<Button
on:click={() => {
closeModal(DUPLICATE_TABLE_MODAL)
}}
variant="outline"
>
Cancel
</Button>
<Button
disabled={$duplicateTableMutation.isPending}
on:click={() => {
$duplicateTableMutation.mutate({ tableId: table.id.value })
}}
>
{#if $duplicateTableMutation.isPending}
<LoaderCircleIcon class="mr-2 h-5 w-5 animate-spin" />
{/if}
Duplicate
</Button>
</div>
<form method="POST" use:enhance>
<Form.Field {form} name="name">
<Form.Control let:attrs>
<Form.Label>Name</Form.Label>
<Input {...attrs} bind:value={$formData.name} />
</Form.Control>
<Form.Description>This is new table display name.</Form.Description>
<Form.FieldErrors />
</Form.Field>
<Form.Field {form} name="includeData" class="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
<Form.Control let:attrs>
<Checkbox {...attrs} bind:checked={$formData.includeData} />
<div class="space-y-1 leading-none">
<Form.Label>Include data</Form.Label>
<Form.Description>Include data in the new table.</Form.Description>
</div>
<input name={attrs.name} value={$formData.includeData} hidden />
</Form.Control>
</Form.Field>
<div class="item-center mt-2 flex justify-end gap-2">
<Button
on:click={() => {
closeModal(DUPLICATE_TABLE_MODAL)
}}
variant="outline"
>
Cancel
</Button>
<Form.Button disabled={$duplicateTableMutation.isPending}>
{#if $duplicateTableMutation.isPending}
<LoaderCircleIcon class="mr-2 h-5 w-5 animate-spin" />
{/if}
Duplicate
</Form.Button>
</div>
</form>
</Dialog.Content>
</Dialog.Root>
6 changes: 4 additions & 2 deletions packages/base/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -39,11 +40,12 @@ export class Base extends AggregateRoot<any> {
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)
Expand Down
6 changes: 5 additions & 1 deletion packages/base/src/dto/duplicate-base.dto.ts
Original file line number Diff line number Diff line change
@@ -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<typeof duplicateBaseDTO>
6 changes: 5 additions & 1 deletion packages/base/src/rules/base-name-should-be-unique.rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ class BaseNameShouldBeUniqueError extends ExceptionBase {
}

export class BaseNameShouldBeUnique extends DomainRules<BaseNameShouldBeUniqueError> {
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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class DuplicateBaseCommandHandler implements ICommandHandler<DuplicateBas
async execute(command: DuplicateBaseCommand): Promise<any> {
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
}
Expand Down
4 changes: 4 additions & 0 deletions packages/commands/src/duplicate-base.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ export type IDuplicateBaseCommand = z.infer<typeof duplicateBaseCommand>

export class DuplicateBaseCommand extends Command implements IDuplicateBaseCommand {
public readonly id: string
public readonly name?: string
public readonly includeData?: boolean

constructor(props: CommandProps<IDuplicateBaseCommand>) {
super(props)
this.id = props.id
this.name = props.name
this.includeData = props.includeData
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand Down
3 changes: 3 additions & 0 deletions packages/table/src/dto/duplicate-table.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof duplicateTableDTO>
4 changes: 2 additions & 2 deletions packages/table/src/methods/duplicate-table.method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
13 changes: 8 additions & 5 deletions packages/table/src/services/methods/duplicate-base.method.ts
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit 9600296

Please sign in to comment.