diff --git a/bruno/forms/createForm.bru b/bruno/forms/createForm.bru index 87fb5de..fb6fd4a 100644 --- a/bruno/forms/createForm.bru +++ b/bruno/forms/createForm.bru @@ -23,7 +23,7 @@ settings { } docs { - # 2. Create Form + # Create Form Creates a new empty form container. diff --git a/bruno/forms/deleteForm.bru b/bruno/forms/deleteForm.bru index bcdd42b..eb04476 100644 --- a/bruno/forms/deleteForm.bru +++ b/bruno/forms/deleteForm.bru @@ -20,7 +20,7 @@ settings { } docs { - # 5. Delete Form + # Delete Form Permanently removes a specific form. This endpoint ensures security by only allowing the **owner** of the form to delete it. If the form does not exist or belongs to another user, no action is taken, and a 404 error is returned. diff --git a/bruno/forms/getAllForms.bru b/bruno/forms/getAllForms.bru index c94f10e..2958095 100644 --- a/bruno/forms/getAllForms.bru +++ b/bruno/forms/getAllForms.bru @@ -16,7 +16,7 @@ settings { } docs { - # 1. Get All Forms + # Get All Forms Retrieves a list of all forms created by the currently authenticated user. This endpoint provides a summary view, returning key metadata like the title and publication status, but not the detailed form fields. diff --git a/bruno/forms/getFormById.bru b/bruno/forms/getFormById.bru index cdaf970..b70e338 100644 --- a/bruno/forms/getFormById.bru +++ b/bruno/forms/getFormById.bru @@ -20,45 +20,75 @@ settings { } docs { - # 3. Get Form By ID + # Get Form By ID - Retrieves the details of a specific form. This endpoint is scoped to the **owner**, meaning a user can only fetch forms they created. If a form exists but belongs to another user, it will return a 404 error. + ## Endpoint + `GET /forms/:formId` - * **URL:** `/forms/:formId` - * **Method:** `GET` - * **Auth Required:** Yes + ## Description + Fetches a form owned by the authenticated user along with its fields in order. - ### Path Parameters + ## Controller + `getFormById` - | Parameter | Type | Description | - | :--- | :--- | :--- | - | `formId` | `string` (UUID) | The unique ID of the form to retrieve. | + ## Auth Required + ✅ Yes (User must be the form owner) - ### Responses + ## Path Parameters - #### ✅ 200 OK: Success + | Name | Type | Required | Description | + |--------|--------|----------|-------------| + | formId | string | Yes | ID of the form | - Returns the full form object. + ## Request Body + None + + ## Responses + + ### Success Response (Form with Fields) + + #### Status: 200 OK ```json { "success": true, "message": "Form fetched successfully", - "data": { - "id": "form_uuid_123", - "title": "Customer Feedback Survey", - "description": "A survey to collect customer opinions.", - "isPublished": true, - "ownerId": "user_uuid_555", - "createdAt": "2023-10-27T10:00:00.000Z", - "updatedAt": "2023-10-27T12:00:00.000Z" - } + "form": { + "id": "form_456", + "title": "Job Application", + "description": "Apply for internship", + "isPublished": false, + "createdAt": "2026-02-07T10:30:00.000Z" + }, + "fields": [ + { + "id": "field_1", + "formId": "form_456", + "fieldName": "Full Name", + "fieldType": "text", + "prevFieldId": null + }, + { + "id": "field_2", + "formId": "form_456", + "fieldName": "Email", + "fieldType": "email", + "prevFieldId": "field_1" + } + ] } - ❌ 404 Not Found + Success Response (No Fields Found) + Status: 200 OK + + { + "success": true, + "message": "No forms fields found", + "data": [] + } - Occurs if the formId does not exist OR if the form belongs to a different user. - JSON + Error Responses + Status: 404 Not Found { "success": false, diff --git a/bruno/forms/publishForm.bru b/bruno/forms/publishForm.bru index f17bb25..b510ffd 100644 --- a/bruno/forms/publishForm.bru +++ b/bruno/forms/publishForm.bru @@ -20,7 +20,7 @@ settings { } docs { - # 6. Publish Form + # Publish Form Changes the status of a form to **Published**. Once published, the form becomes accessible to respondents for submission. diff --git a/bruno/forms/unPublishForm.bru b/bruno/forms/unPublishForm.bru index 62016f0..84895d6 100644 --- a/bruno/forms/unPublishForm.bru +++ b/bruno/forms/unPublishForm.bru @@ -20,7 +20,7 @@ settings { } docs { - # 7. Unpublish Form + # Unpublish Form Reverts the status of a form to **Unpublished**. This hides the form from the public, preventing any new submissions until it is published again. diff --git a/src/api/forms/controller.ts b/src/api/forms/controller.ts index 1e8334b..00dfe0e 100644 --- a/src/api/forms/controller.ts +++ b/src/api/forms/controller.ts @@ -63,6 +63,13 @@ export async function getFormById({ user, params, set }: GetFormByIdContext) { id: params.formId, ownerId: user.id, }, + select: { + id: true, + title: true, + description: true, + isPublished: true, + createdAt: true, + }, }); if (!form) { @@ -73,11 +80,39 @@ export async function getFormById({ user, params, set }: GetFormByIdContext) { }; } + const fields = await prisma.formFields.findMany({ + where: { formId: params.formId }, + }); + + if (fields.length === 0) { + logger.info(`No fields found for formId: ${params.formId}`); + return { + success: true, + message: "No forms fields found", + data: [], + }; + } + + 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, + ); + } + logger.info("Fetched form for user", { userId: user.id, formId: form.id }); return { success: true, message: "Form fetched successfully", - data: form, + form: form, + fields: ordered, }; } diff --git a/src/test/forms.test.ts b/src/test/forms.test.ts index 9b25b34..6379b8d 100644 --- a/src/test/forms.test.ts +++ b/src/test/forms.test.ts @@ -1,7 +1,8 @@ import { beforeEach, describe, expect, it, mock } from "bun:test"; // ---------- MOCK PRISMA ---------- -const findManyMock = mock(); +const formFindManyMock = mock(); +const formFieldsFindManyMock = mock(); const createMock = mock(); const findFirstMock = mock(); const updateMock = mock(); @@ -10,12 +11,15 @@ const deleteManyMock = mock(); mock.module("../db/prisma", () => ({ prisma: { form: { - findMany: findManyMock, + findMany: formFindManyMock, create: createMock, findFirst: findFirstMock, update: updateMock, deleteMany: deleteManyMock, }, + formFields: { + findMany: formFieldsFindManyMock, + }, }, })); @@ -37,7 +41,8 @@ const { getAllForms, createForm, getFormById, updateForm, deleteForm } = describe("Forms Controller Tests", () => { beforeEach(() => { - findManyMock.mockReset(); + formFindManyMock.mockReset(); + formFieldsFindManyMock.mockReset(); createMock.mockReset(); findFirstMock.mockReset(); updateMock.mockReset(); @@ -51,7 +56,7 @@ describe("Forms Controller Tests", () => { // ===== getAllForms ===== it("getAllForms → success", async () => { - findManyMock.mockResolvedValue([ + formFindManyMock.mockResolvedValue([ { id: "1", title: "A", isPublished: true, createdAt: new Date() }, ]); @@ -62,7 +67,7 @@ describe("Forms Controller Tests", () => { }); it("getAllForms → empty", async () => { - findManyMock.mockResolvedValue([]); + formFindManyMock.mockResolvedValue([]); const res = await getAllForms({ user } as any); @@ -71,7 +76,7 @@ describe("Forms Controller Tests", () => { }); it("getAllForms → DB error", async () => { - findManyMock.mockRejectedValue(new Error("DB fail")); + formFindManyMock.mockRejectedValue(new Error("DB fail")); expect(getAllForms({ user } as any)).rejects.toThrow(); }); @@ -113,26 +118,69 @@ describe("Forms Controller Tests", () => { // ===== getFormById ===== - it("getFormById → found", async () => { - findFirstMock.mockResolvedValue({ id: "1" }); + it("getFormById → found with ordered fields", async () => { + findFirstMock.mockResolvedValue({ + id: "1", + title: "Test Form", + description: "Desc", + isPublished: false, + createdAt: new Date(), + }); + + formFieldsFindManyMock.mockResolvedValue([ + { id: "f1", formId: "1", prevFieldId: null }, + { id: "f2", formId: "1", prevFieldId: "f1" }, + ]); const set: any = {}; + const res = await getFormById({ user, - params: { id: "1" }, + params: { formId: "1" }, + set, + } as any); + + const result: any = res; + + expect(result.success).toBe(true); + expect(result.form.id).toBe("1"); + expect(result.fields.length).toBe(2); + expect(result.fields[0].id).toBe("f1"); + expect(result.fields[1].id).toBe("f2"); + }); + + it("getFormById → no fields", async () => { + findFirstMock.mockResolvedValue({ + id: "1", + title: "Test Form", + description: "Desc", + isPublished: false, + createdAt: new Date(), + }); + + formFieldsFindManyMock.mockResolvedValue([]); + + const set: any = {}; + + const res = await getFormById({ + user, + params: { formId: "1" }, set, } as any); expect(res.success).toBe(true); + expect(res.data).toEqual([]); + expect(res.message).toBe("No forms fields found"); }); it("getFormById → not found", async () => { findFirstMock.mockResolvedValue(null); const set: any = {}; + const res = await getFormById({ user, - params: { id: "2" }, + params: { formId: "2" }, set, } as any); @@ -145,10 +193,10 @@ describe("Forms Controller Tests", () => { const set: any = {}; - expect( + await expect( getFormById({ user, - params: { id: "1" }, + params: { formId: "1" }, set, } as any), ).rejects.toThrow();