feat: new management api crud endpoint for responses (#4716)

Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
Co-authored-by: Victor Santos <victor@formbricks.com>
Co-authored-by: victorvhs017 <115753265+victorvhs017@users.noreply.github.com>
This commit is contained in:
Piyush Gupta
2025-03-06 22:46:06 +05:30
committed by GitHub
parent 4113dd1873
commit 140aee749b
129 changed files with 9360 additions and 284 deletions
@@ -0,0 +1,47 @@
import { type ContactAttributeKey, ContactAttributeType } from "@prisma/client";
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
extendZodWithOpenApi(z);
export const ZContactAttributeKey = z.object({
id: z.string().cuid2().openapi({
description: "The ID of the contact attribute key",
}),
createdAt: z.coerce.date().openapi({
description: "The date and time the contact attribute key was created",
example: "2021-01-01T00:00:00.000Z",
}),
updatedAt: z.coerce.date().openapi({
description: "The date and time the contact attribute key was last updated",
example: "2021-01-01T00:00:00.000Z",
}),
isUnique: z.boolean().openapi({
description: "Whether the attribute must have unique values across contacts",
example: false,
}),
key: z.string().openapi({
description: "The attribute identifier used in the system",
example: "email",
}),
name: z.string().nullable().openapi({
description: "Display name for the attribute",
example: "Email Address",
}),
description: z.string().nullable().openapi({
description: "Description of the attribute",
example: "The user's email address",
}),
type: z.nativeEnum(ContactAttributeType).openapi({
description: "Whether this is a default or custom attribute",
example: "custom",
}),
environmentId: z.string().cuid2().openapi({
description: "The ID of the environment this attribute belongs to",
}),
}) satisfies z.ZodType<ContactAttributeKey>;
ZContactAttributeKey.openapi({
ref: "contactAttributeKey",
description: "Defines a possible attribute that can be assigned to contacts",
});
@@ -0,0 +1,34 @@
import type { ContactAttribute } from "@prisma/client";
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
extendZodWithOpenApi(z);
export const ZContactAttribute = z.object({
id: z.string().cuid2().openapi({
description: "The ID of the contact attribute",
}),
createdAt: z.coerce.date().openapi({
description: "The date and time the contact attribute was created",
example: "2021-01-01T00:00:00.000Z",
}),
updatedAt: z.coerce.date().openapi({
description: "The date and time the contact attribute was last updated",
example: "2021-01-01T00:00:00.000Z",
}),
attributeKeyId: z.string().cuid2().openapi({
description: "The ID of the attribute key",
}),
contactId: z.string().cuid2().openapi({
description: "The ID of the contact",
}),
value: z.string().openapi({
description: "The value of the attribute",
example: "example@email.com",
}),
}) satisfies z.ZodType<ContactAttribute>;
ZContactAttribute.openapi({
ref: "contactAttribute",
description: "A contact attribute value associated with a contact",
});
+30
View File
@@ -0,0 +1,30 @@
import type { Contact } from "@prisma/client";
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
extendZodWithOpenApi(z);
export const ZContact = z.object({
id: z.string().cuid2().openapi({
description: "Unique identifier for the contact",
}),
userId: z.string().nullable().openapi({
description: "Optional external user identifier",
}),
createdAt: z.coerce.date().openapi({
description: "When the contact was created",
example: "2021-01-01T00:00:00.000Z",
}),
updatedAt: z.coerce.date().openapi({
description: "When the contact was last updated",
example: "2021-01-01T00:00:00.000Z",
}),
environmentId: z.string().openapi({
description: "The environment this contact belongs to",
}),
}) satisfies z.ZodType<Contact>;
ZContact.openapi({
ref: "contact",
description: "A person or user who can receive and respond to surveys",
});
+116
View File
@@ -0,0 +1,116 @@
import type { Response } from "@prisma/client";
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
extendZodWithOpenApi(z);
export const ZResponse = z.object({
id: z.string().cuid2().openapi({
description: "The ID of the response",
}),
createdAt: z.coerce.date().openapi({
description: "The date and time the response was created",
example: "2021-01-01T00:00:00.000Z",
}),
updatedAt: z.coerce.date().openapi({
description: "The date and time the response was last updated",
example: "2021-01-01T00:00:00.000Z",
}),
finished: z.boolean().openapi({
description: "Whether the response is finished",
example: true,
}),
surveyId: z.string().cuid2().openapi({
description: "The ID of the survey",
}),
contactId: z.string().cuid2().nullable().openapi({
description: "The ID of the contact",
}),
endingId: z.string().cuid2().nullable().openapi({
description: "The ID of the ending",
}),
data: z.record(z.union([z.string(), z.number(), z.array(z.string()), z.record(z.string())])).openapi({
description: "The data of the response",
example: {
question1: "answer1",
question2: 2,
question3: ["answer3", "answer4"],
question4: {
subquestion1: "answer5",
},
},
}),
variables: z.record(z.union([z.string(), z.number()])).openapi({
description: "The variables of the response",
example: {
variable1: "answer1",
variable2: 2,
},
}),
ttc: z.record(z.number()).openapi({
description: "The TTC of the response",
example: {
question1: 10,
question2: 20,
},
}),
meta: z
.object({
source: z.string().optional().openapi({
description: "The source of the response",
example: "https://example.com",
}),
url: z.string().optional().openapi({
description: "The URL of the response",
example: "https://example.com",
}),
userAgent: z
.object({
browser: z.string().optional(),
os: z.string().optional(),
device: z.string().optional(),
})
.optional(),
country: z.string().optional(),
action: z.string().optional(),
})
.openapi({
description: "The meta data of the response",
example: {
source: "https://example.com",
url: "https://example.com",
userAgent: {
browser: "Chrome",
os: "Windows",
device: "Desktop",
},
country: "US",
action: "click",
},
}),
contactAttributes: z
.record(z.string())
.nullable()
.openapi({
description: "The attributes of the contact",
example: {
attribute1: "value1",
attribute2: "value2",
},
}),
singleUseId: z.string().nullable().openapi({
description: "The single use ID of the response",
}),
language: z.string().nullable().openapi({
description: "The language of the response",
example: "en",
}),
displayId: z.string().nullable().openapi({
description: "The display ID of the response",
}),
}) satisfies z.ZodType<Response>;
ZResponse.openapi({
ref: "response",
description: "A response",
});
+235
View File
@@ -0,0 +1,235 @@
import { type Survey, SurveyStatus, SurveyType } from "@prisma/client";
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
// eslint-disable-next-line import/no-relative-packages -- Need to import from parent package
import { ZSurveyEnding, ZSurveyQuestion, ZSurveyVariable } from "../../types/surveys/types";
extendZodWithOpenApi(z);
const ZColor = z.string().regex(/^#(?:[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/);
export const ZStylingColor = z.object({
light: ZColor,
dark: ZColor.nullish(),
});
export const ZCardArrangementOptions = z.enum(["casual", "straight", "simple"]);
export const ZCardArrangement = z.object({
linkSurveys: ZCardArrangementOptions,
appSurveys: ZCardArrangementOptions,
});
export const ZSurveyStylingBackground = z.object({
bg: z.string().nullish(),
bgType: z.enum(["animation", "color", "image", "upload"]).nullish(),
brightness: z.number().nullish(),
});
export const ZPlacement = z.enum(["bottomLeft", "bottomRight", "topLeft", "topRight", "center"]);
const ZSurveyBase = z.object({
id: z.string().cuid2().openapi({
description: "The ID of the survey",
}),
createdAt: z.coerce.date().openapi({
description: "The date and time the survey was created",
example: "2021-01-01T00:00:00.000Z",
}),
updatedAt: z.coerce.date().openapi({
description: "The date and time the survey was last updated",
example: "2021-01-01T00:00:00.000Z",
}),
name: z.string().openapi({
description: "The name of the survey",
}),
redirectUrl: z.string().url().nullable().openapi({
description: "The URL to redirect to after the survey is completed",
}),
type: z.nativeEnum(SurveyType).openapi({
description: "The type of the survey",
}),
status: z.nativeEnum(SurveyStatus).openapi({
description: "The status of the survey",
}),
thankYouMessage: z.string().nullable().openapi({
description: "The thank you message of the survey",
}),
showLanguageSwitch: z.boolean().nullable().openapi({
description: "Whether to show the language switch",
}),
showThankYouMessage: z.boolean().nullable().openapi({
description: "Whether to show the thank you message",
}),
welcomeCard: z
.object({
enabled: z.boolean(),
timeToFinish: z.boolean(),
showResponseCount: z.boolean(),
headline: z.record(z.string()).optional(),
html: z.record(z.string()).optional(),
fileUrl: z.string().optional(),
buttonLabel: z.record(z.string()).optional(),
videoUrl: z.string().optional(),
})
.openapi({
description: "The welcome card configuration",
}),
displayProgressBar: z.boolean().nullable().openapi({
description: "Whether to display the progress bar",
}),
resultShareKey: z.string().nullable().openapi({
description: "The result share key of the survey",
}),
pin: z.string().nullable().openapi({
description: "The pin of the survey",
}),
createdBy: z.string().nullable().openapi({
description: "The user who created the survey",
}),
environmentId: z.string().cuid2().openapi({
description: "The environment ID of the survey",
}),
questions: z.array(ZSurveyQuestion).openapi({
description: "The questions of the survey",
}) as z.ZodType<Survey["questions"]>,
endings: z.array(ZSurveyEnding).default([]).openapi({
description: "The endings of the survey",
}) as z.ZodType<Survey["endings"]>,
thankYouCard: z
.object({
enabled: z.boolean(),
message: z.string(),
})
.nullable()
.openapi({
description: "The thank you card of the survey (deprecated)",
}),
hiddenFields: z
.object({
enabled: z.boolean(),
fieldIds: z.array(z.string()).optional(),
})
.openapi({
description: "Hidden fields configuration",
}),
variables: z.array(ZSurveyVariable).openapi({
description: "Survey variables",
}) as z.ZodType<Survey["variables"]>,
displayOption: z.enum(["displayOnce", "displayMultiple", "displaySome", "respondMultiple"]).openapi({
description: "Display options for the survey",
}),
recontactDays: z.number().nullable().openapi({
description: "Days before recontacting",
}),
displayLimit: z.number().nullable().openapi({
description: "Display limit for the survey",
}),
autoClose: z.number().nullable().openapi({
description: "Auto close time in seconds",
}),
autoComplete: z.number().nullable().openapi({
description: "Auto complete time in seconds",
}),
delay: z.number().openapi({
description: "Delay before showing survey",
}),
runOnDate: z.date().nullable().openapi({
description: "Date to run the survey",
}),
closeOnDate: z.date().nullable().openapi({
description: "Date to close the survey",
}),
surveyClosedMessage: z
.object({
enabled: z.boolean(),
heading: z.string(),
subheading: z.string(),
})
.nullable()
.openapi({
description: "Message shown when survey is closed",
}),
segmentId: z.string().nullable().openapi({
description: "ID of the segment",
}),
projectOverwrites: z
.object({
brandColor: ZColor.nullish(),
highlightBorderColor: ZColor.nullish(),
placement: ZPlacement.nullish(),
clickOutsideClose: z.boolean().nullish(),
darkOverlay: z.boolean().nullish(),
})
.nullable()
.openapi({
description: "Project specific overwrites",
}),
styling: z
.object({
brandColor: ZStylingColor.nullish(),
questionColor: ZStylingColor.nullish(),
inputColor: ZStylingColor.nullish(),
inputBorderColor: ZStylingColor.nullish(),
cardBackgroundColor: ZStylingColor.nullish(),
cardBorderColor: ZStylingColor.nullish(),
cardShadowColor: ZStylingColor.nullish(),
highlightBorderColor: ZStylingColor.nullish(),
isDarkModeEnabled: z.boolean().nullish(),
roundness: z.number().nullish(),
cardArrangement: ZCardArrangement.nullish(),
background: ZSurveyStylingBackground.nullish(),
hideProgressBar: z.boolean().nullish(),
isLogoHidden: z.boolean().nullish(),
})
.nullable()
.openapi({
description: "Survey styling configuration",
}),
singleUse: z
.object({
enabled: z.boolean(),
isEncrypted: z.boolean(),
})
.openapi({
description: "Single use configuration",
}),
isVerifyEmailEnabled: z.boolean().openapi({
description: "Whether email verification is enabled",
}),
isSingleResponsePerEmailEnabled: z.boolean().openapi({
description: "Whether single response per email is enabled",
}),
inlineTriggers: z.array(z.any()).nullable().openapi({
description: "Inline triggers configuration",
}),
isBackButtonHidden: z.boolean().openapi({
description: "Whether the back button is hidden",
}),
verifyEmail: z
.object({
enabled: z.boolean(),
message: z.string(),
})
.openapi({
description: "Email verification configuration (deprecated)",
}),
displayPercentage: z.number().nullable().openapi({
description: "The display percentage of the survey",
}) as z.ZodType<Survey["displayPercentage"]>,
});
export const ZSurvey = ZSurveyBase satisfies z.ZodType<Survey>;
export const ZSurveyWithoutQuestionType = ZSurveyBase.omit({
questions: true,
}).extend({
questions: z.array(z.any()).openapi({
description: "The questions of the survey.",
}),
});
ZSurvey.openapi({
ref: "survey",
description: "A survey",
});