Skip to content

Commit

Permalink
feat: button openapi
Browse files Browse the repository at this point in the history
  • Loading branch information
nichenqin committed Aug 31, 2024
1 parent 1e4a35a commit 0cef3e4
Show file tree
Hide file tree
Showing 14 changed files with 194 additions and 21 deletions.
27 changes: 25 additions & 2 deletions apps/backend/src/modules/openapi/record.openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import {
CreateRecordsCommand,
DeleteRecordCommand,
DuplicateRecordCommand,
TriggerRecordButtonCommand,
UpdateRecordCommand,
} from "@undb/commands"
import { CommandBus, QueryBus } from "@undb/cqrs"
import { inject, singleton } from "@undb/di"
import { type ICommandBus, type IQueryBus, type PaginatedDTO } from "@undb/domain"
import { Option, type ICommandBus, type IQueryBus, type PaginatedDTO } from "@undb/domain"
import { injectQueryBuilder, type IQueryBuilder } from "@undb/persistence"
import { GetReadableRecordByIdQuery, GetReadableRecordsQuery } from "@undb/queries"
import { type IRecordReadableValueDTO } from "@undb/table"
import { RecordDO, type IRecordReadableValueDTO } from "@undb/table"
import Elysia, { t } from "elysia"
import { withTransaction } from "../../db"

Expand Down Expand Up @@ -226,6 +227,28 @@ export class RecordOpenApi {
},
},
)
.post(
"/records/:recordId/trigger/:field",
async (ctx) => {
const { baseName, tableName, recordId, field } = ctx.params
return withTransaction(this.qb)(async () => {
const result = (await this.commandBus.execute(
new TriggerRecordButtonCommand({ baseName, tableName, recordId, field }),
)) as Option<RecordDO>
return {
mutated: result.isSome(),
}
})
},
{
params: t.Object({ baseName: t.String(), tableName: t.String(), recordId: t.String(), field: t.String() }),
detail: {
tags: ["Record", "Button"],
summary: "Trigger record button",
description: "Trigger record button",
},
},
)
.post(
"/records/duplicate",
async (ctx) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
const table = getTable()
const updateCell = createMutation({
mutationKey: ["record", tableId, field.id.value, recordId],
mutationFn: trpc.record.update.mutate,
const trigger = createMutation({
mutationKey: ["record", tableId, field.id.value, recordId, "trigger"],
mutationFn: trpc.record.trigger.mutate,
async onSuccess(data, variables, context) {
gridViewStore.exitEditing()
await recordsStore.invalidateRecord($table, recordId)
Expand All @@ -41,19 +41,10 @@
}
function handleUpdate() {
const option = field.option.into(undefined)
if (!option) return
const action = option.action
if (!action.values.length) return
$updateCell.mutate({
$trigger.mutate({
tableId,
id: recordId,
values: objectify(
action.values,
(v) => v.field!,
(v) => v.value ?? null,
),
recordId,
field: field.id.value,
})
}
Expand All @@ -63,8 +54,8 @@
</script>

<div class={$$restProps.class}>
<Button disabled={$updateCell.isPending || disabled} on:click={handleClick} size="xs" variant="outline">
{#if $updateCell.isPending}
<Button disabled={$trigger.isPending || disabled} on:click={handleClick} size="xs" variant="outline">
{#if $trigger.isPending}
<LoaderCircleIcon className="h-3 w-3 animate-spin" />
{:else}
{field.label ?? "Button"}
Expand Down
2 changes: 2 additions & 0 deletions packages/command-handlers/src/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { SetViewFieldsCommandHandler } from "./set-view-fields.command-handler"
import { SetViewFilterCommandHandler } from "./set-view-filter.command-handler"
import { SetViewOptionCommandHandler } from "./set-view-option.command-handler"
import { SetViewSortCommandHandler } from "./set-view-sort.command-handler"
import { TriggerRecordButtonCommandHandler } from "./trigger-record-button.command-handler"
import { UpdateAccountCommandHandler } from "./update-account.command-handler"
import { UpdateBaseCommandHandler } from "./update-base.command-handler"
import { UpdateRecordCommandHandler } from "./update-record.command-handler"
Expand Down Expand Up @@ -96,4 +97,5 @@ export const commandHandlers = [
DuplicateTableCommandHandler,
DuplicateBaseCommandHandler,
DeleteBaseCommandHandler,
TriggerRecordButtonCommandHandler,
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { TriggerRecordButtonCommand } from "@undb/commands"
import { commandHandler } from "@undb/cqrs"
import { singleton } from "@undb/di"
import { type ICommandHandler } from "@undb/domain"
import { injectRecordsService, type IRecordsService } from "@undb/table"

@commandHandler(TriggerRecordButtonCommand)
@singleton()
export class TriggerRecordButtonCommandHandler implements ICommandHandler<TriggerRecordButtonCommand, any> {
constructor(
@injectRecordsService()
private readonly service: IRecordsService,
) {}

async execute(command: TriggerRecordButtonCommand): Promise<any> {
return this.service.triggerRecordButton(command, { recordId: command.recordId, field: command.field })
}
}
1 change: 1 addition & 0 deletions packages/commands/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export * from "./set-view-fields.command"
export * from "./set-view-filter.command"
export * from "./set-view-option.command"
export * from "./set-view-sort.command"
export * from "./trigger-record-button.command"
export * from "./update-account.command"
export * from "./update-base.command"
export * from "./update-record.command"
Expand Down
29 changes: 29 additions & 0 deletions packages/commands/src/trigger-record-button.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Command, type CommandProps } from "@undb/domain"
import { fieldId, fieldName, recordId, uniqueTableDTO } from "@undb/table"
import { z } from "@undb/zod"

export const triggerRecordButtonCommand = z
.object({
recordId,
field: fieldId.or(fieldName),
})
.merge(uniqueTableDTO)

export type ITriggerRecordButtonCommand = z.infer<typeof triggerRecordButtonCommand>

export class TriggerRecordButtonCommand extends Command implements ITriggerRecordButtonCommand {
public readonly recordId: string
public readonly field: string
public readonly tableId?: string
public readonly tableName?: string
public readonly baseName?: string

constructor(props: CommandProps<ITriggerRecordButtonCommand>) {
super(props)
this.recordId = props.recordId
this.field = props.field
this.tableId = props.tableId
this.baseName = props.baseName
this.tableName = props.tableName
}
}
6 changes: 6 additions & 0 deletions packages/openapi/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
getViewRecordById,
getViewRecords,
recordSubscription,
triggerButton,
updateRecord,
} from "./openapi/record.openapi"

Expand Down Expand Up @@ -61,6 +62,11 @@ export const createOpenApiSpec = (
recordSubscription(base, table),
]

const buttons = table.schema.getButtonFields()
for (const button of buttons) {
routes.push(triggerButton(base, table, button))
}

for (const { view, record } of views) {
const viewRecordSchema = createRecordComponent(table, view, record)
registry.register(
Expand Down
34 changes: 33 additions & 1 deletion packages/openapi/src/openapi/record.openapi.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { RouteConfig } from "@asteasolutions/zod-to-openapi"
import type { Base } from "@undb/base"
import { recordId, type IReadableRecordDTO, type TableDo, type View } from "@undb/table"
import { ButtonField, recordId, type IReadableRecordDTO, type TableDo, type View } from "@undb/table"
import { z, type ZodTypeAny } from "@undb/zod"

export const RECORD_ID_COMPONENT = "RecordId"
export const RECORD_COMPONENT = "Record"
export const BUTTON_COMPONENT = "Button"
export const VIEW_COMPONENT = "View"
export const VIEW_RECORD_COMPONENT = "ViewRecord"
export const RECORD_VALUES_COMPONENT = "RecordValues"
Expand Down Expand Up @@ -135,6 +136,37 @@ export const getViewRecordById = (base: Base, table: TableDo, view: View, record
}
}

export const triggerButton = (base: Base, table: TableDo, field: ButtonField): RouteConfig => {
return {
method: "post",
path: `/bases/${base.name.value}/tables/${table.name.value}/records/{recordId}/trigger/${field.name.value}`,
description: `Trigger ${table.name.value} ${field.name.value} button`,
summary: `Trigger ${table.name.value} ${field.name.value} button`,
tags: [RECORD_COMPONENT, BUTTON_COMPONENT],
request: {
params: z.object({
recordId: recordId,
}),
},
responses: {
200: {
description: "button triggered",
content: {
"application/json": {
schema: z.object({
mutated: z
.boolean()
.openapi("TriggerButtonOutput", {
description: "true if the button is triggered and record is updated",
}),
}),
},
},
},
},
}
}

export const createRecord = (base: Base, table: TableDo): RouteConfig => {
return {
method: "post",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { z } from "@undb/zod"
import { fieldId, fieldName } from "../../../schema"
import { recordId } from "../record-id.vo"

export const triggerRecordButtonDTO = z.object({
recordId,
field: fieldId.or(fieldName),
})

export type ITriggerRecordButtonDTO = z.infer<typeof triggerRecordButtonDTO>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { None, Option, Some } from "@undb/domain"
import { objectify } from "radash"
import type { IUniqueTableDTO } from "../../../../dto"
import { withUniqueTable } from "../../../../specifications"
import { RecordDO, RecordIdVO } from "../../record"
import type { ITriggerRecordButtonDTO } from "../../record/dto/trigger-record-button.dto"
import type { RecordsService } from "../records.service"

export async function triggerRecordButtonMethod(
this: RecordsService,
t: IUniqueTableDTO,
dto: ITriggerRecordButtonDTO,
): Promise<Option<RecordDO>> {
const ts = withUniqueTable(t).unwrap()
const table = (await this.tableRepository.findOne(Some(ts))).expect("Table not found")

const field = table.schema.getFieldByIdOrName(dto.field).expect("Field not found")
if (field.type !== "button") {
throw new Error("Field is not a button")
}

const option = field.option.into(undefined)
if (!option) return None
const action = option.action
if (!action.values.length) return None

const record = (await this.repo.findOneById(table, new RecordIdVO(dto.recordId))).expect(
"Record not found: " + dto.recordId,
)

const disabled = field.getIsDisabled(table, record)
if (disabled) return None

const values = objectify(
action.values,
(v) => v.field!,
(v) => v.value ?? null,
)
const spec = record.update(table, values)
await this.repo.updateOneById(table, record, spec)

return Some(record)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { singleton } from "@undb/di"
import type { Option } from "@undb/domain"
import type { IUniqueTableDTO } from "../../../dto"
import type { ITableRepository } from "../../../table.repository"
import { injectTableRepository } from "../../../table.repository.provider"
Expand All @@ -14,19 +15,22 @@ import {
type IUpdateRecordDTO,
type RecordDO,
} from "../record"
import type { ITriggerRecordButtonDTO } from "../record/dto/trigger-record-button.dto"
import { bulkdeleteRecordsMethod } from "./methods/bulk-delete-records.method"
import { bulkduplicateRecordsMethod } from "./methods/bulk-duplicate-records.method"
import { bulkUpdateRecordsMethod } from "./methods/bulk-update-records.method"
import { createRecordMethod } from "./methods/create-record.method"
import { createRecordsMethod } from "./methods/create-records.method"
import { deleteRecordMethod } from "./methods/delete-record.method"
import { duplicateRecordMethod } from "./methods/duplicate-record.method"
import { triggerRecordButtonMethod } from "./methods/trigger-record-button.method"
import { updateRecordMethod } from "./methods/update-record.method"

export interface IRecordsService {
createRecord(table: IUniqueTableDTO, dto: ICreateRecordDTO): Promise<RecordDO>
createRecords(table: IUniqueTableDTO, dto: ICreateRecordDTO[]): Promise<RecordDO[]>
updateRecord(table: IUniqueTableDTO, dto: IUpdateRecordDTO): Promise<RecordDO>
triggerRecordButton(table: IUniqueTableDTO, dto: ITriggerRecordButtonDTO): Promise<Option<RecordDO>>
bulkUpdateRecords(table: IUniqueTableDTO, dto: IBulkUpdateRecordsDTO): Promise<RecordDO[]>
deleteRecord(table: IUniqueTableDTO, dto: IDeleteRecordDTO): Promise<RecordDO>
bulkDeleteRecords(table: IUniqueTableDTO, dto: IBulkDeleteRecordsDTO): Promise<RecordDO[]>
Expand All @@ -46,6 +50,7 @@ export class RecordsService implements IRecordsService {
createRecord = createRecordMethod
createRecords = createRecordsMethod
updateRecord = updateRecordMethod
triggerRecordButton = triggerRecordButtonMethod
bulkUpdateRecords = bulkUpdateRecordsMethod
deleteRecord = deleteRecordMethod
bulkDeleteRecords = bulkdeleteRecordsMethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export const buttonFieldUpdateAction = z.object({
confirm: z.boolean().optional(),
})

export const buttonDisabled = createConditionGroup(z.undefined(), z.undefined())
const buttonCondition = z.null()

export const buttonDisabled = createConditionGroup(buttonCondition, buttonCondition)
export type IButtonDisabled = z.infer<typeof buttonDisabled>

export const buttonFieldOption = z.object({
Expand Down Expand Up @@ -97,6 +99,7 @@ export class ButtonField extends AbstractField<ButtonFieldValue, undefined, IBut
return None
}

protected computed = true
override getConditionSchema() {
// @ts-ignore
return z.union([])
Expand Down
4 changes: 4 additions & 0 deletions packages/table/src/modules/schema/schema.vo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,10 @@ export class Schema extends ValueObject<Field[]> {
return references
}

getButtonFields(fields: Field[] = this.fields): ButtonField[] {
return fields.filter((f) => f.type === "button") as ButtonField[]
}

getUserFields(fields: Field[] = this.fields): UserField[] {
return fields.filter((f) => f.type === "user") as UserField[]
}
Expand Down
6 changes: 6 additions & 0 deletions packages/trpc/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
SetViewFilterCommand,
SetViewOptionCommand,
SetViewSortCommand,
TriggerRecordButtonCommand,
UpdateAccountCommand,
UpdateBaseCommand,
UpdateRecordCommand,
Expand Down Expand Up @@ -82,6 +83,7 @@ import {
setViewFilterCommand,
setViewOptionCommand,
setViewSortCommand,
triggerRecordButtonCommand,
updateBaseCommand,
updateRecordCommand,
updateSpaceCommand,
Expand Down Expand Up @@ -282,6 +284,10 @@ const recordRouter = t.router({
.use(authz("record:read"))
.input(getAggregatesQuery)
.query(({ input }) => queryBus.execute(new GetAggregatesQuery(input))),
trigger: privateProcedure
.use(authz("record:update"))
.input(triggerRecordButtonCommand)
.mutation(({ input }) => commandBus.execute(new TriggerRecordButtonCommand(input))),
})

const webhookRouter = t.router({
Expand Down

0 comments on commit 0cef3e4

Please sign in to comment.