fix: openAPI spec for contact endpoints (#5247)

Co-authored-by: Victor Santos <victor@formbricks.com>
This commit is contained in:
Piyush Gupta
2025-04-10 15:52:40 +05:30
committed by GitHub
parent c7d6ed9ea3
commit 1132bdd66a
22 changed files with 181 additions and 1259 deletions

View File

@@ -27,7 +27,7 @@ const secondaryNavigation = [
export function Sidebar(): React.JSX.Element {
return (
<div className="flex grow flex-col overflow-y-auto bg-cyan-700 pb-4 pt-5">
<div className="flex grow flex-col overflow-y-auto bg-cyan-700 pt-5 pb-4">
<nav
className="mt-5 flex flex-1 flex-col divide-y divide-cyan-800 overflow-y-auto"
aria-label="Sidebar">
@@ -38,7 +38,7 @@ export function Sidebar(): React.JSX.Element {
href={item.href}
className={classNames(
item.current ? "bg-cyan-800 text-white" : "text-cyan-100 hover:bg-cyan-600 hover:text-white",
"group flex items-center rounded-md px-2 py-2 text-sm font-medium leading-6"
"group flex items-center rounded-md px-2 py-2 text-sm leading-6 font-medium"
)}
aria-current={item.current ? "page" : undefined}>
<item.icon className="mr-4 h-6 w-6 shrink-0 text-cyan-200" aria-hidden="true" />
@@ -52,7 +52,7 @@ export function Sidebar(): React.JSX.Element {
<a
key={item.name}
href={item.href}
className="group flex items-center rounded-md px-2 py-2 text-sm font-medium leading-6 text-cyan-100 hover:bg-cyan-600 hover:text-white">
className="group flex items-center rounded-md px-2 py-2 text-sm leading-6 font-medium text-cyan-100 hover:bg-cyan-600 hover:text-white">
<item.icon className="mr-4 h-6 w-6 text-cyan-200" aria-hidden="true" />
{item.name}
</a>

View File

@@ -96,10 +96,10 @@ export default function AppPage(): React.JSX.Element {
<p className="text-slate-700 dark:text-slate-300">
Copy the environment ID of your Formbricks app to the env variable in /apps/demo/.env
</p>
<Image src={fbsetup} alt="fb setup" className="rounded-xs mt-4" priority />
<Image src={fbsetup} alt="fb setup" className="mt-4 rounded-xs" priority />
<div className="mt-4 flex-col items-start text-sm text-slate-700 sm:flex sm:items-center sm:text-base dark:text-slate-300">
<p className="mb-1 sm:mb-0 sm:mr-2">You&apos;re connected with env:</p>
<p className="mb-1 sm:mr-2 sm:mb-0">You&apos;re connected with env:</p>
<div className="flex items-center">
<strong className="w-32 truncate sm:w-auto">
{process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID}

View File

@@ -265,7 +265,7 @@ export const MainNavigation = ({
size="icon"
onClick={toggleSidebar}
className={cn(
"rounded-xl bg-slate-50 p-1 text-slate-600 transition-all hover:bg-slate-100 focus:outline-none focus:ring-0 focus:ring-transparent"
"rounded-xl bg-slate-50 p-1 text-slate-600 transition-all hover:bg-slate-100 focus:ring-0 focus:ring-transparent focus:outline-none"
)}>
{isCollapsed ? (
<PanelLeftOpenIcon strokeWidth={1.5} />

View File

@@ -1,6 +1,9 @@
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
import { ZContactAttributeKey } from "@formbricks/database/zod/contact-attribute-keys";
extendZodWithOpenApi(z);
export const ZGetContactAttributeKeysFilter = z
.object({
limit: z.coerce.number().positive().min(1).max(100).optional().default(10),

View File

@@ -1,6 +1,9 @@
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
import { ZContact } from "@formbricks/database/zod/contact";
extendZodWithOpenApi(z);
export const ZGetContactsFilter = z
.object({
limit: z.coerce.number().positive().min(1).max(100).optional().default(10),

View File

@@ -0,0 +1,30 @@
import { ZContactLinkParams } from "@/modules/api/v2/management/surveys/[surveyId]/contact-links/contacts/[contactId]/types/survey";
import { makePartialSchema } from "@/modules/api/v2/types/openapi-response";
import { z } from "zod";
import { ZodOpenApiOperationObject } from "zod-openapi";
export const getPersonalizedSurveyLink: ZodOpenApiOperationObject = {
operationId: "getPersonalizedSurveyLink",
summary: "Get personalized survey link for a contact",
description: "Retrieves a personalized link for a specific survey.",
requestParams: {
path: ZContactLinkParams,
},
tags: ["Management API > Surveys > Contact Links"],
responses: {
"200": {
description: "Personalized survey link retrieved successfully.",
content: {
"application/json": {
schema: makePartialSchema(
z.object({
data: z.object({
surveyUrl: z.string().url(),
}),
})
),
},
},
},
},
};

View File

@@ -5,20 +5,14 @@ import { getEnvironmentId } from "@/modules/api/v2/management/lib/helper";
import { getContact } from "@/modules/api/v2/management/surveys/[surveyId]/contact-links/contacts/[contactId]/lib/contacts";
import { getResponse } from "@/modules/api/v2/management/surveys/[surveyId]/contact-links/contacts/[contactId]/lib/response";
import { getSurvey } from "@/modules/api/v2/management/surveys/[surveyId]/contact-links/contacts/[contactId]/lib/surveys";
import {
TContactLinkParams,
ZContactLinkParams,
} from "@/modules/api/v2/management/surveys/[surveyId]/contact-links/contacts/[contactId]/types/survey";
import { getContactSurveyLink } from "@/modules/ee/contacts/lib/contact-survey-link";
import { hasPermission } from "@/modules/organization/settings/api-keys/lib/utils";
import { z } from "zod";
import { ZId } from "@formbricks/types/common";
const ZContactLinkParams = z.object({
surveyId: ZId,
contactId: ZId,
});
export const GET = async (
request: Request,
props: { params: Promise<{ surveyId: string; contactId: string }> }
) =>
export const GET = async (request: Request, props: { params: Promise<TContactLinkParams> }) =>
authenticatedApiClient({
request,
externalParams: props.params,

View File

@@ -0,0 +1,23 @@
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
extendZodWithOpenApi(z);
export const ZContactLinkParams = z.object({
surveyId: z
.string()
.cuid2()
.openapi({
description: "The ID of the survey",
param: { name: "surveyId", in: "path" },
}),
contactId: z
.string()
.cuid2()
.openapi({
description: "The ID of the contact",
param: { name: "contactId", in: "path" },
}),
});
export type TContactLinkParams = z.infer<typeof ZContactLinkParams>;

View File

@@ -4,7 +4,6 @@ import {
ZContactLinksBySegmentQuery,
} from "@/modules/api/v2/management/surveys/[surveyId]/contact-links/segments/[segmentId]/types/contact";
import { makePartialSchema, responseWithMetaSchema } from "@/modules/api/v2/types/openapi-response";
import { z } from "zod";
import { ZodOpenApiOperationObject } from "zod-openapi";
export const getContactLinksBySegmentEndpoint: ZodOpenApiOperationObject = {
@@ -21,7 +20,7 @@ export const getContactLinksBySegmentEndpoint: ZodOpenApiOperationObject = {
description: "Contact links generated successfully.",
content: {
"application/json": {
schema: z.array(responseWithMetaSchema(makePartialSchema(ZContactLinkResponse))),
schema: responseWithMetaSchema(makePartialSchema(ZContactLinkResponse)),
},
},
},

View File

@@ -1,9 +1,24 @@
import { ZGetFilter } from "@/modules/api/v2/types/api-filter";
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
extendZodWithOpenApi(z);
export const ZContactLinksBySegmentParams = z.object({
surveyId: z.string().cuid2().describe("The ID of the survey"),
segmentId: z.string().cuid2().describe("The ID of the segment"),
surveyId: z
.string()
.cuid2()
.openapi({
description: "The ID of the survey",
param: { name: "surveyId", in: "path" },
}),
segmentId: z
.string()
.cuid2()
.openapi({
description: "The ID of the segment",
param: { name: "segmentId", in: "path" },
}),
});
export const ZContactLinksBySegmentQuery = ZGetFilter.pick({

View File

@@ -1,9 +1,10 @@
// import {
// deleteSurveyEndpoint,
// getSurveyEndpoint,
// updateSurveyEndpoint,
// } from "@/modules/api/v2/management/surveys/[surveyId]/lib/openapi";
import { managementServer } from "@/modules/api/v2/management/lib/openapi";
import {
deleteSurveyEndpoint,
getSurveyEndpoint,
updateSurveyEndpoint,
} from "@/modules/api/v2/management/surveys/[surveyId]/lib/openapi";
import { getPersonalizedSurveyLink } from "@/modules/api/v2/management/surveys/[surveyId]/contact-links/contacts/[contactId]/lib/openapi";
import { ZGetSurveysFilter, ZSurveyInput } from "@/modules/api/v2/management/surveys/types/surveys";
import { z } from "zod";
import { ZodOpenApiOperationObject, ZodOpenApiPathsObject } from "zod-openapi";
@@ -56,15 +57,19 @@ export const createSurveyEndpoint: ZodOpenApiOperationObject = {
};
export const surveyPaths: ZodOpenApiPathsObject = {
"/surveys": {
// "/surveys": {
// servers: managementServer,
// get: getSurveysEndpoint,
// post: createSurveyEndpoint,
// },
// "/surveys/{id}": {
// servers: managementServer,
// get: getSurveyEndpoint,
// put: updateSurveyEndpoint,
// delete: deleteSurveyEndpoint,
// },
"/surveys/{surveyId}/contact-links/contacts/{contactId}/": {
servers: managementServer,
get: getSurveysEndpoint,
post: createSurveyEndpoint,
},
"/surveys/{id}": {
servers: managementServer,
get: getSurveyEndpoint,
put: updateSurveyEndpoint,
delete: deleteSurveyEndpoint,
get: getPersonalizedSurveyLink,
},
};

View File

@@ -1,6 +1,9 @@
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
import { ZSurveyWithoutQuestionType } from "@formbricks/database/zod/surveys";
extendZodWithOpenApi(z);
export const ZGetSurveysFilter = z
.object({
limit: z.coerce.number().positive().min(1).max(100).optional().default(10),

View File

@@ -1,6 +1,6 @@
import { contactAttributeKeyPaths } from "@/modules/api/v2/management/contact-attribute-keys/lib/openapi";
import { contactAttributePaths } from "@/modules/api/v2/management/contact-attributes/lib/openapi";
import { contactPaths } from "@/modules/api/v2/management/contacts/lib/openapi";
// import { contactAttributeKeyPaths } from "@/modules/api/v2/management/contact-attribute-keys/lib/openapi";
// import { contactAttributePaths } from "@/modules/api/v2/management/contact-attributes/lib/openapi";
// import { contactPaths } from "@/modules/api/v2/management/contacts/lib/openapi";
import { responsePaths } from "@/modules/api/v2/management/responses/lib/openapi";
import { surveyContactLinksBySegmentPaths } from "@/modules/api/v2/management/surveys/[surveyId]/contact-links/segments/lib/openapi";
import { surveyPaths } from "@/modules/api/v2/management/surveys/lib/openapi";
@@ -40,9 +40,9 @@ const document = createDocument({
...mePaths,
...responsePaths,
...bulkContactPaths,
...contactPaths,
...contactAttributePaths,
...contactAttributeKeyPaths,
// ...contactPaths,
// ...contactAttributePaths,
// ...contactAttributeKeyPaths,
...surveyPaths,
...surveyContactLinksBySegmentPaths,
...webhookPaths,
@@ -52,7 +52,7 @@ const document = createDocument({
},
servers: [
{
url: `https://app.formbricks.com/api/v2/`,
url: "https://app.formbricks.com/api/v2",
description: "Formbricks Cloud",
},
],

View File

@@ -751,7 +751,7 @@ export const FollowUpModal = ({
</div>
</div>
<div className="absolute bottom-0 right-0 z-20 h-12 w-full bg-white p-2">
<div className="absolute right-0 bottom-0 z-20 h-12 w-full bg-white p-2">
<div className="flex justify-end space-x-2">
<Button
type="button"

File diff suppressed because it is too large Load Diff

View File

@@ -212,6 +212,7 @@
"insights": "Einblicke",
"integration": "Integration",
"integrations": "Integrationen",
"invalid_date": "Ungültiges Datum",
"invalid_file_type": "Ungültiger Dateityp",
"invite": "Einladen",
"invite_them": "Lade sie ein",

View File

@@ -212,6 +212,7 @@
"insights": "Insights",
"integration": "integration",
"integrations": "Integrations",
"invalid_date": "Invalid date",
"invalid_file_type": "Invalid file type",
"invite": "Invite",
"invite_them": "Invite them",

View File

@@ -212,6 +212,7 @@
"insights": "Perspectives",
"integration": "intégration",
"integrations": "Intégrations",
"invalid_date": "Date invalide",
"invalid_file_type": "Type de fichier invalide",
"invite": "Inviter",
"invite_them": "Invitez-les",

View File

@@ -212,6 +212,7 @@
"insights": "Percepções",
"integration": "integração",
"integrations": "Integrações",
"invalid_date": "Data inválida",
"invalid_file_type": "Tipo de arquivo inválido",
"invite": "convidar",
"invite_them": "Convida eles",

View File

@@ -212,6 +212,7 @@
"insights": "Informações",
"integration": "integração",
"integrations": "Integrações",
"invalid_date": "Data inválida",
"invalid_file_type": "Tipo de ficheiro inválido",
"invite": "Convidar",
"invite_them": "Convide-os",

View File

@@ -212,6 +212,7 @@
"insights": "洞察",
"integration": "整合",
"integrations": "整合",
"invalid_date": "無效日期",
"invalid_file_type": "無效的檔案類型",
"invite": "邀請",
"invite_them": "邀請他們",