Merge branch 'main' of https://github.com/formbricks/formbricks into feat-advanced-logic-editor

This commit is contained in:
Piyush Gupta
2024-09-09 20:39:43 +05:30
4 changed files with 92 additions and 4 deletions

View File

@@ -4,7 +4,7 @@ import { z } from "zod";
import { createActionClass } from "@formbricks/lib/actionClass/service";
import { actionClient, authenticatedActionClient } from "@formbricks/lib/actionClient";
import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
import { UNSPLASH_ACCESS_KEY } from "@formbricks/lib/constants";
import { UNSPLASH_ACCESS_KEY, UNSPLASH_ALLOWED_DOMAINS } from "@formbricks/lib/constants";
import {
getOrganizationIdFromEnvironmentId,
getOrganizationIdFromProductId,
@@ -227,13 +227,26 @@ export const getImagesFromUnsplashAction = actionClient
});
});
const isValidUnsplashUrl = (url: string): boolean => {
try {
const parsedUrl = new URL(url);
return parsedUrl.protocol === "https:" && UNSPLASH_ALLOWED_DOMAINS.includes(parsedUrl.hostname);
} catch {
return false;
}
};
const ZTriggerDownloadUnsplashImageAction = z.object({
downloadUrl: z.string(),
downloadUrl: z.string().url(),
});
export const triggerDownloadUnsplashImageAction = actionClient
.schema(ZTriggerDownloadUnsplashImageAction)
.action(async ({ parsedInput }) => {
if (!isValidUnsplashUrl(parsedInput.downloadUrl)) {
throw new Error("Invalid Unsplash URL");
}
const response = await fetch(`${parsedInput.downloadUrl}/?client_id=${UNSPLASH_ACCESS_KEY}`, {
method: "GET",
headers: { "Content-Type": "application/json" },

View File

@@ -168,6 +168,7 @@ export const RATE_LIMITING_DISABLED = env.RATE_LIMITING_DISABLED === "1";
export const CUSTOMER_IO_SITE_ID = env.CUSTOMER_IO_SITE_ID;
export const CUSTOMER_IO_API_KEY = env.CUSTOMER_IO_API_KEY;
export const UNSPLASH_ACCESS_KEY = env.UNSPLASH_ACCESS_KEY;
export const UNSPLASH_ALLOWED_DOMAINS = ["api.unsplash.com"];
export const STRIPE_API_VERSION = "2024-06-20";

View File

@@ -16,7 +16,7 @@ import {
ZResponseInput,
ZResponseUpdateInput,
} from "@formbricks/types/responses";
import { TSurveySummary } from "@formbricks/types/surveys/types";
import { TSurvey, TSurveyQuestionTypeEnum, TSurveySummary } from "@formbricks/types/surveys/types";
import { TTag } from "@formbricks/types/tags";
import { getAttributes } from "../attribute/service";
import { cache } from "../cache";
@@ -28,7 +28,7 @@ import { createPerson, getPersonByUserId } from "../person/service";
import { sendPlanLimitsReachedEventToPosthogWeekly } from "../posthogServer";
import { responseNoteCache } from "../responseNote/cache";
import { getResponseNotes } from "../responseNote/service";
import { putFile } from "../storage/service";
import { deleteFile, putFile } from "../storage/service";
import { getSurvey } from "../survey/service";
import { captureTelemetry } from "../telemetry";
import { convertToCsv, convertToXlsxBuffer } from "../utils/fileConversion";
@@ -706,6 +706,35 @@ export const updateResponse = async (
}
};
const findAndDeleteUploadedFilesInResponse = async (response: TResponse, survey: TSurvey): Promise<void> => {
const fileUploadQuestions = new Set(
survey.questions
.filter((question) => question.type === TSurveyQuestionTypeEnum.FileUpload)
.map((q) => q.id)
);
const fileUrls = Object.entries(response.data)
.filter(([questionId]) => fileUploadQuestions.has(questionId))
.flatMap(([, questionResponse]) => questionResponse as string[]);
const deletionPromises = fileUrls.map(async (fileUrl) => {
try {
const { pathname } = new URL(fileUrl);
const [, environmentId, accessType, fileName] = pathname.split("/").filter(Boolean);
if (!environmentId || !accessType || !fileName) {
throw new Error(`Invalid file path: ${pathname}`);
}
return deleteFile(environmentId, accessType as "private" | "public", fileName);
} catch (error) {
console.error(`Failed to delete file ${fileUrl}:`, error);
}
});
await Promise.all(deletionPromises);
};
export const deleteResponse = async (responseId: string): Promise<TResponse> => {
validateInputs([responseId, ZId]);
try {
@@ -727,6 +756,16 @@ export const deleteResponse = async (responseId: string): Promise<TResponse> =>
const survey = await getSurvey(response.surveyId);
if (survey) {
await findAndDeleteUploadedFilesInResponse(
{
...responsePrisma,
tags: responsePrisma.tags.map((tag) => tag.tag),
},
survey
);
}
responseCache.revalidate({
environmentId: survey?.environmentId,
id: response.id,

View File

@@ -571,7 +571,42 @@ export const updateSurvey = async (updatedSurvey: TSurvey): Promise<TSurvey> =>
id: segment.id,
environmentId: segment.environmentId,
});
} else if (type === "app") {
if (!currentSurvey.segment) {
await prisma.survey.update({
where: {
id: surveyId,
},
data: {
segment: {
connectOrCreate: {
where: {
environmentId_title: {
environmentId,
title: surveyId,
},
},
create: {
title: surveyId,
isPrivate: true,
filters: [],
environment: {
connect: {
id: environmentId,
},
},
},
},
},
},
});
segmentCache.revalidate({
environmentId,
});
}
}
data.questions = questions.map((question) => {
const { isDraft, ...rest } = question;
return rest;