Skip to content

Commit

Permalink
refactor: Added input validation using zod (formbricks#790)
Browse files Browse the repository at this point in the history
* added input validation using zod

* changed console.log to console.error

* fix formatting issues

---------

Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
  • Loading branch information
Dhruwang and mattinannt committed Sep 13, 2023
1 parent 7517e1a commit 339c295
Show file tree
Hide file tree
Showing 21 changed files with 219 additions and 74 deletions.
13 changes: 10 additions & 3 deletions packages/lib/services/actionClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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: {
Expand All @@ -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: {
Expand All @@ -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: {
Expand All @@ -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: {
Expand Down
7 changes: 6 additions & 1 deletion packages/lib/services/actions.ts
Original file line number Diff line number Diff line change
@@ -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: {
Expand Down
10 changes: 8 additions & 2 deletions packages/lib/services/activity.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -75,4 +81,4 @@ export const getActivityTimeline = async (personId: string): Promise<TActivityFe
const unifiedList: TActivityFeedItem[] = [...unifiedAttributes, ...unifiedDisplays, ...unifiedEvents];

return unifiedList;
};
});
11 changes: 9 additions & 2 deletions packages/lib/services/apiKey.ts
Original file line number Diff line number Diff line change
@@ -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.");
}
Expand All @@ -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: {
Expand All @@ -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);
Expand All @@ -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.");
}
Expand All @@ -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: {
Expand Down
12 changes: 10 additions & 2 deletions packages/lib/services/attributeClass.ts
Original file line number Diff line number Diff line change
@@ -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 => {
Expand All @@ -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: {
Expand All @@ -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: {
Expand Down
103 changes: 58 additions & 45 deletions packages/lib/services/displays.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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");

Expand Down Expand Up @@ -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,
},
select: {
id: true,
createdAt: true,
updatedAt: true,
surveyId: true,
survey: {
select: {
name: true,
},
},
status: 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;
}
};
);
6 changes: 5 additions & 1 deletion packages/lib/services/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions packages/lib/services/person.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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: {
Expand All @@ -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: {
Expand All @@ -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: {
Expand All @@ -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: {
Expand Down
Loading

0 comments on commit 339c295

Please sign in to comment.