Skip to content

Commit

Permalink
feat: duplicate base
Browse files Browse the repository at this point in the history
  • Loading branch information
nichenqin committed Aug 16, 2024
1 parent d56b298 commit b26ca76
Show file tree
Hide file tree
Showing 33 changed files with 475 additions and 18 deletions.
1 change: 1 addition & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@undb/queries": "workspace:*",
"@undb/share": "workspace:*",
"@undb/table": "workspace:*",
"@undb/utils": "workspace:*",
"@undb/trpc": "workspace:*",
"autoprefixer": "^10.4.20",
"date-fns": "^3.6.0",
Expand Down
13 changes: 10 additions & 3 deletions apps/frontend/src/lib/components/blocks/base/base-header.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<script lang="ts">
import type { GetBaseQuery$result } from "$houdini"
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js"
import { UPDATE_BASE_MODAL, toggleModal } from "$lib/store/modal.store"
import { HardDriveIcon, PencilIcon, ChevronDownIcon } from "lucide-svelte"
import { DUPLICATE_BASE_MODAL, UPDATE_BASE_MODAL, toggleModal } from "$lib/store/modal.store"
import { HardDriveIcon, PencilIcon, ChevronDownIcon, CopyIcon } from "lucide-svelte"
import DuplicateBase from "./duplicate-base.svelte"
export let base: GetBaseQuery$result["base"]
</script>
Expand All @@ -18,12 +19,18 @@
<ChevronDownIcon class="h-4 w-4" />
</div>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Content class="w-[200px]">
<DropdownMenu.Item class="text-xs" on:click={() => toggleModal(UPDATE_BASE_MODAL)}>
<PencilIcon class="mr-2 h-3 w-3" />
Update Base Name
</DropdownMenu.Item>
<DropdownMenu.Item class="text-xs" on:click={() => toggleModal(DUPLICATE_BASE_MODAL)}>
<CopyIcon class="mr-2 h-3 w-3" />
Duplicate Base
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
{/if}
</header>

<DuplicateBase {base} />
63 changes: 63 additions & 0 deletions apps/frontend/src/lib/components/blocks/base/duplicate-base.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<script lang="ts">
import { goto, invalidateAll } from "$app/navigation"
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 { trpc } from "$lib/trpc/client"
import { createMutation } from "@tanstack/svelte-query"
import type { Base, IBaseDTO } from "@undb/base"
import { LoaderCircleIcon } from "lucide-svelte"
import { toast } from "svelte-sonner"
export let base: Omit<IBaseDTO, "spaceId">
const duplicateBaseMutation = createMutation({
mutationFn: trpc.base.duplicate.mutate,
onSuccess: async (data) => {
await invalidateAll()
closeModal(DUPLICATE_BASE_MODAL)
await goto(`/bases/${data}`)
},
onError: (error) => {
toast.error(error.message)
},
})
</script>

<Dialog.Root
open={$isModalOpen(DUPLICATE_BASE_MODAL)}
onOpenChange={(open) => {
if (!open) {
closeModal(DUPLICATE_BASE_MODAL)
}
}}
>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Duplicate Base {base.name}</Dialog.Title>
<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>
</Dialog.Content>
</Dialog.Root>
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
FieldIdVo,
fieldTypes,
getIsDisplayFieldType,
getNextName,
OptionIdVo,
systemFieldTypes,
type ICreateSchemaDTO,
type NoneSystemFieldType,
} from "@undb/table"
import { getNextName } from "@undb/utils"
import * as Popover from "$lib/components/ui/popover"
import * as Card from "$lib/components/ui/card"
import * as Form from "$lib/components/ui/form"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import * as Popover from "$lib/components/ui/popover"
import { trpc } from "$lib/trpc/client"
import { createMutation } from "@tanstack/svelte-query"
import { createViewDTO, getNextName } from "@undb/table"
import { createViewDTO } from "@undb/table"
import { getNextName } from "@undb/utils"
import { PlusCircleIcon } from "lucide-svelte"
import { toast } from "svelte-sonner"
import { defaults, superForm } from "sveltekit-superforms"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import * as Form from "$lib/components/ui/form"
import { Input } from "$lib/components/ui/input"
import { toggleModal, DUPLICATE_VIEW } from "$lib/store/modal.store"
import { getNextName } from "@undb/table"
import { getNextName } from "@undb/utils"
const table = getTable()
Expand Down
2 changes: 2 additions & 0 deletions apps/frontend/src/lib/store/modal.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const UPDATE_VIEW = "updateView" as const
export const DUPLICATE_VIEW = "duplicateView" as const
export const DELETE_VIEW = "deleteView" as const
export const CREATE_BASE_MODAL = "createBase" as const
export const DUPLICATE_BASE_MODAL = "duplicateBase" as const
export const UPDATE_BASE_MODAL = "updateBase" as const
export const DELETE_TABLE_MODAL = "deleteTable" as const

Expand All @@ -36,6 +37,7 @@ type ModalType =
| typeof CREATE_BASE_MODAL
| typeof UPDATE_BASE_MODAL
| typeof DELETE_TABLE_MODAL
| typeof DUPLICATE_BASE_MODAL

export const toggleModal = (type: ModalType) => {
modal.update(($modal) => {
Expand Down
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions packages/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@undb/di": "workspace:*",
"@undb/domain": "workspace:*",
"@undb/space": "workspace:*",
"@undb/utils": "workspace:*",
"@undb/zod": "workspace:*"
}
}
15 changes: 14 additions & 1 deletion packages/base/src/base.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { AggregateRoot, and } from "@undb/domain"
import type { ISpaceId } from "@undb/space"
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 { IUpdateBaseDTO } from "./dto/update-base.dto.js"
import { BaseUpdatedEvent } from "./events/base-updated.event.js"
import type { IBaseSpecification } from "./interface.js"
import { WithBaseName } from "./specifications/base-name.specification.js"
import type { BaseId, BaseName } from "./value-objects/index.js"
import { DuplicatedBaseSpecification } from "./specifications/base.specification.js"
import { BaseId, type BaseName } from "./value-objects/index.js"

export class Base extends AggregateRoot<any> {
id!: BaseId
Expand Down Expand Up @@ -36,6 +39,16 @@ export class Base extends AggregateRoot<any> {
return spec
}

public $duplicate(baseNames: string[]): DuplicatedBaseSpecification {
const duplicatedBase = BaseFactory.fromJSON({
...this.toJSON(),
id: BaseId.create().value,
name: getNextName(baseNames, this.name.value),
})

return new DuplicatedBaseSpecification(this, duplicatedBase)
}

public toJSON(): IBaseDTO {
return {
id: this.id.value,
Expand Down
2 changes: 2 additions & 0 deletions packages/base/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import type { WithBaseId } from "./specifications/base-id.specification.js"
import type { WithBaseName } from "./specifications/base-name.specification.js"
import type { WithBaseQ } from "./specifications/base-q.specification.js"
import type { WithBaseSpaceId } from "./specifications/base-space-id.specification.js"
import type { DuplicatedBaseSpecification } from "./specifications/base.specification.js"

export interface IBaseSpecVisitor extends ISpecVisitor {
withId(v: WithBaseId): void
withBaseSpaceId(v: WithBaseSpaceId): void
duplicatedBase(v: DuplicatedBaseSpecification): void
withName(v: WithBaseName): void
withQ(v: WithBaseQ): void
}
Expand Down
22 changes: 22 additions & 0 deletions packages/base/src/specifications/base.specification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { CompositeSpecification, Ok, Result } from "@undb/domain"
import type { Base } from "../base"
import type { IBaseSpecVisitor } from "../interface"

export class DuplicatedBaseSpecification extends CompositeSpecification<Base, IBaseSpecVisitor> {
constructor(
public readonly originalBase: Base,
public readonly duplicatedBase: Base,
) {
super()
}
isSatisfiedBy(t: Base): boolean {
throw new Error("Method not implemented.")
}
mutate(t: Base): Result<Base, string> {
return Ok(t)
}
accept(v: IBaseSpecVisitor): Result<void, string> {
v.duplicatedBase(this)
return Ok(undefined)
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { injectBaseRepository, type IBaseRepository } from "@undb/base"
import { DuplicateBaseCommand } from "@undb/commands"
import { mustGetCurrentSpaceId } from "@undb/context/server"
import { commandHandler } from "@undb/cqrs"
import { singleton } from "@undb/di"
import { type ICommandHandler } from "@undb/domain"
import { createLogger } from "@undb/logger"
import { injectTableService, type ITableService } from "@undb/table"

@commandHandler(DuplicateBaseCommand)
@singleton()
Expand All @@ -12,12 +14,16 @@ export class DuplicateBaseCommandHandler implements ICommandHandler<DuplicateBas

constructor(
@injectBaseRepository()
private readonly repository: IBaseRepository,
private readonly baseRepository: IBaseRepository,
@injectTableService()
private readonly tableService: ITableService,
) {}

async execute(command: DuplicateBaseCommand): Promise<any> {
const base = (await this.repository.findOneById(command.id)).expect("Base not found")
const base = (await this.baseRepository.findOneById(command.id)).expect("Base not found")

throw new Error("Not implemented")
const duplicatedBase = await this.tableService.duplicateBase(base, mustGetCurrentSpaceId())

return duplicatedBase.id.value
}
}
4 changes: 4 additions & 0 deletions packages/persistence/src/base/base.filter-visitor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Base, IBaseSpecVisitor, WithBaseId, WithBaseName, WithBaseQ, WithBaseSpaceId } from "@undb/base"
import type { DuplicatedBaseSpecification } from "@undb/base/src/specifications/base.specification"
import { mustGetCurrentSpaceId } from "@undb/context/server"
import type { ExpressionBuilder } from "kysely"
import { AbstractQBVisitor } from "../abstract-qb.visitor"
Expand All @@ -11,6 +12,9 @@ export class BaseFilterVisitor extends AbstractQBVisitor<Base> implements IBaseS
this.addCond(this.eb.eb("space_id", "=", spaceId))
}

duplicatedBase(v: DuplicatedBaseSpecification): void {
throw new Error("Not implemented")
}
withId(v: WithBaseId): void {
this.addCond(this.eb.eb("id", "=", v.id.value))
}
Expand Down
4 changes: 4 additions & 0 deletions packages/persistence/src/base/base.mutate-visitor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type { IBaseSpecVisitor, WithBaseId, WithBaseName, WithBaseQ, WithBaseSpaceId } from "@undb/base"
import type { DuplicatedBaseSpecification } from "@undb/base/src/specifications/base.specification"
import { AbstractQBMutationVisitor } from "../abstract-qb.visitor"

export class BaseMutateVisitor extends AbstractQBMutationVisitor implements IBaseSpecVisitor {
duplicatedBase(v: DuplicatedBaseSpecification): void {
throw new Error("Method not implemented.")
}
withId(v: WithBaseId): void {
throw new Error("Method not implemented.")
}
Expand Down
15 changes: 13 additions & 2 deletions packages/persistence/src/base/base.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,19 @@ export class BaseRepository implements IBaseRepository {
private readonly qb: IQueryBuilder,
) {}

find(spec: IBaseSpecification): Promise<Base[]> {
throw new Error("Method not implemented.")
async find(spec: IBaseSpecification): Promise<Base[]> {
const tx = getCurrentTransaction() ?? this.qb
const bases = await tx
.selectFrom("undb_base")
.selectAll()
.where((eb) => {
const visitor = new BaseFilterVisitor(eb)
spec.accept(visitor)
return visitor.cond
})
.execute()

return bases.map((base) => this.mapper.toDo(base))
}
async findOne(spec: IBaseSpecification): Promise<Option<Base>> {
const base = await (getCurrentTransaction() ?? this.qb)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,11 @@ export class UnderlyingTableSpecVisitor implements ITableSpecVisitor {

this.addSql(duplicateDataSql)

const referenceFields = duplicatedTable.schema.fields.filter((f) => f.type === "reference")
const referenceFields = duplicatedTable.schema.getReferenceFields()

for (const field of referenceFields) {
if (!field.isOwner) continue

const joinTable = new JoinTable(duplicatedTable, field)
const originalField = originalTable.schema.fields.find((f) => f.id.value === field.id.value)
if (!(originalField instanceof ReferenceField)) continue
Expand Down
1 change: 1 addition & 0 deletions packages/table/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@undb/logger": "workspace:*",
"@undb/space": "workspace:*",
"@undb/zod": "workspace:*",
"@undb/utils": "workspace:*",
"date-fns": "^3.6.0",
"radash": "^12.1.0",
"ts-pattern": "^5.3.1"
Expand Down
4 changes: 4 additions & 0 deletions packages/table/src/dto/duplicate-table.dto.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { baseIdSchema } from "@undb/base"
import { spaceIdSchema } from "@undb/space"
import { z } from "@undb/zod"
import { tableId } from "../table-id.vo"

export const duplicateTableDTO = z.object({
tableId,
baseId: baseIdSchema.optional(),
spaceId: spaceIdSchema.optional(),
})

export type IDuplicateTableDTO = z.infer<typeof duplicateTableDTO>
6 changes: 4 additions & 2 deletions packages/table/src/methods/duplicate-table.method.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getNextName } from "@undb/utils"
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"
Expand All @@ -12,7 +12,9 @@ export function duplicateTableMethod(
): DuplicatedTableSpecification {
const duplicated = new TableCreator().fromJSON({
...this.toJSON(),
id: TableIdVo.create().value,
id: dto.tableId ?? TableIdVo.create().value,
baseId: dto.baseId ?? this.baseId,
spaceId: dto.spaceId ?? this.spaceId,
name: getNextName(tableNames, this.name.value),
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ export class ReferenceField extends AbstractField<
return this.option.unwrap().foreignTableId
}

public set foreignTableId(foreignTableId: string) {
this.option.unwrap().foreignTableId = foreignTableId
}

public get condition(): IViewFilterGroup | undefined {
return this.option.unwrap().condition
}
Expand Down
1 change: 0 additions & 1 deletion packages/table/src/modules/schema/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from "./dto"
export * from "./fields"
export * from "./schema.vo"
export * from "./schema.util"
2 changes: 1 addition & 1 deletion packages/table/src/modules/schema/schema.vo.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Option, ValueObject } from "@undb/domain"
import { getNextName } from "@undb/utils"
import { z, ZodSchema } from "@undb/zod"
import { objectify } from "radash"
import {
Expand Down Expand Up @@ -31,7 +32,6 @@ import type { Field, MutableFieldValue, NoneSystemField, SystemField } from "./f
import { AutoIncrementField } from "./fields/variants/autoincrement-field"
import { CreatedAtField } from "./fields/variants/created-at-field"
import type { SchemaIdMap, SchemaNameMap } from "./schema.type"
import { getNextName } from "./schema.util"

export class Schema extends ValueObject<Field[]> {
public fieldMapById: SchemaIdMap
Expand Down
Loading

0 comments on commit b26ca76

Please sign in to comment.