mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-22 11:29:22 -05:00
259 lines
6.6 KiB
TypeScript
259 lines
6.6 KiB
TypeScript
import "server-only";
|
|
import { Prisma } from "@prisma/client";
|
|
import { cache as reactCache } from "react";
|
|
import { createCacheKey } from "@formbricks/cache";
|
|
import { prisma } from "@formbricks/database";
|
|
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
|
import { TSurvey } from "@formbricks/types/surveys/types";
|
|
import { cache } from "@/lib/cache";
|
|
import { transformPrismaSurvey } from "@/modules/survey/lib/utils";
|
|
|
|
/**
|
|
* Comprehensive survey data fetcher for link surveys
|
|
* Combines all necessary data in a single optimized query
|
|
*/
|
|
export const getSurveyWithMetadata = reactCache(async (surveyId: string) => {
|
|
try {
|
|
const survey = await prisma.survey.findUnique({
|
|
where: { id: surveyId },
|
|
select: {
|
|
// Core survey fields
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
name: true,
|
|
type: true,
|
|
environmentId: true,
|
|
createdBy: true,
|
|
status: true,
|
|
|
|
// Survey configuration
|
|
welcomeCard: true,
|
|
questions: true,
|
|
endings: true,
|
|
hiddenFields: true,
|
|
variables: true,
|
|
displayOption: true,
|
|
recontactDays: true,
|
|
displayLimit: true,
|
|
autoClose: true,
|
|
delay: true,
|
|
displayPercentage: true,
|
|
autoComplete: true,
|
|
|
|
// Authentication & access
|
|
isVerifyEmailEnabled: true,
|
|
isSingleResponsePerEmailEnabled: true,
|
|
redirectUrl: true,
|
|
pin: true,
|
|
isBackButtonHidden: true,
|
|
|
|
// Single use configuration
|
|
singleUse: true,
|
|
|
|
// Styling & branding
|
|
projectOverwrites: true,
|
|
styling: true,
|
|
surveyClosedMessage: true,
|
|
showLanguageSwitch: true,
|
|
recaptcha: true,
|
|
metadata: true,
|
|
|
|
// Related data
|
|
languages: {
|
|
select: {
|
|
default: true,
|
|
enabled: true,
|
|
language: {
|
|
select: {
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
code: true,
|
|
projectId: true,
|
|
alias: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
triggers: {
|
|
select: {
|
|
actionClass: {
|
|
select: {
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
environmentId: true,
|
|
name: true,
|
|
description: true,
|
|
type: true,
|
|
key: true,
|
|
noCodeConfig: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
segment: {
|
|
select: {
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
environmentId: true,
|
|
title: true,
|
|
description: true,
|
|
isPrivate: true,
|
|
filters: true,
|
|
surveys: {
|
|
select: {
|
|
id: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
followUps: true,
|
|
},
|
|
});
|
|
|
|
if (!survey) {
|
|
throw new ResourceNotFoundError("Survey", surveyId);
|
|
}
|
|
|
|
return transformPrismaSurvey<TSurvey>(survey);
|
|
} catch (error) {
|
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
throw new DatabaseError(error.message);
|
|
}
|
|
throw error;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Lightweight survey metadata for use in generateMetadata()
|
|
* Extracts only needed fields from the cached full survey
|
|
*/
|
|
export const getSurveyMetadata = async (surveyId: string) => {
|
|
const fullSurvey = await getSurveyWithMetadata(surveyId);
|
|
|
|
// Extract only metadata-relevant fields
|
|
return {
|
|
id: fullSurvey.id,
|
|
type: fullSurvey.type,
|
|
status: fullSurvey.status,
|
|
environmentId: fullSurvey.environmentId,
|
|
name: fullSurvey.name,
|
|
styling: fullSurvey.styling,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Combined response lookup for single use surveys
|
|
* NO CACHING - responses change frequently during survey taking
|
|
*/
|
|
export const getResponseBySingleUseId = reactCache((surveyId: string, singleUseId: string) => async () => {
|
|
try {
|
|
const response = await prisma.response.findFirst({
|
|
where: {
|
|
surveyId,
|
|
singleUseId,
|
|
},
|
|
select: {
|
|
id: true,
|
|
finished: true,
|
|
// Include additional fields that might be useful
|
|
createdAt: true,
|
|
data: true,
|
|
},
|
|
});
|
|
|
|
return response;
|
|
} catch (error) {
|
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
throw new DatabaseError(error.message);
|
|
}
|
|
throw error;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Check if email verification response exists
|
|
* NO CACHING - response data changes frequently and needs to be fresh
|
|
*/
|
|
export const isSurveyResponsePresent = reactCache((surveyId: string, email: string) => async () => {
|
|
try {
|
|
const response = await prisma.response.findFirst({
|
|
where: {
|
|
surveyId,
|
|
data: {
|
|
path: ["verifiedEmail"],
|
|
equals: email,
|
|
},
|
|
},
|
|
select: { id: true },
|
|
});
|
|
|
|
return !!response;
|
|
} catch (error) {
|
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
throw new DatabaseError(error.message);
|
|
}
|
|
throw error;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Get existing contact response for contact surveys
|
|
* NO CACHING - response data changes frequently and needs to be fresh
|
|
*/
|
|
export const getExistingContactResponse = reactCache((surveyId: string, contactId: string) => async () => {
|
|
try {
|
|
const response = await prisma.response.findFirst({
|
|
where: {
|
|
surveyId,
|
|
contactId,
|
|
},
|
|
select: {
|
|
id: true,
|
|
finished: true,
|
|
},
|
|
});
|
|
|
|
return response ?? undefined;
|
|
} catch (error) {
|
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
throw new DatabaseError(error.message);
|
|
}
|
|
throw error;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Get organization billing information for survey limits
|
|
* Cached separately with longer TTL
|
|
*/
|
|
export const getOrganizationBilling = reactCache(
|
|
async (organizationId: string) =>
|
|
await cache.withCache(
|
|
async () => {
|
|
try {
|
|
const organization = await prisma.organization.findUnique({
|
|
where: { id: organizationId },
|
|
select: { billing: true },
|
|
});
|
|
|
|
if (!organization) {
|
|
throw new ResourceNotFoundError("Organization", organizationId);
|
|
}
|
|
|
|
return organization.billing;
|
|
} catch (error) {
|
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
throw new DatabaseError(error.message);
|
|
}
|
|
throw error;
|
|
}
|
|
},
|
|
createCacheKey.organization.billing(organizationId),
|
|
60 * 60 * 24 * 1000 // 24 hours in milliseconds - billing info changes rarely
|
|
)
|
|
);
|