diff --git a/apps/frontend/src/lib/components/blocks/base/base-setting.svelte b/apps/frontend/src/lib/components/blocks/base/base-setting.svelte index 391c487e6..7437ac7d8 100644 --- a/apps/frontend/src/lib/components/blocks/base/base-setting.svelte +++ b/apps/frontend/src/lib/components/blocks/base/base-setting.svelte @@ -1,5 +1,5 @@ -
+
Base Setting @@ -76,4 +90,46 @@ Submit + +
+

Danger Zone

+
Delete Base
+ + + + + + + + Are you absolutely sure to delete base? + + This action cannot be undone. This will permanently delete your database state and remove your data from our + servers. + + + +

Please type DELETE to confirm.

+ + + + Cancel + + + + +
+
+
diff --git a/packages/base/src/dto/delete-base.dto.ts b/packages/base/src/dto/delete-base.dto.ts new file mode 100644 index 000000000..062f9595c --- /dev/null +++ b/packages/base/src/dto/delete-base.dto.ts @@ -0,0 +1,8 @@ +import { z } from "@undb/zod" +import { baseIdSchema } from "../value-objects" + +export const deleteBaseDTO = z.object({ + id: baseIdSchema, +}) + +export type IDeleteBaseDTO = z.infer diff --git a/packages/base/src/dto/index.ts b/packages/base/src/dto/index.ts index 6e164a7d2..185736b43 100644 --- a/packages/base/src/dto/index.ts +++ b/packages/base/src/dto/index.ts @@ -1,4 +1,5 @@ export * from "./base.dto" export * from "./create-base.dto" +export * from "./delete-base.dto" export * from "./duplicate-base.dto" export * from "./update-base.dto" diff --git a/packages/command-handlers/src/handlers/delete-base.command-handler.ts b/packages/command-handlers/src/handlers/delete-base.command-handler.ts new file mode 100644 index 000000000..4446558cc --- /dev/null +++ b/packages/command-handlers/src/handlers/delete-base.command-handler.ts @@ -0,0 +1,22 @@ +import { injectBaseRepository, type IBaseRepository } from "@undb/base" +import { DeleteBaseCommand } from "@undb/commands" +import { commandHandler } from "@undb/cqrs" +import { singleton } from "@undb/di" +import { type ICommandHandler } from "@undb/domain" + +@commandHandler(DeleteBaseCommand) +@singleton() +export class DeleteBaseCommandHandler implements ICommandHandler { + constructor( + @injectBaseRepository() + private readonly repository: IBaseRepository, + ) {} + + async execute(command: DeleteBaseCommand): Promise { + const base = (await this.repository.findOneById(command.id)).expect("base not found") + + await this.repository.deleteOneById(base.id.value) + + return base.id.value + } +} 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 069d8290b..a40bd04e7 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,8 @@ 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(), command) + const spaceId = mustGetCurrentSpaceId() + const duplicatedBase = await this.tableService.duplicateBase(base, spaceId, spaceId, command) return duplicatedBase.id.value } diff --git a/packages/command-handlers/src/handlers/index.ts b/packages/command-handlers/src/handlers/index.ts index 74dd354b8..0efb8ed5c 100644 --- a/packages/command-handlers/src/handlers/index.ts +++ b/packages/command-handlers/src/handlers/index.ts @@ -13,6 +13,7 @@ import { CreateTableFormCommandHandler } from "./create-table-form.command-handl import { CreateTableViewCommandHandler } from "./create-table-view.command-handler" import { CreateTableCommandHandler } from "./create-table.command-handler" import { CreateWebhookCommandHandler } from "./create-webhook.command-handler" +import { DeleteBaseCommandHandler } from "./delete-base.command-handler" import { DeleteInvitationCommandHandler } from "./delete-invitation.command-handler" import { DeleteRecordCommandHandler } from "./delete-record.command-handler" import { DeleteSpaceCommandHandler } from "./delete-space.command-handler" @@ -94,4 +95,5 @@ export const commandHandlers = [ DeleteWebhookCommandHandler, DuplicateTableCommandHandler, DuplicateBaseCommandHandler, + DeleteBaseCommandHandler, ] diff --git a/packages/commands/src/delete-base.command.ts b/packages/commands/src/delete-base.command.ts new file mode 100644 index 000000000..526c9d1f3 --- /dev/null +++ b/packages/commands/src/delete-base.command.ts @@ -0,0 +1,16 @@ +import { deleteBaseDTO } from "@undb/base" +import { Command, type CommandProps } from "@undb/domain" +import { z } from "@undb/zod" + +export const deleteBaseCommand = deleteBaseDTO + +export type IDeleteBaseCommand = z.infer + +export class DeleteBaseCommand extends Command implements IDeleteBaseCommand { + public readonly id: string + + constructor(props: CommandProps) { + super(props) + this.id = props.id + } +} diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index 914f77d84..91cd96093 100644 --- a/packages/commands/src/index.ts +++ b/packages/commands/src/index.ts @@ -13,6 +13,7 @@ export * from "./create-table-form.command" export * from "./create-table-view.command" export * from "./create-table.command" export * from "./create-webhook.command" +export * from "./delete-base.command" export * from "./delete-invitation.command" export * from "./delete-record.command" export * from "./delete-space.command" diff --git a/packages/persistence/src/base/base.repository.ts b/packages/persistence/src/base/base.repository.ts index dcce8443f..b81d572fb 100644 --- a/packages/persistence/src/base/base.repository.ts +++ b/packages/persistence/src/base/base.repository.ts @@ -1,7 +1,7 @@ import { + injectBaseOutboxService, WithBaseId, WithBaseSpaceId, - injectBaseOutboxService, type Base, type IBaseOutboxService, type IBaseRepository, @@ -10,9 +10,11 @@ import { import { executionContext, mustGetCurrentSpaceId } from "@undb/context/server" import { inject, singleton } from "@undb/di" import { None, Some, type Option } from "@undb/domain" +import { injectTableRepository, TableBaseIdSpecification, type ITableRepository } from "@undb/table" import { getCurrentTransaction } from "../ctx" import type { IQueryBuilder } from "../qb" import { injectQueryBuilder } from "../qb.provider" +import { UnderlyingTableService } from "../underlying/underlying-table.service" import { BaseFilterVisitor } from "./base.filter-visitor" import { BaseMapper } from "./base.mapper" import { BaseMutateVisitor } from "./base.mutate-visitor" @@ -26,6 +28,10 @@ export class BaseRepository implements IBaseRepository { private readonly outboxService: IBaseOutboxService, @injectQueryBuilder() private readonly qb: IQueryBuilder, + @injectTableRepository() + private readonly tableRepository: ITableRepository, + @inject(UnderlyingTableService) + private readonly underlyingTableService: UnderlyingTableService, ) {} async find(spec: IBaseSpecification): Promise { @@ -101,7 +107,37 @@ export class BaseRepository implements IBaseRepository { await this.outboxService.save(base) } - deleteOneById(id: string): Promise { - throw new Error("Method not implemented.") + async deleteOneById(id: string): Promise { + const trx = getCurrentTransaction() + + const tables = await this.tableRepository.find(Some(new TableBaseIdSpecification(id))) + const tableIds = tables.map((t) => t.id.value) + + await trx + .deleteFrom("undb_table_id_mapping") + .where((eb) => eb.eb("table_id", "in", tableIds)) + .execute() + + await trx + .deleteFrom("undb_rollup_id_mapping") + .where((eb) => eb.eb("table_id", "in", tableIds)) + .execute() + + await trx + .deleteFrom("undb_reference_id_mapping") + .where((eb) => eb.eb("table_id", "in", tableIds)) + .execute() + + await trx + .deleteFrom("undb_table") + .where((eb) => eb.eb("id", "in", tableIds)) + .execute() + + await trx + .deleteFrom("undb_base") + .where((eb) => eb.eb("id", "=", id)) + .execute() + + await this.underlyingTableService.deleteTables(tables) } } diff --git a/packages/persistence/src/underlying/underlying-table.service.ts b/packages/persistence/src/underlying/underlying-table.service.ts index 3504da735..825812898 100644 --- a/packages/persistence/src/underlying/underlying-table.service.ts +++ b/packages/persistence/src/underlying/underlying-table.service.ts @@ -3,6 +3,7 @@ import { createLogger } from "@undb/logger" import type { TableComositeSpecification, TableDo } from "@undb/table" import type { CompiledQuery } from "kysely" import { getAnonymousTransaction, getCurrentTransaction } from "../ctx" +import { JoinTable } from "./reference/join-table" import { UnderlyingTable } from "./underlying-table" import { UnderlyingTableFieldVisitor } from "./underlying-table-field.visitor" import { UnderlyingTableSpecVisitor } from "./underlying-table-spec.visitor" @@ -46,5 +47,16 @@ export class UnderlyingTableService { const t = new UnderlyingTable(table) const trx = getCurrentTransaction() await trx.schema.dropTable(t.name).execute() + const referenceFields = table.schema.getReferenceFields() + for (const field of referenceFields) { + const joinTable = new JoinTable(table, field) + await trx.schema.dropTable(joinTable.getTableName()).execute() + } + } + + async deleteTables(tables: TableDo[]) { + for (const table of tables) { + await this.delete(table) + } } } diff --git a/packages/trpc/src/router.ts b/packages/trpc/src/router.ts index ac0a73db2..121772bb6 100644 --- a/packages/trpc/src/router.ts +++ b/packages/trpc/src/router.ts @@ -13,6 +13,7 @@ import { CreateTableFormCommand, CreateTableViewCommand, CreateWebhookCommand, + DeleteBaseCommand, DeleteInvitationCommand, DeleteRecordCommand, DeleteTableCommand, @@ -58,6 +59,7 @@ import { createTableFormCommand, createTableViewCommand, createWebhookCommand, + deleteBaseCommand, deleteInvitationCommand, deleteRecordCommand, deleteTableCommand, @@ -322,6 +324,10 @@ const baseRouter = t.router({ .use(authz("base:update")) .input(updateBaseCommand) .mutation(({ input }) => commandBus.execute(new UpdateBaseCommand(input))), + delete: privateProcedure + .use(authz("base:delete")) + .input(deleteBaseCommand) + .mutation(({ input }) => commandBus.execute(new DeleteBaseCommand(input))), }) const shareRouter = t.router({