From 2575b649a030defd0a9dd4cd7d93992f469a6761 Mon Sep 17 00:00:00 2001 From: Piyush Gupta <56182734+gupta-piyush19@users.noreply.github.com> Date: Wed, 15 Jan 2025 13:17:05 +0530 Subject: [PATCH] chore: moved insights model to database package (#4575) --- .gitignore | 3 - .../(analysis)/summary/lib/insights.ts | 9 ++- .../api/(internal)/insights/lib/insights.ts | 34 ++------ .../app/api/(internal)/insights/lib/types.ts | 16 ++++ .../api/(internal)/pipeline/lib/documents.ts | 4 +- .../components/insight-sheet/index.tsx | 4 +- .../ee/insights/components/insights-view.tsx | 19 ++--- .../modules/ee/insights/experience/actions.ts | 3 +- .../experience/components/category-select.tsx | 12 +-- .../experience/components/insight-view.tsx | 12 ++- .../ee/insights/experience/lib/insights.ts | 12 ++- .../ee/insights/experience/types/insights.ts | 21 +++++ packages/database/package.json | 7 +- packages/database/schema.prisma | 36 --------- packages/database/tsconfig.json | 2 +- packages/database/zod-utils.ts | 27 ------- packages/database/zod/insights.ts | 12 +++ packages/types/documents.ts | 4 +- packages/types/insights.ts | 43 ---------- packages/types/surveys/types.ts | 10 ++- pnpm-lock.yaml | 79 ------------------- 21 files changed, 114 insertions(+), 255 deletions(-) create mode 100644 apps/web/app/api/(internal)/insights/lib/types.ts create mode 100644 apps/web/modules/ee/insights/experience/types/insights.ts delete mode 100644 packages/database/zod-utils.ts create mode 100644 packages/database/zod/insights.ts delete mode 100644 packages/types/insights.ts diff --git a/.gitignore b/.gitignore index d2baa57b3a..da9df0ff90 100644 --- a/.gitignore +++ b/.gitignore @@ -35,9 +35,6 @@ yarn-error.log* !packages/database/.env !apps/web/.env -# Prisma generated files -packages/database/zod - # turbo .turbo diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/insights.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/insights.ts index e8d7498a54..eaf819667e 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/insights.ts +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/insights.ts @@ -7,8 +7,11 @@ import { INSIGHTS_PER_PAGE } from "@formbricks/lib/constants"; import { validateInputs } from "@formbricks/lib/utils/validate"; import { ZId } from "@formbricks/types/common"; import { DatabaseError } from "@formbricks/types/errors"; -import { TInsight } from "@formbricks/types/insights"; -import { TSurveyQuestionId, ZSurveyQuestionId } from "@formbricks/types/surveys/types"; +import { + TSurveyQuestionId, + TSurveyQuestionSummaryOpenText, + ZSurveyQuestionId, +} from "@formbricks/types/surveys/types"; export const getInsightsBySurveyIdQuestionId = reactCache( async ( @@ -16,7 +19,7 @@ export const getInsightsBySurveyIdQuestionId = reactCache( questionId: TSurveyQuestionId, limit?: number, offset?: number - ): Promise => + ): Promise => cache( async () => { validateInputs([surveyId, ZId], [questionId, ZSurveyQuestionId]); diff --git a/apps/web/app/api/(internal)/insights/lib/insights.ts b/apps/web/app/api/(internal)/insights/lib/insights.ts index 5e56857dbb..e3503c3b76 100644 --- a/apps/web/app/api/(internal)/insights/lib/insights.ts +++ b/apps/web/app/api/(internal)/insights/lib/insights.ts @@ -2,7 +2,7 @@ import { createDocument } from "@/app/api/(internal)/insights/lib/document"; import { doesResponseHasAnyOpenTextAnswer } from "@/app/api/(internal)/insights/lib/utils"; import { documentCache } from "@/lib/cache/document"; import { insightCache } from "@/lib/cache/insight"; -import { Prisma } from "@prisma/client"; +import { Insight, InsightCategory, Prisma } from "@prisma/client"; import { embed } from "ai"; import { prisma } from "@formbricks/database"; import { embeddingsModel } from "@formbricks/lib/aiModels"; @@ -12,12 +12,6 @@ import { validateInputs } from "@formbricks/lib/utils/validate"; import { ZId } from "@formbricks/types/common"; import { TCreatedDocument } from "@formbricks/types/documents"; import { DatabaseError } from "@formbricks/types/errors"; -import { - TInsight, - TInsightCategory, - TInsightCreateInput, - ZInsightCreateInput, -} from "@formbricks/types/insights"; import { TSurvey, TSurveyQuestionId, @@ -25,6 +19,7 @@ import { ZSurveyQuestions, } from "@formbricks/types/surveys/types"; import { getContactAttributes } from "./contact-attribute"; +import { TInsightCreateInput, TNearestInsights, ZInsightCreateInput } from "./types"; export const generateInsightsForSurveyResponsesConcept = async ( survey: Pick @@ -328,23 +323,16 @@ export const getQuestionResponseReferenceId = (surveyId: string, questionId: TSu return `${surveyId}-${questionId}`; }; -export const createInsight = async (insightGroupInput: TInsightCreateInput): Promise => { +export const createInsight = async (insightGroupInput: TInsightCreateInput): Promise => { validateInputs([insightGroupInput, ZInsightCreateInput]); try { // create document const { vector, ...data } = insightGroupInput; - const prismaInsight = await prisma.insight.create({ + const insight = await prisma.insight.create({ data, }); - const insight = { - ...prismaInsight, - _count: { - documentInsights: 0, - }, - }; - // update document vector with the embedding const vectorString = `[${insightGroupInput.vector.join(",")}]`; await prisma.$executeRaw` @@ -373,7 +361,7 @@ export const handleInsightAssignments = async ( insight: { title: string; description: string; - category: TInsightCategory; + category: InsightCategory; } ) => { try { @@ -427,21 +415,15 @@ export const findNearestInsights = async ( vector: number[], limit: number = 5, threshold: number = 0.5 -): Promise => { +): Promise => { validateInputs([environmentId, ZId]); // Convert the embedding array to a JSON-like string representation const vectorString = `[${vector.join(",")}]`; // Execute raw SQL query to find nearest neighbors and exclude the vector column - const insights: TInsight[] = await prisma.$queryRaw` + const insights: TNearestInsights[] = await prisma.$queryRaw` SELECT - id, - created_at AS "createdAt", - updated_at AS "updatedAt", - title, - description, - category, - "environmentId" + id FROM "Insight" d WHERE d."environmentId" = ${environmentId} AND d."vector" <=> ${vectorString}::vector(512) <= ${threshold} diff --git a/apps/web/app/api/(internal)/insights/lib/types.ts b/apps/web/app/api/(internal)/insights/lib/types.ts new file mode 100644 index 0000000000..bde4dd350f --- /dev/null +++ b/apps/web/app/api/(internal)/insights/lib/types.ts @@ -0,0 +1,16 @@ +import { Insight } from "@prisma/client"; +import { z } from "zod"; +import { ZInsight } from "@formbricks/database/zod/insights"; + +export const ZInsightCreateInput = ZInsight.pick({ + environmentId: true, + title: true, + description: true, + category: true, +}).extend({ + vector: z.array(z.number()).length(512), +}); + +export type TInsightCreateInput = z.infer; + +export type TNearestInsights = Pick; diff --git a/apps/web/app/api/(internal)/pipeline/lib/documents.ts b/apps/web/app/api/(internal)/pipeline/lib/documents.ts index 2a408d0ecd..9a0d1ae449 100644 --- a/apps/web/app/api/(internal)/pipeline/lib/documents.ts +++ b/apps/web/app/api/(internal)/pipeline/lib/documents.ts @@ -4,6 +4,7 @@ import { Prisma } from "@prisma/client"; import { embed, generateObject } from "ai"; import { z } from "zod"; import { prisma } from "@formbricks/database"; +import { ZInsight } from "@formbricks/database/zod/insights"; import { embeddingsModel, llmModel } from "@formbricks/lib/aiModels"; import { validateInputs } from "@formbricks/lib/utils/validate"; import { @@ -13,7 +14,6 @@ import { ZDocumentSentiment, } from "@formbricks/types/documents"; import { DatabaseError } from "@formbricks/types/errors"; -import { ZInsightCategory } from "@formbricks/types/insights"; export const createDocumentAndAssignInsight = async ( surveyName: string, @@ -38,7 +38,7 @@ export const createDocumentAndAssignInsight = async ( z.object({ title: z.string().describe("insight title, very specific"), description: z.string().describe("very brief insight description"), - category: ZInsightCategory, + category: ZInsight.shape.category, }) ), isSpam: z.boolean(), diff --git a/apps/web/modules/ee/insights/components/insight-sheet/index.tsx b/apps/web/modules/ee/insights/components/insight-sheet/index.tsx index c75b5686c0..80097b6068 100644 --- a/apps/web/modules/ee/insights/components/insight-sheet/index.tsx +++ b/apps/web/modules/ee/insights/components/insight-sheet/index.tsx @@ -1,6 +1,7 @@ "use client"; import { getFormattedErrorMessage } from "@/lib/utils/helper"; +import { TInsightWithDocumentCount } from "@/modules/ee/insights/experience/types/insights"; import { Button } from "@/modules/ui/components/button"; import { Card, CardContent, CardFooter } from "@/modules/ui/components/card"; import { @@ -16,7 +17,6 @@ import { useDeferredValue, useEffect, useState } from "react"; import Markdown from "react-markdown"; import { timeSince } from "@formbricks/lib/time"; import { TDocument, TDocumentFilterCriteria } from "@formbricks/types/documents"; -import { TInsight } from "@formbricks/types/insights"; import { TUserLocale } from "@formbricks/types/user"; import CategoryBadge from "../../experience/components/category-select"; import SentimentSelect from "../sentiment-select"; @@ -25,7 +25,7 @@ import { getDocumentsByInsightIdAction, getDocumentsByInsightIdSurveyIdQuestionI interface InsightSheetProps { isOpen: boolean; setIsOpen: (isOpen: boolean) => void; - insight: TInsight | null; + insight: TInsightWithDocumentCount | null; surveyId?: string; questionId?: string; handleFeedback: (feedback: "positive" | "negative") => void; diff --git a/apps/web/modules/ee/insights/components/insights-view.tsx b/apps/web/modules/ee/insights/components/insights-view.tsx index 7dec460383..5c0b4f0c26 100644 --- a/apps/web/modules/ee/insights/components/insights-view.tsx +++ b/apps/web/modules/ee/insights/components/insights-view.tsx @@ -4,18 +4,19 @@ import { InsightSheet } from "@/modules/ee/insights/components/insight-sheet"; import { Button } from "@/modules/ui/components/button"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/modules/ui/components/table"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/modules/ui/components/tabs"; +import { Insight, InsightCategory } from "@prisma/client"; import { UserIcon } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useState } from "react"; import formbricks from "@formbricks/js"; import { cn } from "@formbricks/lib/cn"; import { TDocumentFilterCriteria } from "@formbricks/types/documents"; -import { TInsight, TInsightCategory } from "@formbricks/types/insights"; +import { TSurveyQuestionSummaryOpenText } from "@formbricks/types/surveys/types"; import { TUserLocale } from "@formbricks/types/user"; import CategoryBadge from "../experience/components/category-select"; interface InsightViewProps { - insights: TInsight[]; + insights: TSurveyQuestionSummaryOpenText["insights"]; questionId?: string; surveyId?: string; documentsFilter?: TDocumentFilterCriteria; @@ -35,8 +36,10 @@ export const InsightView = ({ }: InsightViewProps) => { const t = useTranslations(); const [isInsightSheetOpen, setIsInsightSheetOpen] = useState(true); - const [localInsights, setLocalInsights] = useState(insights); - const [currentInsight, setCurrentInsight] = useState(null); + const [localInsights, setLocalInsights] = useState(insights); + const [currentInsight, setCurrentInsight] = useState< + TSurveyQuestionSummaryOpenText["insights"][number] | null + >(null); const [activeTab, setActiveTab] = useState("all"); const [visibleInsights, setVisibleInsights] = useState(10); @@ -61,9 +64,7 @@ export const InsightView = ({ if (filterValue === "all") { setLocalInsights(insights); } else { - setLocalInsights( - insights.filter((insight) => insight.category === (filterValue as TInsightCategory)) - ); + setLocalInsights(insights.filter((insight) => insight.category === (filterValue as InsightCategory))); } }, [insights] @@ -88,13 +89,13 @@ export const InsightView = ({ setVisibleInsights((prevVisibleInsights) => Math.min(prevVisibleInsights + 10, insights.length)); }; - const updateLocalInsight = (insightId: string, updates: Partial) => { + const updateLocalInsight = (insightId: string, updates: Partial) => { setLocalInsights((prevInsights) => prevInsights.map((insight) => (insight.id === insightId ? { ...insight, ...updates } : insight)) ); }; - const onCategoryChange = async (insightId: string, newCategory: TInsight["category"]) => { + const onCategoryChange = async (insightId: string, newCategory: InsightCategory) => { updateLocalInsight(insightId, { category: newCategory }); }; diff --git a/apps/web/modules/ee/insights/experience/actions.ts b/apps/web/modules/ee/insights/experience/actions.ts index f14304ef73..0e705b170a 100644 --- a/apps/web/modules/ee/insights/experience/actions.ts +++ b/apps/web/modules/ee/insights/experience/actions.ts @@ -9,9 +9,10 @@ import { getProjectIdFromInsightId, } from "@/lib/utils/helper"; import { checkAIPermission } from "@/modules/ee/insights/actions"; +import { ZInsightFilterCriteria } from "@/modules/ee/insights/experience/types/insights"; import { z } from "zod"; +import { ZInsight } from "@formbricks/database/zod/insights"; import { ZId } from "@formbricks/types/common"; -import { ZInsight, ZInsightFilterCriteria } from "@formbricks/types/insights"; import { getInsights, updateInsight } from "./lib/insights"; import { getStats } from "./lib/stats"; diff --git a/apps/web/modules/ee/insights/experience/components/category-select.tsx b/apps/web/modules/ee/insights/experience/components/category-select.tsx index 350852bb81..aa293e8edf 100644 --- a/apps/web/modules/ee/insights/experience/components/category-select.tsx +++ b/apps/web/modules/ee/insights/experience/components/category-select.tsx @@ -1,14 +1,14 @@ import { BadgeSelect, TBadgeSelectOption } from "@/modules/ui/components/badge-select"; +import { InsightCategory } from "@prisma/client"; import { useTranslations } from "next-intl"; import { useState } from "react"; import { toast } from "react-hot-toast"; -import { TInsight } from "@formbricks/types/insights"; import { updateInsightAction } from "../actions"; interface CategoryBadgeProps { - category: TInsight["category"]; + category: InsightCategory; insightId: string; - onCategoryChange?: (insightId: string, category: TInsight["category"]) => void; + onCategoryChange?: (insightId: string, category: InsightCategory) => void; } const categoryOptions: TBadgeSelectOption[] = [ @@ -18,14 +18,14 @@ const categoryOptions: TBadgeSelectOption[] = [ { text: "Other", type: "gray" }, ]; -const categoryMapping: Record = { +const categoryMapping: Record = { Complaint: "complaint", Request: "featureRequest", Praise: "praise", Other: "other", }; -const getCategoryIndex = (category: TInsight["category"]) => { +const getCategoryIndex = (category: InsightCategory) => { switch (category) { case "complaint": return 0; @@ -41,7 +41,7 @@ const getCategoryIndex = (category: TInsight["category"]) => { const CategoryBadge = ({ category, insightId, onCategoryChange }: CategoryBadgeProps) => { const [isUpdating, setIsUpdating] = useState(false); const t = useTranslations(); - const handleUpdateCategory = async (newCategory: TInsight["category"]) => { + const handleUpdateCategory = async (newCategory: InsightCategory) => { setIsUpdating(true); try { await updateInsightAction({ insightId, data: { category: newCategory } }); diff --git a/apps/web/modules/ee/insights/experience/components/insight-view.tsx b/apps/web/modules/ee/insights/experience/components/insight-view.tsx index ecdfe22874..f2b6b90175 100644 --- a/apps/web/modules/ee/insights/experience/components/insight-view.tsx +++ b/apps/web/modules/ee/insights/experience/components/insight-view.tsx @@ -1,15 +1,19 @@ "use client"; import { InsightSheet } from "@/modules/ee/insights/components/insight-sheet"; +import { + TInsightFilterCriteria, + TInsightWithDocumentCount, +} from "@/modules/ee/insights/experience/types/insights"; import { Button } from "@/modules/ui/components/button"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/modules/ui/components/table"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/modules/ui/components/tabs"; +import { InsightCategory } from "@prisma/client"; import { UserIcon } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useMemo, useState } from "react"; import formbricks from "@formbricks/js"; import { TDocumentFilterCriteria } from "@formbricks/types/documents"; -import { TInsight, TInsightFilterCriteria } from "@formbricks/types/insights"; import { TUserLocale } from "@formbricks/types/user"; import { getEnvironmentInsightsAction } from "../actions"; import CategoryBadge from "./category-select"; @@ -31,11 +35,11 @@ export const InsightView = ({ locale, }: InsightViewProps) => { const t = useTranslations(); - const [insights, setInsights] = useState([]); + const [insights, setInsights] = useState([]); const [hasMore, setHasMore] = useState(true); const [isFetching, setIsFetching] = useState(false); const [isInsightSheetOpen, setIsInsightSheetOpen] = useState(false); - const [currentInsight, setCurrentInsight] = useState(null); + const [currentInsight, setCurrentInsight] = useState(null); const [activeTab, setActiveTab] = useState("featureRequest"); const handleFeedback = (feedback: "positive" | "negative") => { @@ -56,7 +60,7 @@ export const InsightView = ({ documentCreatedAt: { min: statsFrom, }, - category: activeTab === "all" ? undefined : (activeTab as TInsight["category"]), + category: activeTab === "all" ? undefined : (activeTab as InsightCategory), }), [statsFrom, activeTab] ); diff --git a/apps/web/modules/ee/insights/experience/lib/insights.ts b/apps/web/modules/ee/insights/experience/lib/insights.ts index 2b48a9c3af..4fdcf8962b 100644 --- a/apps/web/modules/ee/insights/experience/lib/insights.ts +++ b/apps/web/modules/ee/insights/experience/lib/insights.ts @@ -1,5 +1,10 @@ import { insightCache } from "@/lib/cache/insight"; -import { Prisma } from "@prisma/client"; +import { + TInsightFilterCriteria, + TInsightWithDocumentCount, + ZInsightFilterCriteria, +} from "@/modules/ee/insights/experience/types/insights"; +import { Insight, Prisma } from "@prisma/client"; import { cache as reactCache } from "react"; import { prisma } from "@formbricks/database"; import { cache } from "@formbricks/lib/cache"; @@ -8,7 +13,6 @@ import { responseCache } from "@formbricks/lib/response/cache"; import { validateInputs } from "@formbricks/lib/utils/validate"; import { ZId, ZOptionalNumber } from "@formbricks/types/common"; import { DatabaseError } from "@formbricks/types/errors"; -import { TInsight, TInsightFilterCriteria, ZInsightFilterCriteria } from "@formbricks/types/insights"; export const getInsights = reactCache( async ( @@ -16,7 +20,7 @@ export const getInsights = reactCache( limit?: number, offset?: number, filterCriteria?: TInsightFilterCriteria - ): Promise => + ): Promise => cache( async () => { validateInputs( @@ -84,7 +88,7 @@ export const getInsights = reactCache( )() ); -export const updateInsight = async (insightId: string, updates: Partial): Promise => { +export const updateInsight = async (insightId: string, updates: Partial): Promise => { try { const updatedInsight = await prisma.insight.update({ where: { id: insightId }, diff --git a/apps/web/modules/ee/insights/experience/types/insights.ts b/apps/web/modules/ee/insights/experience/types/insights.ts new file mode 100644 index 0000000000..5cd8207ded --- /dev/null +++ b/apps/web/modules/ee/insights/experience/types/insights.ts @@ -0,0 +1,21 @@ +import { Insight } from "@prisma/client"; +import { z } from "zod"; +import { ZInsight } from "@formbricks/database/zod/insights"; + +export const ZInsightFilterCriteria = z.object({ + documentCreatedAt: z + .object({ + min: z.date().optional(), + max: z.date().optional(), + }) + .optional(), + category: ZInsight.shape.category.optional(), +}); + +export type TInsightFilterCriteria = z.infer; + +export interface TInsightWithDocumentCount extends Insight { + _count: { + documentInsights: number; + }; +} diff --git a/packages/database/package.json b/packages/database/package.json index 5d6756518b..0b3f99cde8 100644 --- a/packages/database/package.json +++ b/packages/database/package.json @@ -4,9 +4,7 @@ "version": "0.1.0", "main": "./src/index.ts", "files": [ - "src", - "zod", - "zod-utils.ts" + "src" ], "scripts": { "clean": "rimraf .turbo node_modules", @@ -37,7 +35,6 @@ "prisma-dbml-generator": "0.12.0", "prisma-json-types-generator": "3.1.1", "ts-node": "10.9.2", - "zod": "3.24.1", - "zod-prisma": "0.5.4" + "zod": "3.24.1" } } diff --git a/packages/database/schema.prisma b/packages/database/schema.prisma index d7deeabb12..db35503728 100644 --- a/packages/database/schema.prisma +++ b/packages/database/schema.prisma @@ -16,13 +16,6 @@ generator client { // provider = "prisma-dbml-generator" // } -generator zod { - provider = "zod-prisma" - output = "./zod" - imports = "./zod-utils" - relationModel = "default" -} - generator json { provider = "prisma-json-types-generator" } @@ -117,20 +110,15 @@ model Response { contactId String? endingId String? notes ResponseNote[] - /// @zod.custom(imports.ZResponseData) /// [ResponseData] data Json @default("{}") - /// @zod.custom(imports.ZResponseVariables) /// [ResponseVariables] variables Json @default("{}") - /// @zod.custom(imports.ZResponseTtc) /// [ResponseTtc] ttc Json @default("{}") - /// @zod.custom(imports.ZResponseMeta) /// [ResponseMeta] meta Json @default("{}") tags TagsOnResponses[] - /// @zod.custom(imports.ZResponseContactAttributes) /// [ResponseContactAttributes] contactAttributes Json? // singleUseId, used to prevent multiple responses @@ -273,20 +261,15 @@ model Survey { creator User? @relation(fields: [createdBy], references: [id]) createdBy String? status SurveyStatus @default(draft) - /// @zod.custom(imports.ZSurveyWelcomeCard) /// [SurveyWelcomeCard] welcomeCard Json @default("{\"enabled\": false}") - /// @zod.custom(imports.ZSurveyQuestions) /// [SurveyQuestions] questions Json @default("[]") - /// @zod.custom(imports.ZSurveyEnding) /// [SurveyEnding] endings Json[] @default([]) thankYouCard Json? //deprecated - /// @zod.custom(imports.ZSurveyHiddenFields) /// [SurveyHiddenFields] hiddenFields Json @default("{\"enabled\": false}") - /// @zod.custom(imports.ZSurveyVariables) /// [SurveyVariables] variables Json @default("[]") responses Response[] @@ -294,7 +277,6 @@ model Survey { recontactDays Int? displayLimit Int? triggers SurveyTrigger[] - /// @zod.custom(imports.ZSurveyInlineTriggers) /// [SurveyInlineTriggers] inlineTriggers Json? attributeFilters SurveyAttributeFilter[] @@ -304,25 +286,20 @@ model Survey { delay Int @default(0) runOnDate DateTime? closeOnDate DateTime? - /// @zod.custom(imports.ZSurveyClosedMessage) /// [SurveyClosedMessage] surveyClosedMessage Json? segmentId String? segment Segment? @relation(fields: [segmentId], references: [id]) - /// @zod.custom(imports.ZSurveyProjectOverwrites) /// [SurveyProjectOverwrites] projectOverwrites Json? - /// @zod.custom(imports.ZSurveyStyling) /// [SurveyStyling] styling Json? - /// @zod.custom(imports.ZSurveySingleUse) /// [SurveySingleUse] singleUse Json? @default("{\"enabled\": false, \"isEncrypted\": true}") - /// @zod.custom(imports.ZSurveyVerifyEmail) /// [SurveyVerifyEmail] verifyEmail Json? // deprecated isVerifyEmailEnabled Boolean @default(false) @@ -347,10 +324,8 @@ model SurveyFollowUp { surveyId String name String /// [SurveyFollowUpTrigger] - /// @zod.custom(imports.ZSurveyFollowUpTrigger) trigger Json /// [SurveyFollowUpAction] - /// @zod.custom(imports.ZSurveyFollowUpAction) action Json } @@ -368,7 +343,6 @@ model ActionClass { description String? type ActionType key String? - /// @zod.custom(imports.ZActionClassNoCodeConfig) /// [ActionClassNoCodeConfig] noCodeConfig Json? environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade) @@ -396,7 +370,6 @@ model Integration { id String @id @default(cuid()) type IntegrationType environmentId String - /// @zod.custom(imports.ZIntegrationConfig) /// [IntegrationConfig] config Json environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade) @@ -461,10 +434,8 @@ model Project { environments Environment[] brandColor String? // deprecated; use styling.brandColor instead highlightBorderColor String? // deprecated - /// @zod.custom(imports.ZProjectStyling) /// [Styling] styling Json @default("{\"allowStyleOverwrite\":true}") - /// @zod.custom(imports.ZProjectConfig) /// [ProjectConfig] config Json @default("{}") recontactDays Int @default(7) @@ -474,7 +445,6 @@ model Project { clickOutsideClose Boolean @default(true) darkOverlay Boolean @default(false) languages Language[] - /// @zod.custom(imports.ZLogo) /// [Logo] logo Json? projectTeams ProjectTeam[] @@ -490,10 +460,8 @@ model Organization { name String memberships Membership[] projects Project[] - /// @zod.custom(imports.ZOrganizationBilling) /// [OrganizationBilling] billing Json - /// @zod.custom(imports.ZOrganizationWhitelabel) /// [OrganizationWhitelabel] whitelabel Json @default("{}") invites Invite[] @@ -640,11 +608,8 @@ model User { invitesAccepted Invite[] @relation("inviteAcceptedBy") role Role? objective Objective? - /// @zod.custom(imports.ZUserNotificationSettings) - /// @zod.custom(imports.ZUserNotificationSettings) /// [UserNotificationSettings] notificationSettings Json @default("{}") - /// @zod.custom(imports.ZUserLocale) /// [Locale] locale String @default("en-US") surveys Survey[] @@ -667,7 +632,6 @@ model Segment { title String description String? isPrivate Boolean @default(true) - /// @zod.custom(imports.ZSegmentFilters) /// [SegmentFilter] filters Json @default("[]") environmentId String diff --git a/packages/database/tsconfig.json b/packages/database/tsconfig.json index ceebccd707..2853bd4d55 100644 --- a/packages/database/tsconfig.json +++ b/packages/database/tsconfig.json @@ -1,5 +1,5 @@ { - "exclude": ["node_modules", "dist", "zod"], + "exclude": ["node_modules", "dist"], "extends": "@formbricks/config-typescript/node16.json", "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "tsup.config.ts"] } diff --git a/packages/database/zod-utils.ts b/packages/database/zod-utils.ts deleted file mode 100644 index 539ab6ce28..0000000000 --- a/packages/database/zod-utils.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* eslint-disable import/no-relative-packages -- required for importing types */ -import { z } from "zod"; - -export const ZActionProperties = z.record(z.string()); -export { ZActionClassNoCodeConfig } from "../types/action-classes"; -export { ZIntegrationConfig } from "../types/integration"; - -export { ZResponseData, ZResponseMeta, ZResponseTtc } from "../types/responses"; - -export { - ZSurveyWelcomeCard, - ZSurveyQuestions, - ZSurveyHiddenFields, - ZSurveyVariables, - ZSurveyClosedMessage, - ZSurveyProjectOverwrites, - ZSurveyStyling, - ZSurveySingleUse, - ZSurveyInlineTriggers, - ZSurveyEnding, -} from "../types/surveys/types"; - -export { ZSurveyFollowUpAction, ZSurveyFollowUpTrigger } from "./types/survey-follow-up"; - -export { ZSegmentFilters } from "../types/segment"; -export { ZOrganizationBilling, ZOrganizationWhitelabel } from "../types/organizations"; -export { ZUserNotificationSettings } from "../types/user"; diff --git a/packages/database/zod/insights.ts b/packages/database/zod/insights.ts new file mode 100644 index 0000000000..9f3fcb14ff --- /dev/null +++ b/packages/database/zod/insights.ts @@ -0,0 +1,12 @@ +import { type Insight } from "@prisma/client"; +import { z } from "zod"; + +export const ZInsight = z.object({ + id: z.string().cuid2(), + createdAt: z.date(), + updatedAt: z.date(), + environmentId: z.string().cuid2(), + title: z.string(), + description: z.string(), + category: z.enum(["featureRequest", "complaint", "praise", "other"]), +}) satisfies z.ZodType; diff --git a/packages/types/documents.ts b/packages/types/documents.ts index 1b3082c29e..ea68a83153 100644 --- a/packages/types/documents.ts +++ b/packages/types/documents.ts @@ -1,6 +1,6 @@ import { z } from "zod"; +import { ZInsight } from "@formbricks/database/zod/insights"; import { ZId } from "./common"; -import { ZInsightCategory } from "./insights"; import { ZSurveyQuestionId } from "./surveys/types"; export const ZDocumentSentiment = z.enum(["positive", "negative", "neutral"]); @@ -47,7 +47,7 @@ export const ZGenerateDocumentObjectSchema = z.object({ z.object({ title: z.string().describe("insight title, very specific"), description: z.string().describe("very brief insight description"), - category: ZInsightCategory, + category: ZInsight.shape.category, }) ), isSpam: z.boolean(), diff --git a/packages/types/insights.ts b/packages/types/insights.ts deleted file mode 100644 index 69b250f70a..0000000000 --- a/packages/types/insights.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { z } from "zod"; -import { ZId } from "./common"; - -export const ZInsightCategory = z.enum(["featureRequest", "complaint", "praise", "other"]); - -export type TInsightCategory = z.infer; - -export const ZInsight = z.object({ - id: ZId, - createdAt: z.date(), - updatedAt: z.date(), - environmentId: ZId, - title: z.string(), - description: z.string(), - category: ZInsightCategory, - _count: z.object({ - documentInsights: z.number(), - }), -}); - -export type TInsight = z.infer; - -export const ZInsightCreateInput = z.object({ - environmentId: ZId, - title: z.string(), - description: z.string(), - category: ZInsightCategory, - vector: z.array(z.number()).length(512), -}); - -export type TInsightCreateInput = z.infer; - -export const ZInsightFilterCriteria = z.object({ - documentCreatedAt: z - .object({ - min: z.date().optional(), - max: z.date().optional(), - }) - .optional(), - category: ZInsightCategory.optional(), -}); - -export type TInsightFilterCriteria = z.infer; diff --git a/packages/types/surveys/types.ts b/packages/types/surveys/types.ts index 6f24609300..6dc657a086 100644 --- a/packages/types/surveys/types.ts +++ b/packages/types/surveys/types.ts @@ -1,10 +1,10 @@ /* eslint-disable no-new -- required for error */ import { type ZodIssue, z } from "zod"; import { ZSurveyFollowUp } from "@formbricks/database/types/survey-follow-up"; +import { ZInsight } from "@formbricks/database/zod/insights"; import { ZActionClass, ZActionClassNoCodeConfig } from "../action-classes"; import { ZAllowedFileExtension, ZColor, ZId, ZPlacement, getZSafeUrl } from "../common"; import { ZContactAttributes } from "../contact-attribute"; -import { ZInsight } from "../insights"; import { ZLanguage } from "../project"; import { ZSegment } from "../segment"; import { ZBaseStyling } from "../styling"; @@ -2371,7 +2371,13 @@ export const ZSurveyQuestionSummaryOpenText = z.object({ contactAttributes: ZContactAttributes.nullable(), }) ), - insights: z.array(ZInsight), + insights: z.array( + ZInsight.extend({ + _count: z.object({ + documentInsights: z.number(), + }), + }) + ), insightsEnabled: z.boolean().optional(), }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b8a552df33..ef81e35705 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -831,9 +831,6 @@ importers: zod: specifier: 3.24.1 version: 3.24.1 - zod-prisma: - specifier: 0.5.4 - version: 0.5.4(decimal.js@10.4.3)(prisma@6.0.1)(zod@3.24.1) packages/js: dependencies: @@ -3707,9 +3704,6 @@ packages: prisma: optional: true - '@prisma/debug@3.8.1': - resolution: {integrity: sha512-ft4VPTYME1UBJ7trfrBuF2w9jX1ipDy786T9fAEskNGb+y26gPDqz5fiEWc2kgHNeVdz/qTI/V3wXILRyEcgxQ==} - '@prisma/debug@5.0.0': resolution: {integrity: sha512-3q/M/KqlQ01/HJXifU/zCNOHkoTWu24kGelMF/IBrRxm7njPqTTbwfnT1dh4JK+nuWM5/Dg1Lv00u2c0l7AHxg==} @@ -3740,9 +3734,6 @@ packages: '@prisma/fetch-engine@6.0.1': resolution: {integrity: sha512-T36bWFVGeGYYSyYOj9d+O9G3sBC+pAyMC+jc45iSL63/Haq1GrYjQPgPMxrEj9m739taXrupoysRedQ+VyvM/Q==} - '@prisma/generator-helper@3.8.1': - resolution: {integrity: sha512-3zSy+XTEjmjLj6NO+/YPN1Cu7or3xA11TOoOnLRJ9G4pTT67RJXjK0L9Xy5n+3I0Xlb7xrWCgo8MvQQLMWzxPA==} - '@prisma/generator-helper@5.0.0': resolution: {integrity: sha512-pufQ1mhoH6WzKNtzL79HZDoW4Ql3Lf8QEKVmBoW8e3Tdb50bxpYBYue5LBqp9vNW1xd1pgZO53cNiRfLX2d4Zg==} @@ -5401,9 +5392,6 @@ packages: svelte: optional: true - '@ts-morph/common@0.12.3': - resolution: {integrity: sha512-4tUmeLyXJnJWvTFOKtcNJ1yh0a3SsTLi2MUoyj8iUNznFRN1ZquaNe7Oukqrnki2FzZkm0J9adCNLDZxUzvj+w==} - '@tsconfig/node10@1.0.11': resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} @@ -5449,9 +5437,6 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - '@types/debug@4.1.7': - resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==} - '@types/debug@4.1.8': resolution: {integrity: sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==} @@ -6857,9 +6842,6 @@ packages: react: ^18 || ^19 || ^19.0.0-rc react-dom: ^18 || ^19 || ^19.0.0-rc - code-block-writer@11.0.3: - resolution: {integrity: sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==} - codepage@1.15.0: resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==} engines: {node: '>=0.8'} @@ -10567,9 +10549,6 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - parenthesis@3.1.8: - resolution: {integrity: sha512-KF/U8tk54BgQewkJPvB4s/US3VQY68BRDpH638+7O/n58TpnwiwnOtGIOsT2/i+M78s61BBpeC83STB88d8sqw==} - parse-asn1@5.1.7: resolution: {integrity: sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==} engines: {node: '>= 0.10'} @@ -12501,9 +12480,6 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - ts-morph@13.0.3: - resolution: {integrity: sha512-pSOfUMx8Ld/WUreoSzvMFQG5i9uEiWIsBYjpU9+TTASOeUa89j5HykomeqVULm1oqWtBdleI3KEFRLrlA3zGIw==} - ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true @@ -13349,18 +13325,6 @@ packages: resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} engines: {node: '>= 10'} - zod-prisma@0.5.4: - resolution: {integrity: sha512-5Ca4Qd1a1jy1T/NqCEpbr0c+EsbjJfJ/7euEHob3zDvtUK2rTuD1Rc/vfzH8q8PtaR2TZbysD88NHmrLwpv3Xg==} - engines: {node: '>=14'} - hasBin: true - peerDependencies: - decimal.js: ^10.0.0 - prisma: ^3.0.0 - zod: ^3.0.0 - peerDependenciesMeta: - decimal.js: - optional: true - zod-to-json-schema@3.24.1: resolution: {integrity: sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==} peerDependencies: @@ -16886,12 +16850,6 @@ snapshots: optionalDependencies: prisma: 6.0.1 - '@prisma/debug@3.8.1': - dependencies: - '@types/debug': 4.1.7 - ms: 2.1.3 - strip-ansi: 6.0.1 - '@prisma/debug@5.0.0': dependencies: '@types/debug': 4.1.8 @@ -16948,13 +16906,6 @@ snapshots: '@prisma/engines-version': 5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e '@prisma/get-platform': 6.0.1 - '@prisma/generator-helper@3.8.1': - dependencies: - '@prisma/debug': 3.8.1 - '@types/cross-spawn': 6.0.2 - chalk: 4.1.2 - cross-spawn: 7.0.3 - '@prisma/generator-helper@5.0.0': dependencies: '@prisma/debug': 5.0.0 @@ -19265,13 +19216,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@ts-morph/common@0.12.3': - dependencies: - fast-glob: 3.3.2 - minimatch: 3.1.2 - mkdirp: 1.0.4 - path-browserify: 1.0.1 - '@tsconfig/node10@1.0.11': {} '@tsconfig/node12@1.0.11': {} @@ -19323,10 +19267,6 @@ snapshots: dependencies: '@types/ms': 0.7.34 - '@types/debug@4.1.7': - dependencies: - '@types/ms': 0.7.34 - '@types/debug@4.1.8': dependencies: '@types/ms': 0.7.34 @@ -21023,8 +20963,6 @@ snapshots: - '@types/react' - '@types/react-dom' - code-block-writer@11.0.3: {} - codepage@1.15.0: {} collapse-white-space@2.1.0: {} @@ -25719,8 +25657,6 @@ snapshots: dependencies: callsites: 3.1.0 - parenthesis@3.1.8: {} - parse-asn1@5.1.7: dependencies: asn1.js: 4.10.1 @@ -27967,11 +27903,6 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-morph@13.0.3: - dependencies: - '@ts-morph/common': 0.12.3 - code-block-writer: 11.0.3 - ts-node@10.9.2(@types/node@22.10.2)(typescript@5.7.2): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -28820,16 +28751,6 @@ snapshots: compress-commons: 4.1.2 readable-stream: 3.6.2 - zod-prisma@0.5.4(decimal.js@10.4.3)(prisma@6.0.1)(zod@3.24.1): - dependencies: - '@prisma/generator-helper': 3.8.1 - parenthesis: 3.1.8 - prisma: 6.0.1 - ts-morph: 13.0.3 - zod: 3.24.1 - optionalDependencies: - decimal.js: 10.4.3 - zod-to-json-schema@3.24.1(zod@3.24.1): dependencies: zod: 3.24.1