From 7204541a31328968f47517dda606f02c028bc30e Mon Sep 17 00:00:00 2001 From: nichenqin Date: Sat, 17 Aug 2024 11:20:31 +0800 Subject: [PATCH 1/4] feat: create from template --- apps/frontend/schema.graphql | 7 + .../[spaceId]/[baseId]/+layout.gql | 19 +++ .../[spaceId]/[baseId]/+layout.svelte | 3 + .../[spaceId]/[baseId]/+layout.ts | 17 ++ .../[spaceId]/[baseId]/+page.svelte | 150 ++++++++++++++++++ .../create-from-template.command-handler.ts | 35 ++++ .../command-handlers/src/handlers/index.ts | 2 + .../src/create-from-template.command.ts | 31 ++++ packages/commands/src/index.ts | 1 + packages/graphql/src/index.ts | 13 ++ packages/queries/src/get-template.query.ts | 30 ++++ packages/queries/src/index.ts | 1 + .../handlers/get-template.query-handler.ts | 26 +++ packages/query-handlers/src/handlers/index.ts | 2 + packages/trpc/src/router.ts | 6 + 15 files changed, 343 insertions(+) create mode 100644 apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+layout.gql create mode 100644 apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+layout.svelte create mode 100644 apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+layout.ts create mode 100644 apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+page.svelte create mode 100644 packages/command-handlers/src/handlers/create-from-template.command-handler.ts create mode 100644 packages/commands/src/create-from-template.command.ts create mode 100644 packages/queries/src/get-template.query.ts create mode 100644 packages/query-handlers/src/handlers/get-template.query-handler.ts diff --git a/apps/frontend/schema.graphql b/apps/frontend/schema.graphql index af9793a3c..2bb73b79f 100644 --- a/apps/frontend/schema.graphql +++ b/apps/frontend/schema.graphql @@ -97,6 +97,7 @@ type Query { tableByShare(shareId: ID!): Table tableForeignTables(tableId: ID!): [Table!]! tables: [Table]! + template(baseId: ID!, spaceId: ID!): Template } type RLS { @@ -166,6 +167,12 @@ type Table { views: [View!]! } +type Template { + baseId: ID! + name: String! + spaceId: ID! +} + type User { avatar: String email: String! diff --git a/apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+layout.gql b/apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+layout.gql new file mode 100644 index 000000000..28e18aa63 --- /dev/null +++ b/apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+layout.gql @@ -0,0 +1,19 @@ +query GetCreateFromTemplateData($spaceId: ID!, $baseId: ID!) { + space { + id + name + } + + template(spaceId: $spaceId, baseId: $baseId) { + name + } + + spaces { + id + name + isPersonal + member { + role + } + } +} diff --git a/apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+layout.svelte b/apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+layout.svelte new file mode 100644 index 000000000..09487b1b3 --- /dev/null +++ b/apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+layout.svelte @@ -0,0 +1,3 @@ +
+ +
diff --git a/apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+layout.ts b/apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+layout.ts new file mode 100644 index 000000000..403e6ce69 --- /dev/null +++ b/apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+layout.ts @@ -0,0 +1,17 @@ +import { GetCreateFromTemplateDataStore } from "$houdini" +import type { LayoutLoad } from "./$types" + +export const load: LayoutLoad = async (event) => { + const { spaceId, baseId } = event.params + + const store = new GetCreateFromTemplateDataStore() + + await store.fetch({ + event, + variables: { spaceId, baseId }, + }) + + return { + store, + } +} diff --git a/apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+page.svelte b/apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+page.svelte new file mode 100644 index 000000000..9d9a88091 --- /dev/null +++ b/apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+page.svelte @@ -0,0 +1,150 @@ + + +
+
+ undb +
+ +
+ + + Create from template + Create a new base from a template. + + +
+
+ + + Name + + + + + +
+ + + Space + { + v && ($formData.targetSpaceId = v.value) + }} + > + + + + + {#each spaces as space} + + {/each} + + + + + Select a space to create the new base in. + + + + + + +
+ Include data + Include data in the new base. +
+ +
+
+ + + + + System fields will be updated to the current user and timestamp. + + + + {#if $createFromTemplateMutation.isPending} + + {/if} + Create + +
+
+
+
+
diff --git a/packages/command-handlers/src/handlers/create-from-template.command-handler.ts b/packages/command-handlers/src/handlers/create-from-template.command-handler.ts new file mode 100644 index 000000000..3ce212145 --- /dev/null +++ b/packages/command-handlers/src/handlers/create-from-template.command-handler.ts @@ -0,0 +1,35 @@ +import { BaseId, injectBaseRepository, WithBaseId, WithBaseSpaceId, type IBaseRepository } from "@undb/base" +import { CreateFromTemplateCommand } 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(CreateFromTemplateCommand) +@singleton() +export class CreateFromTemplateCommandHandler implements ICommandHandler { + private readonly logger = createLogger(CreateFromTemplateCommandHandler.name) + + constructor( + @injectBaseRepository() + private readonly baseRepository: IBaseRepository, + @injectTableService() + private readonly tableService: ITableService, + ) {} + + async execute(command: CreateFromTemplateCommand): Promise { + const spec = new WithBaseId(new BaseId(command.baseId)).and(new WithBaseSpaceId(command.spaceId)) + const base = (await this.baseRepository.findOne(spec)).expect("Base not found") + + const targetSpaceId = command.targetSpaceId ?? mustGetCurrentSpaceId() + const duplicatedBase = await this.tableService.duplicateBase(base, targetSpaceId, { + id: command.baseId, + name: command.name, + includeData: command.includeData, + }) + + return duplicatedBase.id.value + } +} diff --git a/packages/command-handlers/src/handlers/index.ts b/packages/command-handlers/src/handlers/index.ts index f86511974..74dd354b8 100644 --- a/packages/command-handlers/src/handlers/index.ts +++ b/packages/command-handlers/src/handlers/index.ts @@ -4,6 +4,7 @@ import { BulkDuplicateRecordsCommandHandler } from "./bulk-duplicate-records.com import { BulkUpdateRecordsCommandHandler } from "./bulk-update-records.command-handler" import { CreateApiTokenCommandHandler } from "./create-api-token.command-handler" import { CreateBaseCommandHandler } from "./create-base.command-handler" +import { CreateFromTemplateCommandHandler } from "./create-from-template.command-handler" import { CreateRecordCommandHandler } from "./create-record.command-handler" import { CreateRecordsCommandHandler } from "./create-records.command-handler" import { CreateSpaceCommandHandler } from "./create-space.command-handler" @@ -72,6 +73,7 @@ export const commandHandlers = [ BulkUpdateRecordsCommandHandler, CreateTableViewCommandHandler, DuplicateViewCommandHandler, + CreateFromTemplateCommandHandler, DeleteViewCommandHandler, CreateBaseCommandHandler, UpdateBaseCommandHandler, diff --git a/packages/commands/src/create-from-template.command.ts b/packages/commands/src/create-from-template.command.ts new file mode 100644 index 000000000..1be7e990d --- /dev/null +++ b/packages/commands/src/create-from-template.command.ts @@ -0,0 +1,31 @@ +import { baseIdSchema } from "@undb/base" +import { Command, type CommandProps } from "@undb/domain" +import { spaceIdSchema } from "@undb/space" +import { z } from "@undb/zod" + +export const createFromTemplateCommand = z.object({ + spaceId: spaceIdSchema, + baseId: baseIdSchema, + targetSpaceId: spaceIdSchema.optional(), + name: z.string().optional(), + includeData: z.boolean().optional(), +}) + +export type ICreateFromTemplateCommand = z.infer + +export class CreateFromTemplateCommand extends Command implements ICreateFromTemplateCommand { + public readonly spaceId: string + public readonly baseId: string + public readonly targetSpaceId?: string + public readonly name?: string + public readonly includeData?: boolean + + constructor(props: CommandProps) { + super(props) + this.spaceId = props.spaceId + this.baseId = props.baseId + this.targetSpaceId = props.targetSpaceId + this.name = props.name + this.includeData = props.includeData + } +} diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index 55d8327c8..914f77d84 100644 --- a/packages/commands/src/index.ts +++ b/packages/commands/src/index.ts @@ -4,6 +4,7 @@ export * from "./bulk-duplicate-records.command" export * from "./bulk-update-records.command" export * from "./create-api-token.command" export * from "./create-base.command" +export * from "./create-from-template.command" export * from "./create-record.command" export * from "./create-records.command" export * from "./create-space.command" diff --git a/packages/graphql/src/index.ts b/packages/graphql/src/index.ts index 422b9bc09..c40c88cfb 100644 --- a/packages/graphql/src/index.ts +++ b/packages/graphql/src/index.ts @@ -25,6 +25,7 @@ import { GetTableQuery, GetTablesByBaseIdQuery, GetTablesQuery, + GetTemplateQuery, } from "@undb/queries" import { injectShareService, type IShareService } from "@undb/share" import type { ISpaceDTO } from "@undb/space" @@ -234,6 +235,12 @@ export class Graphql { tables: [Table]! } + type Template { + baseId: ID! + spaceId: ID! + name: String! + } + enum InvitationStatus { pending accepted @@ -271,6 +278,8 @@ export class Graphql { share(id: ID!): Share tableByShare(shareId: ID!): Table + + template(spaceId: ID!, baseId: ID!): Template } `, @@ -370,6 +379,10 @@ export class Graphql { invitations: async (_, args) => { return await this.queryBus.execute(new GetInivitationsQuery({ status: args?.status })) }, + template: async (_, { spaceId, baseId }) => { + const template = await this.queryBus.execute(new GetTemplateQuery({ spaceId, baseId })) + return template + }, }, Base: { // @ts-ignore diff --git a/packages/queries/src/get-template.query.ts b/packages/queries/src/get-template.query.ts new file mode 100644 index 000000000..bbdea39ad --- /dev/null +++ b/packages/queries/src/get-template.query.ts @@ -0,0 +1,30 @@ +import { baseIdSchema } from "@undb/base" +import { Query, type QueryProps } from "@undb/domain" +import { spaceIdSchema } from "@undb/space" +import { z } from "@undb/zod" + +export const getTemplateQuery = z.object({ + spaceId: spaceIdSchema, + baseId: baseIdSchema, +}) + +export type IGetTemplateQuery = z.infer + +export const getTemplateOutput = z.object({ + baseId: baseIdSchema, + spaceId: spaceIdSchema, + name: z.string(), +}) + +export type IGetTemplateOutput = z.infer + +export class GetTemplateQuery extends Query implements IGetTemplateQuery { + public readonly spaceId: string + public readonly baseId: string + + constructor(props: QueryProps) { + super() + this.spaceId = props.spaceId + this.baseId = props.baseId + } +} diff --git a/packages/queries/src/index.ts b/packages/queries/src/index.ts index e00a93b93..32f1b09c7 100644 --- a/packages/queries/src/index.ts +++ b/packages/queries/src/index.ts @@ -24,4 +24,5 @@ export * from "./get-table-foreign-tables.query" export * from "./get-table.query" export * from "./get-tables-by-base-id.query" export * from "./get-tables.query" +export * from "./get-template.query" export * from "./get-webhooks.query" diff --git a/packages/query-handlers/src/handlers/get-template.query-handler.ts b/packages/query-handlers/src/handlers/get-template.query-handler.ts new file mode 100644 index 000000000..ee6002e0d --- /dev/null +++ b/packages/query-handlers/src/handlers/get-template.query-handler.ts @@ -0,0 +1,26 @@ +import { BaseId, injectBaseRepository, WithBaseId, WithBaseSpaceId, type IBaseRepository } from "@undb/base" +import { queryHandler } from "@undb/cqrs" +import { singleton } from "@undb/di" +import type { IQueryHandler } from "@undb/domain" +import { GetTemplateQuery, type IGetTemplateOutput, type IGetTemplateQuery } from "@undb/queries" + +@queryHandler(GetTemplateQuery) +@singleton() +export class GetTemplateQueryHandler implements IQueryHandler { + constructor( + @injectBaseRepository() + private readonly baseRepo: IBaseRepository, + ) {} + + async execute({ spaceId, baseId }: IGetTemplateQuery): Promise { + const base = ( + await this.baseRepo.findOne(new WithBaseId(new BaseId(baseId)).and(new WithBaseSpaceId(spaceId))) + ).expect("base not found") + + return { + baseId: base.id.value, + spaceId: base.spaceId, + name: base.name.value, + } + } +} diff --git a/packages/query-handlers/src/handlers/index.ts b/packages/query-handlers/src/handlers/index.ts index 69dd6588d..b06202b0f 100644 --- a/packages/query-handlers/src/handlers/index.ts +++ b/packages/query-handlers/src/handlers/index.ts @@ -24,6 +24,7 @@ import { GetTableForeignTablesQueryHandler } from "./get-table-foreign-tables.qu import { GetTableQueryHandler } from "./get-table.query-handler" import { GetTablesByBaseIdQueryHandler } from "./get-tables-by-base-id.query-handler" import { GetTablesQueryHandler } from "./get-tables.query-handler" +import { GetTemplateQueryHandler } from "./get-template.query-handler" import { GetWebhooksQueryHandler } from "./get-webhooks.query-handler" export const queryHandlers = [ @@ -54,4 +55,5 @@ export const queryHandlers = [ GetApiTokensQueryHandler, GetSpaceByIdQueryHandler, GetSpaceMemberQueryHandler, + GetTemplateQueryHandler, ] diff --git a/packages/trpc/src/router.ts b/packages/trpc/src/router.ts index 9a5830afd..ac0a73db2 100644 --- a/packages/trpc/src/router.ts +++ b/packages/trpc/src/router.ts @@ -4,6 +4,7 @@ import { BulkUpdateRecordsCommand, CreateApiTokenCommand, CreateBaseCommand, + CreateFromTemplateCommand, CreateRecordCommand, CreateRecordsCommand, CreateSpaceCommand, @@ -48,6 +49,7 @@ import { bulkduplicateRecordsCommand, createApiTokenCommand, createBaseCommand, + createFromTemplateCommand, createRecordCommand, createRecordsCommand, createSpaceCommand, @@ -308,6 +310,10 @@ const baseRouter = t.router({ } return commandBus.execute(new CreateBaseCommand({ ...input, spaceId })) }), + createFromTemplate: privateProcedure + .use(authz("base:create")) + .input(createFromTemplateCommand) + .mutation(({ input }) => commandBus.execute(new CreateFromTemplateCommand(input))), duplicate: privateProcedure .use(authz("base:create")) .input(duplicateBaseCommand) From 96be80f70312eb3b5a2173ca3a244b671c44f94d Mon Sep 17 00:00:00 2001 From: nichenqin Date: Sat, 17 Aug 2024 11:52:39 +0800 Subject: [PATCH 2/4] fix: fix ap token create --- .../persistence/src/api-token/api-token.repository.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/persistence/src/api-token/api-token.repository.ts b/packages/persistence/src/api-token/api-token.repository.ts index 3dd0cc2e7..78717b642 100644 --- a/packages/persistence/src/api-token/api-token.repository.ts +++ b/packages/persistence/src/api-token/api-token.repository.ts @@ -1,5 +1,6 @@ import { singleton } from "@undb/di" import type { ApiTokenDo, IApiTokenRepository } from "@undb/openapi" +import { getCurrentTransaction } from "../ctx" import type { IQueryBuilder } from "../qb" import { injectQueryBuilder } from "../qb.provider" @@ -10,7 +11,7 @@ export class ApiTokenRepository implements IApiTokenRepository { private readonly qb: IQueryBuilder, ) {} async insert(token: ApiTokenDo): Promise { - await this.qb + await (getCurrentTransaction() ?? this.qb) .insertInto("undb_api_token") .values({ id: token.id.value, @@ -23,6 +24,9 @@ export class ApiTokenRepository implements IApiTokenRepository { } async deleteOneById(id: string): Promise { - await this.qb.deleteFrom("undb_api_token").where("undb_api_token.id", "=", id).execute() + await (getCurrentTransaction() ?? this.qb) + .deleteFrom("undb_api_token") + .where("undb_api_token.id", "=", id) + .execute() } } From 7adc3613fc7505412c6e7bd0d77e95849f9c5209 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Sat, 17 Aug 2024 11:55:26 +0800 Subject: [PATCH 3/4] fix: fix route prerender --- .../create-from-template/[spaceId]/[baseId]/+layout.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+layout.ts b/apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+layout.ts index 403e6ce69..c8720254f 100644 --- a/apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+layout.ts +++ b/apps/frontend/src/routes/(template)/create-from-template/[spaceId]/[baseId]/+layout.ts @@ -1,6 +1,9 @@ import { GetCreateFromTemplateDataStore } from "$houdini" import type { LayoutLoad } from "./$types" +export const ssr = false +export const prerender = "auto" + export const load: LayoutLoad = async (event) => { const { spaceId, baseId } = event.params From 5857dd9ab4c1d553bbb219ab1cfd2cd1b90e5188 Mon Sep 17 00:00:00 2001 From: GitHub actions Date: Sat, 17 Aug 2024 03:57:56 +0000 Subject: [PATCH 4/4] Prepare release v1.0.0-17 --- CHANGELOG.md | 11 +++++++++++ package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eb01b560..2d2d90e35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ # Changelog +## v1.0.0-17 + + +### 🩹 Fixes + +- Fix route prerender ([7adc361](https://github.com/undb-io/undb/commit/7adc361)) + +### ❤️ Contributors + +- Nichenqin ([@nichenqin](http://github.com/nichenqin)) + ## v1.0.0-16 diff --git a/package.json b/package.json index e9b126ecd..3857745f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "undb", - "version": "1.0.0-16", + "version": "1.0.0-17", "private": true, "scripts": { "build": "NODE_ENV=production bun --bun turbo build",