mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-07 10:32:50 -06:00
Compare commits
2 Commits
fix/add-ne
...
fix/1306-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe08e7de1f | ||
|
|
0d12e71f99 |
@@ -8,7 +8,9 @@ import { withV1ApiWrapper } from "@/app/lib/api/with-api-logging";
|
||||
import { sendToPipeline } from "@/app/lib/pipelines";
|
||||
import { getResponse } from "@/lib/response/service";
|
||||
import { getSurvey } from "@/lib/survey/service";
|
||||
import { formatValidationErrors } from "@/modules/api/lib/validation";
|
||||
import { validateOtherOptionLengthForMultipleChoice } from "@/modules/api/v2/lib/element";
|
||||
import { validateResponseData } from "@/modules/api/v2/management/responses/lib/validation";
|
||||
import { createQuotaFullObject } from "@/modules/ee/quotas/lib/helpers";
|
||||
import { validateFileUploads } from "@/modules/storage/utils";
|
||||
import { updateResponseWithQuotaEvaluation } from "./lib/response";
|
||||
@@ -113,6 +115,27 @@ export const PUT = withV1ApiWrapper({
|
||||
};
|
||||
}
|
||||
|
||||
// Validate response data against validation rules (only if data is provided)
|
||||
const updateData = inputValidation.data.data;
|
||||
if (updateData) {
|
||||
const validationErrors = validateResponseData(
|
||||
survey.blocks,
|
||||
updateData,
|
||||
inputValidation.data.language ?? "en",
|
||||
survey.questions
|
||||
);
|
||||
|
||||
if (validationErrors) {
|
||||
return {
|
||||
response: responses.badRequestResponse(
|
||||
"Response validation failed",
|
||||
formatValidationErrors(validationErrors),
|
||||
true
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// update response with quota evaluation
|
||||
let updatedResponse;
|
||||
try {
|
||||
|
||||
@@ -12,6 +12,8 @@ import { withV1ApiWrapper } from "@/app/lib/api/with-api-logging";
|
||||
import { sendToPipeline } from "@/app/lib/pipelines";
|
||||
import { getSurvey } from "@/lib/survey/service";
|
||||
import { getClientIpFromHeaders } from "@/lib/utils/client-ip";
|
||||
import { formatValidationErrors } from "@/modules/api/lib/validation";
|
||||
import { validateResponseData } from "@/modules/api/v2/management/responses/lib/validation";
|
||||
import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils";
|
||||
import { createQuotaFullObject } from "@/modules/ee/quotas/lib/helpers";
|
||||
import { validateFileUploads } from "@/modules/storage/utils";
|
||||
@@ -123,6 +125,24 @@ export const POST = withV1ApiWrapper({
|
||||
};
|
||||
}
|
||||
|
||||
// Validate response data against validation rules
|
||||
const validationErrors = validateResponseData(
|
||||
survey.blocks,
|
||||
responseInputData.data,
|
||||
responseInputData.language ?? "en",
|
||||
survey.questions
|
||||
);
|
||||
|
||||
if (validationErrors) {
|
||||
return {
|
||||
response: responses.badRequestResponse(
|
||||
"Response validation failed",
|
||||
formatValidationErrors(validationErrors),
|
||||
true
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
let response: TResponseWithQuotaFull;
|
||||
try {
|
||||
const meta: TResponseInput["meta"] = {
|
||||
|
||||
@@ -8,10 +8,8 @@ import { TApiAuditLog, TApiKeyAuthentication, withV1ApiWrapper } from "@/app/lib
|
||||
import { sendToPipeline } from "@/app/lib/pipelines";
|
||||
import { deleteResponse, getResponse } from "@/lib/response/service";
|
||||
import { getSurvey } from "@/lib/survey/service";
|
||||
import {
|
||||
formatValidationErrorsForV1Api,
|
||||
validateResponseData,
|
||||
} from "@/modules/api/v2/management/responses/lib/validation";
|
||||
import { formatValidationErrors } from "@/modules/api/lib/validation";
|
||||
import { validateResponseData } from "@/modules/api/v2/management/responses/lib/validation";
|
||||
import { hasPermission } from "@/modules/organization/settings/api-keys/lib/utils";
|
||||
import { validateFileUploads } from "@/modules/storage/utils";
|
||||
import { updateResponseWithQuotaEvaluation } from "./lib/response";
|
||||
@@ -156,7 +154,7 @@ export const PUT = withV1ApiWrapper({
|
||||
return {
|
||||
response: responses.badRequestResponse(
|
||||
"Validation failed",
|
||||
formatValidationErrorsForV1Api(validationErrors),
|
||||
formatValidationErrors(validationErrors),
|
||||
true
|
||||
),
|
||||
};
|
||||
|
||||
@@ -7,10 +7,8 @@ import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { TApiAuditLog, TApiKeyAuthentication, withV1ApiWrapper } from "@/app/lib/api/with-api-logging";
|
||||
import { sendToPipeline } from "@/app/lib/pipelines";
|
||||
import { getSurvey } from "@/lib/survey/service";
|
||||
import {
|
||||
formatValidationErrorsForV1Api,
|
||||
validateResponseData,
|
||||
} from "@/modules/api/v2/management/responses/lib/validation";
|
||||
import { formatValidationErrors } from "@/modules/api/lib/validation";
|
||||
import { validateResponseData } from "@/modules/api/v2/management/responses/lib/validation";
|
||||
import { hasPermission } from "@/modules/organization/settings/api-keys/lib/utils";
|
||||
import { validateFileUploads } from "@/modules/storage/utils";
|
||||
import {
|
||||
@@ -165,7 +163,7 @@ export const POST = withV1ApiWrapper({
|
||||
return {
|
||||
response: responses.badRequestResponse(
|
||||
"Validation failed",
|
||||
formatValidationErrorsForV1Api(validationErrors),
|
||||
formatValidationErrors(validationErrors),
|
||||
true
|
||||
),
|
||||
};
|
||||
|
||||
@@ -11,7 +11,9 @@ import { sendToPipeline } from "@/app/lib/pipelines";
|
||||
import { getSurvey } from "@/lib/survey/service";
|
||||
import { getElementsFromBlocks } from "@/lib/survey/utils";
|
||||
import { getClientIpFromHeaders } from "@/lib/utils/client-ip";
|
||||
import { formatValidationErrors } from "@/modules/api/lib/validation";
|
||||
import { validateOtherOptionLengthForMultipleChoice } from "@/modules/api/v2/lib/element";
|
||||
import { validateResponseData } from "@/modules/api/v2/management/responses/lib/validation";
|
||||
import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils";
|
||||
import { createQuotaFullObject } from "@/modules/ee/quotas/lib/helpers";
|
||||
import { createResponseWithQuotaEvaluation } from "./lib/response";
|
||||
@@ -106,6 +108,22 @@ export const POST = async (request: Request, context: Context): Promise<Response
|
||||
);
|
||||
}
|
||||
|
||||
// Validate response data against validation rules
|
||||
const validationErrors = validateResponseData(
|
||||
survey.blocks,
|
||||
responseInputData.data,
|
||||
responseInputData.language ?? "en",
|
||||
survey.questions
|
||||
);
|
||||
|
||||
if (validationErrors) {
|
||||
return responses.badRequestResponse(
|
||||
"Response validation failed",
|
||||
formatValidationErrors(validationErrors),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
let response: TResponseWithQuotaFull;
|
||||
try {
|
||||
const meta: TResponseInputV2["meta"] = {
|
||||
|
||||
21
apps/web/modules/api/lib/validation.ts
Normal file
21
apps/web/modules/api/lib/validation.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import "server-only";
|
||||
import { TValidationErrorMap } from "@formbricks/types/surveys/validation-rules";
|
||||
|
||||
/**
|
||||
* Converts validation error map to API error response format as Record<string, string>
|
||||
* Used by both v1 and v2 client APIs for consistent error formatting
|
||||
*
|
||||
* @param errorMap - Validation error map from validateResponseData
|
||||
* @returns API error details as Record<string, string> where keys are field paths and values are combined error messages
|
||||
*/
|
||||
export const formatValidationErrors = (errorMap: TValidationErrorMap): Record<string, string> => {
|
||||
const details: Record<string, string> = {};
|
||||
|
||||
for (const [elementId, errors] of Object.entries(errorMap)) {
|
||||
// Combine all error messages for each element
|
||||
const errorMessages = errors.map((error) => error.message).join("; ");
|
||||
details[`response.data.${elementId}`] = errorMessages;
|
||||
}
|
||||
|
||||
return details;
|
||||
};
|
||||
@@ -4,11 +4,8 @@ import { TSurveyBlock } from "@formbricks/types/surveys/blocks";
|
||||
import { TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements";
|
||||
import { TSurveyQuestion, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
|
||||
import { TValidationErrorMap } from "@formbricks/types/surveys/validation-rules";
|
||||
import {
|
||||
formatValidationErrorsForApi,
|
||||
formatValidationErrorsForV1Api,
|
||||
validateResponseData,
|
||||
} from "./validation";
|
||||
import { formatValidationErrors } from "@/modules/api/lib/validation";
|
||||
import { formatValidationErrorsForApi, validateResponseData } from "./validation";
|
||||
|
||||
const mockTransformQuestionsToBlocks = vi.fn();
|
||||
const mockGetElementsFromBlocks = vi.fn();
|
||||
@@ -172,13 +169,13 @@ describe("formatValidationErrorsForApi", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatValidationErrorsForV1Api", () => {
|
||||
test("should convert error map to V1 API format", () => {
|
||||
describe("formatValidationErrors", () => {
|
||||
test("should convert error map to Record format", () => {
|
||||
const errorMap: TValidationErrorMap = {
|
||||
element1: [{ ruleId: "minLength", ruleType: "minLength", message: "Min length required" }],
|
||||
};
|
||||
|
||||
expect(formatValidationErrorsForV1Api(errorMap)).toEqual({
|
||||
expect(formatValidationErrors(errorMap)).toEqual({
|
||||
"response.data.element1": "Min length required",
|
||||
});
|
||||
});
|
||||
@@ -191,7 +188,7 @@ describe("formatValidationErrorsForV1Api", () => {
|
||||
],
|
||||
};
|
||||
|
||||
expect(formatValidationErrorsForV1Api(errorMap)).toEqual({
|
||||
expect(formatValidationErrors(errorMap)).toEqual({
|
||||
"response.data.element1": "Min length; Max length",
|
||||
});
|
||||
});
|
||||
@@ -202,7 +199,7 @@ describe("formatValidationErrorsForV1Api", () => {
|
||||
element2: [{ ruleId: "maxLength", ruleType: "maxLength", message: "Max length" }],
|
||||
};
|
||||
|
||||
expect(formatValidationErrorsForV1Api(errorMap)).toEqual({
|
||||
expect(formatValidationErrors(errorMap)).toEqual({
|
||||
"response.data.element1": "Min length",
|
||||
"response.data.element2": "Max length",
|
||||
});
|
||||
|
||||
@@ -72,21 +72,3 @@ export const formatValidationErrorsForApi = (errorMap: TValidationErrorMap) => {
|
||||
|
||||
return details;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts validation error map to V1 API error response format
|
||||
*
|
||||
* @param errorMap - Validation error map from validateResponseData
|
||||
* @returns V1 API error details as Record<string, string>
|
||||
*/
|
||||
export const formatValidationErrorsForV1Api = (errorMap: TValidationErrorMap): Record<string, string> => {
|
||||
const details: Record<string, string> = {};
|
||||
|
||||
for (const [elementId, errors] of Object.entries(errorMap)) {
|
||||
// Combine all error messages for each element
|
||||
const errorMessages = errors.map((error) => error.message).join("; ");
|
||||
details[`response.data.${elementId}`] = errorMessages;
|
||||
}
|
||||
|
||||
return details;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user