diff --git a/packages/lib/services/actionClass.ts b/packages/lib/services/actionClass.ts index 480d3a3161..d4c5c34518 100644 --- a/packages/lib/services/actionClass.ts +++ b/packages/lib/services/actionClass.ts @@ -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 => { +export const getActionClasses = cache(async (environmentId: string): Promise => { + validateInputs([environmentId, ZId]); try { let actionClasses = await prisma.eventClass.findMany({ where: { @@ -32,12 +36,13 @@ export const getActionClasses = async (environmentId: string): Promise => { + 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 => { + 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 ): Promise => { + validateInputs([environmentId, ZId], [actionClassId, ZId], [inputActionClass, ZActionClassInput.partial()]); try { const result = await prisma.eventClass.update({ where: { diff --git a/packages/lib/services/actions.ts b/packages/lib/services/actions.ts index 64b5301431..c254e27cde 100644 --- a/packages/lib/services/actions.ts +++ b/packages/lib/services/actions.ts @@ -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 => { + validateInputs([environmentId, ZId], [limit, z.number().optional()]); try { const actionsPrisma = await prisma.event.findMany({ where: { diff --git a/packages/lib/services/activity.tsx b/packages/lib/services/activity.tsx index 8cb9ba547f..55ba044ffd 100644 --- a/packages/lib/services/activity.tsx +++ b/packages/lib/services/activity.tsx @@ -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 => { +export const getActivityTimeline = cache(async (personId: string): Promise => { + validateInputs([personId, ZId]); const person = await prisma.person.findUnique({ where: { id: personId, @@ -75,4 +81,4 @@ export const getActivityTimeline = async (personId: string): Promise => { + 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 => { }; export const getApiKeys = cache(async (environmentId: string): Promise => { + validateInputs([environmentId, ZId]); try { const apiKeys = await prisma.apiKey.findMany({ where: { @@ -54,6 +58,7 @@ export const getApiKeys = cache(async (environmentId: string): Promise createHash("sha256").update(key).digest("hex"); export async function createApiKey(environmentId: string, apiKeyData: TApiKeyCreateInput): Promise { + 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 => { + 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 }; export const deleteApiKey = async (id: string): Promise => { + validateInputs([id, ZId]); try { await prisma.apiKey.delete({ where: { diff --git a/packages/lib/services/attributeClass.ts b/packages/lib/services/attributeClass.ts index 9ec60b3b65..619a5505f4 100644 --- a/packages/lib/services/attributeClass.ts +++ b/packages/lib/services/attributeClass.ts @@ -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 => { + 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 ): Promise => { + validateInputs([attributeClassId, ZId], [data, ZAttributeClassUpdateInput.partial()]); try { let attributeClass = await prisma.attributeClass.update({ where: { diff --git a/packages/lib/services/displays.ts b/packages/lib/services/displays.ts index 5c7be7cefd..cec20cd679 100644 --- a/packages/lib/services/displays.ts +++ b/packages/lib/services/displays.ts @@ -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 => { + validateInputs([displayInput, ZDisplayInput]); try { const displayPrisma = await prisma.display.create({ data: { @@ -67,6 +76,7 @@ export const createDisplay = async (displayInput: TDisplayInput): Promise => { + validateInputs([displayId, ZId]); try { if (!displayId) throw new Error("Display ID is required"); @@ -99,51 +109,54 @@ export const markDisplayResponded = async (displayId: string): Promise } }; -export const getDisplaysOfPerson = async (personId: string): Promise => { - 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 => { + 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; } -}; +); diff --git a/packages/lib/services/environment.ts b/packages/lib/services/environment.ts index 5fc46353f5..a975f290f0 100644 --- a/packages/lib/services/environment.ts +++ b/packages/lib/services/environment.ts @@ -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 => { + validateInputs([environmentId, ZId]); let environmentPrisma; try { environmentPrisma = await prisma.environment.findUnique({ @@ -39,6 +41,7 @@ export const getEnvironment = cache(async (environmentId: string): Promise => { + validateInputs([productId, ZId]); let productPrisma; try { productPrisma = await prisma.product.findFirst({ @@ -80,6 +83,7 @@ export const updateEnvironment = async ( environmentId: string, data: Partial ): Promise => { + validateInputs([environmentId, ZId], [data, ZEnvironmentUpdateInput.partial()]); const newData = { ...data, updatedAt: new Date() }; let updatedEnvironment; try { diff --git a/packages/lib/services/person.ts b/packages/lib/services/person.ts index 9b6101b2c9..8e4118484d 100644 --- a/packages/lib/services/person.ts +++ b/packages/lib/services/person.ts @@ -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 => { + validateInputs([personId, ZId]); try { const personPrisma = await prisma.person.findUnique({ where: { @@ -83,6 +86,7 @@ export const getPerson = cache(async (personId: string): Promise }); export const getPeople = cache(async (environmentId: string): Promise => { + validateInputs([environmentId, ZId]); try { const personsPrisma = await prisma.person.findMany({ where: { @@ -109,6 +113,7 @@ export const getPeople = cache(async (environmentId: string): Promise }); export const createPerson = async (environmentId: string): Promise => { + validateInputs([environmentId, ZId]); try { const personPrisma = await prisma.person.create({ data: { @@ -134,6 +139,7 @@ export const createPerson = async (environmentId: string): Promise => { }; export const deletePerson = async (personId: string): Promise => { + validateInputs([personId, ZId]); try { await prisma.person.delete({ where: { diff --git a/packages/lib/services/product.ts b/packages/lib/services/product.ts index cdd708f4db..5dd9ccbf58 100644 --- a/packages/lib/services/product.ts +++ b/packages/lib/services/product.ts @@ -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 => { + validateInputs([teamId, ZId]); try { const products = await prisma.product.findMany({ where: { @@ -71,6 +74,7 @@ export const updateProduct = async ( productId: string, inputProduct: Partial ): Promise => { + validateInputs([productId, ZId], [inputProduct, ZProductUpdateInput]); let updatedProduct; try { updatedProduct = await prisma.product.update({ diff --git a/packages/lib/services/profile.ts b/packages/lib/services/profile.ts index abb81c5879..44fda97daf 100644 --- a/packages/lib/services/profile.ts +++ b/packages/lib/services/profile.ts @@ -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 => { + validateInputs([userId, ZId]); try { const profile = await prisma.user.findUnique({ where: { @@ -43,7 +43,8 @@ export const getProfile = cache(async (userId: string): Promise } }); -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 => { +export const updateProfile = async (personId: string, data: TProfileUpdateInput): Promise => { + 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 => { + validateInputs([personId, ZId]); try { const currentUserMemberships = await prisma.membership.findMany({ where: { diff --git a/packages/lib/services/response.ts b/packages/lib/services/response.ts index 2fd7e39d18..e17884ce54 100644 --- a/packages/lib/services/response.ts +++ b/packages/lib/services/response.ts @@ -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 | null> => { + validateInputs([personId, ZId]); try { const responsePrisma = await prisma.response.findMany({ where: { @@ -98,6 +107,7 @@ export const getResponsesByPersonId = async (personId: string): Promise): Promise => { + validateInputs([responseInput, ZResponseInput.partial()]); captureTelemetry("response created"); try { let person: TPerson | null = null; @@ -145,6 +155,7 @@ export const createResponse = async (responseInput: Partial): Pr }; export const getResponse = async (responseId: string): Promise => { + validateInputs([responseId, ZId]); try { const responsePrisma = await prisma.response.findUnique({ where: { @@ -174,10 +185,12 @@ export const getResponse = async (responseId: string): Promise }; export const preloadSurveyResponses = (surveyId: string) => { + validateInputs([surveyId, ZId]); void getSurveyResponses(surveyId); }; export const getSurveyResponses = cache(async (surveyId: string): Promise => { + 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 => { + validateInputs([environmentId, ZId]); try { const responsesPrisma = await prisma.response.findMany({ where: { @@ -247,6 +261,7 @@ export const updateResponse = async ( responseId: string, responseInput: TResponseUpdateInput ): Promise => { + validateInputs([responseId, ZId], [responseInput, ZResponseUpdateInput]); try { const currentResponse = await getResponse(responseId); diff --git a/packages/lib/services/session.ts b/packages/lib/services/session.ts index d5acf2298d..d74150328a 100644 --- a/packages/lib/services/session.ts +++ b/packages/lib/services/session.ts @@ -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 => { + validateInputs([sessionId, ZId]); try { const session = await prisma.session.findUnique({ where: { @@ -39,6 +42,7 @@ export const getSession = async (sessionId: string): Promise => export const getSessionWithActionsOfPerson = async ( personId: string ): Promise => { + 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 => { + validateInputs([personId, ZId]); try { const sessionCount = await prisma.session.count({ where: { @@ -89,6 +94,7 @@ export const getSessionCount = cache(async (personId: string): Promise = }); export const createSession = async (personId: string): Promise => { + validateInputs([personId, ZId]); try { const session = await prisma.session.create({ data: { @@ -113,6 +119,7 @@ export const createSession = async (personId: string): Promise => { }; export const extendSession = async (sessionId: string): Promise => { + validateInputs([sessionId, ZId]); try { const session = await prisma.session.update({ where: { diff --git a/packages/lib/services/survey.ts b/packages/lib/services/survey.ts index d383b15d64..d2c820252e 100644 --- a/packages/lib/services/survey.ts +++ b/packages/lib/services/survey.ts @@ -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 => { + 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 => { + validateInputs([surveyId, ZId]); let surveyPrisma; try { surveyPrisma = await prisma.survey.findUnique({ @@ -162,6 +167,7 @@ export const getSurvey = cache(async (surveyId: string): Promise }); export const getSurveys = cache(async (environmentId: string): Promise => { + validateInputs([environmentId, ZId]); let surveysPrisma; try { surveysPrisma = await prisma.survey.findMany({ @@ -200,6 +206,7 @@ export const getSurveys = cache(async (environmentId: string): Promise => { + 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, diff --git a/packages/lib/services/team.ts b/packages/lib/services/team.ts index 018d05b6ca..0b40430417 100644 --- a/packages/lib/services/team.ts +++ b/packages/lib/services/team.ts @@ -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 = }); export const getTeamByEnvironmentId = cache(async (environmentId: string): Promise => { + 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()({ include: { environments: true, diff --git a/packages/lib/services/teamDetails.ts b/packages/lib/services/teamDetails.ts index 61ba690e65..02dcb90832 100644 --- a/packages/lib/services/teamDetails.ts +++ b/packages/lib/services/teamDetails.ts @@ -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: { diff --git a/packages/lib/services/webhook.ts b/packages/lib/services/webhook.ts index aa7bb4138e..c4b3884df4 100644 --- a/packages/lib/services/webhook.ts +++ b/packages/lib/services/webhook.ts @@ -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 => { +export const getWebhooks = cache(async (environmentId: string): Promise => { + validateInputs([environmentId, ZId]); try { return await prisma.webhook.findMany({ where: { @@ -16,9 +20,10 @@ export const getWebhooks = async (environmentId: string): Promise => } catch (error) { throw new DatabaseError(`Database error when fetching webhooks for environment ${environmentId}`); } -}; +}); export const getWebhook = async (id: string): Promise => { + validateInputs([id, ZId]); const webhook = await prisma.webhook.findUnique({ where: { id, @@ -31,6 +36,7 @@ export const createWebhook = async ( environmentId: string, webhookInput: TWebhookInput ): Promise => { + 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 ): Promise => { + 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 => { + validateInputs([id, ZId]); try { return await prisma.webhook.delete({ where: { diff --git a/packages/lib/utils/validate.ts b/packages/lib/utils/validate.ts new file mode 100644 index 0000000000..5db6547d68 --- /dev/null +++ b/packages/lib/utils/validate.ts @@ -0,0 +1,15 @@ +import z from "zod"; +import { ValidationError } from "@formbricks/types/v1/errors"; + +type ValidationPair = [any, z.ZodSchema]; + +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"); + } + } +}; diff --git a/packages/surveys/postcss.config.js b/packages/surveys/postcss.config.js index 2e7af2b7f1..2aa7205d4b 100644 --- a/packages/surveys/postcss.config.js +++ b/packages/surveys/postcss.config.js @@ -3,4 +3,4 @@ export default { tailwindcss: {}, autoprefixer: {}, }, -} +}; diff --git a/packages/types/v1/attributeClasses.ts b/packages/types/v1/attributeClasses.ts index 7e60b14656..b2c8ebfa60 100644 --- a/packages/types/v1/attributeClasses.ts +++ b/packages/types/v1/attributeClasses.ts @@ -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; + export type TAttributeClass = z.infer; diff --git a/packages/types/v1/environment.ts b/packages/types/v1/environment.ts index 8629d6b500..daac4519ee 100644 --- a/packages/types/v1/environment.ts +++ b/packages/types/v1/environment.ts @@ -17,4 +17,6 @@ export const ZEnvironmentUpdateInput = z.object({ widgetSetupCompleted: z.boolean(), }); +export const ZId = z.string().cuid2(); + export type TEnvironmentUpdateInput = z.infer; diff --git a/packages/types/v1/membership.ts b/packages/types/v1/membership.ts new file mode 100644 index 0000000000..f735046875 --- /dev/null +++ b/packages/types/v1/membership.ts @@ -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; +export type TMembershipRole = z.infer;