feat: use formbricks/api package internally in /js & /lib (#955)

Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
Shubham Palriwala
2023-10-24 19:22:38 +05:30
committed by GitHub
parent 7dcadc660f
commit 0115c58364
15 changed files with 83 additions and 239 deletions

View File

@@ -1,6 +1,6 @@
import { responses } from "@/app/lib/api/response";
import { updateDisplay } from "@formbricks/lib/display/service";
import { TDisplayInput, ZDisplayUpdate } from "@formbricks/types/displays";
import { TDisplayCreateInput, ZDisplayUpdateInput } from "@formbricks/types/displays";
import { NextResponse } from "next/server";
import { transformErrorToDetails } from "@/app/lib/api/validator";
@@ -16,8 +16,8 @@ export async function PUT(
if (!displayId) {
return responses.badRequestResponse("Missing displayId", undefined, true);
}
const displayInput: TDisplayInput = await request.json();
const inputValidation = ZDisplayUpdate.safeParse(displayInput);
const displayInput: TDisplayCreateInput = await request.json();
const inputValidation = ZDisplayUpdateInput.safeParse(displayInput);
if (!inputValidation.success) {
return responses.badRequestResponse(

View File

@@ -5,7 +5,7 @@ import { capturePosthogEvent } from "@formbricks/lib/posthogServer";
import { createDisplay } from "@formbricks/lib/display/service";
import { getSurvey } from "@formbricks/lib/survey/service";
import { getTeamDetails } from "@formbricks/lib/teamDetail/service";
import { TDisplay, ZDisplayInput } from "@formbricks/types/displays";
import { TDisplay, ZDisplayCreateInput } from "@formbricks/types/displays";
import { NextResponse } from "next/server";
export async function OPTIONS(): Promise<NextResponse> {
@@ -14,7 +14,7 @@ export async function OPTIONS(): Promise<NextResponse> {
export async function POST(request: Request): Promise<NextResponse> {
const jsonInput: unknown = await request.json();
const inputValidation = ZDisplayInput.safeParse(jsonInput);
const inputValidation = ZDisplayCreateInput.safeParse(jsonInput);
if (!inputValidation.success) {
return responses.badRequestResponse(

View File

@@ -1,19 +1,19 @@
"use client";
import ContentWrapper from "@formbricks/ui/ContentWrapper";
import { SurveyInline } from "@formbricks/ui/Survey";
import SurveyLinkUsed from "@/app/s/[surveyId]/components/SurveyLinkUsed";
import VerifyEmail from "@/app/s/[surveyId]/components/VerifyEmail";
import { getPrefillResponseData } from "@/app/s/[surveyId]/lib/prefilling";
import { createDisplay } from "@formbricks/lib/client/display";
import { ResponseQueue } from "@formbricks/lib/responseQueue";
import { SurveyState } from "@formbricks/lib/surveyState";
import { TProduct } from "@formbricks/types/product";
import { TResponse, TResponseData, TResponseUpdate } from "@formbricks/types/responses";
import { TSurvey } from "@formbricks/types/surveys";
import ContentWrapper from "@formbricks/ui/ContentWrapper";
import { SurveyInline } from "@formbricks/ui/Survey";
import { ArrowPathIcon } from "@heroicons/react/24/solid";
import { useSearchParams } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
import { FormbricksAPI } from "@formbricks/api";
interface LinkSurveyProps {
survey: TSurvey;
@@ -138,7 +138,18 @@ export default function LinkSurvey({
formbricksSignature={product.formbricksSignature}
onDisplay={async () => {
if (!isPreview) {
const { id } = await createDisplay({ surveyId: survey.id }, webAppUrl);
const api = new FormbricksAPI({
apiHost: webAppUrl,
environmentId: survey.environmentId,
});
const res = await api.client.display.create({
surveyId: survey.id,
});
if (!res.ok) {
throw new Error("Could not create display");
}
const { id } = res.data;
const newSurveyState = surveyState.copy();
newSurveyState.updateDisplayId(id);
setSurveyState(newSurveyState);

View File

@@ -32,7 +32,6 @@
},
"devDependencies": {
"@formbricks/types": "workspace:*",
"@formbricks/lib": "workspace:*",
"@formbricks/tsconfig": "workspace:*",
"eslint-config-formbricks": "workspace:*",
"terser": "^5.22.0",

View File

@@ -1,7 +1,7 @@
import { Result } from "@formbricks/types/errorHandlers";
import { NetworkError } from "@formbricks/types/errors";
import { makeRequest } from "../../utils/makeRequest";
import { TDisplay, TDisplayInput } from "@formbricks/types/displays";
import { TDisplay, TDisplayCreateInput, TDisplayUpdateInput } from "@formbricks/types/displays";
export class DisplayAPI {
private apiHost: string;
@@ -10,14 +10,14 @@ export class DisplayAPI {
this.apiHost = baseUrl;
}
async markDisplayedForPerson({
surveyId,
personId,
}: TDisplayInput): Promise<Result<TDisplay, NetworkError | Error>> {
return makeRequest(this.apiHost, "/api/v1/client/displays", "POST", { surveyId, personId });
async create(displayInput: TDisplayCreateInput): Promise<Result<TDisplay, NetworkError | Error>> {
return makeRequest(this.apiHost, "/api/v1/client/displays", "POST", displayInput);
}
async markResponded({ displayId }: { displayId: string }): Promise<Result<TDisplay, NetworkError | Error>> {
return makeRequest(this.apiHost, `/api/v1/client/displays/${displayId}/responded`, "POST");
async update(
displayId: string,
displayInput: TDisplayUpdateInput
): Promise<Result<TDisplay, NetworkError | Error>> {
return makeRequest(this.apiHost, `/api/v1/client/displays/${displayId}`, "PUT", displayInput);
}
}

View File

@@ -1,54 +0,0 @@
import type { TDisplay, TDisplayInput } from "../../../types/displays";
import type { TJsConfig } from "../../../types/js";
import { NetworkError, Result, err, ok, okVoid } from "./errors";
export const createDisplay = async (
displayCreateRequest: TDisplayInput,
config: TJsConfig
): Promise<Result<TDisplay, NetworkError>> => {
const url = `${config.apiHost}/api/v1/client/displays`;
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(displayCreateRequest),
});
if (!res.ok) {
return err({
code: "network_error",
message: "Could not create display",
status: res.status,
url,
responseMessage: await res.text(),
});
}
const jsonRes = await res.json();
return ok(jsonRes.data as TDisplay);
};
export const markDisplayResponded = async (
displayId: string,
config: TJsConfig
): Promise<Result<void, NetworkError>> => {
const url = `${config.apiHost}/api/v1/client/displays/${displayId}/responded`;
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
});
if (!res.ok) {
return err({
code: "network_error",
message: "Could not mark display as responded",
status: res.status,
url,
responseMessage: await res.text(),
});
}
return okVoid();
};

View File

@@ -1,58 +0,0 @@
import type { TJsConfig } from "../../../types/js";
import type { TResponse, TResponseInput } from "../../../types/responses";
import { NetworkError, Result, err, ok } from "./errors";
export const createResponse = async (
responseInput: TResponseInput,
config: TJsConfig
): Promise<Result<TResponse, NetworkError>> => {
const url = `${config.apiHost}/api/v1/client/responses`;
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(responseInput),
});
const jsonRes = await res.json();
if (!res.ok) {
return err({
code: "network_error",
message: "Could not create response",
status: res.status,
url,
responseMessage: jsonRes.message,
});
}
return ok(jsonRes.data as TResponse);
};
export const updateResponse = async (
responseInput: TResponseInput,
responseId: string,
config: TJsConfig
): Promise<Result<TResponse, NetworkError>> => {
const url = `${config.apiHost}/api/v1/client/responses/${responseId}`;
const res = await fetch(url, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(responseInput),
});
const resJson = await res.json();
if (!res.ok) {
return err({
code: "network_error",
message: "Could not update response",
status: res.status,
url,
responseMessage: resJson.message,
});
}
return ok(resJson.data as TResponse);
};

View File

@@ -1,4 +1,3 @@
import { createDisplay } from "@formbricks/lib/client/display";
import { ResponseQueue } from "@formbricks/lib/responseQueue";
import SurveyState from "@formbricks/lib/surveyState";
import { renderSurveyModal } from "@formbricks/surveys";
@@ -8,6 +7,7 @@ import { Config } from "./config";
import { ErrorHandler } from "./errors";
import { Logger } from "./logger";
import { sync } from "./sync";
import { FormbricksAPI } from "@formbricks/api";
const containerId = "formbricks-web-container";
const config = Config.getInstance();
@@ -59,13 +59,19 @@ export const renderWidget = (survey: TSurveyWithTriggers) => {
highlightBorderColor,
placement,
onDisplay: async () => {
const { id } = await createDisplay(
{
surveyId: survey.id,
personId: config.get().state.person.id,
},
config.get().apiHost
);
const api = new FormbricksAPI({
apiHost: config.get().apiHost,
environmentId: config.get().environmentId,
});
const res = await api.client.display.create({
surveyId: survey.id,
personId: config.get().state.person.id,
});
if (!res.ok) {
throw new Error("Could not create display");
}
const { id } = res.data;
surveyState.updateDisplayId(id);
responseQueue.updateSurveyState(surveyState);
},

View File

@@ -1,41 +0,0 @@
import { TDisplay, TDisplayInput } from "@formbricks/types/displays";
export const createDisplay = async (
displayCreateRequest: TDisplayInput,
apiHost: string
): Promise<TDisplay> => {
const res = await fetch(`${apiHost}/api/v1/client/displays`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(displayCreateRequest),
});
if (!res.ok) {
console.error(res.text);
throw new Error("Could not create display");
}
const resJson = await res.json();
return resJson.data;
};
export const updateDisplay = async (displayId: string, displayInput: any, apiHost: string): Promise<void> => {
const res = await fetch(`${apiHost}/api/v1/client/displays/${displayId}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(displayInput),
});
if (!res.ok) {
throw new Error("Could not update display");
}
return;
};
export const markDisplayResponded = async (displayId: string, apiHost: string): Promise<void> => {
const res = await fetch(`${apiHost}/api/v1/client/displays/${displayId}/responded`, {
method: "POST",
headers: { "Content-Type": "application/json" },
});
if (!res.ok) {
throw new Error("Could not update display");
}
return;
};

View File

@@ -1,32 +0,0 @@
import { TResponse, TResponseInput, TResponseUpdateInput } from "@formbricks/types/responses";
export const createResponse = async (responseInput: TResponseInput, apiHost: string): Promise<TResponse> => {
const res = await fetch(`${apiHost}/api/v1/client/responses`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(responseInput),
});
if (!res.ok) {
console.error(res.text);
throw new Error("Could not create response");
}
const resJson = await res.json();
return resJson.data;
};
export const updateResponse = async (
responseInput: TResponseUpdateInput,
responseId: string,
apiHost: string
): Promise<TResponse> => {
const res = await fetch(`${apiHost}/api/v1/client/responses/${responseId}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(responseInput),
});
if (!res.ok) {
throw new Error("Could not update response");
}
const resJson = await res.json();
return resJson.data;
};

View File

@@ -2,7 +2,14 @@ import "server-only";
import { prisma } from "@formbricks/database";
import { ZOptionalNumber } from "@formbricks/types/common";
import { TDisplay, TDisplayInput, TDisplaysWithSurveyName, ZDisplayInput } from "@formbricks/types/displays";
import {
TDisplay,
TDisplayCreateInput,
TDisplayUpdateInput,
TDisplaysWithSurveyName,
ZDisplayCreateInput,
ZDisplayUpdateInput,
} from "@formbricks/types/displays";
import { ZId } from "@formbricks/types/environment";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
import { Prisma } from "@prisma/client";
@@ -42,9 +49,9 @@ const selectDisplay = {
export const updateDisplay = async (
displayId: string,
displayInput: Partial<TDisplayInput>
displayInput: Partial<TDisplayUpdateInput>
): Promise<TDisplay> => {
validateInputs([displayInput, ZDisplayInput.partial()]);
validateInputs([displayInput, ZDisplayUpdateInput.partial()]);
try {
const displayPrisma = await prisma.display.update({
where: {
@@ -74,9 +81,8 @@ export const updateDisplay = async (
}
};
export const createDisplay = async (displayInput: TDisplayInput): Promise<TDisplay> => {
validateInputs([displayInput, ZDisplayInput]);
export const createDisplay = async (displayInput: TDisplayCreateInput): Promise<TDisplay> => {
validateInputs([displayInput, ZDisplayCreateInput]);
try {
const displayPrisma = await prisma.display.create({
data: {
@@ -271,7 +277,7 @@ export const getDisplayCountBySurveyId = async (surveyId: string): Promise<numbe
validateInputs([surveyId, ZId]);
try {
const displayCount = await prisma.response.count({
const displayCount = await prisma.display.count({
where: {
surveyId: surveyId,
},

View File

@@ -12,6 +12,7 @@
"lint:report": "eslint . --format json --output-file ../../lint-results/app-store.json"
},
"dependencies": {
"@formbricks/api": "*",
"@aws-sdk/client-s3": "^3.429.0",
"@aws-sdk/s3-request-presigner": "^3.429.0",
"mime": "3.0.0",

View File

@@ -1,6 +1,5 @@
import { FormbricksAPI } from "@formbricks/api";
import { TResponseUpdate } from "@formbricks/types/responses";
import { createResponse, updateResponse } from "./client/response";
import { updateDisplay } from "./client/display";
import SurveyState from "./surveyState";
interface QueueConfig {
@@ -16,10 +15,15 @@ export class ResponseQueue {
private config: QueueConfig;
private surveyState: SurveyState;
private isRequestInProgress = false;
private api: FormbricksAPI;
constructor(config: QueueConfig, surveyState: SurveyState) {
this.config = config;
this.surveyState = surveyState;
this.api = new FormbricksAPI({
apiHost: config.apiHost,
environmentId: "",
});
}
add(responseUpdate: TResponseUpdate) {
@@ -69,21 +73,21 @@ export class ResponseQueue {
async sendResponse(responseUpdate: TResponseUpdate): Promise<boolean> {
try {
if (this.surveyState.responseId !== null) {
await updateResponse(responseUpdate, this.surveyState.responseId, this.config.apiHost);
await this.api.client.response.update({ ...responseUpdate, responseId: this.surveyState.responseId });
} else {
const response = await createResponse(
{
...responseUpdate,
surveyId: this.surveyState.surveyId,
personId: this.config.personId || null,
singleUseId: this.surveyState.singleUseId || null,
},
this.config.apiHost
);
if (this.surveyState.displayId) {
await updateDisplay(this.surveyState.displayId, { responseId: response.id }, this.config.apiHost);
const response = await this.api.client.response.create({
...responseUpdate,
surveyId: this.surveyState.surveyId,
personId: this.config.personId || null,
singleUseId: this.surveyState.singleUseId || null,
});
if (!response.ok) {
throw new Error("Could not create response");
}
this.surveyState.updateResponseId(response.id);
if (this.surveyState.displayId) {
await this.api.client.display.update(this.surveyState.displayId, { responseId: response.data.id });
}
this.surveyState.updateResponseId(response.data.id);
if (this.config.setSurveyState) {
this.config.setSurveyState(this.surveyState);
}

View File

@@ -13,17 +13,19 @@ export const ZDisplay = z.object({
export type TDisplay = z.infer<typeof ZDisplay>;
export const ZDisplayInput = z.object({
export const ZDisplayCreateInput = z.object({
surveyId: z.string().cuid2(),
personId: z.string().cuid2().optional(),
responseId: z.string().cuid2().optional(),
});
export const ZDisplayUpdate = z.object({
export type TDisplayCreateInput = z.infer<typeof ZDisplayCreateInput>;
export const ZDisplayUpdateInput = z.object({
responseId: z.string().cuid2().optional(),
});
export type TDisplayInput = z.infer<typeof ZDisplayInput>;
export type TDisplayUpdateInput = z.infer<typeof ZDisplayUpdateInput>;
export const ZDisplaysWithSurveyName = ZDisplay.extend({
surveyName: z.string(),

6
pnpm-lock.yaml generated
View File

@@ -432,9 +432,6 @@ importers:
packages/api:
devDependencies:
'@formbricks/lib':
specifier: workspace:*
version: link:../lib
'@formbricks/tsconfig':
specifier: workspace:*
version: link:../tsconfig
@@ -611,6 +608,9 @@ importers:
'@aws-sdk/s3-request-presigner':
specifier: ^3.429.0
version: 3.429.0
'@formbricks/api':
specifier: '*'
version: link:../api
'@formbricks/database':
specifier: '*'
version: link:../database