Skip to content

Commit 874aea8

Browse files
committed
feat: submit from form
1 parent 4b93450 commit 874aea8

File tree

48 files changed

+392
-42
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+392
-42
lines changed

apps/backend/src/modules/openapi/openapi.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ export class OpenAPI {
9393
.get(
9494
"/api/bases/:baseName/tables/:tableName",
9595
async (ctx) => {
96-
const spec = await this.getSpec(ctx.params.baseName, ctx.params.tableName)
96+
const baseName = decodeURIComponent(ctx.params.baseName)
97+
const tableName = decodeURIComponent(ctx.params.tableName)
98+
const spec = await this.getSpec(baseName, tableName)
9799

98100
return `<html>
99101
<head>
@@ -130,7 +132,6 @@ export class OpenAPI {
130132
)
131133
.group("/openapi/bases/:baseName/tables/:tableName", (app) =>
132134
app
133-
134135
.guard({
135136
beforeHandle: async (context) => {
136137
const apiToken =
@@ -165,7 +166,9 @@ export class OpenAPI {
165166
.get(
166167
"/openapi.json",
167168
async (ctx) => {
168-
const spec = await this.getSpec(ctx.params.baseName, ctx.params.tableName)
169+
const baseName = decodeURIComponent(ctx.params.baseName)
170+
const tableName = decodeURIComponent(ctx.params.tableName)
171+
const spec = await this.getSpec(baseName, tableName)
169172
return spec
170173
},
171174
{

apps/backend/src/modules/openapi/record.openapi.ts

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
CreateRecordsCommand,
77
DeleteRecordCommand,
88
DuplicateRecordCommand,
9+
SubmitFormCommand,
910
TriggerRecordButtonCommand,
1011
UpdateRecordCommand,
1112
} from "@undb/commands"
@@ -36,7 +37,8 @@ export class RecordOpenApi {
3637
.get(
3738
"/records",
3839
async (ctx) => {
39-
const { baseName, tableName } = ctx.params
40+
const baseName = decodeURIComponent(ctx.params.baseName)
41+
const tableName = decodeURIComponent(ctx.params.tableName)
4042
const result = (await this.queryBus.execute(
4143
new GetReadableRecordsQuery({ baseName, tableName, ignoreView: true }),
4244
)) as PaginatedDTO<IRecordReadableValueDTO>
@@ -57,7 +59,9 @@ export class RecordOpenApi {
5759
.get(
5860
"/views/:viewName/records",
5961
async (ctx) => {
60-
const { baseName, tableName, viewName } = ctx.params
62+
const baseName = decodeURIComponent(ctx.params.baseName)
63+
const tableName = decodeURIComponent(ctx.params.tableName)
64+
const viewName = decodeURIComponent(ctx.params.viewName)
6165
const result = (await this.queryBus.execute(
6266
new GetReadableRecordsQuery({ baseName, tableName, viewName }),
6367
)) as PaginatedDTO<IRecordReadableValueDTO>
@@ -78,7 +82,10 @@ export class RecordOpenApi {
7882
.get(
7983
"/views/:viewName/records/:recordId",
8084
async (ctx) => {
81-
const { baseName, tableName, viewName, recordId } = ctx.params
85+
const baseName = decodeURIComponent(ctx.params.baseName)
86+
const tableName = decodeURIComponent(ctx.params.tableName)
87+
const viewName = decodeURIComponent(ctx.params.viewName)
88+
const recordId = ctx.params.recordId
8289
const result = await this.queryBus.execute(
8390
new GetReadableRecordByIdQuery({ baseName, tableName, viewName, id: recordId }),
8491
)
@@ -103,9 +110,11 @@ export class RecordOpenApi {
103110
.get(
104111
"/records/:recordId",
105112
async (ctx) => {
106-
const { baseName, tableName } = ctx.params
113+
const baseName = decodeURIComponent(ctx.params.baseName)
114+
const tableName = decodeURIComponent(ctx.params.tableName)
115+
const recordId = ctx.params.recordId
107116
const result = await this.queryBus.execute(
108-
new GetReadableRecordByIdQuery({ baseName, tableName, id: ctx.params.recordId, ignoreView: true }),
117+
new GetReadableRecordByIdQuery({ baseName, tableName, id: recordId, ignoreView: true }),
109118
)
110119
return {
111120
data: result,
@@ -123,7 +132,8 @@ export class RecordOpenApi {
123132
.post(
124133
"/records",
125134
async (ctx) => {
126-
const { baseName, tableName } = ctx.params
135+
const baseName = decodeURIComponent(ctx.params.baseName)
136+
const tableName = decodeURIComponent(ctx.params.tableName)
127137
return withTransaction(this.qb)(() =>
128138
this.commandBus.execute(new CreateRecordCommand({ baseName, tableName, values: ctx.body.values })),
129139
)
@@ -141,7 +151,8 @@ export class RecordOpenApi {
141151
.post(
142152
"/records/bulk",
143153
async (ctx) => {
144-
const { baseName, tableName } = ctx.params
154+
const baseName = decodeURIComponent(ctx.params.baseName)
155+
const tableName = decodeURIComponent(ctx.params.tableName)
145156
return withTransaction(this.qb)(() =>
146157
this.commandBus.execute(new CreateRecordsCommand({ baseName, tableName, records: ctx.body.records })),
147158
)
@@ -159,7 +170,8 @@ export class RecordOpenApi {
159170
.patch(
160171
"/records/:recordId",
161172
async (ctx) => {
162-
const { baseName, tableName } = ctx.params
173+
const baseName = decodeURIComponent(ctx.params.baseName)
174+
const tableName = decodeURIComponent(ctx.params.tableName)
163175
return withTransaction(this.qb)(() =>
164176
this.commandBus.execute(
165177
new UpdateRecordCommand({
@@ -184,7 +196,8 @@ export class RecordOpenApi {
184196
.patch(
185197
"/records",
186198
async (ctx) => {
187-
const { tableName, baseName } = ctx.params
199+
const baseName = decodeURIComponent(ctx.params.baseName)
200+
const tableName = decodeURIComponent(ctx.params.tableName)
188201
return withTransaction(this.qb)(() =>
189202
this.commandBus.execute(
190203
new BulkUpdateRecordsCommand({
@@ -213,7 +226,8 @@ export class RecordOpenApi {
213226
.post(
214227
"/records/:recordId/duplicate",
215228
async (ctx) => {
216-
const { baseName, tableName } = ctx.params
229+
const baseName = decodeURIComponent(ctx.params.baseName)
230+
const tableName = decodeURIComponent(ctx.params.tableName)
217231
return withTransaction(this.qb)(() =>
218232
this.commandBus.execute(new DuplicateRecordCommand({ baseName, tableName, id: ctx.params.recordId })),
219233
)
@@ -230,7 +244,10 @@ export class RecordOpenApi {
230244
.post(
231245
"/records/:recordId/trigger/:field",
232246
async (ctx) => {
233-
const { baseName, tableName, recordId, field } = ctx.params
247+
const baseName = decodeURIComponent(ctx.params.baseName)
248+
const tableName = decodeURIComponent(ctx.params.tableName)
249+
const recordId = ctx.params.recordId
250+
const field = ctx.params.field
234251
return withTransaction(this.qb)(async () => {
235252
const result = (await this.commandBus.execute(
236253
new TriggerRecordButtonCommand({ baseName, tableName, recordId, field }),
@@ -241,7 +258,12 @@ export class RecordOpenApi {
241258
})
242259
},
243260
{
244-
params: t.Object({ baseName: t.String(), tableName: t.String(), recordId: t.String(), field: t.String() }),
261+
params: t.Object({
262+
baseName: t.String(),
263+
tableName: t.String(),
264+
recordId: t.String(),
265+
field: t.String(),
266+
}),
245267
detail: {
246268
tags: ["Record", "Button"],
247269
summary: "Trigger record button",
@@ -252,7 +274,8 @@ export class RecordOpenApi {
252274
.post(
253275
"/records/duplicate",
254276
async (ctx) => {
255-
const { baseName, tableName } = ctx.params
277+
const baseName = decodeURIComponent(ctx.params.baseName)
278+
const tableName = decodeURIComponent(ctx.params.tableName)
256279
return withTransaction(this.qb)(() =>
257280
this.commandBus.execute(
258281
new BulkDuplicateRecordsCommand({
@@ -277,7 +300,8 @@ export class RecordOpenApi {
277300
.delete(
278301
"/records/:recordId",
279302
async (ctx) => {
280-
const { baseName, tableName } = ctx.params
303+
const baseName = decodeURIComponent(ctx.params.baseName)
304+
const tableName = decodeURIComponent(ctx.params.tableName)
281305
return withTransaction(this.qb)(() =>
282306
this.commandBus.execute(new DeleteRecordCommand({ baseName, tableName, id: ctx.params.recordId })),
283307
)
@@ -294,7 +318,8 @@ export class RecordOpenApi {
294318
.delete(
295319
"/records",
296320
async (ctx) => {
297-
const { baseName, tableName } = ctx.params
321+
const baseName = decodeURIComponent(ctx.params.baseName)
322+
const tableName = decodeURIComponent(ctx.params.tableName)
298323
return withTransaction(this.qb)(() =>
299324
this.commandBus.execute(
300325
new BulkDeleteRecordsCommand({
@@ -316,6 +341,28 @@ export class RecordOpenApi {
316341
},
317342
},
318343
)
344+
.post(
345+
"/forms/:formName/submit",
346+
async (ctx) => {
347+
const baseName = decodeURIComponent(ctx.params.baseName)
348+
const tableName = decodeURIComponent(ctx.params.tableName)
349+
const formName = decodeURIComponent(ctx.params.formName)
350+
return withTransaction(this.qb)(() =>
351+
this.commandBus.execute(
352+
new SubmitFormCommand({ baseName, tableName, form: formName, values: ctx.body.values }),
353+
),
354+
)
355+
},
356+
{
357+
params: t.Object({ baseName: t.String(), tableName: t.String(), formName: t.String() }),
358+
body: t.Object({ values: t.Record(t.String(), t.Any()) }),
359+
detail: {
360+
tags: ["Record", "Form"],
361+
summary: "Submit form",
362+
description: "Submit form",
363+
},
364+
},
365+
)
319366
})
320367
}
321368
}

packages/base/src/value-objects/base-name.vo.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import { ValueObject } from "@undb/domain"
22
import * as z from "@undb/zod"
33

4-
export const baseNameSchema = z
5-
.string()
6-
.min(1)
7-
.regex(/^[^\s]*$/, "Base name cannot contain spaces")
4+
export const baseNameSchema = z.string().min(1)
85

96
export class BaseName extends ValueObject<z.infer<typeof baseNameSchema>> {
107
static from(name: string): BaseName {

packages/command-handlers/src/handlers/submit-form.command-handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export class SubmitFormCommandHandler implements ICommandHandler<SubmitFormComma
1616

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

2121
return record.id.value
2222
}

packages/commands/src/submit-form.command.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ export type ISubmitFormCommand = z.infer<typeof submitFormCommand>
99

1010
export class SubmitFormCommand extends Command implements ISubmitFormCommand {
1111
public readonly tableId?: string
12-
public readonly formId: string
12+
public readonly form: string
1313
public readonly baseName?: string
1414
public readonly tableName?: string
1515
public readonly values: IRecordValues
1616

1717
constructor(props: CommandProps<ISubmitFormCommand>) {
1818
super(props)
1919
this.tableId = props.tableId
20-
this.formId = props.formId
20+
this.form = props.form
2121
this.baseName = props.baseName
2222
this.tableName = props.tableName
2323
this.values = props.values

packages/openapi/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
getViewRecordById,
2727
getViewRecords,
2828
recordSubscription,
29+
submitForm,
2930
triggerButton,
3031
updateRecord,
3132
} from "./openapi/record.openapi"
@@ -67,6 +68,11 @@ export const createOpenApiSpec = (
6768
routes.push(triggerButton(base, table, button))
6869
}
6970

71+
const forms = table.forms?.forms ?? []
72+
for (const form of forms) {
73+
routes.push(submitForm(base, table, form))
74+
}
75+
7076
for (const { view, record } of views) {
7177
const viewRecordSchema = createRecordComponent(table, view, record)
7278
registry.register(

packages/openapi/src/openapi/record.openapi.ts

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import type { RouteConfig } from "@asteasolutions/zod-to-openapi"
22
import type { Base } from "@undb/base"
3-
import { ButtonField, recordId, type IReadableRecordDTO, type TableDo, type View } from "@undb/table"
3+
import { ButtonField, FormVO, recordId, type IReadableRecordDTO, type TableDo, type View } from "@undb/table"
44
import { z, type ZodTypeAny } from "@undb/zod"
55

66
export const RECORD_ID_COMPONENT = "RecordId"
77
export const RECORD_COMPONENT = "Record"
88
export const BUTTON_COMPONENT = "Button"
9+
export const FORM_COMPONENT = "Form"
910
export const VIEW_COMPONENT = "View"
1011
export const VIEW_RECORD_COMPONENT = "ViewRecord"
12+
export const FORM_SUBMIT_RECORD_COMPONENT = "FormSubmitRecord"
1113
export const RECORD_VALUES_COMPONENT = "RecordValues"
1214
export const VIEW_RECORD_VALUES_COMPONENT = "ViewRecordValues"
1315
export const RECORD_DISPLAY_VALUES_COMPONENT = "RecordDisplayValues"
@@ -154,11 +156,9 @@ export const triggerButton = (base: Base, table: TableDo, field: ButtonField): R
154156
content: {
155157
"application/json": {
156158
schema: z.object({
157-
mutated: z
158-
.boolean()
159-
.openapi("TriggerButtonOutput", {
160-
description: "true if the button is triggered and record is updated",
161-
}),
159+
mutated: z.boolean().openapi("TriggerButtonOutput", {
160+
description: "true if the button is triggered and record is updated",
161+
}),
162162
}),
163163
},
164164
},
@@ -233,6 +233,41 @@ export const createRecords = (base: Base, table: TableDo): RouteConfig => {
233233
}
234234
}
235235

236+
export const submitFormCreateRecordComponent = (table: TableDo, form: FormVO, record?: IReadableRecordDTO) => {
237+
return z
238+
.object({
239+
id: recordId.openapi(RECORD_ID_COMPONENT, { example: record?.id }),
240+
values: table.schema.getMutableSchemaFromForm(form, table.schema.mutableFields, false),
241+
})
242+
.openapi(form.name + ":" + FORM_SUBMIT_RECORD_COMPONENT, {
243+
description: `submit record in ${form.name} form`,
244+
})
245+
}
246+
247+
export const submitForm = (base: Base, table: TableDo, form: FormVO): RouteConfig => {
248+
return {
249+
method: "post",
250+
path: `/bases/${base.name.value}/tables/${table.name.value}/forms/${form.name}/submit`,
251+
description: `Submit ${table.name.value} form ${form.name}`,
252+
summary: `Submit ${table.name.value} form ${form.name}`,
253+
tags: [RECORD_COMPONENT, FORM_COMPONENT],
254+
request: {
255+
body: {
256+
content: {
257+
"application/json": {
258+
schema: submitFormCreateRecordComponent(table, form),
259+
},
260+
},
261+
},
262+
},
263+
responses: {
264+
201: {
265+
description: "Form submitted",
266+
},
267+
},
268+
}
269+
}
270+
236271
export const updateRecord = (base: Base, table: TableDo): RouteConfig => {
237272
return {
238273
method: "patch",

packages/table/src/modules/forms/form/form-fields.vo.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ValueObject } from "@undb/domain"
1+
import { Option, ValueObject } from "@undb/domain"
22
import type { Field } from "../.."
33
import type { TableDo } from "../../../table.do"
44
import type { SchemaIdMap } from "../../schema/schema.type"
@@ -31,6 +31,10 @@ export class FormFieldsVO extends ValueObject<FormFieldVO[]> {
3131
return fields.slice(0, index)
3232
}
3333

34+
public getFormField(fieldId: string): Option<FormFieldVO> {
35+
return Option(this.props.find((field) => field.fieldId === fieldId))
36+
}
37+
3438
public getNextFields(fieldId: string): FormFieldVO[] {
3539
const fields = this.props
3640
const index = fields.findIndex((field) => field.fieldId === fieldId)
@@ -67,6 +71,10 @@ export class FormFieldsVO extends ValueObject<FormFieldVO[]> {
6771
)
6872
}
6973

74+
public getVisibleFields(): FormFieldVO[] {
75+
return this.props.filter((formField) => !formField.hidden)
76+
}
77+
7078
toJSON() {
7179
return this.props.map((field) => field.toJSON())
7280
}

packages/table/src/modules/forms/forms.vo.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ export class FormsVO extends ValueObject<FormVO[]> {
3636
return this.props.find((form) => form.id === formId)
3737
}
3838

39+
getFormByName(formName: string) {
40+
return this.props.find((form) => form.name === formName)
41+
}
42+
43+
getFormnByIdOrName(idOrName: string) {
44+
return this.props.find((form) => form.id === idOrName || form.name === idOrName)
45+
}
46+
3947
toJSON() {
4048
return this.props.map((form) => form.toJSON())
4149
}

0 commit comments

Comments
 (0)