diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..2999acac8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "json.schemas": [ + { + "fileMatch": ["packages/template/src/templates/*.base.json"], + "url": "http://localhost:3721/api/template/base/schema.json" + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ee7ce5f5..65bcea585 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.0.0-84 + + +### 🏡 Chore + +- Fix ts issue ([68c5291](https://github.com/undb-io/undb/commit/68c5291)) + +### ❤️ Contributors + +- Nichenqin ([@nichenqin](http://github.com/nichenqin)) + ## v1.0.0-83 diff --git a/apps/backend/package.json b/apps/backend/package.json index c0507500a..9a9f176e3 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -36,6 +36,7 @@ "@undb/realtime": "workspace:*", "@undb/share": "workspace:*", "@undb/space": "workspace:*", + "@undb/template": "workspace:*", "@undb/trpc": "workspace:*", "@undb/webhook": "workspace:*", "arctic": "^1.9.2", diff --git a/apps/backend/src/app.ts b/apps/backend/src/app.ts index 09317b95e..546fa5dc5 100644 --- a/apps/backend/src/app.ts +++ b/apps/backend/src/app.ts @@ -26,12 +26,14 @@ import * as pkg from "../../../package.json" import { Auth, OpenAPI, Realtime, SpaceModule, TableModule, Web } from "./modules" import { FileService } from "./modules/file/file" import { OpenTelemetryModule } from "./modules/opentelemetry/opentelemetry.module" +import { TemplateModule } from "./modules/template/template.module" import { loggerPlugin } from "./plugins/logging" const auth = container.resolve(Auth) const web = container.resolve(Web) const openapi = container.resolve(OpenAPI) const opentelemetry = container.resolve(OpenTelemetryModule) +const template = container.resolve(TemplateModule) export const app = new Elysia() .onStart(async () => { @@ -132,6 +134,7 @@ export const app = new Elysia() .use(auth.route()) .use(web.route()) .use(openapi.route()) + .use(template.route()) .use(trpc(route)) .guard( { diff --git a/apps/backend/src/modules/template/template.module.ts b/apps/backend/src/modules/template/template.module.ts new file mode 100644 index 000000000..f16390717 --- /dev/null +++ b/apps/backend/src/modules/template/template.module.ts @@ -0,0 +1,12 @@ +import { singleton } from "@undb/di" +import { baseTemplateSchema } from "@undb/template" +import Elysia from "elysia" + +@singleton() +export class TemplateModule { + route() { + return new Elysia().get("/api/template/base/schema.json", () => { + return baseTemplateSchema + }) + } +} diff --git a/apps/frontend/src/lib/components/blocks/nav/nav-tools.svelte b/apps/frontend/src/lib/components/blocks/nav/nav-tools.svelte index ec025fbdf..9cd2cbc24 100644 --- a/apps/frontend/src/lib/components/blocks/nav/nav-tools.svelte +++ b/apps/frontend/src/lib/components/blocks/nav/nav-tools.svelte @@ -8,9 +8,24 @@ import SpaceDropdown from "../space/space-dropdown.svelte" import type { ISpaceDTO } from "@undb/space" import { preferences } from "$lib/store/persisted.store" + import { createMutation } from "@tanstack/svelte-query" + import { trpc } from "$lib/trpc/client" + import { toast } from "svelte-sonner" + import { goto, invalidateAll } from "$app/navigation" export let space: ISpaceDTO | undefined | null export let me: any + + const createFromTemplateMutation = createMutation({ + mutationFn: trpc.template.createFromTemplate.mutate, + onSuccess: async (data) => { + await invalidateAll() + toast.success("Base created successfully") + if (data.baseIds.length > 0) { + goto(`/bases/${data.baseIds[0]}`) + } + }, + })
@@ -67,5 +82,15 @@ Create New Base + + {/if}
diff --git a/apps/frontend/src/routes/(authed)/(template)/create-from-template/[shareId]/+layout.gql b/apps/frontend/src/routes/(authed)/(template)/create-from-share/[shareId]/+layout.gql similarity index 75% rename from apps/frontend/src/routes/(authed)/(template)/create-from-template/[shareId]/+layout.gql rename to apps/frontend/src/routes/(authed)/(template)/create-from-share/[shareId]/+layout.gql index d562fc602..fc5e8196e 100644 --- a/apps/frontend/src/routes/(authed)/(template)/create-from-template/[shareId]/+layout.gql +++ b/apps/frontend/src/routes/(authed)/(template)/create-from-share/[shareId]/+layout.gql @@ -1,4 +1,4 @@ -query GetCreateFromTemplateData($shareId: ID!) { +query GetCreateFromShareData($shareId: ID!) { space { id name diff --git a/apps/frontend/src/routes/(authed)/(template)/create-from-template/[shareId]/+layout.svelte b/apps/frontend/src/routes/(authed)/(template)/create-from-share/[shareId]/+layout.svelte similarity index 100% rename from apps/frontend/src/routes/(authed)/(template)/create-from-template/[shareId]/+layout.svelte rename to apps/frontend/src/routes/(authed)/(template)/create-from-share/[shareId]/+layout.svelte diff --git a/apps/frontend/src/routes/(authed)/(template)/create-from-template/[shareId]/+layout.ts b/apps/frontend/src/routes/(authed)/(template)/create-from-share/[shareId]/+layout.ts similarity index 71% rename from apps/frontend/src/routes/(authed)/(template)/create-from-template/[shareId]/+layout.ts rename to apps/frontend/src/routes/(authed)/(template)/create-from-share/[shareId]/+layout.ts index 0c07ee103..102e215b6 100644 --- a/apps/frontend/src/routes/(authed)/(template)/create-from-template/[shareId]/+layout.ts +++ b/apps/frontend/src/routes/(authed)/(template)/create-from-share/[shareId]/+layout.ts @@ -1,4 +1,4 @@ -import { GetCreateFromTemplateDataStore } from "$houdini" +import { GetCreateFromShareDataStore } from "$houdini" import type { LayoutLoad } from "./$types" export const ssr = false @@ -7,7 +7,7 @@ export const prerender = "auto" export const load: LayoutLoad = async (event) => { const { shareId } = event.params - const store = new GetCreateFromTemplateDataStore() + const store = new GetCreateFromShareDataStore() await store.fetch({ event, diff --git a/apps/frontend/src/routes/(authed)/(template)/create-from-template/[shareId]/+page.svelte b/apps/frontend/src/routes/(authed)/(template)/create-from-share/[shareId]/+page.svelte similarity index 93% rename from apps/frontend/src/routes/(authed)/(template)/create-from-template/[shareId]/+page.svelte rename to apps/frontend/src/routes/(authed)/(template)/create-from-share/[shareId]/+page.svelte index 6a8b0ed27..ac81bfe87 100644 --- a/apps/frontend/src/routes/(authed)/(template)/create-from-template/[shareId]/+page.svelte +++ b/apps/frontend/src/routes/(authed)/(template)/create-from-share/[shareId]/+page.svelte @@ -9,7 +9,7 @@ import * as Form from "$lib/components/ui/form" import * as Alert from "$lib/components/ui/alert/index.js" import { LoaderCircleIcon, SirenIcon, Store } from "lucide-svelte" - import { createFromTemplateCommand } from "@undb/commands" + import { createFromShareCommand } from "@undb/commands" import { Checkbox } from "$lib/components/ui/checkbox" import { trpc } from "$lib/trpc/client" import { page } from "$app/stores" @@ -39,8 +39,8 @@ }) } - const createFromTemplateMutation = createMutation({ - mutationFn: trpc.base.createFromTemplate.mutate, + const createFromShareMutation = createMutation({ + mutationFn: trpc.base.createFromShare.mutate, onError(error, variables, context) { toast.error(error.message) }, @@ -60,12 +60,12 @@ name: template?.name, includeData: true, }, - zodClient(createFromTemplateCommand), + zodClient(createFromShareCommand), ), { SPA: true, dataType: "json", - validators: zodClient(createFromTemplateCommand), + validators: zodClient(createFromShareCommand), resetForm: false, invalidateAll: false, onSubmit(input) { @@ -77,7 +77,7 @@ return } - await $createFromTemplateMutation.mutateAsync(event.form.data) + await $createFromShareMutation.mutateAsync(event.form.data) }, }, ) @@ -176,8 +176,8 @@ System fields will be updated to the current user and timestamp. - - {#if $createFromTemplateMutation.isPending} + + {#if $createFromShareMutation.isPending} {/if} Create diff --git a/bun.lockb b/bun.lockb index 889d213c6..c0e652b0e 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 1f64171f0..49f8107f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "undb", - "version": "1.0.0-83", + "version": "1.0.0-84", "private": true, "scripts": { "build": "NODE_ENV=production bun --bun turbo build", diff --git a/packages/base/src/dto/index.ts b/packages/base/src/dto/index.ts index 185736b43..ff94ea2ba 100644 --- a/packages/base/src/dto/index.ts +++ b/packages/base/src/dto/index.ts @@ -2,4 +2,5 @@ export * from "./base.dto" export * from "./create-base.dto" export * from "./delete-base.dto" export * from "./duplicate-base.dto" +export * from "./unique-base.dto" export * from "./update-base.dto" diff --git a/packages/base/src/dto/unique-base.dto.ts b/packages/base/src/dto/unique-base.dto.ts new file mode 100644 index 000000000..87d482ce9 --- /dev/null +++ b/packages/base/src/dto/unique-base.dto.ts @@ -0,0 +1,13 @@ +import { spaceIdSchema } from "@undb/space" +import { z } from "@undb/zod" +import { baseIdSchema, baseNameSchema } from "../value-objects" + +export const uniqueBaseDTO = z + .object({ + baseId: baseIdSchema, + baseName: baseNameSchema, + spaceId: spaceIdSchema, + }) + .partial() + +export type IUniqueBaseDTO = z.infer diff --git a/packages/base/src/specifications/index.ts b/packages/base/src/specifications/index.ts index d883b1ee4..fee4e50c1 100644 --- a/packages/base/src/specifications/index.ts +++ b/packages/base/src/specifications/index.ts @@ -2,3 +2,23 @@ export * from "./base-id.specification.js" export * from "./base-name.specification.js" export * from "./base-q.specification.js" export * from "./base-space-id.specification.js" + +import { CompositeSpecification, Err, Ok, Result } from "@undb/domain" +import type { Base } from "../base.js" +import type { IUniqueBaseDTO } from "../dto/unique-base.dto.js" +import type { IBaseSpecVisitor } from "../interface.js" +import { BaseId } from "../value-objects/base-id.vo.js" +import { WithBaseId } from "./base-id.specification.js" +import { WithBaseName } from "./base-name.specification.js" + +type BaseComositeSpecification = CompositeSpecification + +export const withUniqueBase = (dto: IUniqueBaseDTO): Result => { + if (dto.baseId) { + return Ok(new WithBaseId(new BaseId(dto.baseId))) + } + if (dto.baseName && dto.spaceId) { + return Ok(WithBaseName.fromString(dto.baseName)) + } + return Err("Invalid base specification") +} diff --git a/packages/base/src/value-objects/base-name.vo.ts b/packages/base/src/value-objects/base-name.vo.ts index e135c98eb..b81117aa6 100644 --- a/packages/base/src/value-objects/base-name.vo.ts +++ b/packages/base/src/value-objects/base-name.vo.ts @@ -1,7 +1,7 @@ import { ValueObject } from "@undb/domain" import * as z from "@undb/zod" -export const baseNameSchema = z.string().min(1) +export const baseNameSchema = z.string().min(1, { message: "Base name must be at least 1 character" }) export class BaseName extends ValueObject> { static from(name: string): BaseName { diff --git a/packages/command-handlers/package.json b/packages/command-handlers/package.json index bd9085882..3aae4ceed 100644 --- a/packages/command-handlers/package.json +++ b/packages/command-handlers/package.json @@ -16,6 +16,7 @@ "@undb/cqrs": "workspace:*", "@undb/di": "workspace:*", "@undb/logger": "workspace:*", + "@undb/template": "workspace:*", "@undb/openapi": "workspace:*", "@undb/user": "workspace:*", "ts-pattern": "^5.3.1" diff --git a/packages/command-handlers/src/handlers/create-from-share.command-handler.ts b/packages/command-handlers/src/handlers/create-from-share.command-handler.ts new file mode 100644 index 000000000..272e0cc3b --- /dev/null +++ b/packages/command-handlers/src/handlers/create-from-share.command-handler.ts @@ -0,0 +1,58 @@ +import { checkPermission, injectSpaceMemberService, type ISpaceMemberService } from "@undb/authz" +import { BaseId, injectBaseRepository, WithBaseId, WithBaseSpaceId, type IBaseRepository } from "@undb/base" +import { CreateFromShareCommand } from "@undb/commands" +import { getCurrentUserId, 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 { injectShareRepository, WithShareId, type IShareRepository } from "@undb/share" +import { injectTableService, type ITableService } from "@undb/table" + +@commandHandler(CreateFromShareCommand) +@singleton() +export class CreateFromShareCommandHandler implements ICommandHandler { + private readonly logger = createLogger(CreateFromShareCommandHandler.name) + + constructor( + @injectBaseRepository() + private readonly baseRepository: IBaseRepository, + @injectTableService() + private readonly tableService: ITableService, + @injectSpaceMemberService() + private readonly spaceMemberService: ISpaceMemberService, + @injectShareRepository() + private readonly shareRepository: IShareRepository, + ) {} + + async execute(command: CreateFromShareCommand): Promise { + this.logger.debug("CreateFromShareCommandHandler execute command", command) + const share = (await this.shareRepository.findOne(WithShareId.fromString(command.shareId))).expect( + "Share not found", + ) + + if (share.target.type !== "base") { + throw new Error("Share target is not base") + } + + const baseId = share.target.id + const spaceId = share.spaceId + + const userId = getCurrentUserId() + const targetSpaceId = command.targetSpaceId ?? mustGetCurrentSpaceId() + + const member = (await this.spaceMemberService.getSpaceMember(userId, targetSpaceId)).expect("Member not found") + checkPermission(member.props.role, ["base:create"]) + + const spec = new WithBaseId(new BaseId(baseId)).and(new WithBaseSpaceId(spaceId)) + const base = (await this.baseRepository.findOne(spec)).expect("Base not found") + + const duplicatedBase = await this.tableService.duplicateBase(base, spaceId, targetSpaceId, { + id: baseId, + name: command.name, + includeData: command.includeData, + }) + + return duplicatedBase.id.value + } +} 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 index 376ffb96b..9f93951f6 100644 --- a/packages/command-handlers/src/handlers/create-from-template.command-handler.ts +++ b/packages/command-handlers/src/handlers/create-from-template.command-handler.ts @@ -1,58 +1,31 @@ -import { checkPermission, injectSpaceMemberService, type ISpaceMemberService } from "@undb/authz" -import { BaseId, injectBaseRepository, WithBaseId, WithBaseSpaceId, type IBaseRepository } from "@undb/base" -import { CreateFromTemplateCommand } from "@undb/commands" -import { getCurrentUserId, mustGetCurrentSpaceId } from "@undb/context/server" +import { CreateFromTemplateCommand, type ICreateFromTemplateCommandOutput } 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 { injectShareRepository, WithShareId, type IShareRepository } from "@undb/share" -import { injectTableService, type ITableService } from "@undb/table" +import { injectTemplateService, type ITemplateService, templates } from "@undb/template" @commandHandler(CreateFromTemplateCommand) @singleton() -export class CreateFromTemplateCommandHandler implements ICommandHandler { +export class CreateFromTemplateCommandHandler + implements ICommandHandler +{ private readonly logger = createLogger(CreateFromTemplateCommandHandler.name) constructor( - @injectBaseRepository() - private readonly baseRepository: IBaseRepository, - @injectTableService() - private readonly tableService: ITableService, - @injectSpaceMemberService() - private readonly spaceMemberService: ISpaceMemberService, - @injectShareRepository() - private readonly shareRepository: IShareRepository, + @injectTemplateService() + private readonly templateService: ITemplateService, ) {} - async execute(command: CreateFromTemplateCommand): Promise { - this.logger.debug("CreateFromTemplateCommandHandler execute command", command) - const share = (await this.shareRepository.findOne(WithShareId.fromString(command.shareId))).expect( - "Share not found", - ) + async execute(command: CreateFromTemplateCommand): Promise { + this.logger.info(`create from template command received: ${command.templateName}`) - if (share.target.type !== "base") { - throw new Error("Share target is not base") - } + const template = templates["test"] - const baseId = share.target.id - const spaceId = share.spaceId + const spaceId = mustGetCurrentSpaceId() + const result = await this.templateService.createBase(template, spaceId) - const userId = getCurrentUserId() - const targetSpaceId = command.targetSpaceId ?? mustGetCurrentSpaceId() - - const member = (await this.spaceMemberService.getSpaceMember(userId, targetSpaceId)).expect("Member not found") - checkPermission(member.props.role, ["base:create"]) - - const spec = new WithBaseId(new BaseId(baseId)).and(new WithBaseSpaceId(spaceId)) - const base = (await this.baseRepository.findOne(spec)).expect("Base not found") - - const duplicatedBase = await this.tableService.duplicateBase(base, spaceId, targetSpaceId, { - id: baseId, - name: command.name, - includeData: command.includeData, - }) - - return duplicatedBase.id.value + return { baseIds: result.map(({ base }) => base.id.value) } } } diff --git a/packages/command-handlers/src/handlers/index.ts b/packages/command-handlers/src/handlers/index.ts index 103e70821..0be613430 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 { CreateFromShareCommandHandler } from "./create-from-share.command-handler" import { CreateFromTemplateCommandHandler } from "./create-from-template.command-handler" import { CreateRecordCommandHandler } from "./create-record.command-handler" import { CreateRecordsCommandHandler } from "./create-records.command-handler" @@ -79,7 +80,7 @@ export const commandHandlers = [ BulkUpdateRecordsCommandHandler, CreateTableViewCommandHandler, DuplicateViewCommandHandler, - CreateFromTemplateCommandHandler, + CreateFromShareCommandHandler, DeleteViewCommandHandler, CreateBaseCommandHandler, UpdateBaseCommandHandler, @@ -106,4 +107,5 @@ export const commandHandlers = [ SubmitFormCommandHandler, SetFieldWidthCommandHandler, DuplicateTableFormCommandHandler, + CreateFromTemplateCommandHandler, ] diff --git a/packages/commands/package.json b/packages/commands/package.json index 421c37f60..d993de07b 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@undb/base": "workspace:*", + "@undb/template": "workspace:*", "@undb/domain": "workspace:*", "@undb/openapi": "workspace:*", "@undb/share": "workspace:*", diff --git a/packages/commands/src/create-base.command.ts b/packages/commands/src/create-base.command.ts index 4421cc8b7..5dc77541c 100644 --- a/packages/commands/src/create-base.command.ts +++ b/packages/commands/src/create-base.command.ts @@ -1,8 +1,17 @@ import { createBaseDTO } from "@undb/base" import { Command, type CommandProps } from "@undb/domain" import { z } from "@undb/zod" +import { createTableCommand } from "./create-table.command" -export const createBaseCommand = createBaseDTO.omit({ id: true }) +export const createBaseCommand = createBaseDTO + .omit({ + id: true, + }) + .merge( + z.object({ + tables: createTableCommand.array().optional(), + }), + ) export type ICreateBaseCommand = z.infer diff --git a/packages/commands/src/create-from-share.command.ts b/packages/commands/src/create-from-share.command.ts new file mode 100644 index 000000000..51291da75 --- /dev/null +++ b/packages/commands/src/create-from-share.command.ts @@ -0,0 +1,28 @@ +import { Command, type CommandProps } from "@undb/domain" +import { shareIdSchema } from "@undb/share" +import { spaceIdSchema } from "@undb/space" +import { z } from "@undb/zod" + +export const createFromShareCommand = z.object({ + shareId: shareIdSchema, + targetSpaceId: spaceIdSchema.optional(), + name: z.string().optional(), + includeData: z.boolean().optional(), +}) + +export type ICreateFromShareCommand = z.infer + +export class CreateFromShareCommand extends Command implements ICreateFromShareCommand { + public readonly shareId: string + public readonly targetSpaceId?: string + public readonly name?: string + public readonly includeData?: boolean + + constructor(props: CommandProps) { + super(props) + this.shareId = props.shareId + this.targetSpaceId = props.targetSpaceId + this.name = props.name + this.includeData = props.includeData + } +} diff --git a/packages/commands/src/create-from-template.command.ts b/packages/commands/src/create-from-template.command.ts index b67a9d9c3..316aa444d 100644 --- a/packages/commands/src/create-from-template.command.ts +++ b/packages/commands/src/create-from-template.command.ts @@ -1,28 +1,24 @@ +import { baseIdSchema } from "@undb/base" import { Command, type CommandProps } from "@undb/domain" -import { shareIdSchema } from "@undb/share" -import { spaceIdSchema } from "@undb/space" import { z } from "@undb/zod" export const createFromTemplateCommand = z.object({ - shareId: shareIdSchema, - targetSpaceId: spaceIdSchema.optional(), - name: z.string().optional(), - includeData: z.boolean().optional(), + templateName: z.string(), }) export type ICreateFromTemplateCommand = z.infer +export const createFromTemplateCommandOutput = z.object({ + baseIds: z.array(baseIdSchema), +}) + +export type ICreateFromTemplateCommandOutput = z.infer + export class CreateFromTemplateCommand extends Command implements ICreateFromTemplateCommand { - public readonly shareId: string - public readonly targetSpaceId?: string - public readonly name?: string - public readonly includeData?: boolean + public readonly templateName: string constructor(props: CommandProps) { super(props) - this.shareId = props.shareId - this.targetSpaceId = props.targetSpaceId - this.name = props.name - this.includeData = props.includeData + this.templateName = props.templateName } } diff --git a/packages/commands/src/create-table.command.ts b/packages/commands/src/create-table.command.ts index 2bc7520b9..5287c13c5 100644 --- a/packages/commands/src/create-table.command.ts +++ b/packages/commands/src/create-table.command.ts @@ -1,16 +1,20 @@ +import { baseIdSchema, baseNameSchema } from "@undb/base" import { Command, type CommandProps } from "@undb/domain" import type { ICreateSchemaDTO } from "@undb/table" import { createTableDTO } from "@undb/table" import { z } from "@undb/zod" -export const createTableCommand = createTableDTO.omit({ spaceId: true }) +export const createTableCommand = createTableDTO + .omit({ spaceId: true }) + .merge(z.object({ baseId: baseIdSchema.optional(), baseName: baseNameSchema.optional() })) export type ICreateTableCommand = z.infer export class CreateTableCommand extends Command implements ICreateTableCommand { public readonly id?: string public readonly name: string - public readonly baseId: string + public readonly baseId?: string + public readonly baseName?: string public readonly schema: ICreateSchemaDTO constructor(props: CommandProps) { @@ -18,6 +22,7 @@ export class CreateTableCommand extends Command implements ICreateTableCommand { this.id = props.id this.name = props.name this.baseId = props.baseId + this.baseName = props.baseName this.schema = props.schema } } diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index 18f1e17db..adfca09be 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-share.command" export * from "./create-from-template.command" export * from "./create-record.command" export * from "./create-records.command" diff --git a/packages/persistence/src/table/table.repository.ts b/packages/persistence/src/table/table.repository.ts index 9f2eaa58e..8dc23e877 100644 --- a/packages/persistence/src/table/table.repository.ts +++ b/packages/persistence/src/table/table.repository.ts @@ -106,6 +106,12 @@ export class TableRepository implements ITableRepository { await this.outboxService.save(table) } + async insertMany(tables: TableDo[]): Promise { + for (const table of tables) { + await this.insert(table) + } + } + async bulkUpdate(updates: { table: TableDo; spec: Option }[]): Promise { for (const update of updates) { await this.#updateOneById(update.table, update.spec) diff --git a/packages/table/src/modules/schema/fields/condition/condition.type.ts b/packages/table/src/modules/schema/fields/condition/condition.type.ts index 209d15ce0..962b59d9a 100644 --- a/packages/table/src/modules/schema/fields/condition/condition.type.ts +++ b/packages/table/src/modules/schema/fields/condition/condition.type.ts @@ -29,12 +29,16 @@ export type MaybeConditionGroupChildren = Maybe export function createConditionGroup( optionType: OptionType, fieldType: FieldOptionType, + level = 1, ): z.ZodType> { + const nested = level < 3 ? z.lazy(() => createConditionGroup(optionType, fieldType, level + 1)) : undefined + const child = nested + ? z.union([...createConditionSchema(fieldType).options, nested]) + : z.union([...createConditionSchema(fieldType).options]) + return z.object({ conjunction: z.enum(["and", "or"]), - children: z.array( - z.union([...createConditionSchema(fieldType).options, z.lazy(() => createConditionGroup(optionType, fieldType))]), - ), + children: z.array(child), disabled: z.boolean().optional(), option: optionType, }) as z.ZodType> diff --git a/packages/table/src/modules/schema/fields/dto/create-field.dto.ts b/packages/table/src/modules/schema/fields/dto/create-field.dto.ts index f876f1c61..48d319fd2 100644 --- a/packages/table/src/modules/schema/fields/dto/create-field.dto.ts +++ b/packages/table/src/modules/schema/fields/dto/create-field.dto.ts @@ -1,13 +1,13 @@ import { z } from "@undb/zod" -import { createAttachmentFieldDTO } from "../variants/attachment-field" +import { createAttachmentFieldDTO } from "../variants/attachment-field/attachment-field.vo" import { createButtonFieldDTO } from "../variants/button-field/button-field.vo" -import { createCheckboxFieldDTO } from "../variants/checkbox-field" -import { createCurrencyFieldDTO } from "../variants/currency-field" +import { createCheckboxFieldDTO } from "../variants/checkbox-field/checkbox-field.vo" +import { createCurrencyFieldDTO } from "../variants/currency-field/currency-field.vo" import { createDateFieldDTO } from "../variants/date-field/date-field.vo" import { createDurationFieldDTO } from "../variants/duration-field/duration-field.vo" -import { createEmailFieldDTO } from "../variants/email-field" +import { createEmailFieldDTO } from "../variants/email-field/email-field.vo" import { createJsonFieldDTO } from "../variants/json-field/json-field.vo" -import { createLongTextFieldDTO } from "../variants/long-text-field" +import { createLongTextFieldDTO } from "../variants/long-text-field/long-text-field.vo" import { createNumberFieldDTO } from "../variants/number-field/number-field.vo" import { createPercentageFieldDTO } from "../variants/percentage-field/percentage-field.vo" import { createRatingFieldDTO } from "../variants/rating-field/rating-field.vo" @@ -16,7 +16,7 @@ import { createRollupFieldDTO } from "../variants/rollup-field/rollup-field.vo" import { createSelectFieldDTO } from "../variants/select-field/select-field.vo" import { createStringFieldDTO } from "../variants/string-field/string-field.vo" import { createUrlFieldDTO } from "../variants/url-field/url-field.vo" -import { createUserFieldDTO } from "../variants/user-field" +import { createUserFieldDTO } from "../variants/user-field/user-field.vo" export const createFieldDTO = z.discriminatedUnion("type", [ createStringFieldDTO, @@ -39,4 +39,25 @@ export const createFieldDTO = z.discriminatedUnion("type", [ createPercentageFieldDTO, ]) +export const createFieldWithoutNameDTO = z.discriminatedUnion("type", [ + createStringFieldDTO.omit({ name: true }), + createNumberFieldDTO.omit({ name: true }), + createReferenceFieldDTO.omit({ name: true }), + createRollupFieldDTO.omit({ name: true }), + createSelectFieldDTO.omit({ name: true }), + createRatingFieldDTO.omit({ name: true }), + createEmailFieldDTO.omit({ name: true }), + createUrlFieldDTO.omit({ name: true }), + createAttachmentFieldDTO.omit({ name: true }), + createDateFieldDTO.omit({ name: true }), + createJsonFieldDTO.omit({ name: true }), + createCheckboxFieldDTO.omit({ name: true }), + createUserFieldDTO.omit({ name: true }), + createLongTextFieldDTO.omit({ name: true }), + createCurrencyFieldDTO.omit({ name: true }), + createButtonFieldDTO.omit({ name: true }), + createDurationFieldDTO.omit({ name: true }), + createPercentageFieldDTO.omit({ name: true }), +]) + export type ICreateFieldDTO = z.infer diff --git a/packages/table/src/modules/schema/fields/variants/reference-field/reference-field.vo.ts b/packages/table/src/modules/schema/fields/variants/reference-field/reference-field.vo.ts index 0eac8748d..3b6788f64 100644 --- a/packages/table/src/modules/schema/fields/variants/reference-field/reference-field.vo.ts +++ b/packages/table/src/modules/schema/fields/variants/reference-field/reference-field.vo.ts @@ -21,7 +21,7 @@ import { createReferenceFieldCondition, type IReferenceFieldConditionSchema } fr export const REFERENCE_TYPE = "reference" as const -const referenceFieldOption = z.object({ +export const referenceFieldOption = z.object({ isOwner: z.boolean(), foreignTableId: tableId, symmetricFieldId: fieldId.optional(), diff --git a/packages/table/src/modules/schema/fields/variants/rollup-field/rollup-field.vo.ts b/packages/table/src/modules/schema/fields/variants/rollup-field/rollup-field.vo.ts index 927334c3a..c98ee7034 100644 --- a/packages/table/src/modules/schema/fields/variants/rollup-field/rollup-field.vo.ts +++ b/packages/table/src/modules/schema/fields/variants/rollup-field/rollup-field.vo.ts @@ -17,7 +17,7 @@ export const rollupFn = z.enum(["sum", "average", "count", "min", "max", "lookup export type IRollupFn = z.infer -const rollupFieldOption = z.object({ +export const rollupFieldOption = z.object({ referenceFieldId: fieldId, rollupFieldId: fieldId, fn: rollupFn, diff --git a/packages/table/src/services/methods/create-table.method.ts b/packages/table/src/services/methods/create-table.method.ts index 86b5b31f1..f9967986c 100644 --- a/packages/table/src/services/methods/create-table.method.ts +++ b/packages/table/src/services/methods/create-table.method.ts @@ -1,3 +1,4 @@ +import { withUniqueBase } from "@undb/base" import { applyRules, Some } from "@undb/domain" import type { ICreateTableDTO } from "../../dto" import { TableNameShouldBeUnique } from "../../rules/table-name-should-be-unique.rule" @@ -6,8 +7,11 @@ import type { TableDo } from "../../table.do" import type { TableService } from "../table.service" export async function createTableMethod(this: TableService, dto: ICreateTableDTO): Promise { - const spec = new TableBaseIdSpecification(dto.baseId) - const baseTables = await this.repository.find(Some(spec)) + const spec = withUniqueBase(dto).ok() + const base = (await this.baseRepository.findOne(spec.unwrap())).expect("base not found") + + const baseIdSpec = new TableBaseIdSpecification(base.id.value) + const baseTables = await this.repository.find(Some(baseIdSpec)) const names = baseTables.map((table) => table.name.value).concat(dto.name) applyRules(new TableNameShouldBeUnique(names)) diff --git a/packages/table/src/table.repository.ts b/packages/table/src/table.repository.ts index a81ea405c..60230e4e0 100644 --- a/packages/table/src/table.repository.ts +++ b/packages/table/src/table.repository.ts @@ -6,8 +6,10 @@ import type { TableDo } from "./table.do" export interface ITableRepository { insert(table: TableDo): Promise + insertMany(tables: TableDo[]): Promise updateOneById(table: TableDo, spec: Option): Promise bulkUpdate(updates: { table: TableDo; spec: Option }[]): Promise + deleteOneById(table: TableDo): Promise find(spec: Option, ignoreSpace?: boolean): Promise diff --git a/packages/template/.gitignore b/packages/template/.gitignore new file mode 100644 index 000000000..9b1ee42e8 --- /dev/null +++ b/packages/template/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/packages/template/README.md b/packages/template/README.md new file mode 100644 index 000000000..5ce3e9add --- /dev/null +++ b/packages/template/README.md @@ -0,0 +1,15 @@ +# @undb/template + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run src/index.ts +``` + +This project was created using `bun init` in bun v1.1.29. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/packages/template/package.json b/packages/template/package.json new file mode 100644 index 000000000..ead1da7f6 --- /dev/null +++ b/packages/template/package.json @@ -0,0 +1,20 @@ +{ + "name": "@undb/template", + "module": "src/index.ts", + "types": "src/index.d.ts", + "type": "module", + "dependencies": { + "@undb/base": "workspace:*", + "@undb/table": "workspace:*", + "@undb/zod": "workspace:*", + "@undb/di": "workspace:*", + "@undb/logger": "workspace:*", + "zod-to-json-schema": "^3.23.3" + }, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/packages/template/src/dto/index.ts b/packages/template/src/dto/index.ts new file mode 100644 index 000000000..8d39f46f6 --- /dev/null +++ b/packages/template/src/dto/index.ts @@ -0,0 +1 @@ +export * from "./template.dto" diff --git a/packages/template/src/dto/template.dto.ts b/packages/template/src/dto/template.dto.ts new file mode 100644 index 000000000..d979de096 --- /dev/null +++ b/packages/template/src/dto/template.dto.ts @@ -0,0 +1,26 @@ +import { baseNameSchema } from "@undb/base" +import { createFieldWithoutNameDTO, fieldId, tableName } from "@undb/table" +import { z } from "@undb/zod" + +const templateSchemaDTO = z.record(fieldId, createFieldWithoutNameDTO) + +const basicTemplateTableDTO = z.object({ + schema: templateSchemaDTO, +}) + +export const baseTemplateDTO = z.record( + baseNameSchema, + z.object({ + tables: z.record(tableName, basicTemplateTableDTO), + }), +) + +export type IBaseTemplateDTO = z.infer + +export const tableTemplateDTO = z + .object({ + name: tableName, + }) + .merge(basicTemplateTableDTO) + +export type ITableTemplateDTO = z.infer diff --git a/packages/template/src/index.ts b/packages/template/src/index.ts new file mode 100644 index 000000000..fa809484b --- /dev/null +++ b/packages/template/src/index.ts @@ -0,0 +1,4 @@ +export * from "./dto" +export * from "./schema" +export * from "./service" +export * from "./templates" diff --git a/packages/template/src/schema/index.ts b/packages/template/src/schema/index.ts new file mode 100644 index 000000000..485ab3c44 --- /dev/null +++ b/packages/template/src/schema/index.ts @@ -0,0 +1 @@ +export * from "./template.schema" diff --git a/packages/template/src/schema/template.schema.ts b/packages/template/src/schema/template.schema.ts new file mode 100644 index 000000000..78b4ba4f4 --- /dev/null +++ b/packages/template/src/schema/template.schema.ts @@ -0,0 +1,68 @@ +import { baseNameSchema } from "@undb/base" +import { + attachmentFieldConstraint, + buttonFieldOption, + checkboxFieldConstraint, + currencyFieldConstraint, + currencyFieldOption, + dateFieldConstraint, + durationFieldConstraint, + emailFieldConstraint, + fieldId, + fieldName, + jsonFieldConstraint, + longTextFieldConstraint, + numberFieldConstraint, + percentageFieldConstraint, + ratingFieldConstraint, + referenceFieldConstraint, + referenceFieldOption, + rollupFieldOption, + selectFieldConstraint, + selectFieldOption, + stringFieldConstraint, + tableId, + tableName, + urlFieldConstraint, + userFieldConstraint, + viewFilterGroup, +} from "@undb/table" +import zodToJsonSchema from "zod-to-json-schema" +import { baseTemplateDTO } from "../dto/template.dto" + +export const baseTemplateSchema = zodToJsonSchema(baseTemplateDTO, { + errorMessages: true, + definitions: { + tableId, + tableName, + baseName: baseNameSchema, + fieldId, + fieldName, + viewFilterGroup, + + stringFieldConstraint, + numberFieldConstraint, + checkboxFieldConstraint, + attachmentFieldConstraint, + buttonFieldOption, + + referenceFieldConstraint, + referenceFieldOption, + rollupFieldOption, + + selectFieldOption, + selectFieldConstraint, + + ratingFieldConstraint, + emailFieldConstraint, + urlFieldConstraint, + dateFieldConstraint, + jsonFieldConstraint, + userFieldConstraint, + longTextFieldConstraint, + currencyFieldConstraint, + currencyFieldOption, + durationFieldConstraint, + percentageFieldConstraint, + }, +}) diff --git a/packages/template/src/service/index.ts b/packages/template/src/service/index.ts new file mode 100644 index 000000000..169ce6f84 --- /dev/null +++ b/packages/template/src/service/index.ts @@ -0,0 +1,2 @@ +export * from "./template.service" +export * from "./template.service.provider" diff --git a/packages/template/src/service/template.service.provider.ts b/packages/template/src/service/template.service.provider.ts new file mode 100644 index 000000000..2586c7df4 --- /dev/null +++ b/packages/template/src/service/template.service.provider.ts @@ -0,0 +1,6 @@ +import { container, inject } from "@undb/di" +import { TemplateService } from "./template.service" + +export const TEMPLATE_SERVICE = Symbol.for("TemplateService") +export const injectTemplateService = () => inject(TEMPLATE_SERVICE) +container.register(TEMPLATE_SERVICE, { useClass: TemplateService }) diff --git a/packages/template/src/service/template.service.ts b/packages/template/src/service/template.service.ts new file mode 100644 index 000000000..1d98bea5f --- /dev/null +++ b/packages/template/src/service/template.service.ts @@ -0,0 +1,36 @@ +import { Base, injectBaseRepository, WithBaseSpaceId, type IBaseRepository } from "@undb/base" +import { singleton } from "@undb/di" +import { createLogger } from "@undb/logger" +import { injectTableRepository, TableDo, type ITableRepository } from "@undb/table" +import type { IBaseTemplateDTO } from "../dto" +import { TemplateFactory } from "../template.factory" + +export interface ITemplateService { + createBase(dto: IBaseTemplateDTO, spaceId: string): Promise<{ base: Base; tables: TableDo[] }[]> +} + +@singleton() +export class TemplateService implements ITemplateService { + private readonly logger = createLogger(TemplateService.name) + + constructor( + @injectBaseRepository() + private readonly baseRepository: IBaseRepository, + @injectTableRepository() + private readonly tableRepository: ITableRepository, + ) {} + + async createBase(dto: IBaseTemplateDTO, spaceId: string): Promise<{ base: Base; tables: TableDo[] }[]> { + this.logger.info(dto) + const bases = await this.baseRepository.find(new WithBaseSpaceId(spaceId)) + const baseNames = bases.map((base) => base.name.value) + const result = TemplateFactory.create(dto, baseNames, spaceId) + + for (const { base, tables } of result) { + await this.baseRepository.insert(base) + await this.tableRepository.insertMany(tables) + } + + return result + } +} diff --git a/packages/template/src/template.factory.ts b/packages/template/src/template.factory.ts new file mode 100644 index 000000000..c8a93001b --- /dev/null +++ b/packages/template/src/template.factory.ts @@ -0,0 +1,27 @@ +import { Base, BaseFactory } from "@undb/base" +import { type ICreateSchemaDTO, TableCreator, TableDo } from "@undb/table" +import { getNextName } from "@undb/utils" +import { type IBaseTemplateDTO } from "./dto/template.dto" + +export class TemplateFactory { + static create(template: IBaseTemplateDTO, baseNames: string[], spaceId: string): { base: Base; tables: TableDo[] }[] { + const result: { base: Base; tables: TableDo[] }[] = [] + for (const [name, b] of Object.entries(template)) { + const baseName = getNextName(baseNames, name) + const base = BaseFactory.create({ name: baseName, spaceId }) + const baseId = base.id.value + + const tables: TableDo[] = [] + for (const [name, table] of Object.entries(b.tables)) { + const schema = Object.entries(table.schema).map(([name, field]) => ({ ...field, name })) as ICreateSchemaDTO + + const t = new TableCreator().create({ baseId, name, schema, spaceId }) + tables.push(t) + } + + result.push({ base, tables }) + } + + return result + } +} diff --git a/packages/template/src/templates/index.ts b/packages/template/src/templates/index.ts new file mode 100644 index 000000000..f12a7b369 --- /dev/null +++ b/packages/template/src/templates/index.ts @@ -0,0 +1,6 @@ +import type { IBaseTemplateDTO } from "../dto" +import { default as test } from "./test.base.json" + +const templates = { test } as Record + +export { templates } diff --git a/packages/template/src/templates/test.base.json b/packages/template/src/templates/test.base.json new file mode 100644 index 000000000..6fe83d4f2 --- /dev/null +++ b/packages/template/src/templates/test.base.json @@ -0,0 +1,32 @@ +{ + "hello": { + "tables": { + "world": { + "schema": { + "name": { + "type": "string", + "constraint": { + "max": 10 + }, + "defaultValue": "world", + "display": true + }, + "age": { + "type": "number", + "constraint": { + "min": 0 + } + }, + "parent": { + "type": "user", + "defaultValue": "@me" + }, + "isActive": { + "type": "checkbox", + "defaultValue": true + } + } + } + } + } +} diff --git a/packages/template/tsconfig.json b/packages/template/tsconfig.json new file mode 100644 index 000000000..34ae0fe3d --- /dev/null +++ b/packages/template/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false, + "experimentalDecorators": true, + "emitDecoratorMetadata": true + } +} diff --git a/packages/trpc/package.json b/packages/trpc/package.json index a19dd241d..b6d4e771e 100644 --- a/packages/trpc/package.json +++ b/packages/trpc/package.json @@ -20,6 +20,7 @@ "@undb/persistence": "workspace:*", "@undb/queries": "workspace:*", "@undb/table": "workspace:*", + "@undb/template": "workspace:*", "@undb/zod": "workspace:*", "zod-validation-error": "^3.3.1" } diff --git a/packages/trpc/src/router.ts b/packages/trpc/src/router.ts index d6257bb1e..48e2f6920 100644 --- a/packages/trpc/src/router.ts +++ b/packages/trpc/src/router.ts @@ -4,6 +4,7 @@ import { BulkUpdateRecordsCommand, CreateApiTokenCommand, CreateBaseCommand, + CreateFromShareCommand, CreateFromTemplateCommand, CreateRecordCommand, CreateRecordsCommand, @@ -55,7 +56,9 @@ import { bulkduplicateRecordsCommand, createApiTokenCommand, createBaseCommand, + createFromShareCommand, createFromTemplateCommand, + createFromTemplateCommandOutput, createRecordCommand, createRecordsCommand, createSpaceCommand, @@ -106,9 +109,9 @@ import { updateaccountCommand, } from "@undb/commands" import { getCurrentSpaceId } from "@undb/context/server" -import { CommandBus,QueryBus } from "@undb/cqrs" +import { CommandBus, QueryBus } from "@undb/cqrs" import { container } from "@undb/di" -import type { ICommandBus,IQueryBus } from "@undb/domain" +import type { ICommandBus, IQueryBus } from "@undb/domain" import { CountRecordsQuery, GetAggregatesQuery, @@ -136,7 +139,7 @@ import { import { tableDTO } from "@undb/table" import { z } from "@undb/zod" import { authz } from "./authz.middleware" -import { privateProcedure,publicProcedure,t } from "./trpc" +import { privateProcedure, publicProcedure, t } from "./trpc" const commandBus = container.resolve(CommandBus) const queryBus = container.resolve(QueryBus) @@ -361,11 +364,11 @@ const baseRouter = t.router({ } return commandBus.execute(new CreateBaseCommand({ ...input, spaceId })) }), - createFromTemplate: privateProcedure + createFromShare: privateProcedure // check authz in handler, because we can create base to another space // .use(authz("base:create")) - .input(createFromTemplateCommand) - .mutation(({ input }) => commandBus.execute(new CreateFromTemplateCommand(input))), + .input(createFromShareCommand) + .mutation(({ input }) => commandBus.execute(new CreateFromShareCommand(input))), duplicate: privateProcedure .use(authz("base:create")) .input(duplicateBaseCommand) @@ -425,18 +428,26 @@ const apiTokenRouter = t.router({ const spaceRouter = t.router({ list: privateProcedure - .input(getMemberSpacesQuery) .use(authz("space:list")) + .input(getMemberSpacesQuery) .query(({ input }) => queryBus.execute(new GetMemberSpacesQuery(input))), create: privateProcedure .input(createSpaceCommand) .mutation(({ input }) => commandBus.execute(new CreateSpaceCommand(input))), update: privateProcedure - .input(updateSpaceCommand) .use(authz("space:update")) + .input(updateSpaceCommand) .mutation(({ input }) => commandBus.execute(new UpdateSpaceCommand(input))), }) +const templateRouter = t.router({ + createFromTemplate: privateProcedure + .use(authz("base:create")) + .input(createFromTemplateCommand) + .output(createFromTemplateCommandOutput) + .mutation(({ input }) => commandBus.execute(new CreateFromTemplateCommand(input))), +}) + export const route = t.router({ table: tableRouter, record: recordRouter, @@ -448,6 +459,7 @@ export const route = t.router({ space: spaceRouter, apiToken: apiTokenRouter, shareData: shareDataRouter, + template: templateRouter, }) export type AppRouter = typeof route