Skip to content

Commit

Permalink
feat: create from template from share
Browse files Browse the repository at this point in the history
  • Loading branch information
nichenqin committed Aug 23, 2024
1 parent 9c52afd commit db86be8
Show file tree
Hide file tree
Showing 26 changed files with 157 additions and 74 deletions.
2 changes: 1 addition & 1 deletion apps/frontend/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ type Query {
tableByShareBase(shareId: ID!, tableId: ID!): Table
tableForeignTables(tableId: ID!): [Table!]!
tables(baseId: ID): [Table]!
template(baseId: ID!, spaceId: ID!): Template
template(shareId: ID!): Template
}

type RLS {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { SettingsIcon } from "lucide-svelte"
import ShareButton from "../share/share-button.svelte"
import type { IBaseOption } from "@undb/base"
import { invalidate } from "$app/navigation"
export let readonly = false
export let base: {
Expand Down Expand Up @@ -52,7 +53,13 @@
</DropdownMenu.Root>

{#if !readonly}
<ShareButton type="base" id={base.id} />
<ShareButton
type="base"
id={base.id}
onSuccess={() => {
invalidate(`undb:base:${base.id}`)
}}
/>
{/if}
{/if}
</header>
Expand Down
37 changes: 35 additions & 2 deletions apps/frontend/src/lib/components/blocks/share/share-button.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,16 @@
export let type: IShareTarget["type"]
export let id: IShareTarget["id"]
export let onSuccess: () => void = () => {}
const enableShareMutation = createMutation({
mutationKey: ["share", "enable", type, id],
mutationFn: trpc.share.enable.mutate,
async onSuccess(data, variables, context) {
await invalidate(`table:${$t.id.value}`)
if ($t) {
await invalidate(`table:${$t.id.value}`)
}
onSuccess()
},
onError(error, variables, context) {
toast.error(error.message)
Expand All @@ -45,7 +50,10 @@
const disableShareMutation = createMutation({
mutationFn: trpc.share.disable.mutate,
async onSuccess(data, variables, context) {
await invalidate(`table:${$t.id.value}`)
if ($t) {
await invalidate(`table:${$t.id.value}`)
}
onSuccess()
},
})
Expand Down Expand Up @@ -91,6 +99,17 @@
.with("base", () => $hasPermission("share:base"))
.with("view", () => $hasPermission("share:view"))
.otherwise(() => false)
let shareIdCopied = false
const copyShareId = async () => {
if (!share?.id) return
await copyToClipboard(share.id)
shareIdCopied = true
setTimeout(() => {
shareIdCopied = false
}, 2000)
toast.success("Copied to clipboard")
}
</script>

{#if permission}
Expand Down Expand Up @@ -177,6 +196,20 @@
</div>
</div>
{/if}

<div class="space-y-2">
<p class="text-xs font-semibold">Share ID</p>
<div class="flex items-center gap-2">
<Input value={share?.id} readonly />
<button type="button" on:click={copyShareId}>
{#if shareIdCopied}
<CopyCheckIcon class="h-4 w-4" />
{:else}
<CopyIcon class="h-4 w-4" />
{/if}
</button>
</div>
</div>
</div>
{/if}
</Popover.Content>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
query GetCreateFromTemplateData($spaceId: ID!, $baseId: ID!) {
query GetCreateFromTemplateData($shareId: ID!) {
space {
id
name
}

template(spaceId: $spaceId, baseId: $baseId) {
template(shareId: $shareId) {
name
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ export const ssr = false
export const prerender = "auto"

export const load: LayoutLoad = async (event) => {
const { spaceId, baseId } = event.params
const { shareId } = event.params

const store = new GetCreateFromTemplateDataStore()

await store.fetch({
event,
variables: { spaceId, baseId },
variables: { shareId },
})

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
export let data: PageData
let { spaceId, baseId } = $page.params
let { shareId } = $page.params
let store = data.store
let spaces = $store.data?.spaces ?? []
Expand All @@ -42,8 +42,7 @@
const form = superForm(
defaults(
{
spaceId,
baseId,
shareId,
targetSpaceId: space?.id,
name: template?.name,
includeData: true,
Expand Down
2 changes: 1 addition & 1 deletion packages/base/src/dto/update-base.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { baseIdSchema, baseNameSchema } from "../value-objects"
export const updateBaseDTO = z.object({
id: baseIdSchema,
name: baseNameSchema.optional(),
allowTemplate: z.boolean().optional(),
allowTemplate: z.boolean().optional().nullable(),
})

export type IUpdateBaseDTO = z.infer<typeof updateBaseDTO>
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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(CreateFromTemplateCommand)
Expand All @@ -20,26 +21,38 @@ export class CreateFromTemplateCommandHandler implements ICommandHandler<CreateF
private readonly tableService: ITableService,
@injectSpaceMemberService()
private readonly spaceMemberService: ISpaceMemberService,
@injectShareRepository()
private readonly shareRepository: IShareRepository,
) {}

async execute(command: CreateFromTemplateCommand): Promise<any> {
this.logger.debug("CreateFromTemplateCommandHandler 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(command.baseId)).and(new WithBaseSpaceId(command.spaceId))
const spec = new WithBaseId(new BaseId(baseId)).and(new WithBaseSpaceId(spaceId))
const base = (await this.baseRepository.findOne(spec)).expect("Base not found")

if (!base.option.allowTemplate) {
throw new Error("Base does not allow to create template")
}

const duplicatedBase = await this.tableService.duplicateBase(base, command.spaceId, targetSpaceId, {
id: command.baseId,
const duplicatedBase = await this.tableService.duplicateBase(base, spaceId, targetSpaceId, {
id: baseId,
name: command.name,
includeData: command.includeData,
})
Expand Down
11 changes: 4 additions & 7 deletions packages/commands/src/create-from-template.command.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
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({
spaceId: spaceIdSchema,
baseId: baseIdSchema,
shareId: shareIdSchema,
targetSpaceId: spaceIdSchema.optional(),
name: z.string().optional(),
includeData: z.boolean().optional(),
Expand All @@ -14,16 +13,14 @@ export const createFromTemplateCommand = z.object({
export type ICreateFromTemplateCommand = z.infer<typeof createFromTemplateCommand>

export class CreateFromTemplateCommand extends Command implements ICreateFromTemplateCommand {
public readonly spaceId: string
public readonly baseId: string
public readonly shareId: string
public readonly targetSpaceId?: string
public readonly name?: string
public readonly includeData?: boolean

constructor(props: CommandProps<ICreateFromTemplateCommand>) {
super(props)
this.spaceId = props.spaceId
this.baseId = props.baseId
this.shareId = props.shareId
this.targetSpaceId = props.targetSpaceId
this.name = props.name
this.includeData = props.includeData
Expand Down
20 changes: 13 additions & 7 deletions packages/graphql/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { yoga } from "@elysiajs/graphql-yoga"
import { useOpenTelemetry } from "@envelop/opentelemetry"
import * as otel from "@opentelemetry/api"
import type { ISpaceMemberDTO } from "@undb/authz"
import { executionContext, getCurrentSpaceId, getCurrentUserId } from "@undb/context/server"
import { executionContext, getCurrentSpaceId, getCurrentUserId, mustGetCurrentSpaceId } from "@undb/context/server"
import { QueryBus } from "@undb/cqrs"
import { inject, singleton } from "@undb/di"
import type { Option } from "@undb/domain"
Expand Down Expand Up @@ -291,7 +291,7 @@ export class Graphql {
baseByShare(shareId: ID!): Base
tableByShareBase(shareId: ID!, tableId: ID!): Table
template(spaceId: ID!, baseId: ID!): Template
template(shareId: ID!): Template
}
`,
Expand Down Expand Up @@ -401,8 +401,8 @@ 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 }))
template: async (_, { shareId }) => {
const template = await this.queryBus.execute(new GetTemplateQuery({ shareId }))
return template
},
},
Expand All @@ -413,7 +413,9 @@ export class Graphql {
},
// @ts-ignore
share: async (base) => {
return (await this.shareService.getShareByTarget({ type: "base", id: base.id })).into(null)
return (
await this.shareService.getShareByTarget({ type: "base", id: base.id }, mustGetCurrentSpaceId())
).into(null)
},
},
Table: {
Expand Down Expand Up @@ -441,13 +443,17 @@ export class Graphql {
View: {
// @ts-ignore
share: async (view) => {
return (await this.shareService.getShareByTarget({ type: "view", id: view.id })).into(null)
return (
await this.shareService.getShareByTarget({ type: "view", id: view.id }, mustGetCurrentSpaceId())
).into(null)
},
},
Form: {
// @ts-ignore
share: async (form) => {
return (await this.shareService.getShareByTarget({ type: "form", id: form.id })).into(null)
return (
await this.shareService.getShareByTarget({ type: "form", id: form.id }, mustGetCurrentSpaceId())
).into(null)
},
},
Audit: {
Expand Down
17 changes: 7 additions & 10 deletions packages/persistence/src/share/share.filter-visitor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { mustGetCurrentSpaceId } from "@undb/context/server"
import type {
IShareSpecVisitor,
Share,
Expand All @@ -9,20 +8,18 @@ import type {
WithShareTable,
WithShareView,
} from "@undb/share"
import type { WithShareSpaceId } from "@undb/share/src/specifications/share-space-id.specification"
import type { ExpressionBuilder } from "kysely"
import { AbstractQBVisitor } from "../abstract-qb.visitor"
import type { Database } from "../db"

export class ShareFilterVisitor extends AbstractQBVisitor<Share> implements IShareSpecVisitor {
constructor(
protected readonly eb: ExpressionBuilder<Database, "undb_share">,
cloned = false,
) {
constructor(protected readonly eb: ExpressionBuilder<Database, "undb_share">) {
super(eb)
if (!cloned) {
const spaceId = mustGetCurrentSpaceId()
this.addCond(this.eb.eb("space_id", "=", spaceId))
}
}
withShareSpaceId(s: WithShareSpaceId): void {
const cond = this.eb.eb("space_id", "=", s.spaceId)
this.addCond(cond)
}
idEqual(s: WithShareId): void {
this.addCond(this.eb.eb("id", "=", s.shareId.value))
Expand All @@ -48,6 +45,6 @@ export class ShareFilterVisitor extends AbstractQBVisitor<Share> implements ISha
this.addCond(cond)
}
clone(): this {
return new ShareFilterVisitor(this.eb, true) as this
return new ShareFilterVisitor(this.eb) as this
}
}
2 changes: 2 additions & 0 deletions packages/persistence/src/share/share.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class ShareMapper implements Mapper<ShareDo, Share, IShareDTO> {
id: entity.target_id,
} as IShareTarget,
enabled: entity.enabled,
spaceId: entity.space_id,
})
}
toEntity(domain: ShareDo): Share {
Expand All @@ -32,6 +33,7 @@ export class ShareMapper implements Mapper<ShareDo, Share, IShareDTO> {
id: entity.target_id,
type: entity.target_type,
} as IShareTarget,
spaceId: entity.space_id,
enabled: entity.enabled,
}
}
Expand Down
7 changes: 4 additions & 3 deletions packages/persistence/src/table/table.filter-visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ export class TableFilterVisitor extends AbstractQBVisitor<TableDo> implements IT
constructor(
private readonly qb: IQueryBuilder,
protected readonly eb: ExpressionBuilder<Database, "undb_table" | "undb_table_id_mapping">,
ignoreSpace = false,
private readonly ignoreSpace = false,
cloned = false,
) {
super(eb)
if (!ignoreSpace) {
if (!ignoreSpace && !cloned) {
const spaceId = mustGetCurrentSpaceId()
this.addCond(this.eb.eb("undb_table.space_id", "=", spaceId))
}
Expand Down Expand Up @@ -159,6 +160,6 @@ export class TableFilterVisitor extends AbstractQBVisitor<TableDo> implements IT
}
withTableUnqueName(spec: TableUniqueNameSpecification): void {}
clone(): this {
return new TableFilterVisitor(this.qb, this.eb) as this
return new TableFilterVisitor(this.qb, this.eb, this.ignoreSpace, true) as this
}
}
4 changes: 2 additions & 2 deletions packages/persistence/src/table/table.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,12 @@ export class TableRepository implements ITableRepository {
}

async find(spec: Option<TableComositeSpecification>, ignoreSpace?: boolean): Promise<TableDo[]> {
const tbs = await (getCurrentTransaction() ?? this.qb)
const query = (getCurrentTransaction() ?? this.qb)
.selectFrom("undb_table")
.selectAll("undb_table")
.$if(spec.isSome(), (qb) => new TableReferenceVisitor(qb).call(spec.unwrap()))
.where((eb) => new TableDbQuerySpecHandler(this.qb, eb, ignoreSpace).handle(spec))
.execute()
const tbs = await query.execute()

return tbs.map((t) => this.mapper.toDo(t))
}
Expand Down
Loading

0 comments on commit db86be8

Please sign in to comment.