refactor: Added input validation using zod (#790)

* added input validation using zod

* changed console.log to console.error

* fix formatting issues

---------

Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
Dhruwang Jariwala
2023-09-13 09:29:37 +05:30
committed by GitHub
parent 50d7c9666c
commit 4f4e95fecb
21 changed files with 219 additions and 74 deletions

View File

@@ -2,8 +2,11 @@
import "server-only";
import { prisma } from "@formbricks/database";
import { TActionClass, TActionClassInput, ZActionClassInput } from "@formbricks/types/v1/actionClasses";
import { validateInputs } from "../utils/validate";
import { ZId } from "@formbricks/types/v1/environment";
import { cache } from "react";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
import { TActionClass, TActionClassInput } from "@formbricks/types/v1/actionClasses";
const select = {
id: true,
@@ -16,7 +19,8 @@ const select = {
environmentId: true,
};
export const getActionClasses = async (environmentId: string): Promise<TActionClass[]> => {
export const getActionClasses = cache(async (environmentId: string): Promise<TActionClass[]> => {
validateInputs([environmentId, ZId]);
try {
let actionClasses = await prisma.eventClass.findMany({
where: {
@@ -32,12 +36,13 @@ export const getActionClasses = async (environmentId: string): Promise<TActionCl
} catch (error) {
throw new DatabaseError(`Database error when fetching actions for environment ${environmentId}`);
}
};
});
export const deleteActionClass = async (
environmentId: string,
actionClassId: string
): Promise<TActionClass> => {
validateInputs([environmentId, ZId], [actionClassId, ZId]);
try {
const result = await prisma.eventClass.delete({
where: {
@@ -59,6 +64,7 @@ export const createActionClass = async (
environmentId: string,
actionClass: TActionClassInput
): Promise<TActionClass> => {
validateInputs([environmentId, ZId], [actionClass, ZActionClassInput]);
try {
const result = await prisma.eventClass.create({
data: {
@@ -83,6 +89,7 @@ export const updateActionClass = async (
actionClassId: string,
inputActionClass: Partial<TActionClassInput>
): Promise<TActionClass> => {
validateInputs([environmentId, ZId], [actionClassId, ZId], [inputActionClass, ZActionClassInput.partial()]);
try {
const result = await prisma.eventClass.update({
where: {

View File

@@ -1,12 +1,17 @@
import "server-only";
import z from "zod";
import { prisma } from "@formbricks/database";
import { DatabaseError } from "@formbricks/types/v1/errors";
import { TAction } from "@formbricks/types/v1/actions";
import { ZId } from "@formbricks/types/v1/environment";
import { Prisma } from "@prisma/client";
import { cache } from "react";
import "server-only";
import { validateInputs } from "../utils/validate";
export const getActionsByEnvironmentId = cache(
async (environmentId: string, limit?: number): Promise<TAction[]> => {
validateInputs([environmentId, ZId], [limit, z.number().optional()]);
try {
const actionsPrisma = await prisma.event.findMany({
where: {

View File

@@ -1,7 +1,13 @@
import "server-only";
import { prisma } from "@formbricks/database";
import { TActivityFeedItem } from "@formbricks/types/v1/activity";
import { validateInputs } from "../utils/validate";
import { ZId } from "@formbricks/types/v1/environment";
import { cache } from "react";
export const getActivityTimeline = async (personId: string): Promise<TActivityFeedItem[]> => {
export const getActivityTimeline = cache(async (personId: string): Promise<TActivityFeedItem[]> => {
validateInputs([personId, ZId]);
const person = await prisma.person.findUnique({
where: {
id: personId,
@@ -75,4 +81,4 @@ export const getActivityTimeline = async (personId: string): Promise<TActivityFe
const unifiedList: TActivityFeedItem[] = [...unifiedAttributes, ...unifiedDisplays, ...unifiedEvents];
return unifiedList;
};
});

View File

@@ -1,14 +1,17 @@
import "server-only";
import z from "zod";
import { prisma } from "@formbricks/database";
import { TApiKey, TApiKeyCreateInput } from "@formbricks/types/v1/apiKeys";
import { TApiKey, TApiKeyCreateInput, ZApiKeyCreateInput } from "@formbricks/types/v1/apiKeys";
import { Prisma } from "@prisma/client";
import { getHash } from "../crypto";
import { createHash, randomBytes } from "crypto";
import { DatabaseError, InvalidInputError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
import { cache } from "react";
import { validateInputs } from "../utils/validate";
import { ZId } from "@formbricks/types/v1/environment";
export const getApiKey = async (apiKey: string): Promise<TApiKey | null> => {
validateInputs([apiKey, z.string()]);
if (!apiKey) {
throw new InvalidInputError("API key cannot be null or undefined.");
}
@@ -35,6 +38,7 @@ export const getApiKey = async (apiKey: string): Promise<TApiKey | null> => {
};
export const getApiKeys = cache(async (environmentId: string): Promise<TApiKey[]> => {
validateInputs([environmentId, ZId]);
try {
const apiKeys = await prisma.apiKey.findMany({
where: {
@@ -54,6 +58,7 @@ export const getApiKeys = cache(async (environmentId: string): Promise<TApiKey[]
export const hashApiKey = (key: string): string => createHash("sha256").update(key).digest("hex");
export async function createApiKey(environmentId: string, apiKeyData: TApiKeyCreateInput): Promise<TApiKey> {
validateInputs([environmentId, ZId], [apiKeyData, ZApiKeyCreateInput]);
try {
const key = randomBytes(16).toString("hex");
const hashedKey = hashApiKey(key);
@@ -76,6 +81,7 @@ export async function createApiKey(environmentId: string, apiKeyData: TApiKeyCre
}
export const getApiKeyFromKey = async (apiKey: string): Promise<TApiKey | null> => {
validateInputs([apiKey, z.string()]);
if (!apiKey) {
throw new InvalidInputError("API key cannot be null or undefined.");
}
@@ -98,6 +104,7 @@ export const getApiKeyFromKey = async (apiKey: string): Promise<TApiKey | null>
};
export const deleteApiKey = async (id: string): Promise<void> => {
validateInputs([id, ZId]);
try {
await prisma.apiKey.delete({
where: {

View File

@@ -1,8 +1,14 @@
"use server";
import "server-only";
import { prisma } from "@formbricks/database";
import {
TAttributeClass,
TAttributeClassUpdateInput,
ZAttributeClassUpdateInput,
} from "@formbricks/types/v1/attributeClasses";
import { ZId } from "@formbricks/types/v1/environment";
import { validateInputs } from "../utils/validate";
import { DatabaseError } from "@formbricks/types/v1/errors";
import { TAttributeClass } from "@formbricks/types/v1/attributeClasses";
import { cache } from "react";
export const transformPrismaAttributeClass = (attributeClass: any): TAttributeClass | null => {
@@ -18,6 +24,7 @@ export const transformPrismaAttributeClass = (attributeClass: any): TAttributeCl
};
export const getAttributeClasses = cache(async (environmentId: string): Promise<TAttributeClass[]> => {
validateInputs([environmentId, ZId]);
try {
let attributeClasses = await prisma.attributeClass.findMany({
where: {
@@ -39,8 +46,9 @@ export const getAttributeClasses = cache(async (environmentId: string): Promise<
export const updatetAttributeClass = async (
attributeClassId: string,
data: { description?: string; archived?: boolean }
data: Partial<TAttributeClassUpdateInput>
): Promise<TAttributeClass | null> => {
validateInputs([attributeClassId, ZId], [data, ZAttributeClassUpdateInput.partial()]);
try {
let attributeClass = await prisma.attributeClass.update({
where: {

View File

@@ -1,8 +1,16 @@
import { prisma } from "@formbricks/database";
import { TDisplay, TDisplayInput, TDisplaysWithSurveyName } from "@formbricks/types/v1/displays";
import {
TDisplay,
TDisplayInput,
TDisplaysWithSurveyName,
ZDisplayInput,
} from "@formbricks/types/v1/displays";
import { Prisma } from "@prisma/client";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
import { transformPrismaPerson } from "./person";
import { validateInputs } from "../utils/validate";
import { ZId } from "@formbricks/types/v1/environment";
import { cache } from "react";
const selectDisplay = {
id: true,
@@ -30,6 +38,7 @@ const selectDisplay = {
};
export const createDisplay = async (displayInput: TDisplayInput): Promise<TDisplay> => {
validateInputs([displayInput, ZDisplayInput]);
try {
const displayPrisma = await prisma.display.create({
data: {
@@ -67,6 +76,7 @@ export const createDisplay = async (displayInput: TDisplayInput): Promise<TDispl
};
export const markDisplayResponded = async (displayId: string): Promise<TDisplay> => {
validateInputs([displayId, ZId]);
try {
if (!displayId) throw new Error("Display ID is required");
@@ -99,51 +109,54 @@ export const markDisplayResponded = async (displayId: string): Promise<TDisplay>
}
};
export const getDisplaysOfPerson = async (personId: string): Promise<TDisplaysWithSurveyName[] | null> => {
try {
const displaysPrisma = await prisma.display.findMany({
where: {
personId: personId,
},
select: {
id: true,
createdAt: true,
updatedAt: true,
surveyId: true,
survey: {
select: {
name: true,
},
export const getDisplaysOfPerson = cache(
async (personId: string): Promise<TDisplaysWithSurveyName[] | null> => {
validateInputs([personId, ZId]);
try {
const displaysPrisma = await prisma.display.findMany({
where: {
personId: personId,
},
status: true,
},
});
select: {
id: true,
createdAt: true,
updatedAt: true,
surveyId: true,
survey: {
select: {
name: true,
},
},
status: true,
},
});
if (!displaysPrisma) {
throw new ResourceNotFoundError("Display from PersonId", personId);
if (!displaysPrisma) {
throw new ResourceNotFoundError("Display from PersonId", personId);
}
let displays: TDisplaysWithSurveyName[] = [];
displaysPrisma.forEach((displayPrisma) => {
const display: TDisplaysWithSurveyName = {
id: displayPrisma.id,
createdAt: displayPrisma.createdAt,
updatedAt: displayPrisma.updatedAt,
person: null,
status: displayPrisma.status,
surveyId: displayPrisma.surveyId,
surveyName: displayPrisma.survey.name,
};
displays.push(display);
});
return displays;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError("Database operation failed");
}
throw error;
}
let displays: TDisplaysWithSurveyName[] = [];
displaysPrisma.forEach((displayPrisma) => {
const display: TDisplaysWithSurveyName = {
id: displayPrisma.id,
createdAt: displayPrisma.createdAt,
updatedAt: displayPrisma.updatedAt,
person: null,
status: displayPrisma.status,
surveyId: displayPrisma.surveyId,
surveyName: displayPrisma.survey.name,
};
displays.push(display);
});
return displays;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError("Database operation failed");
}
throw error;
}
};
);

View File

@@ -2,12 +2,14 @@ import "server-only";
import { prisma } from "@formbricks/database";
import { z } from "zod";
import { Prisma } from "@prisma/client";
import { ZEnvironment, ZEnvironmentUpdateInput, ZId } from "@formbricks/types/v1/environment";
import { DatabaseError, ResourceNotFoundError, ValidationError } from "@formbricks/types/v1/errors";
import { ZEnvironment } from "@formbricks/types/v1/environment";
import type { TEnvironment, TEnvironmentUpdateInput } from "@formbricks/types/v1/environment";
import { cache } from "react";
import { validateInputs } from "../utils/validate";
export const getEnvironment = cache(async (environmentId: string): Promise<TEnvironment> => {
validateInputs([environmentId, ZId]);
let environmentPrisma;
try {
environmentPrisma = await prisma.environment.findUnique({
@@ -39,6 +41,7 @@ export const getEnvironment = cache(async (environmentId: string): Promise<TEnvi
});
export const getEnvironments = cache(async (productId: string): Promise<TEnvironment[]> => {
validateInputs([productId, ZId]);
let productPrisma;
try {
productPrisma = await prisma.product.findFirst({
@@ -80,6 +83,7 @@ export const updateEnvironment = async (
environmentId: string,
data: Partial<TEnvironmentUpdateInput>
): Promise<TEnvironment> => {
validateInputs([environmentId, ZId], [data, ZEnvironmentUpdateInput.partial()]);
const newData = { ...data, updatedAt: new Date() };
let updatedEnvironment;
try {

View File

@@ -5,6 +5,8 @@ import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/error
import { TPerson } from "@formbricks/types/v1/people";
import { Prisma } from "@prisma/client";
import { cache } from "react";
import { validateInputs } from "../utils/validate";
import { ZId } from "@formbricks/types/v1/environment";
import { getAttributeClassByName } from "./attributeClass";
export const selectPerson = {
@@ -58,6 +60,7 @@ export const transformPrismaPerson = (person: TransformPersonInput): TPerson =>
};
export const getPerson = cache(async (personId: string): Promise<TPerson | null> => {
validateInputs([personId, ZId]);
try {
const personPrisma = await prisma.person.findUnique({
where: {
@@ -83,6 +86,7 @@ export const getPerson = cache(async (personId: string): Promise<TPerson | null>
});
export const getPeople = cache(async (environmentId: string): Promise<TPerson[]> => {
validateInputs([environmentId, ZId]);
try {
const personsPrisma = await prisma.person.findMany({
where: {
@@ -109,6 +113,7 @@ export const getPeople = cache(async (environmentId: string): Promise<TPerson[]>
});
export const createPerson = async (environmentId: string): Promise<TPerson> => {
validateInputs([environmentId, ZId]);
try {
const personPrisma = await prisma.person.create({
data: {
@@ -134,6 +139,7 @@ export const createPerson = async (environmentId: string): Promise<TPerson> => {
};
export const deletePerson = async (personId: string): Promise<void> => {
validateInputs([personId, ZId]);
try {
await prisma.person.delete({
where: {

View File

@@ -1,11 +1,13 @@
import "server-only";
import { prisma } from "@formbricks/database";
import { z } from "zod";
import { Prisma } from "@prisma/client";
import { ZProduct, ZProductUpdateInput } from "@formbricks/types/v1/product";
import { DatabaseError, ValidationError } from "@formbricks/types/v1/errors";
import type { TProduct, TProductUpdateInput } from "@formbricks/types/v1/product";
import { ZProduct } from "@formbricks/types/v1/product";
import { Prisma } from "@prisma/client";
import { cache } from "react";
import "server-only";
import { z } from "zod";
import { validateInputs } from "../utils/validate";
import { ZId } from "@formbricks/types/v1/environment";
const selectProduct = {
id: true,
@@ -23,6 +25,7 @@ const selectProduct = {
};
export const getProducts = cache(async (teamId: string): Promise<TProduct[]> => {
validateInputs([teamId, ZId]);
try {
const products = await prisma.product.findMany({
where: {
@@ -71,6 +74,7 @@ export const updateProduct = async (
productId: string,
inputProduct: Partial<TProductUpdateInput>
): Promise<TProduct> => {
validateInputs([productId, ZId], [inputProduct, ZProductUpdateInput]);
let updatedProduct;
try {
updatedProduct = await prisma.product.update({

View File

@@ -1,10 +1,14 @@
import { prisma } from "@formbricks/database";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
import { Prisma } from "@prisma/client";
import { TProfile } from "@formbricks/types/v1/profile";
import { TProfile, ZProfileUpdateInput } from "@formbricks/types/v1/profile";
import { deleteTeam } from "./team";
import { MembershipRole } from "@prisma/client";
import { cache } from "react";
import { validateInputs } from "../utils/validate";
import { ZId } from "@formbricks/types/v1/environment";
import { TMembership, TMembershipRole, ZMembershipRole } from "@formbricks/types/v1/membership";
import { TProfileUpdateInput } from "@formbricks/types/v1/profile";
const responseSelection = {
id: true,
@@ -14,13 +18,9 @@ const responseSelection = {
updatedAt: true,
};
interface Membership {
role: MembershipRole;
userId: string;
}
// function to retrive basic information about a user's profile
export const getProfile = cache(async (userId: string): Promise<TProfile | null> => {
validateInputs([userId, ZId]);
try {
const profile = await prisma.user.findUnique({
where: {
@@ -43,7 +43,8 @@ export const getProfile = cache(async (userId: string): Promise<TProfile | null>
}
});
const updateUserMembership = async (teamId: string, userId: string, role: MembershipRole) => {
const updateUserMembership = async (teamId: string, userId: string, role: TMembershipRole) => {
validateInputs([teamId, ZId], [userId, ZId], [role, ZMembershipRole]);
await prisma.membership.update({
where: {
userId_teamId: {
@@ -57,11 +58,12 @@ const updateUserMembership = async (teamId: string, userId: string, role: Member
});
};
const getAdminMemberships = (memberships: Membership[]) =>
const getAdminMemberships = (memberships: TMembership[]) =>
memberships.filter((membership) => membership.role === MembershipRole.admin);
// function to update a user's profile
export const updateProfile = async (personId: string, data: Prisma.UserUpdateInput): Promise<TProfile> => {
export const updateProfile = async (personId: string, data: TProfileUpdateInput): Promise<TProfile> => {
validateInputs([personId, ZId], [data, ZProfileUpdateInput]);
try {
const updatedProfile = await prisma.user.update({
where: {
@@ -80,6 +82,7 @@ export const updateProfile = async (personId: string, data: Prisma.UserUpdateInp
}
};
const deleteUser = async (userId: string) => {
validateInputs([userId, ZId]);
await prisma.user.delete({
where: {
id: userId,
@@ -89,6 +92,7 @@ const deleteUser = async (userId: string) => {
// function to delete a user's profile including teams
export const deleteProfile = async (personId: string): Promise<void> => {
validateInputs([personId, ZId]);
try {
const currentUserMemberships = await prisma.membership.findMany({
where: {

View File

@@ -1,6 +1,12 @@
import { prisma } from "@formbricks/database";
import {
TResponse,
TResponseInput,
TResponseUpdateInput,
ZResponseInput,
ZResponseUpdateInput,
} from "@formbricks/types/v1/responses";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
import { TResponse, TResponseInput, TResponseUpdateInput } from "@formbricks/types/v1/responses";
import { TPerson } from "@formbricks/types/v1/people";
import { TTag } from "@formbricks/types/v1/tags";
import { Prisma } from "@prisma/client";
@@ -8,6 +14,8 @@ import { cache } from "react";
import "server-only";
import { getPerson, transformPrismaPerson } from "./person";
import { captureTelemetry } from "../telemetry";
import { validateInputs } from "../utils/validate";
import { ZId } from "@formbricks/types/v1/environment";
const responseSelection = {
id: true,
@@ -65,6 +73,7 @@ const responseSelection = {
};
export const getResponsesByPersonId = async (personId: string): Promise<Array<TResponse> | null> => {
validateInputs([personId, ZId]);
try {
const responsePrisma = await prisma.response.findMany({
where: {
@@ -98,6 +107,7 @@ export const getResponsesByPersonId = async (personId: string): Promise<Array<TR
};
export const createResponse = async (responseInput: Partial<TResponseInput>): Promise<TResponse> => {
validateInputs([responseInput, ZResponseInput.partial()]);
captureTelemetry("response created");
try {
let person: TPerson | null = null;
@@ -145,6 +155,7 @@ export const createResponse = async (responseInput: Partial<TResponseInput>): Pr
};
export const getResponse = async (responseId: string): Promise<TResponse | null> => {
validateInputs([responseId, ZId]);
try {
const responsePrisma = await prisma.response.findUnique({
where: {
@@ -174,10 +185,12 @@ export const getResponse = async (responseId: string): Promise<TResponse | null>
};
export const preloadSurveyResponses = (surveyId: string) => {
validateInputs([surveyId, ZId]);
void getSurveyResponses(surveyId);
};
export const getSurveyResponses = cache(async (surveyId: string): Promise<TResponse[]> => {
validateInputs([surveyId, ZId]);
try {
const responsesPrisma = await prisma.response.findMany({
where: {
@@ -212,6 +225,7 @@ export const preloadEnvironmentResponses = (environmentId: string) => {
};
export const getEnvironmentResponses = cache(async (environmentId: string): Promise<TResponse[]> => {
validateInputs([environmentId, ZId]);
try {
const responsesPrisma = await prisma.response.findMany({
where: {
@@ -247,6 +261,7 @@ export const updateResponse = async (
responseId: string,
responseInput: TResponseUpdateInput
): Promise<TResponse> => {
validateInputs([responseId, ZId], [responseInput, ZResponseUpdateInput]);
try {
const currentResponse = await getResponse(responseId);

View File

@@ -6,6 +6,8 @@ import { DatabaseError } from "@formbricks/types/v1/errors";
import { TSession, TSessionWithActions } from "@formbricks/types/v1/sessions";
import { Prisma } from "@prisma/client";
import { cache } from "react";
import { validateInputs } from "../utils/validate";
import { ZId } from "@formbricks/types/v1/environment";
const select = {
id: true,
@@ -18,6 +20,7 @@ const select = {
const oneHour = 1000 * 60 * 60;
export const getSession = async (sessionId: string): Promise<TSession | null> => {
validateInputs([sessionId, ZId]);
try {
const session = await prisma.session.findUnique({
where: {
@@ -39,6 +42,7 @@ export const getSession = async (sessionId: string): Promise<TSession | null> =>
export const getSessionWithActionsOfPerson = async (
personId: string
): Promise<TSessionWithActions[] | null> => {
validateInputs([personId, ZId]);
try {
const sessionsWithActionsForPerson = await prisma.session.findMany({
where: {
@@ -73,6 +77,7 @@ export const getSessionWithActionsOfPerson = async (
};
export const getSessionCount = cache(async (personId: string): Promise<number> => {
validateInputs([personId, ZId]);
try {
const sessionCount = await prisma.session.count({
where: {
@@ -89,6 +94,7 @@ export const getSessionCount = cache(async (personId: string): Promise<number> =
});
export const createSession = async (personId: string): Promise<TSession> => {
validateInputs([personId, ZId]);
try {
const session = await prisma.session.create({
data: {
@@ -113,6 +119,7 @@ export const createSession = async (personId: string): Promise<TSession> => {
};
export const extendSession = async (sessionId: string): Promise<TSession> => {
validateInputs([sessionId, ZId]);
try {
const session = await prisma.session.update({
where: {

View File

@@ -6,6 +6,8 @@ import { cache } from "react";
import "server-only";
import { z } from "zod";
import { captureTelemetry } from "../telemetry";
import { validateInputs } from "../utils/validate";
import { ZId } from "@formbricks/types/v1/environment";
export const selectSurvey = {
id: true,
@@ -68,11 +70,13 @@ export const selectSurveyWithAnalytics = {
};
export const preloadSurveyWithAnalytics = (surveyId: string) => {
validateInputs([surveyId, ZId]);
void getSurveyWithAnalytics(surveyId);
};
export const getSurveyWithAnalytics = cache(
async (surveyId: string): Promise<TSurveyWithAnalytics | null> => {
validateInputs([surveyId, ZId]);
let surveyPrisma;
try {
surveyPrisma = await prisma.survey.findUnique({
@@ -125,6 +129,7 @@ export const getSurveyWithAnalytics = cache(
);
export const getSurvey = cache(async (surveyId: string): Promise<TSurvey | null> => {
validateInputs([surveyId, ZId]);
let surveyPrisma;
try {
surveyPrisma = await prisma.survey.findUnique({
@@ -162,6 +167,7 @@ export const getSurvey = cache(async (surveyId: string): Promise<TSurvey | null>
});
export const getSurveys = cache(async (environmentId: string): Promise<TSurvey[]> => {
validateInputs([environmentId, ZId]);
let surveysPrisma;
try {
surveysPrisma = await prisma.survey.findMany({
@@ -200,6 +206,7 @@ export const getSurveys = cache(async (environmentId: string): Promise<TSurvey[]
export const getSurveysWithAnalytics = cache(
async (environmentId: string): Promise<TSurveyWithAnalytics[]> => {
validateInputs([environmentId, ZId]);
let surveysPrisma;
try {
surveysPrisma = await prisma.survey.findMany({
@@ -246,6 +253,7 @@ export const getSurveysWithAnalytics = cache(
);
export async function deleteSurvey(surveyId: string) {
validateInputs([surveyId, ZId]);
const deletedSurvey = await prisma.survey.delete({
where: {
id: surveyId,
@@ -256,6 +264,7 @@ export async function deleteSurvey(surveyId: string) {
}
export async function createSurvey(environmentId: string, surveyBody: any) {
validateInputs([environmentId, ZId]);
const survey = await prisma.survey.create({
data: {
...surveyBody,

View File

@@ -22,6 +22,8 @@ import {
populateEnvironment,
updateEnvironmentArgs,
} from "../utils/createDemoProductHelpers";
import { validateInputs } from "../utils/validate";
import { ZId } from "@formbricks/types/v1/environment";
export const select = {
id: true,
@@ -56,6 +58,7 @@ export const getTeamsByUserId = cache(async (userId: string): Promise<TTeam[]> =
});
export const getTeamByEnvironmentId = cache(async (environmentId: string): Promise<TTeam | null> => {
validateInputs([environmentId, ZId]);
try {
const team = await prisma.team.findFirst({
where: {
@@ -83,6 +86,7 @@ export const getTeamByEnvironmentId = cache(async (environmentId: string): Promi
});
export const deleteTeam = async (teamId: string) => {
validateInputs([teamId, ZId]);
try {
await prisma.team.delete({
where: {
@@ -99,6 +103,7 @@ export const deleteTeam = async (teamId: string) => {
};
export const createDemoProduct = cache(async (teamId: string) => {
validateInputs([teamId, ZId]);
const productWithEnvironment = Prisma.validator<Prisma.ProductArgs>()({
include: {
environments: true,

View File

@@ -1,8 +1,11 @@
import { prisma } from "@formbricks/database";
import { Prisma } from "@prisma/client";
import { validateInputs } from "../utils/validate";
import { ZId } from "@formbricks/types/v1/environment";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
export const getTeamDetails = async (environmentId: string) => {
validateInputs([environmentId, ZId]);
try {
const environment = await prisma.environment.findUnique({
where: {

View File

@@ -1,12 +1,16 @@
"use server";
import "server-only";
import { TWebhook, TWebhookInput } from "@formbricks/types/v1/webhooks";
import { TWebhook, TWebhookInput, ZWebhookInput } from "@formbricks/types/v1/webhooks";
import { prisma } from "@formbricks/database";
import { Prisma } from "@prisma/client";
import { validateInputs } from "../utils/validate";
import { ZId } from "@formbricks/types/v1/environment";
import { cache } from "react";
import { ResourceNotFoundError, DatabaseError, InvalidInputError } from "@formbricks/types/v1/errors";
export const getWebhooks = async (environmentId: string): Promise<TWebhook[]> => {
export const getWebhooks = cache(async (environmentId: string): Promise<TWebhook[]> => {
validateInputs([environmentId, ZId]);
try {
return await prisma.webhook.findMany({
where: {
@@ -16,9 +20,10 @@ export const getWebhooks = async (environmentId: string): Promise<TWebhook[]> =>
} catch (error) {
throw new DatabaseError(`Database error when fetching webhooks for environment ${environmentId}`);
}
};
});
export const getWebhook = async (id: string): Promise<TWebhook | null> => {
validateInputs([id, ZId]);
const webhook = await prisma.webhook.findUnique({
where: {
id,
@@ -31,6 +36,7 @@ export const createWebhook = async (
environmentId: string,
webhookInput: TWebhookInput
): Promise<TWebhook> => {
validateInputs([environmentId, ZId], [webhookInput, ZWebhookInput]);
try {
if (!webhookInput.url || !webhookInput.triggers) {
throw new InvalidInputError("Missing URL or trigger in webhook input");
@@ -61,6 +67,7 @@ export const updateWebhook = async (
webhookId: string,
webhookInput: Partial<TWebhookInput>
): Promise<TWebhook> => {
validateInputs([environmentId, ZId], [webhookId, ZId], [webhookInput, ZWebhookInput]);
try {
const result = await prisma.webhook.update({
where: {
@@ -82,6 +89,7 @@ export const updateWebhook = async (
};
export const deleteWebhook = async (id: string): Promise<TWebhook> => {
validateInputs([id, ZId]);
try {
return await prisma.webhook.delete({
where: {

View File

@@ -0,0 +1,15 @@
import z from "zod";
import { ValidationError } from "@formbricks/types/v1/errors";
type ValidationPair = [any, z.ZodSchema<any>];
export const validateInputs = (...pairs: ValidationPair[]): void => {
for (const [value, schema] of pairs) {
const inputValidation = schema.safeParse(value);
if (!inputValidation.success) {
console.error(`Validation failed for ${schema}: ${inputValidation.error.message}`);
throw new ValidationError("Validation failed");
}
}
};

View File

@@ -3,4 +3,4 @@ export default {
tailwindcss: {},
autoprefixer: {},
},
}
};

View File

@@ -11,4 +11,10 @@ export const ZAttributeClass = z.object({
archived: z.boolean(),
});
export const ZAttributeClassUpdateInput = z.object({
description: z.string(),
archived: z.boolean(),
});
export type TAttributeClassUpdateInput = z.infer<typeof ZAttributeClassUpdateInput>;
export type TAttributeClass = z.infer<typeof ZAttributeClass>;

View File

@@ -17,4 +17,6 @@ export const ZEnvironmentUpdateInput = z.object({
widgetSetupCompleted: z.boolean(),
});
export const ZId = z.string().cuid2();
export type TEnvironmentUpdateInput = z.infer<typeof ZEnvironmentUpdateInput>;

View File

@@ -0,0 +1,11 @@
import { z } from "zod";
export const ZMembershipRole = z.enum(["owner", "admin", "editor", "developer", "viewer"]);
export const ZMembership = z.object({
role: ZMembershipRole,
userId: z.string(),
});
export type TMembership = z.infer<typeof ZMembership>;
export type TMembershipRole = z.infer<typeof ZMembershipRole>;