From 3b00bfa9315aa028d9d1d00cd1f869478ee3b22b Mon Sep 17 00:00:00 2001 From: Nandgopal-R Date: Mon, 2 Feb 2026 22:57:26 +0530 Subject: [PATCH 01/11] fix: fix form fields controllers --- backend/src/api/form-fields/controller.ts | 258 +++++++++++----------- 1 file changed, 128 insertions(+), 130 deletions(-) diff --git a/backend/src/api/form-fields/controller.ts b/backend/src/api/form-fields/controller.ts index dc093c4..86e952e 100644 --- a/backend/src/api/form-fields/controller.ts +++ b/backend/src/api/form-fields/controller.ts @@ -1,15 +1,10 @@ -import { prisma } from "../../db/prisma"; -import { logger } from "../../logger/"; -import type { - CreateFieldContext, - DeleteFieldContext, - GetAllFieldsContext, - UpdateFieldContext, -} from "../../types/form-fields"; +import { prisma } from "../../db/prisma" +import { logger } from "../../logger/" +import { GetAllFieldsContext, CreateFieldContext, UpdateFieldContext, DeleteFieldContext } from "../../types/form-fields" export async function getAllFields({ params, set }: GetAllFieldsContext) { const formExists = await prisma.form.count({ - where: { id: params.formId }, + where: { id: params.formId } }); if (formExists === 0) { @@ -18,113 +13,112 @@ export async function getAllFields({ params, set }: GetAllFieldsContext) { return { success: false, message: "Form not found", - data: [], + data: [] }; } const fields = await prisma.formFields.findMany({ - select: { - id: true, - fieldName: true, - label: true, - fieldValueType: true, - fieldType: true, - validation: true, - prevFieldId: true, - nextField: true, - }, - where: { formId: params.formId }, - }); + where: { formId: params.formId } + }) if (fields.length === 0) { - logger.info(`No fields found for formId: ${params.formId}`); + logger.info(`No fields found for formId: ${params.formId}`) return { success: true, message: "No forms fields found", - data: [], - }; + data: [] + } } - logger.info( - `Fetched all fields for formId: ${params.formId}, fieldCount: ${fields.length}`, - ); - return { - success: true, - message: "All form fields fetched successfully", - data: fields, - }; + + const ordered: typeof fields = [] + + let current = fields.find( + (f): f is typeof fields[number] => f.prevFieldId === null + ) + + while (current) { + ordered.push(current) + + current = fields.find( + (f): f is typeof fields[number] => + f.prevFieldId === current!.id + ) + } + + return { success: true, data: ordered } } -export async function createField({ - params, - body, - set, - user, -}: CreateFieldContext) { +export async function createField({ params, body, set, user }: CreateFieldContext) { const form = await prisma.form.findFirst({ where: { id: params.formId, - ownerId: user.id, - }, - }); + ownerId: user.id + } + }) if (!form) { - set.status = 404; - return { - success: false, - message: "Form not found", - }; + set.status = 404 + return { success: false, message: "Form not found" } } - const field = await prisma.$transaction(async (tx: any) => { - // 1. If we are inserting after a specific field (prevFieldId provided) - if (body.prevFieldId) { - // Fetch the previous field to see if it has a next field - const prevField = await tx.formFields.findUnique({ - where: { id: body.prevFieldId }, - }); + const createdField = await prisma.$transaction(async (tx) => { - if (!prevField) { - throw new Error("Previous field not found"); - } + /** + * INSERT AT HEAD + */ + if (!body.prevFieldId) { + const currentHead = await tx.formFields.findFirst({ + where: { + formId: params.formId, + prevFieldId: null + } + }) - // 2. Create the new field - // It points back to prevFieldId - // It points forward to whatever prevField was pointing to - const newField = await tx.formFields.create({ + const created = await tx.formFields.create({ data: { fieldName: body.fieldName, label: body.label, fieldValueType: body.fieldValueType, fieldType: body.fieldType, validation: body.validation ?? undefined, - prevFieldId: body.prevFieldId, - nextField: prevField.nextField, // Inherit the link formId: params.formId, - }, - }); + prevFieldId: null + } + }) - // 3. Update the previous field to point to the new field - await tx.formFields.update({ - where: { id: body.prevFieldId }, - data: { nextField: newField.id }, - }); - - // 4. If there was a next field, update it to point back to the new field - if (prevField.nextField) { - // We can't query by `id` directly if `nextField` is just a string without relation, - // but typically we can update the row where id matches the string. + if (currentHead) { await tx.formFields.update({ - where: { id: prevField.nextField }, - data: { prevFieldId: newField.id }, - }); + where: { id: currentHead.id }, + data: { prevFieldId: created.id } + }) } - return newField; + return created } - // Fallback: If no prevFieldId is provided, we assume it's the first field - // or simply creating a field without links yet. - return await tx.formFields.create({ + /** + * INSERT AFTER A FIELD + */ + const prevField = await tx.formFields.findFirst({ + where: { + id: body.prevFieldId, + formId: params.formId + } + }) + + if (!prevField) { + // ❗ This will automatically rollback the transaction + throw new Error("Previous field not found in the specified form") + } + + const nextField = await tx.formFields.findFirst({ + where: { + formId: params.formId, + prevFieldId: prevField.id + } + }) + + const created = await tx.formFields.create({ data: { fieldName: body.fieldName, label: body.label, @@ -132,38 +126,44 @@ export async function createField({ fieldType: body.fieldType, validation: body.validation ?? undefined, formId: params.formId, - }, - }); - }); + prevFieldId: prevField.id + } + }) - logger.info(`Created field ${field.id} for form ${params.formId}`); + if (nextField) { + await tx.formFields.update({ + where: { id: nextField.id }, + data: { prevFieldId: created.id } + }) + } + + return created + }) + + logger.info(`Created field ${createdField.id} in form ${params.formId}`) return { success: true, message: "Field created successfully", - data: field, - }; + data: createdField + } } -export async function updateField({ - params, - body, - set, - user, -}: UpdateFieldContext) { + +export async function updateField({ params, body, set, user }: UpdateFieldContext) { const field = await prisma.formFields.findUnique({ where: { id: params.id }, - include: { form: true }, - }); + include: { form: true } + }) if (!field) { - set.status = 404; - return { success: false, message: "Field not found" }; + set.status = 404 + return { success: false, message: "Field not found" } } if (field.form.ownerId !== user.id) { - set.status = 403; - return { success: false, message: "Unauthorized" }; + set.status = 403 + return { success: false, message: "Unauthorized" } } const updatedField = await prisma.formFields.update({ @@ -174,57 +174,55 @@ export async function updateField({ fieldValueType: body.fieldValueType, fieldType: body.fieldType, validation: body.validation ?? undefined, - }, - }); + } + }) - logger.info(`Updated field ${updatedField.id}`); + logger.info(`Updated field ${updatedField.id}`) return { success: true, message: "Field updated successfully", - data: updatedField, - }; + data: updatedField + } } export async function deleteField({ params, set, user }: DeleteFieldContext) { const field = await prisma.formFields.findUnique({ where: { id: params.id }, - include: { form: true }, - }); + include: { form: true } + }) if (!field) { - set.status = 404; - return { success: false, message: "Field not found" }; + set.status = 404 + return { success: false, message: "Field not found" } } if (field.form.ownerId !== user.id) { - set.status = 403; - return { success: false, message: "Unauthorized" }; + set.status = 403 + return { success: false, message: "Unauthorized" } } - await prisma.$transaction(async (tx: any) => { - // 1. Link Previous to Next - if (field.prevFieldId) { - await tx.formFields.update({ - where: { id: field.prevFieldId }, - data: { nextField: field.nextField }, - }); - } + await prisma.$transaction(async (tx) => { + const nextField = await tx.formFields.findFirst({ + where: { + formId: field.formId, + prevFieldId: field.id + } + }) - // 2. Link Next to Previous - if (field.nextField) { + // relink previous → next + if (nextField) { await tx.formFields.update({ - where: { id: field.nextField }, - data: { prevFieldId: field.prevFieldId }, - }); + where: { id: nextField.id }, + data: { prevFieldId: field.prevFieldId } + }) } - // 3. Delete the field await tx.formFields.delete({ - where: { id: params.id }, - }); - }); + where: { id: field.id } + }) + }) - logger.info(`Deleted field ${params.id}`); - return { success: true, message: "Field deleted successfully" }; + logger.info(`Deleted field ${params.id}`) + return { success: true, message: "Field deleted successfully" } } From d6523151703f804b35b1ae75b87a4f7ff6cabaaf Mon Sep 17 00:00:00 2001 From: Nandgopal-R Date: Mon, 2 Feb 2026 22:59:27 +0530 Subject: [PATCH 02/11] feat: add validation for responses --- backend/src/types/form-response.ts | 57 ++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 backend/src/types/form-response.ts diff --git a/backend/src/types/form-response.ts b/backend/src/types/form-response.ts new file mode 100644 index 0000000..d713631 --- /dev/null +++ b/backend/src/types/form-response.ts @@ -0,0 +1,57 @@ +import { t, type Static } from "elysia"; + +export interface Context { + user: { id: string }; + set: { status?: number | string }; +} + +export const formResponseDTO = { + params: t.Object({ + formId: t.String({ + format: "uuid", + }), + }), + body: t.Object({ + answers: t.Record( + t.String(), // Key: The Field ID (UUID or String) + t.Union([ // Value: Can be String, Number, Boolean, or Array (for checkboxes) + t.String(), + t.Number(), + t.Boolean(), + t.Array(t.String()), + t.Null() + ]), + ) + }), +} + +export interface FormResponseContext extends Context { + params: Static; + body: Static; +} + +export const resumeResponseDTO = { + params: t.Object({ + responseId: t.String({ + format: "uuid", + }), + }), + body: t.Object({ + answers: t.Record( + t.String(), // Key: The Field ID (UUID or String) + t.Union([ // Value: Can be String, Number, Boolean, or Array (for checkboxes) + t.String(), + t.Number(), + t.Boolean(), + t.Array(t.String()), + t.Null() + ]), + ) + }), +} + +export interface ResumeResponseContext extends Context { + params: Static; + body: Static; +} + From 21b64e53313d2f290e67df0f137fcd27d1b0a66f Mon Sep 17 00:00:00 2001 From: Nandgopal-R Date: Mon, 2 Feb 2026 23:00:04 +0530 Subject: [PATCH 03/11] feat: add submitResponse controller and resumeResponse controller --- backend/src/api/form-response/controller.ts | 71 +++++++++++++++++++++ backend/src/api/form-response/routes.ts | 10 +++ backend/src/index.ts | 4 +- 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 backend/src/api/form-response/controller.ts create mode 100644 backend/src/api/form-response/routes.ts diff --git a/backend/src/api/form-response/controller.ts b/backend/src/api/form-response/controller.ts new file mode 100644 index 0000000..1b66ebe --- /dev/null +++ b/backend/src/api/form-response/controller.ts @@ -0,0 +1,71 @@ +import { prisma } from "../../db/prisma" +import { logger } from "../../logger/" +import { FormResponseContext, ResumeResponseContext } from "../../types/form-response" + +export async function submitResponse({ params, body, user, set }: FormResponseContext) { + const form = await prisma.form.findUnique({ + where: { + id: params.formId, + }, + }) + + if (!form) { + logger.warn(`Form with ID ${params.formId} not found`) + set.status = 404 + return { + success: false, + message: "Form not found", + } + } + + if (!form.isPublished) { + logger.warn(`Form with ID ${params.formId} is not published`) + set.status = 403 + return { + success: false, + message: "Form is not published", + } + } + + const response = await prisma.formResponse.create({ + data: { + formId: params.formId, + respondentId: user.id, + answers: body.answers + } + }) + logger.info(`User ${user.id} submitted response ${response.id} for form ${params.formId}`) + return { + success: true, + message: "Response submitted successfully", + data: response + } +} + +export async function resumeResponse({ params, body, user }: ResumeResponseContext) { + const response = await prisma.formResponse.updateMany({ + where: { + id: params.responseId, + respondentId: user.id, + }, + data: { + answers: body.answers + } + }) + + if (response.count === 0) { + logger.warn(`No response found with ID ${params.responseId} to update`) + return { + success: false, + message: "No response found to update", + } + } + + logger.info(`Response ${params.responseId} updated successfully`) + return { + success: true, + message: "Response updated successfully", + data: response + } +} + diff --git a/backend/src/api/form-response/routes.ts b/backend/src/api/form-response/routes.ts new file mode 100644 index 0000000..44b8315 --- /dev/null +++ b/backend/src/api/form-response/routes.ts @@ -0,0 +1,10 @@ +import { requireAuth } from "../auth/requireAuth"; +import { submitResponse, resumeResponse } from "./controller"; +import { formResponseDTO, resumeResponseDTO } from "../../types/form-response"; + +import { Elysia } from "elysia"; + +export const formResponseRoutes = new Elysia({ prefix: "/responses" }) + .use(requireAuth) + .post("/:formId", submitResponse, formResponseDTO) + .put("/resume/:responseId", resumeResponse, resumeResponseDTO); diff --git a/backend/src/index.ts b/backend/src/index.ts index 08d1ecb..62b033f 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -3,6 +3,7 @@ import { Elysia } from "elysia"; import { authRoutes } from "./api/auth/routes"; import { formFieldRoutes } from "./api/form-fields/routes"; import { formRoutes } from "./api/forms/routes"; +import { formResponseRoutes } from "./api/form-response/routes"; import { logger } from "./logger/index"; const app = new Elysia() @@ -45,7 +46,8 @@ const app = new Elysia() .get("/", () => "🦊 Elysia server started") .use(authRoutes) .use(formRoutes) - .use(formFieldRoutes); + .use(formFieldRoutes) + .use(formResponseRoutes); app.listen(8000); From b417f42b9b38efee2d181912884af056359fce81 Mon Sep 17 00:00:00 2001 From: Nandgopal-R Date: Mon, 2 Feb 2026 23:00:33 +0530 Subject: [PATCH 04/11] feat: add bruno for form-responses --- backend/bruno/form-response/folder.bru | 8 +++++ .../bruno/form-response/submitResponse.bru | 29 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 backend/bruno/form-response/folder.bru create mode 100644 backend/bruno/form-response/submitResponse.bru diff --git a/backend/bruno/form-response/folder.bru b/backend/bruno/form-response/folder.bru new file mode 100644 index 0000000..d14185a --- /dev/null +++ b/backend/bruno/form-response/folder.bru @@ -0,0 +1,8 @@ +meta { + name: form-response + seq: 5 +} + +auth { + mode: inherit +} diff --git a/backend/bruno/form-response/submitResponse.bru b/backend/bruno/form-response/submitResponse.bru new file mode 100644 index 0000000..a3416e0 --- /dev/null +++ b/backend/bruno/form-response/submitResponse.bru @@ -0,0 +1,29 @@ +meta { + name: submitResponse + type: http + seq: 1 +} + +post { + url: http://localhost:8000/responses/:formId + body: json + auth: inherit +} + +params:path { + formId: fdb4db57-695f-4a2e-aa2d-674b2f188238 +} + +body:json { + { + "answers":{ + "063a2bbb-99e5-492f-ad98-afbca86283ca": "Jack", + "99f13fb0-6100-4f6d-9460-476c0f20967d": "999999999" + } + } +} + +settings { + encodeUrl: true + timeout: 0 +} From 4f89a3a769518fc976c83de2dcae72b87b37f0cd Mon Sep 17 00:00:00 2001 From: Nandgopal-R Date: Tue, 3 Feb 2026 23:06:33 +0530 Subject: [PATCH 05/11] chore: apply formatter --- backend/src/api/form-fields/controller.ts | 172 +++++++++++--------- backend/src/api/form-response/controller.ts | 67 +++++--- backend/src/api/form-response/routes.ts | 7 +- backend/src/index.ts | 2 +- backend/src/types/form-response.ts | 21 +-- backend/tsconfig.json | 2 +- backend/biome.json => biome.json | 11 +- bun.lock | 19 +++ package.json | 13 +- 9 files changed, 185 insertions(+), 129 deletions(-) rename backend/biome.json => biome.json (73%) diff --git a/backend/src/api/form-fields/controller.ts b/backend/src/api/form-fields/controller.ts index 86e952e..f994621 100644 --- a/backend/src/api/form-fields/controller.ts +++ b/backend/src/api/form-fields/controller.ts @@ -1,10 +1,15 @@ -import { prisma } from "../../db/prisma" -import { logger } from "../../logger/" -import { GetAllFieldsContext, CreateFieldContext, UpdateFieldContext, DeleteFieldContext } from "../../types/form-fields" +import { prisma } from "../../db/prisma"; +import { logger } from "../../logger/"; +import type { + CreateFieldContext, + DeleteFieldContext, + GetAllFieldsContext, + UpdateFieldContext, +} from "../../types/form-fields"; export async function getAllFields({ params, set }: GetAllFieldsContext) { const formExists = await prisma.form.count({ - where: { id: params.formId } + where: { id: params.formId }, }); if (formExists === 0) { @@ -13,56 +18,59 @@ export async function getAllFields({ params, set }: GetAllFieldsContext) { return { success: false, message: "Form not found", - data: [] + data: [], }; } const fields = await prisma.formFields.findMany({ - where: { formId: params.formId } - }) + where: { formId: params.formId }, + }); if (fields.length === 0) { - logger.info(`No fields found for formId: ${params.formId}`) + logger.info(`No fields found for formId: ${params.formId}`); return { success: true, message: "No forms fields found", - data: [] - } + data: [], + }; } - const ordered: typeof fields = [] + const ordered: typeof fields = []; let current = fields.find( - (f): f is typeof fields[number] => f.prevFieldId === null - ) + (f): f is (typeof fields)[number] => f.prevFieldId === null, + ); while (current) { - ordered.push(current) + ordered.push(current); current = fields.find( - (f): f is typeof fields[number] => - f.prevFieldId === current!.id - ) + (f): f is (typeof fields)[number] => f.prevFieldId === current!.id, + ); } - return { success: true, data: ordered } + return { success: true, data: ordered }; } -export async function createField({ params, body, set, user }: CreateFieldContext) { +export async function createField({ + params, + body, + set, + user, +}: CreateFieldContext) { const form = await prisma.form.findFirst({ where: { id: params.formId, - ownerId: user.id - } - }) + ownerId: user.id, + }, + }); if (!form) { - set.status = 404 - return { success: false, message: "Form not found" } + set.status = 404; + return { success: false, message: "Form not found" }; } const createdField = await prisma.$transaction(async (tx) => { - /** * INSERT AT HEAD */ @@ -70,9 +78,9 @@ export async function createField({ params, body, set, user }: CreateFieldContex const currentHead = await tx.formFields.findFirst({ where: { formId: params.formId, - prevFieldId: null - } - }) + prevFieldId: null, + }, + }); const created = await tx.formFields.create({ data: { @@ -82,18 +90,18 @@ export async function createField({ params, body, set, user }: CreateFieldContex fieldType: body.fieldType, validation: body.validation ?? undefined, formId: params.formId, - prevFieldId: null - } - }) + prevFieldId: null, + }, + }); if (currentHead) { await tx.formFields.update({ where: { id: currentHead.id }, - data: { prevFieldId: created.id } - }) + data: { prevFieldId: created.id }, + }); } - return created + return created; } /** @@ -102,21 +110,21 @@ export async function createField({ params, body, set, user }: CreateFieldContex const prevField = await tx.formFields.findFirst({ where: { id: body.prevFieldId, - formId: params.formId - } - }) + formId: params.formId, + }, + }); if (!prevField) { // ❗ This will automatically rollback the transaction - throw new Error("Previous field not found in the specified form") + throw new Error("Previous field not found in the specified form"); } const nextField = await tx.formFields.findFirst({ where: { formId: params.formId, - prevFieldId: prevField.id - } - }) + prevFieldId: prevField.id, + }, + }); const created = await tx.formFields.create({ data: { @@ -126,44 +134,48 @@ export async function createField({ params, body, set, user }: CreateFieldContex fieldType: body.fieldType, validation: body.validation ?? undefined, formId: params.formId, - prevFieldId: prevField.id - } - }) + prevFieldId: prevField.id, + }, + }); if (nextField) { await tx.formFields.update({ where: { id: nextField.id }, - data: { prevFieldId: created.id } - }) + data: { prevFieldId: created.id }, + }); } - return created - }) + return created; + }); - logger.info(`Created field ${createdField.id} in form ${params.formId}`) + logger.info(`Created field ${createdField.id} in form ${params.formId}`); return { success: true, message: "Field created successfully", - data: createdField - } + data: createdField, + }; } - -export async function updateField({ params, body, set, user }: UpdateFieldContext) { +export async function updateField({ + params, + body, + set, + user, +}: UpdateFieldContext) { const field = await prisma.formFields.findUnique({ where: { id: params.id }, - include: { form: true } - }) + include: { form: true }, + }); if (!field) { - set.status = 404 - return { success: false, message: "Field not found" } + set.status = 404; + return { success: false, message: "Field not found" }; } if (field.form.ownerId !== user.id) { - set.status = 403 - return { success: false, message: "Unauthorized" } + set.status = 403; + return { success: false, message: "Unauthorized" }; } const updatedField = await prisma.formFields.update({ @@ -174,55 +186,55 @@ export async function updateField({ params, body, set, user }: UpdateFieldContex fieldValueType: body.fieldValueType, fieldType: body.fieldType, validation: body.validation ?? undefined, - } - }) + }, + }); - logger.info(`Updated field ${updatedField.id}`) + logger.info(`Updated field ${updatedField.id}`); return { success: true, message: "Field updated successfully", - data: updatedField - } + data: updatedField, + }; } export async function deleteField({ params, set, user }: DeleteFieldContext) { const field = await prisma.formFields.findUnique({ where: { id: params.id }, - include: { form: true } - }) + include: { form: true }, + }); if (!field) { - set.status = 404 - return { success: false, message: "Field not found" } + set.status = 404; + return { success: false, message: "Field not found" }; } if (field.form.ownerId !== user.id) { - set.status = 403 - return { success: false, message: "Unauthorized" } + set.status = 403; + return { success: false, message: "Unauthorized" }; } await prisma.$transaction(async (tx) => { const nextField = await tx.formFields.findFirst({ where: { formId: field.formId, - prevFieldId: field.id - } - }) + prevFieldId: field.id, + }, + }); // relink previous → next if (nextField) { await tx.formFields.update({ where: { id: nextField.id }, - data: { prevFieldId: field.prevFieldId } - }) + data: { prevFieldId: field.prevFieldId }, + }); } await tx.formFields.delete({ - where: { id: field.id } - }) - }) + where: { id: field.id }, + }); + }); - logger.info(`Deleted field ${params.id}`) - return { success: true, message: "Field deleted successfully" } + logger.info(`Deleted field ${params.id}`); + return { success: true, message: "Field deleted successfully" }; } diff --git a/backend/src/api/form-response/controller.ts b/backend/src/api/form-response/controller.ts index 1b66ebe..9f0810b 100644 --- a/backend/src/api/form-response/controller.ts +++ b/backend/src/api/form-response/controller.ts @@ -1,71 +1,84 @@ -import { prisma } from "../../db/prisma" -import { logger } from "../../logger/" -import { FormResponseContext, ResumeResponseContext } from "../../types/form-response" +import { prisma } from "../../db/prisma"; +import { logger } from "../../logger/"; +import type { + FormResponseContext, + ResumeResponseContext, +} from "../../types/form-response"; -export async function submitResponse({ params, body, user, set }: FormResponseContext) { +export async function submitResponse({ + params, + body, + user, + set, +}: FormResponseContext) { const form = await prisma.form.findUnique({ where: { id: params.formId, }, - }) + }); if (!form) { - logger.warn(`Form with ID ${params.formId} not found`) - set.status = 404 + logger.warn(`Form with ID ${params.formId} not found`); + set.status = 404; return { success: false, message: "Form not found", - } + }; } if (!form.isPublished) { - logger.warn(`Form with ID ${params.formId} is not published`) - set.status = 403 + logger.warn(`Form with ID ${params.formId} is not published`); + set.status = 403; return { success: false, message: "Form is not published", - } + }; } const response = await prisma.formResponse.create({ data: { formId: params.formId, respondentId: user.id, - answers: body.answers - } - }) - logger.info(`User ${user.id} submitted response ${response.id} for form ${params.formId}`) + answers: body.answers, + }, + }); + logger.info( + `User ${user.id} submitted response ${response.id} for form ${params.formId}`, + ); return { success: true, message: "Response submitted successfully", - data: response - } + data: response, + }; } -export async function resumeResponse({ params, body, user }: ResumeResponseContext) { +export async function resumeResponse({ + params, + body, + user, +}: ResumeResponseContext) { const response = await prisma.formResponse.updateMany({ where: { id: params.responseId, respondentId: user.id, }, data: { - answers: body.answers - } - }) + answers: body.answers, + }, + }); if (response.count === 0) { - logger.warn(`No response found with ID ${params.responseId} to update`) + logger.warn(`No response found with ID ${params.responseId} to update`); return { success: false, message: "No response found to update", - } + }; } - logger.info(`Response ${params.responseId} updated successfully`) + logger.info(`Response ${params.responseId} updated successfully`); return { success: true, message: "Response updated successfully", - data: response - } + data: response, + }; } - diff --git a/backend/src/api/form-response/routes.ts b/backend/src/api/form-response/routes.ts index 44b8315..4ddfae3 100644 --- a/backend/src/api/form-response/routes.ts +++ b/backend/src/api/form-response/routes.ts @@ -1,8 +1,7 @@ -import { requireAuth } from "../auth/requireAuth"; -import { submitResponse, resumeResponse } from "./controller"; -import { formResponseDTO, resumeResponseDTO } from "../../types/form-response"; - import { Elysia } from "elysia"; +import { formResponseDTO, resumeResponseDTO } from "../../types/form-response"; +import { requireAuth } from "../auth/requireAuth"; +import { resumeResponse, submitResponse } from "./controller"; export const formResponseRoutes = new Elysia({ prefix: "/responses" }) .use(requireAuth) diff --git a/backend/src/index.ts b/backend/src/index.ts index 62b033f..5c7392c 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -2,8 +2,8 @@ import { cors } from "@elysiajs/cors"; import { Elysia } from "elysia"; import { authRoutes } from "./api/auth/routes"; import { formFieldRoutes } from "./api/form-fields/routes"; -import { formRoutes } from "./api/forms/routes"; import { formResponseRoutes } from "./api/form-response/routes"; +import { formRoutes } from "./api/forms/routes"; import { logger } from "./logger/index"; const app = new Elysia() diff --git a/backend/src/types/form-response.ts b/backend/src/types/form-response.ts index d713631..f52506c 100644 --- a/backend/src/types/form-response.ts +++ b/backend/src/types/form-response.ts @@ -1,4 +1,4 @@ -import { t, type Static } from "elysia"; +import { type Static, t } from "elysia"; export interface Context { user: { id: string }; @@ -14,16 +14,17 @@ export const formResponseDTO = { body: t.Object({ answers: t.Record( t.String(), // Key: The Field ID (UUID or String) - t.Union([ // Value: Can be String, Number, Boolean, or Array (for checkboxes) + t.Union([ + // Value: Can be String, Number, Boolean, or Array (for checkboxes) t.String(), t.Number(), t.Boolean(), t.Array(t.String()), - t.Null() + t.Null(), ]), - ) + ), }), -} +}; export interface FormResponseContext extends Context { params: Static; @@ -39,19 +40,19 @@ export const resumeResponseDTO = { body: t.Object({ answers: t.Record( t.String(), // Key: The Field ID (UUID or String) - t.Union([ // Value: Can be String, Number, Boolean, or Array (for checkboxes) + t.Union([ + // Value: Can be String, Number, Boolean, or Array (for checkboxes) t.String(), t.Number(), t.Boolean(), t.Array(t.String()), - t.Null() + t.Null(), ]), - ) + ), }), -} +}; export interface ResumeResponseContext extends Context { params: Static; body: Static; } - diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 83d6b5f..0c205a4 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -79,7 +79,7 @@ /* Type Checking */ "strict": true /* Enable all strict type-checking options. */, - "noImplicitAny": false, /* Allow explicit 'any' types where needed. */ + "noImplicitAny": false /* Allow explicit 'any' types where needed. */, // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ diff --git a/backend/biome.json b/biome.json similarity index 73% rename from backend/biome.json rename to biome.json index 0bdabf3..65fb01c 100644 --- a/backend/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/latest/schema.json", + "$schema": "https://biomejs.dev/schemas/2.3.14/schema.json", "vcs": { "enabled": true, "clientKind": "git", @@ -7,7 +7,14 @@ }, "files": { "ignoreUnknown": true, - "includes": ["src/**"] + "includes": [ + "backend/src/**/*.{ts,js,json}", + "frontend/src/**/*.{ts,js,json,tsx,jsx}", + "backend/*.json", + "frontend/*.json", + "*.json", + ".github/**/*.yml" + ] }, "formatter": { "enabled": true, diff --git a/bun.lock b/bun.lock index 402e495..e08ee2d 100644 --- a/bun.lock +++ b/bun.lock @@ -4,6 +4,7 @@ "workspaces": { "": { "dependencies": { + "@biomejs/biome": "^2.3.14", "@tanstack/react-query": "^5.90.20", "@tanstack/react-router": "^1.157.16", "@tanstack/react-router-devtools": "^1.157.16", @@ -55,6 +56,24 @@ "@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + "@biomejs/biome": ["@biomejs/biome@2.3.14", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.14", "@biomejs/cli-darwin-x64": "2.3.14", "@biomejs/cli-linux-arm64": "2.3.14", "@biomejs/cli-linux-arm64-musl": "2.3.14", "@biomejs/cli-linux-x64": "2.3.14", "@biomejs/cli-linux-x64-musl": "2.3.14", "@biomejs/cli-win32-arm64": "2.3.14", "@biomejs/cli-win32-x64": "2.3.14" }, "bin": { "biome": "bin/biome" } }, "sha512-QMT6QviX0WqXJCaiqVMiBUCr5WRQ1iFSjvOLoTk6auKukJMvnMzWucXpwZB0e8F00/1/BsS9DzcKgWH+CLqVuA=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UJGPpvWJMkLxSRtpCAKfKh41Q4JJXisvxZL8ChN1eNW3m/WlPFJ6EFDCE7YfUb4XS8ZFi3C1dFpxUJ0Ety5n+A=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-PNkLNQG6RLo8lG7QoWe/hhnMxJIt1tEimoXpGQjwS/dkdNiKBLPv4RpeQl8o3s1OKI3ZOR5XPiYtmbGGHAOnLA=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-KT67FKfzIw6DNnUNdYlBg+eU24Go3n75GWK6NwU4+yJmDYFe9i/MjiI+U/iEzKvo0g7G7MZqoyrhIYuND2w8QQ=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-LInRbXhYujtL3sH2TMCH/UBwJZsoGwfQjBrMfl84CD4hL/41C/EU5mldqf1yoFpsI0iPWuU83U+nB2TUUypWeg=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.14", "", { "os": "linux", "cpu": "x64" }, "sha512-ZsZzQsl9U+wxFrGGS4f6UxREUlgHwmEfu1IrXlgNFrNnd5Th6lIJr8KmSzu/+meSa9f4rzFrbEW9LBBA6ScoMA=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.14", "", { "os": "linux", "cpu": "x64" }, "sha512-KQU7EkbBBuHPW3/rAcoiVmhlPtDSGOGRPv9js7qJVpYTzjQmVR+C9Rfcz+ti8YCH+zT1J52tuBybtP4IodjxZQ=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-+IKYkj/pUBbnRf1G1+RlyA3LWiDgra1xpS7H2g4BuOzzRbRB+hmlw0yFsLprHhbbt7jUzbzAbAjK/Pn0FDnh1A=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.14", "", { "os": "win32", "cpu": "x64" }, "sha512-oizCjdyQ3WJEswpb3Chdngeat56rIdSYK12JI3iI11Mt5T5EXcZ7WLuowzEaFPNJ3zmOQFliMN8QY1Pi+qsfdQ=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="], diff --git a/package.json b/package.json index 86d2307..f0b3f60 100644 --- a/package.json +++ b/package.json @@ -11,15 +11,20 @@ }, "lint-staged": { "backend/**/*.{ts,js,json}": [ - "bunx biome check --write", - "bunx biome check" + "bunx biome format --write", + "bunx biome check --no-errors-on-unmatched" + ], + "frontend/**/*.{ts,tsx,js,jsx,json}": [ + "bunx biome format --write", + "bunx biome check --no-errors-on-unmatched --linter-enabled=false" ], ".github/**/*.yml": [ - "bunx biome check --write", - "bunx biome check" + "bunx biome format --write", + "bunx biome check --no-errors-on-unmatched" ] }, "dependencies": { + "@biomejs/biome": "^2.3.14", "@tanstack/react-query": "^5.90.20", "@tanstack/react-router": "^1.157.16", "@tanstack/react-router-devtools": "^1.157.16" From 98684b9786c529ea1d4ea407f9a7e0aca3168e25 Mon Sep 17 00:00:00 2001 From: Nandgopal-R Date: Wed, 4 Feb 2026 02:04:07 +0530 Subject: [PATCH 06/11] add controller to get all responses for form owner --- backend/src/api/form-response/controller.ts | 45 +++++++++++++++++++++ backend/src/types/form-response.ts | 12 ++++++ 2 files changed, 57 insertions(+) diff --git a/backend/src/api/form-response/controller.ts b/backend/src/api/form-response/controller.ts index 9f0810b..e39bcf5 100644 --- a/backend/src/api/form-response/controller.ts +++ b/backend/src/api/form-response/controller.ts @@ -2,6 +2,7 @@ import { prisma } from "../../db/prisma"; import { logger } from "../../logger/"; import type { FormResponseContext, + FormResponseForFormOwnerContext, ResumeResponseContext, } from "../../types/form-response"; @@ -82,3 +83,47 @@ export async function resumeResponse({ data: response, }; } + +export async function getResponseForFormOwner({ + params, + user, + set, +}: FormResponseForFormOwnerContext) { + const form = await prisma.form.findUnique({ + where: { + id: params.formId, + ownerId: user.id, + }, + }); + + if (!form) { + logger.warn( + `Form with ID ${params.formId} not found or does not belong to user ${user.id}`, + ); + set.status = 404; + return { + success: false, + message: "Form not found or access denied", + }; + } + + const responses = await prisma.formResponse.findMany({ + where: { + formId: params.formId, + }, + }); + if (responses.length === 0) { + logger.warn(`No responses found for form ID ${params.formId}`); + return { + success: false, + message: "No responses found for this form", + }; + } + + logger.info(`Retrieved responses for form ID ${params.formId}`); + return { + success: true, + message: "Responses retrieved successfully", + data: responses, + }; +} diff --git a/backend/src/types/form-response.ts b/backend/src/types/form-response.ts index f52506c..ff70054 100644 --- a/backend/src/types/form-response.ts +++ b/backend/src/types/form-response.ts @@ -56,3 +56,15 @@ export interface ResumeResponseContext extends Context { params: Static; body: Static; } + +export const formResponseForFormOwnerDTO = { + params: t.Object({ + formId: t.String({ + format: "uuid", + }), + }), +}; + +export interface FormResponseForFormOwnerContext extends Context { + params: Static; +} From 21eaef3b5a4e600ed6e560237ed4ce7c57782e6b Mon Sep 17 00:00:00 2001 From: Nandgopal-R Date: Wed, 4 Feb 2026 02:05:01 +0530 Subject: [PATCH 07/11] chore: update backend lint-staged to auto-fix imports --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index f0b3f60..b0e7665 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,7 @@ }, "lint-staged": { "backend/**/*.{ts,js,json}": [ - "bunx biome format --write", - "bunx biome check --no-errors-on-unmatched" + "bunx biome check --write --no-errors-on-unmatched" ], "frontend/**/*.{ts,tsx,js,jsx,json}": [ "bunx biome format --write", From fd198175937d1be120b9fdccc7b2889aba4e53d2 Mon Sep 17 00:00:00 2001 From: Nandgopal-R Date: Wed, 4 Feb 2026 11:06:01 +0530 Subject: [PATCH 08/11] add route for responses --- backend/src/api/form-response/routes.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/backend/src/api/form-response/routes.ts b/backend/src/api/form-response/routes.ts index 4ddfae3..27cf32c 100644 --- a/backend/src/api/form-response/routes.ts +++ b/backend/src/api/form-response/routes.ts @@ -1,9 +1,22 @@ import { Elysia } from "elysia"; -import { formResponseDTO, resumeResponseDTO } from "../../types/form-response"; +import { + formResponseDTO, + formResponseForFormOwnerDTO, + resumeResponseDTO, +} from "../../types/form-response"; import { requireAuth } from "../auth/requireAuth"; -import { resumeResponse, submitResponse } from "./controller"; +import { + getResponseForFormOwner, + resumeResponse, + submitResponse, +} from "./controller"; export const formResponseRoutes = new Elysia({ prefix: "/responses" }) .use(requireAuth) .post("/:formId", submitResponse, formResponseDTO) - .put("/resume/:responseId", resumeResponse, resumeResponseDTO); + .put("/resume/:responseId", resumeResponse, resumeResponseDTO) + .get( + "/responses/:formId", + getResponseForFormOwner, + formResponseForFormOwnerDTO, + ); From 7d2d9261aa04425a59930e4a14de4d01a164947f Mon Sep 17 00:00:00 2001 From: Nandgopal-R Date: Wed, 4 Feb 2026 19:49:09 +0530 Subject: [PATCH 09/11] feat: add controller to view submitter response --- backend/src/api/form-response/controller.ts | 120 +++++++++++++++++++- backend/src/api/form-response/routes.ts | 9 +- backend/src/types/form-response.ts | 12 ++ 3 files changed, 135 insertions(+), 6 deletions(-) diff --git a/backend/src/api/form-response/controller.ts b/backend/src/api/form-response/controller.ts index e39bcf5..b7c6906 100644 --- a/backend/src/api/form-response/controller.ts +++ b/backend/src/api/form-response/controller.ts @@ -3,6 +3,7 @@ import { logger } from "../../logger/"; import type { FormResponseContext, FormResponseForFormOwnerContext, + GetSubmittedResponseContext, ResumeResponseContext, } from "../../types/form-response"; @@ -111,6 +112,16 @@ export async function getResponseForFormOwner({ where: { formId: params.formId, }, + select: { + id: true, + formId: true, + answers: true, + form: { + select: { + title: true, + }, + }, + }, }); if (responses.length === 0) { logger.warn(`No responses found for form ID ${params.formId}`); @@ -120,10 +131,117 @@ export async function getResponseForFormOwner({ }; } + const fields = await prisma.formFields.findMany({ + where: { + formId: params.formId, + }, + select: { + id: true, + fieldName: true, + }, + }); + + const fieldIdToNameMap = Object.fromEntries( + fields.map((f) => [f.id, f.fieldName]), + ); + + const formattedResponses = responses.map((r) => { + const transformedAnswers: Record = {}; + + for (const [fieldId, value] of Object.entries( + r.answers as Record, + )) { + const fieldName = fieldIdToNameMap[fieldId] ?? fieldId; + transformedAnswers[fieldName] = value; + } + + return { + id: r.id, + formId: r.formId, + formTitle: r.form.title, + answers: transformedAnswers, + }; + }); + logger.info(`Retrieved responses for form ID ${params.formId}`); return { success: true, message: "Responses retrieved successfully", - data: responses, + data: formattedResponses, + }; +} + +export async function getSubmittedResponse({ + params, + user, + set, +}: GetSubmittedResponseContext) { + const response = await prisma.formResponse.findMany({ + where: { + respondentId: user.id, + formId: params.formId, + }, + select: { + id: true, + formId: true, + answers: true, + form: { + select: { + title: true, + }, + }, + }, + }); + + if (response.length === 0) { + logger.warn( + `No response found for user ${user.id} on form ${params.formId}`, + ); + set.status = 404; + return { + success: false, + message: "No response found for this form", + }; + } + + const fields = await prisma.formFields.findMany({ + where: { + formId: params.formId, + }, + select: { + id: true, + fieldName: true, + }, + }); + + const fieldIdToNameMap = Object.fromEntries( + fields.map((f) => [f.id, f.fieldName]), + ); + + const formattedResponses = response.map((r) => { + const transformedAnswers: Record = {}; + + for (const [fieldId, value] of Object.entries( + r.answers as Record, + )) { + const fieldName = fieldIdToNameMap[fieldId] ?? fieldId; + transformedAnswers[fieldName] = value; + } + + return { + id: r.id, + formId: r.formId, + formTitle: r.form.title, + answers: transformedAnswers, + }; + }); + + logger.info( + `Retrieved response for user ${user.id} on form ${params.formId}`, + ); + return { + success: true, + message: "Response retrieved successfully", + data: formattedResponses, }; } diff --git a/backend/src/api/form-response/routes.ts b/backend/src/api/form-response/routes.ts index 27cf32c..4e29856 100644 --- a/backend/src/api/form-response/routes.ts +++ b/backend/src/api/form-response/routes.ts @@ -2,11 +2,13 @@ import { Elysia } from "elysia"; import { formResponseDTO, formResponseForFormOwnerDTO, + getSubmittedResponseDTO, resumeResponseDTO, } from "../../types/form-response"; import { requireAuth } from "../auth/requireAuth"; import { getResponseForFormOwner, + getSubmittedResponse, resumeResponse, submitResponse, } from "./controller"; @@ -15,8 +17,5 @@ export const formResponseRoutes = new Elysia({ prefix: "/responses" }) .use(requireAuth) .post("/:formId", submitResponse, formResponseDTO) .put("/resume/:responseId", resumeResponse, resumeResponseDTO) - .get( - "/responses/:formId", - getResponseForFormOwner, - formResponseForFormOwnerDTO, - ); + .get("/:formId", getResponseForFormOwner, formResponseForFormOwnerDTO) + .get("/user/:formId", getSubmittedResponse, getSubmittedResponseDTO); diff --git a/backend/src/types/form-response.ts b/backend/src/types/form-response.ts index ff70054..db817c2 100644 --- a/backend/src/types/form-response.ts +++ b/backend/src/types/form-response.ts @@ -68,3 +68,15 @@ export const formResponseForFormOwnerDTO = { export interface FormResponseForFormOwnerContext extends Context { params: Static; } + +export const getSubmittedResponseDTO = { + params: t.Object({ + formId: t.String({ + format: "uuid", + }), + }), +}; + +export interface GetSubmittedResponseContext extends Context { + params: Static; +} From 3c5b8ab37f59cca0d8526e03c5e5d20c9bbe3730 Mon Sep 17 00:00:00 2001 From: Nandgopal-R Date: Wed, 4 Feb 2026 19:49:30 +0530 Subject: [PATCH 10/11] chore: add bruno files for form-response endpoints --- .../getFormResponsesForFormOwner.bru | 20 +++++++++++++++++++ .../form-response/getSubmittedResponse.bru | 20 +++++++++++++++++++ .../bruno/form-response/submitResponse.bru | 7 ++++--- 3 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 backend/bruno/form-response/getFormResponsesForFormOwner.bru create mode 100644 backend/bruno/form-response/getSubmittedResponse.bru diff --git a/backend/bruno/form-response/getFormResponsesForFormOwner.bru b/backend/bruno/form-response/getFormResponsesForFormOwner.bru new file mode 100644 index 0000000..4be329f --- /dev/null +++ b/backend/bruno/form-response/getFormResponsesForFormOwner.bru @@ -0,0 +1,20 @@ +meta { + name: getFormResponsesForFormOwner + type: http + seq: 2 +} + +get { + url: http://localhost:8000/responses/:formId + body: none + auth: inherit +} + +params:path { + formId: ef50e45d-d095-418d-ab49-7196900fa5c2 +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/backend/bruno/form-response/getSubmittedResponse.bru b/backend/bruno/form-response/getSubmittedResponse.bru new file mode 100644 index 0000000..aba6375 --- /dev/null +++ b/backend/bruno/form-response/getSubmittedResponse.bru @@ -0,0 +1,20 @@ +meta { + name: getSubmittedResponse + type: http + seq: 3 +} + +get { + url: http://localhost:8000/responses/:formId + body: none + auth: inherit +} + +params:path { + formId: ef50e45d-d095-418d-ab49-7196900fa5c2 +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/backend/bruno/form-response/submitResponse.bru b/backend/bruno/form-response/submitResponse.bru index a3416e0..5a38c33 100644 --- a/backend/bruno/form-response/submitResponse.bru +++ b/backend/bruno/form-response/submitResponse.bru @@ -11,14 +11,15 @@ post { } params:path { - formId: fdb4db57-695f-4a2e-aa2d-674b2f188238 + formId: ef50e45d-d095-418d-ab49-7196900fa5c2 } body:json { { "answers":{ - "063a2bbb-99e5-492f-ad98-afbca86283ca": "Jack", - "99f13fb0-6100-4f6d-9460-476c0f20967d": "999999999" + "575f5192-d46c-4974-bb0c-f909d9fb80ce": "Jack", + "894b4cf0-1320-4403-b116-8504f23ef6e3": "jack@gmail.com", + "15026215-127b-44bc-8a06-a12e33f79802":"3" } } } From d33b5987f1d255741c7196f88b443b65ba0981fa Mon Sep 17 00:00:00 2001 From: Nandgopal-R Date: Wed, 4 Feb 2026 21:05:15 +0530 Subject: [PATCH 11/11] feat: remove child-field in formField table --- .../20260204153423_remove_child_field/migration.sql | 8 ++++++++ backend/prisma/schema.prisma | 5 ----- 2 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 backend/prisma/migrations/20260204153423_remove_child_field/migration.sql diff --git a/backend/prisma/migrations/20260204153423_remove_child_field/migration.sql b/backend/prisma/migrations/20260204153423_remove_child_field/migration.sql new file mode 100644 index 0000000..8be99a2 --- /dev/null +++ b/backend/prisma/migrations/20260204153423_remove_child_field/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - You are about to drop the column `nextField` on the `form_fields` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "form_fields" DROP COLUMN "nextField"; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index a9fe392..59797b5 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -111,11 +111,6 @@ model FormFields { // This field stores the ID of the field passing control to this one prevFieldId String? - // The "Next" Field (Child) - // This field is the virtual counterpart to the relation above. - // We don't need a physical 'childFieldId' column because Prisma handles the other side of the relation automatically. - nextField String? - @@index([formId]) @@index([formId, prevFieldId]) @@map("form_fields")