Skip to content

Commit 9600296

Browse files
committed
feat: duplicate include data
1 parent b26ca76 commit 9600296

File tree

15 files changed

+192
-58
lines changed

15 files changed

+192
-58
lines changed

apps/frontend/src/lib/components/blocks/base/duplicate-base.svelte

Lines changed: 75 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,45 @@
33
import Button from "$lib/components/ui/button/button.svelte"
44
import * as Dialog from "$lib/components/ui/dialog"
55
import { closeModal, DUPLICATE_BASE_MODAL, isModalOpen } from "$lib/store/modal.store"
6+
import * as Form from "$lib/components/ui/form"
7+
import { Input } from "$lib/components/ui/input"
68
import { trpc } from "$lib/trpc/client"
79
import { createMutation } from "@tanstack/svelte-query"
810
import type { Base, IBaseDTO } from "@undb/base"
11+
import { duplicateBaseCommand } from "@undb/commands"
912
import { LoaderCircleIcon } from "lucide-svelte"
1013
import { toast } from "svelte-sonner"
14+
import { defaults, superForm } from "sveltekit-superforms"
15+
import { zodClient } from "sveltekit-superforms/adapters"
16+
import { Checkbox } from "$lib/components/ui/checkbox"
1117
1218
export let base: Omit<IBaseDTO, "spaceId">
1319
20+
const form = superForm(
21+
defaults(
22+
{
23+
id: base.id,
24+
name: "",
25+
includeData: true,
26+
},
27+
zodClient(duplicateBaseCommand),
28+
),
29+
{
30+
SPA: true,
31+
dataType: "json",
32+
validators: zodClient(duplicateBaseCommand),
33+
resetForm: false,
34+
invalidateAll: false,
35+
onUpdate(event) {
36+
if (!event.form.valid) {
37+
return
38+
}
39+
40+
$duplicateBaseMutation.mutate(event.form.data)
41+
},
42+
},
43+
)
44+
1445
const duplicateBaseMutation = createMutation({
1546
mutationFn: trpc.base.duplicate.mutate,
1647
onSuccess: async (data) => {
@@ -22,6 +53,8 @@
2253
toast.error(error.message)
2354
},
2455
})
56+
57+
const { form: formData, enhance } = form
2558
</script>
2659

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

41-
<div class="item-center flex justify-end gap-2">
42-
<Button
43-
on:click={() => {
44-
closeModal(DUPLICATE_BASE_MODAL)
45-
}}
46-
variant="outline"
47-
>
48-
Cancel
49-
</Button>
50-
<Button
51-
disabled={$duplicateBaseMutation.isPending}
52-
on:click={() => {
53-
$duplicateBaseMutation.mutate({ id: base.id })
54-
}}
55-
>
56-
{#if $duplicateBaseMutation.isPending}
57-
<LoaderCircleIcon class="mr-2 h-5 w-5 animate-spin" />
58-
{/if}
59-
Duplicate
60-
</Button>
61-
</div>
74+
<form method="POST" use:enhance>
75+
<Form.Field {form} name="name">
76+
<Form.Control let:attrs>
77+
<Form.Label>Name</Form.Label>
78+
<Input {...attrs} bind:value={$formData.name} />
79+
</Form.Control>
80+
<Form.Description>This is base display name.</Form.Description>
81+
<Form.FieldErrors />
82+
</Form.Field>
83+
<Form.Field {form} name="includeData" class="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
84+
<Form.Control let:attrs>
85+
<Checkbox {...attrs} bind:checked={$formData.includeData} />
86+
<div class="space-y-1 leading-none">
87+
<Form.Label>Include data</Form.Label>
88+
<Form.Description>Include data in the new base.</Form.Description>
89+
</div>
90+
<input name={attrs.name} value={$formData.includeData} hidden />
91+
</Form.Control>
92+
</Form.Field>
93+
94+
<div class="mt-4 flex items-center justify-end gap-2">
95+
<Button
96+
on:click={() => {
97+
closeModal(DUPLICATE_BASE_MODAL)
98+
}}
99+
variant="outline"
100+
>
101+
Cancel
102+
</Button>
103+
<Form.Button
104+
disabled={$duplicateBaseMutation.isPending}
105+
on:click={() => {
106+
$duplicateBaseMutation.mutate({ id: base.id })
107+
}}
108+
>
109+
{#if $duplicateBaseMutation.isPending}
110+
<LoaderCircleIcon class="mr-2 h-5 w-5 animate-spin" />
111+
{/if}
112+
Duplicate
113+
</Form.Button>
114+
</div>
115+
</form>
62116
</Dialog.Content>
63117
</Dialog.Root>

apps/frontend/src/lib/components/blocks/duplicate-table/duplicate-table.svelte

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,45 @@
55
import { closeModal, DUPLICATE_TABLE_MODAL, isModalOpen } from "$lib/store/modal.store"
66
import { trpc } from "$lib/trpc/client"
77
import { createMutation } from "@tanstack/svelte-query"
8+
import { duplicateTableCommand } from "@undb/commands"
89
import type { TableDo } from "@undb/table"
910
import { LoaderCircleIcon } from "lucide-svelte"
1011
import { toast } from "svelte-sonner"
12+
import { defaults, superForm } from "sveltekit-superforms"
13+
import { zodClient } from "sveltekit-superforms/adapters"
14+
import * as Form from "$lib/components/ui/form"
15+
import { Input } from "$lib/components/ui/input"
16+
import { Checkbox } from "$lib/components/ui/checkbox"
1117
1218
export let table: TableDo
1319
20+
const form = superForm(
21+
defaults(
22+
{
23+
tableId: table.id.value,
24+
name: "",
25+
includeData: true,
26+
},
27+
zodClient(duplicateTableCommand),
28+
),
29+
{
30+
SPA: true,
31+
dataType: "json",
32+
validators: zodClient(duplicateTableCommand),
33+
resetForm: false,
34+
invalidateAll: false,
35+
onUpdate(event) {
36+
if (!event.form.valid) {
37+
return
38+
}
39+
40+
$duplicateTableMutation.mutate(event.form.data)
41+
},
42+
},
43+
)
44+
45+
const { form: formData, enhance } = form
46+
1447
const duplicateTableMutation = createMutation({
1548
mutationFn: trpc.table.duplicate.mutate,
1649
onSuccess: async (data) => {
@@ -40,26 +73,41 @@
4073
</Dialog.Description>
4174
</Dialog.Header>
4275

43-
<div class="item-center flex justify-end gap-2">
44-
<Button
45-
on:click={() => {
46-
closeModal(DUPLICATE_TABLE_MODAL)
47-
}}
48-
variant="outline"
49-
>
50-
Cancel
51-
</Button>
52-
<Button
53-
disabled={$duplicateTableMutation.isPending}
54-
on:click={() => {
55-
$duplicateTableMutation.mutate({ tableId: table.id.value })
56-
}}
57-
>
58-
{#if $duplicateTableMutation.isPending}
59-
<LoaderCircleIcon class="mr-2 h-5 w-5 animate-spin" />
60-
{/if}
61-
Duplicate
62-
</Button>
63-
</div>
76+
<form method="POST" use:enhance>
77+
<Form.Field {form} name="name">
78+
<Form.Control let:attrs>
79+
<Form.Label>Name</Form.Label>
80+
<Input {...attrs} bind:value={$formData.name} />
81+
</Form.Control>
82+
<Form.Description>This is new table display name.</Form.Description>
83+
<Form.FieldErrors />
84+
</Form.Field>
85+
<Form.Field {form} name="includeData" class="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
86+
<Form.Control let:attrs>
87+
<Checkbox {...attrs} bind:checked={$formData.includeData} />
88+
<div class="space-y-1 leading-none">
89+
<Form.Label>Include data</Form.Label>
90+
<Form.Description>Include data in the new table.</Form.Description>
91+
</div>
92+
<input name={attrs.name} value={$formData.includeData} hidden />
93+
</Form.Control>
94+
</Form.Field>
95+
<div class="item-center mt-2 flex justify-end gap-2">
96+
<Button
97+
on:click={() => {
98+
closeModal(DUPLICATE_TABLE_MODAL)
99+
}}
100+
variant="outline"
101+
>
102+
Cancel
103+
</Button>
104+
<Form.Button disabled={$duplicateTableMutation.isPending}>
105+
{#if $duplicateTableMutation.isPending}
106+
<LoaderCircleIcon class="mr-2 h-5 w-5 animate-spin" />
107+
{/if}
108+
Duplicate
109+
</Form.Button>
110+
</div>
111+
</form>
64112
</Dialog.Content>
65113
</Dialog.Root>

packages/base/src/base.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { getNextName } from "@undb/utils"
44
import type { Option } from "oxide.ts"
55
import { BaseFactory } from "./base.factory.js"
66
import type { IBaseDTO } from "./dto/base.dto.js"
7+
import type { IDuplicateBaseDTO } from "./dto/duplicate-base.dto.js"
78
import type { IUpdateBaseDTO } from "./dto/update-base.dto.js"
89
import { BaseUpdatedEvent } from "./events/base-updated.event.js"
910
import type { IBaseSpecification } from "./interface.js"
@@ -39,11 +40,12 @@ export class Base extends AggregateRoot<any> {
3940
return spec
4041
}
4142

42-
public $duplicate(baseNames: string[]): DuplicatedBaseSpecification {
43+
public $duplicate(dto: IDuplicateBaseDTO, baseNames: string[]): DuplicatedBaseSpecification {
4344
const duplicatedBase = BaseFactory.fromJSON({
4445
...this.toJSON(),
4546
id: BaseId.create().value,
46-
name: getNextName(baseNames, this.name.value),
47+
spaceId: dto.spaceId ?? this.spaceId,
48+
name: dto.name ?? getNextName(baseNames, this.name.value),
4749
})
4850

4951
return new DuplicatedBaseSpecification(this, duplicatedBase)
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
import { spaceIdSchema } from "@undb/space"
12
import { z } from "@undb/zod"
2-
import { baseIdSchema } from "../value-objects"
3+
import { baseIdSchema, baseNameSchema } from "../value-objects"
34

45
export const duplicateBaseDTO = z.object({
56
id: baseIdSchema,
7+
spaceId: spaceIdSchema.optional(),
8+
name: baseNameSchema.optional(),
9+
includeData: z.boolean().optional(),
610
})
711

812
export type IDuplicateBaseDTO = z.infer<typeof duplicateBaseDTO>

packages/base/src/rules/base-name-should-be-unique.rule.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ class BaseNameShouldBeUniqueError extends ExceptionBase {
99
}
1010

1111
export class BaseNameShouldBeUnique extends DomainRules<BaseNameShouldBeUniqueError> {
12-
constructor(private readonly hasBase: boolean) {
12+
constructor(private readonly hasBase: boolean | string[]) {
1313
super()
1414
}
1515

1616
override err = new BaseNameShouldBeUniqueError()
1717

1818
override isBroken(): boolean {
19+
if (Array.isArray(this.hasBase)) {
20+
return this.hasBase.length !== new Set(this.hasBase).size
21+
}
22+
1923
return !!this.hasBase
2024
}
2125
}

packages/command-handlers/src/handlers/duplicate-base.command-handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export class DuplicateBaseCommandHandler implements ICommandHandler<DuplicateBas
2222
async execute(command: DuplicateBaseCommand): Promise<any> {
2323
const base = (await this.baseRepository.findOneById(command.id)).expect("Base not found")
2424

25-
const duplicatedBase = await this.tableService.duplicateBase(base, mustGetCurrentSpaceId())
25+
const duplicatedBase = await this.tableService.duplicateBase(base, mustGetCurrentSpaceId(), command)
2626

2727
return duplicatedBase.id.value
2828
}

packages/commands/src/duplicate-base.command.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@ export type IDuplicateBaseCommand = z.infer<typeof duplicateBaseCommand>
88

99
export class DuplicateBaseCommand extends Command implements IDuplicateBaseCommand {
1010
public readonly id: string
11+
public readonly name?: string
12+
public readonly includeData?: boolean
1113

1214
constructor(props: CommandProps<IDuplicateBaseCommand>) {
1315
super(props)
1416
this.id = props.id
17+
this.name = props.name
18+
this.includeData = props.includeData
1519
}
1620
}

packages/persistence/src/underlying/underlying-table-spec.visitor.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ export class UnderlyingTableSpecVisitor implements ITableSpecVisitor {
123123
}
124124
}
125125
withDuplicatedTable(spec: DuplicatedTableSpecification): void {
126+
if (!spec.includeData) {
127+
return
128+
}
129+
126130
const { originalTable, duplicatedTable } = spec
127131
// TODO: virtual fields common util
128132
const getColumns = (table: TableDo) =>

packages/table/src/dto/duplicate-table.dto.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ import { baseIdSchema } from "@undb/base"
22
import { spaceIdSchema } from "@undb/space"
33
import { z } from "@undb/zod"
44
import { tableId } from "../table-id.vo"
5+
import { tableName } from "../table-name.vo"
56

67
export const duplicateTableDTO = z.object({
78
tableId,
9+
name: tableName.optional(),
810
baseId: baseIdSchema.optional(),
911
spaceId: spaceIdSchema.optional(),
12+
includeData: z.boolean().optional(),
1013
})
1114

1215
export type IDuplicateTableDTO = z.infer<typeof duplicateTableDTO>

packages/table/src/methods/duplicate-table.method.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ export function duplicateTableMethod(
1515
id: dto.tableId ?? TableIdVo.create().value,
1616
baseId: dto.baseId ?? this.baseId,
1717
spaceId: dto.spaceId ?? this.spaceId,
18-
name: getNextName(tableNames, this.name.value),
18+
name: dto.name ?? getNextName(tableNames, this.name.value),
1919
})
2020

21-
return new DuplicatedTableSpecification(this, duplicated)
21+
return new DuplicatedTableSpecification(this, duplicated, dto.includeData ?? false)
2222
}
Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
1-
import { WithBaseSpaceId, type Base } from "@undb/base"
2-
import { Some } from "@undb/domain"
1+
import { BaseNameShouldBeUnique, WithBaseSpaceId, type Base, type IDuplicateBaseDTO } from "@undb/base"
2+
import { applyRules, Some } from "@undb/domain"
33
import type { ISpaceId } from "@undb/space"
44
import { TableBaseIdSpecification } from "../../specifications"
55
import type { TableService } from "../table.service"
66

7-
export async function duplicateBaseMethod(this: TableService, base: Base, spaceId: ISpaceId) {
7+
export async function duplicateBaseMethod(this: TableService, base: Base, spaceId: ISpaceId, dto: IDuplicateBaseDTO) {
88
const bases = await this.baseRepository.find(new WithBaseSpaceId(spaceId))
99
const baseNames = bases.map((b) => b.name.value)
1010

11-
const spec = base.$duplicate(baseNames)
11+
const spec = base.$duplicate(dto, baseNames)
1212

1313
const { duplicatedBase } = spec
14+
15+
applyRules(new BaseNameShouldBeUnique(baseNames.concat(duplicatedBase.name.value)))
16+
1417
await this.baseRepository.insert(duplicatedBase)
1518

1619
const tables = await this.repository.find(Some(new TableBaseIdSpecification(base.id.value)))
17-
await this.duplicateTables(spaceId, duplicatedBase, tables)
20+
await this.duplicateTables(spaceId, duplicatedBase, tables, dto.includeData)
1821

1922
return duplicatedBase
2023
}

0 commit comments

Comments
 (0)