mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-07 22:31:35 -05:00
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:
@@ -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",
|
||||
});
|
||||
@@ -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",
|
||||
});
|
||||
@@ -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",
|
||||
});
|
||||
@@ -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",
|
||||
});
|
||||
Reference in New Issue
Block a user