Skip to content

Commit

Permalink
feat: submit from form
Browse files Browse the repository at this point in the history
  • Loading branch information
nichenqin committed Sep 14, 2024
1 parent 4b93450 commit 874aea8
Show file tree
Hide file tree
Showing 48 changed files with 392 additions and 42 deletions.
9 changes: 6 additions & 3 deletions apps/backend/src/modules/openapi/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ export class OpenAPI {
.get(
"/api/bases/:baseName/tables/:tableName",
async (ctx) => {
const spec = await this.getSpec(ctx.params.baseName, ctx.params.tableName)
const baseName = decodeURIComponent(ctx.params.baseName)
const tableName = decodeURIComponent(ctx.params.tableName)
const spec = await this.getSpec(baseName, tableName)

return `<html>
<head>
Expand Down Expand Up @@ -130,7 +132,6 @@ export class OpenAPI {
)
.group("/openapi/bases/:baseName/tables/:tableName", (app) =>
app

.guard({
beforeHandle: async (context) => {
const apiToken =
Expand Down Expand Up @@ -165,7 +166,9 @@ export class OpenAPI {
.get(
"/openapi.json",
async (ctx) => {
const spec = await this.getSpec(ctx.params.baseName, ctx.params.tableName)
const baseName = decodeURIComponent(ctx.params.baseName)
const tableName = decodeURIComponent(ctx.params.tableName)
const spec = await this.getSpec(baseName, tableName)
return spec
},
{
Expand Down
77 changes: 62 additions & 15 deletions apps/backend/src/modules/openapi/record.openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
CreateRecordsCommand,
DeleteRecordCommand,
DuplicateRecordCommand,
SubmitFormCommand,
TriggerRecordButtonCommand,
UpdateRecordCommand,
} from "@undb/commands"
Expand Down Expand Up @@ -36,7 +37,8 @@ export class RecordOpenApi {
.get(
"/records",
async (ctx) => {
const { baseName, tableName } = ctx.params
const baseName = decodeURIComponent(ctx.params.baseName)
const tableName = decodeURIComponent(ctx.params.tableName)
const result = (await this.queryBus.execute(
new GetReadableRecordsQuery({ baseName, tableName, ignoreView: true }),
)) as PaginatedDTO<IRecordReadableValueDTO>
Expand All @@ -57,7 +59,9 @@ export class RecordOpenApi {
.get(
"/views/:viewName/records",
async (ctx) => {
const { baseName, tableName, viewName } = ctx.params
const baseName = decodeURIComponent(ctx.params.baseName)
const tableName = decodeURIComponent(ctx.params.tableName)
const viewName = decodeURIComponent(ctx.params.viewName)
const result = (await this.queryBus.execute(
new GetReadableRecordsQuery({ baseName, tableName, viewName }),
)) as PaginatedDTO<IRecordReadableValueDTO>
Expand All @@ -78,7 +82,10 @@ export class RecordOpenApi {
.get(
"/views/:viewName/records/:recordId",
async (ctx) => {
const { baseName, tableName, viewName, recordId } = ctx.params
const baseName = decodeURIComponent(ctx.params.baseName)
const tableName = decodeURIComponent(ctx.params.tableName)
const viewName = decodeURIComponent(ctx.params.viewName)
const recordId = ctx.params.recordId
const result = await this.queryBus.execute(
new GetReadableRecordByIdQuery({ baseName, tableName, viewName, id: recordId }),
)
Expand All @@ -103,9 +110,11 @@ export class RecordOpenApi {
.get(
"/records/:recordId",
async (ctx) => {
const { baseName, tableName } = ctx.params
const baseName = decodeURIComponent(ctx.params.baseName)
const tableName = decodeURIComponent(ctx.params.tableName)
const recordId = ctx.params.recordId
const result = await this.queryBus.execute(
new GetReadableRecordByIdQuery({ baseName, tableName, id: ctx.params.recordId, ignoreView: true }),
new GetReadableRecordByIdQuery({ baseName, tableName, id: recordId, ignoreView: true }),
)
return {
data: result,
Expand All @@ -123,7 +132,8 @@ export class RecordOpenApi {
.post(
"/records",
async (ctx) => {
const { baseName, tableName } = ctx.params
const baseName = decodeURIComponent(ctx.params.baseName)
const tableName = decodeURIComponent(ctx.params.tableName)
return withTransaction(this.qb)(() =>
this.commandBus.execute(new CreateRecordCommand({ baseName, tableName, values: ctx.body.values })),
)
Expand All @@ -141,7 +151,8 @@ export class RecordOpenApi {
.post(
"/records/bulk",
async (ctx) => {
const { baseName, tableName } = ctx.params
const baseName = decodeURIComponent(ctx.params.baseName)
const tableName = decodeURIComponent(ctx.params.tableName)
return withTransaction(this.qb)(() =>
this.commandBus.execute(new CreateRecordsCommand({ baseName, tableName, records: ctx.body.records })),
)
Expand All @@ -159,7 +170,8 @@ export class RecordOpenApi {
.patch(
"/records/:recordId",
async (ctx) => {
const { baseName, tableName } = ctx.params
const baseName = decodeURIComponent(ctx.params.baseName)
const tableName = decodeURIComponent(ctx.params.tableName)
return withTransaction(this.qb)(() =>
this.commandBus.execute(
new UpdateRecordCommand({
Expand All @@ -184,7 +196,8 @@ export class RecordOpenApi {
.patch(
"/records",
async (ctx) => {
const { tableName, baseName } = ctx.params
const baseName = decodeURIComponent(ctx.params.baseName)
const tableName = decodeURIComponent(ctx.params.tableName)
return withTransaction(this.qb)(() =>
this.commandBus.execute(
new BulkUpdateRecordsCommand({
Expand Down Expand Up @@ -213,7 +226,8 @@ export class RecordOpenApi {
.post(
"/records/:recordId/duplicate",
async (ctx) => {
const { baseName, tableName } = ctx.params
const baseName = decodeURIComponent(ctx.params.baseName)
const tableName = decodeURIComponent(ctx.params.tableName)
return withTransaction(this.qb)(() =>
this.commandBus.execute(new DuplicateRecordCommand({ baseName, tableName, id: ctx.params.recordId })),
)
Expand All @@ -230,7 +244,10 @@ export class RecordOpenApi {
.post(
"/records/:recordId/trigger/:field",
async (ctx) => {
const { baseName, tableName, recordId, field } = ctx.params
const baseName = decodeURIComponent(ctx.params.baseName)
const tableName = decodeURIComponent(ctx.params.tableName)
const recordId = ctx.params.recordId
const field = ctx.params.field
return withTransaction(this.qb)(async () => {
const result = (await this.commandBus.execute(
new TriggerRecordButtonCommand({ baseName, tableName, recordId, field }),
Expand All @@ -241,7 +258,12 @@ export class RecordOpenApi {
})
},
{
params: t.Object({ baseName: t.String(), tableName: t.String(), recordId: t.String(), field: t.String() }),
params: t.Object({
baseName: t.String(),
tableName: t.String(),
recordId: t.String(),
field: t.String(),
}),
detail: {
tags: ["Record", "Button"],
summary: "Trigger record button",
Expand All @@ -252,7 +274,8 @@ export class RecordOpenApi {
.post(
"/records/duplicate",
async (ctx) => {
const { baseName, tableName } = ctx.params
const baseName = decodeURIComponent(ctx.params.baseName)
const tableName = decodeURIComponent(ctx.params.tableName)
return withTransaction(this.qb)(() =>
this.commandBus.execute(
new BulkDuplicateRecordsCommand({
Expand All @@ -277,7 +300,8 @@ export class RecordOpenApi {
.delete(
"/records/:recordId",
async (ctx) => {
const { baseName, tableName } = ctx.params
const baseName = decodeURIComponent(ctx.params.baseName)
const tableName = decodeURIComponent(ctx.params.tableName)
return withTransaction(this.qb)(() =>
this.commandBus.execute(new DeleteRecordCommand({ baseName, tableName, id: ctx.params.recordId })),
)
Expand All @@ -294,7 +318,8 @@ export class RecordOpenApi {
.delete(
"/records",
async (ctx) => {
const { baseName, tableName } = ctx.params
const baseName = decodeURIComponent(ctx.params.baseName)
const tableName = decodeURIComponent(ctx.params.tableName)
return withTransaction(this.qb)(() =>
this.commandBus.execute(
new BulkDeleteRecordsCommand({
Expand All @@ -316,6 +341,28 @@ export class RecordOpenApi {
},
},
)
.post(
"/forms/:formName/submit",
async (ctx) => {
const baseName = decodeURIComponent(ctx.params.baseName)
const tableName = decodeURIComponent(ctx.params.tableName)
const formName = decodeURIComponent(ctx.params.formName)
return withTransaction(this.qb)(() =>
this.commandBus.execute(
new SubmitFormCommand({ baseName, tableName, form: formName, values: ctx.body.values }),
),
)
},
{
params: t.Object({ baseName: t.String(), tableName: t.String(), formName: t.String() }),
body: t.Object({ values: t.Record(t.String(), t.Any()) }),
detail: {
tags: ["Record", "Form"],
summary: "Submit form",
description: "Submit form",
},
},
)
})
}
}
5 changes: 1 addition & 4 deletions packages/base/src/value-objects/base-name.vo.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { ValueObject } from "@undb/domain"
import * as z from "@undb/zod"

export const baseNameSchema = z
.string()
.min(1)
.regex(/^[^\s]*$/, "Base name cannot contain spaces")
export const baseNameSchema = z.string().min(1)

export class BaseName extends ValueObject<z.infer<typeof baseNameSchema>> {
static from(name: string): BaseName {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class SubmitFormCommandHandler implements ICommandHandler<SubmitFormComma

async execute(command: SubmitFormCommand): Promise<any> {
this.logger.debug(command, "executing submit form command")
const record = await this.service.submitForm(command, { formId: command.formId, values: command.values })
const record = await this.service.submitForm(command, { form: command.form, values: command.values })

return record.id.value
}
Expand Down
4 changes: 2 additions & 2 deletions packages/commands/src/submit-form.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ export type ISubmitFormCommand = z.infer<typeof submitFormCommand>

export class SubmitFormCommand extends Command implements ISubmitFormCommand {
public readonly tableId?: string
public readonly formId: string
public readonly form: string
public readonly baseName?: string
public readonly tableName?: string
public readonly values: IRecordValues

constructor(props: CommandProps<ISubmitFormCommand>) {
super(props)
this.tableId = props.tableId
this.formId = props.formId
this.form = props.form
this.baseName = props.baseName
this.tableName = props.tableName
this.values = props.values
Expand Down
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,
submitForm,
triggerButton,
updateRecord,
} from "./openapi/record.openapi"
Expand Down Expand Up @@ -67,6 +68,11 @@ export const createOpenApiSpec = (
routes.push(triggerButton(base, table, button))
}

const forms = table.forms?.forms ?? []
for (const form of forms) {
routes.push(submitForm(base, table, form))
}

for (const { view, record } of views) {
const viewRecordSchema = createRecordComponent(table, view, record)
registry.register(
Expand Down
47 changes: 41 additions & 6 deletions packages/openapi/src/openapi/record.openapi.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import type { RouteConfig } from "@asteasolutions/zod-to-openapi"
import type { Base } from "@undb/base"
import { ButtonField, recordId, type IReadableRecordDTO, type TableDo, type View } from "@undb/table"
import { ButtonField, FormVO, 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 FORM_COMPONENT = "Form"
export const VIEW_COMPONENT = "View"
export const VIEW_RECORD_COMPONENT = "ViewRecord"
export const FORM_SUBMIT_RECORD_COMPONENT = "FormSubmitRecord"
export const RECORD_VALUES_COMPONENT = "RecordValues"
export const VIEW_RECORD_VALUES_COMPONENT = "ViewRecordValues"
export const RECORD_DISPLAY_VALUES_COMPONENT = "RecordDisplayValues"
Expand Down Expand Up @@ -154,11 +156,9 @@ export const triggerButton = (base: Base, table: TableDo, field: ButtonField): R
content: {
"application/json": {
schema: z.object({
mutated: z
.boolean()
.openapi("TriggerButtonOutput", {
description: "true if the button is triggered and record is updated",
}),
mutated: z.boolean().openapi("TriggerButtonOutput", {
description: "true if the button is triggered and record is updated",
}),
}),
},
},
Expand Down Expand Up @@ -233,6 +233,41 @@ export const createRecords = (base: Base, table: TableDo): RouteConfig => {
}
}

export const submitFormCreateRecordComponent = (table: TableDo, form: FormVO, record?: IReadableRecordDTO) => {
return z
.object({
id: recordId.openapi(RECORD_ID_COMPONENT, { example: record?.id }),
values: table.schema.getMutableSchemaFromForm(form, table.schema.mutableFields, false),
})
.openapi(form.name + ":" + FORM_SUBMIT_RECORD_COMPONENT, {
description: `submit record in ${form.name} form`,
})
}

export const submitForm = (base: Base, table: TableDo, form: FormVO): RouteConfig => {
return {
method: "post",
path: `/bases/${base.name.value}/tables/${table.name.value}/forms/${form.name}/submit`,
description: `Submit ${table.name.value} form ${form.name}`,
summary: `Submit ${table.name.value} form ${form.name}`,
tags: [RECORD_COMPONENT, FORM_COMPONENT],
request: {
body: {
content: {
"application/json": {
schema: submitFormCreateRecordComponent(table, form),
},
},
},
},
responses: {
201: {
description: "Form submitted",
},
},
}
}

export const updateRecord = (base: Base, table: TableDo): RouteConfig => {
return {
method: "patch",
Expand Down
10 changes: 9 additions & 1 deletion packages/table/src/modules/forms/form/form-fields.vo.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ValueObject } from "@undb/domain"
import { Option, ValueObject } from "@undb/domain"
import type { Field } from "../.."
import type { TableDo } from "../../../table.do"
import type { SchemaIdMap } from "../../schema/schema.type"
Expand Down Expand Up @@ -31,6 +31,10 @@ export class FormFieldsVO extends ValueObject<FormFieldVO[]> {
return fields.slice(0, index)
}

public getFormField(fieldId: string): Option<FormFieldVO> {
return Option(this.props.find((field) => field.fieldId === fieldId))
}

public getNextFields(fieldId: string): FormFieldVO[] {
const fields = this.props
const index = fields.findIndex((field) => field.fieldId === fieldId)
Expand Down Expand Up @@ -67,6 +71,10 @@ export class FormFieldsVO extends ValueObject<FormFieldVO[]> {
)
}

public getVisibleFields(): FormFieldVO[] {
return this.props.filter((formField) => !formField.hidden)
}

toJSON() {
return this.props.map((field) => field.toJSON())
}
Expand Down
8 changes: 8 additions & 0 deletions packages/table/src/modules/forms/forms.vo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ export class FormsVO extends ValueObject<FormVO[]> {
return this.props.find((form) => form.id === formId)
}

getFormByName(formName: string) {
return this.props.find((form) => form.name === formName)
}

getFormnByIdOrName(idOrName: string) {
return this.props.find((form) => form.id === idOrName || form.name === idOrName)
}

toJSON() {
return this.props.map((form) => form.toJSON())
}
Expand Down
Loading

0 comments on commit 874aea8

Please sign in to comment.