mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-23 13:48:58 -05:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5239dfd9b1 | |||
| d45cbefcff | |||
| f1c6180ae2 |
@@ -26,7 +26,7 @@ const Page = async (props: ConnectPageProps) => {
|
||||
|
||||
const workspace = await getWorkspaceByEnvironmentId(environment.id);
|
||||
if (!workspace) {
|
||||
throw new ResourceNotFoundError(t("common.workspace"), null);
|
||||
throw new Error(t("common.workspace_not_found"));
|
||||
}
|
||||
|
||||
const channel = workspace.config.channel || null;
|
||||
|
||||
@@ -39,7 +39,7 @@ const Page = async (props: XMTemplatePageProps) => {
|
||||
|
||||
const workspace = await getWorkspaceByEnvironmentId(environment.id);
|
||||
if (!workspace) {
|
||||
throw new ResourceNotFoundError(t("common.workspace"), null);
|
||||
throw new Error(t("common.workspace_not_found"));
|
||||
}
|
||||
|
||||
const workspaces = await getUserWorkspaces(session.user.id, organizationId);
|
||||
|
||||
@@ -43,7 +43,7 @@ export const EnvironmentLayout = async ({ layoutData, children }: EnvironmentLay
|
||||
|
||||
// Validate that workspace permission exists for members
|
||||
if (isMember && !workspacePermission) {
|
||||
throw new ResourceNotFoundError(t("common.workspace"), null);
|
||||
throw new Error(t("common.workspace_permission_not_found"));
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -25,7 +25,7 @@ const AccountSettingsLayout = async (props: {
|
||||
}
|
||||
|
||||
if (!workspace) {
|
||||
throw new ResourceNotFoundError(t("common.workspace"), null);
|
||||
throw new Error(t("common.workspace_not_found"));
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
|
||||
@@ -22,7 +22,7 @@ const Layout = async (props: { params: Promise<{ environmentId: string }>; child
|
||||
}
|
||||
|
||||
if (!workspace) {
|
||||
throw new ResourceNotFoundError(t("common.workspace"), null);
|
||||
throw new Error(t("common.workspace_not_found"));
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ export const getEmailTemplateHtml = async (surveyId: string, locale: string) =>
|
||||
}
|
||||
const workspace = await getWorkspaceByEnvironmentId(survey.environmentId);
|
||||
if (!workspace) {
|
||||
throw new ResourceNotFoundError(t("common.workspace"), null);
|
||||
throw new Error("Workspace not found");
|
||||
}
|
||||
|
||||
const styling = getStyling(workspace, survey);
|
||||
|
||||
@@ -45,6 +45,7 @@ export const responseSelection = {
|
||||
updatedAt: true,
|
||||
name: true,
|
||||
environmentId: true,
|
||||
workspaceId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -19,6 +19,7 @@ const selectActionClass = {
|
||||
key: true,
|
||||
noCodeConfig: true,
|
||||
environmentId: true,
|
||||
workspaceId: true,
|
||||
} satisfies Prisma.ActionClassSelect;
|
||||
|
||||
export const getActionClasses = reactCache(async (environmentIds: string[]): Promise<TActionClass[]> => {
|
||||
|
||||
@@ -50,6 +50,7 @@ export const responseSelection = {
|
||||
updatedAt: true,
|
||||
name: true,
|
||||
environmentId: true,
|
||||
workspaceId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4823,6 +4823,7 @@ export const previewSurvey = (workspaceName: string, t: TFunction): TSurvey => {
|
||||
name: t("templates.preview_survey_name"),
|
||||
type: "link" as const,
|
||||
environmentId: "cltwumfcz0009echxg02fh7oa",
|
||||
workspaceId: null,
|
||||
createdBy: "cltwumfbz0000echxysz6ptvq",
|
||||
status: "inProgress" as const,
|
||||
welcomeCard: {
|
||||
|
||||
@@ -21,6 +21,7 @@ const selectActionClass = {
|
||||
key: true,
|
||||
noCodeConfig: true,
|
||||
environmentId: true,
|
||||
workspaceId: true,
|
||||
} satisfies Prisma.ActionClassSelect;
|
||||
|
||||
export const getActionClasses = reactCache(
|
||||
|
||||
@@ -75,6 +75,7 @@ export const responseSelection = {
|
||||
updatedAt: true,
|
||||
name: true,
|
||||
environmentId: true,
|
||||
workspaceId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -19,6 +19,7 @@ const selectContact = {
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
environmentId: true,
|
||||
workspaceId: true,
|
||||
attributes: {
|
||||
select: {
|
||||
value: true,
|
||||
@@ -41,6 +42,7 @@ const commonMockProperties = {
|
||||
createdAt: currentDate,
|
||||
updatedAt: currentDate,
|
||||
environmentId: mockId,
|
||||
workspaceId: null,
|
||||
};
|
||||
|
||||
type SurveyMock = Prisma.SurveyGetPayload<{
|
||||
|
||||
@@ -30,6 +30,7 @@ export const selectSurvey = {
|
||||
name: true,
|
||||
type: true,
|
||||
environmentId: true,
|
||||
workspaceId: true,
|
||||
createdBy: true,
|
||||
status: true,
|
||||
welcomeCard: true,
|
||||
@@ -84,6 +85,7 @@ export const selectSurvey = {
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
environmentId: true,
|
||||
workspaceId: true,
|
||||
name: true,
|
||||
description: true,
|
||||
type: true,
|
||||
|
||||
@@ -58,6 +58,7 @@ export const getResponseForPipeline = async (
|
||||
updatedAt: true,
|
||||
name: true,
|
||||
environmentId: true,
|
||||
workspaceId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -140,6 +140,7 @@ describe("Response Lib", () => {
|
||||
updatedAt: new Date(),
|
||||
name: "important",
|
||||
environmentId: "env123",
|
||||
workspaceId: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -163,6 +164,7 @@ describe("Response Lib", () => {
|
||||
updatedAt: mockPrismaResponse.tags[0].tag.updatedAt,
|
||||
name: "important",
|
||||
environmentId: "env123",
|
||||
workspaceId: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -184,6 +186,7 @@ describe("Response Lib", () => {
|
||||
updatedAt: true,
|
||||
name: true,
|
||||
environmentId: true,
|
||||
workspaceId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -17,6 +17,7 @@ export const ZWebhookUpdateSchema = ZWebhook.omit({
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
environmentId: true,
|
||||
workspaceId: true,
|
||||
secret: true,
|
||||
}).meta({
|
||||
id: "webhookUpdate",
|
||||
|
||||
@@ -50,7 +50,7 @@ export const ActivitySection = async ({ environment, contactId, environmentTags
|
||||
|
||||
const workspace = await getWorkspaceByEnvironmentId(environment.id);
|
||||
if (!workspace) {
|
||||
throw new ResourceNotFoundError(t("common.workspace"), null);
|
||||
throw new Error(t("common.workspace_not_found"));
|
||||
}
|
||||
|
||||
const workspacePermission = await getWorkspacePermissionByUserId(session.user.id, workspace.id);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { TWorkspace } from "@formbricks/types/workspace";
|
||||
import { getWorkspaceByEnvironmentId } from "@/lib/workspace/service";
|
||||
import { getTranslate } from "@/lingodotdev/server";
|
||||
@@ -21,7 +20,7 @@ export const ContactsSecondaryNavigation = async ({
|
||||
workspace = await getWorkspaceByEnvironmentId(environmentId);
|
||||
|
||||
if (!workspace) {
|
||||
throw new ResourceNotFoundError(t("common.workspace"), null);
|
||||
throw new Error(t("common.workspace_not_found"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ const ConfigLayout = async (props: {
|
||||
|
||||
const workspace = await getWorkspaceByEnvironmentId(params.environmentId);
|
||||
if (!workspace) {
|
||||
throw new ResourceNotFoundError(t("common.workspace"), null);
|
||||
throw new Error(t("common.workspace_not_found"));
|
||||
}
|
||||
|
||||
return children;
|
||||
|
||||
@@ -98,6 +98,7 @@ const selectContact = {
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
environmentId: true,
|
||||
workspaceId: true,
|
||||
attributes: {
|
||||
select: {
|
||||
value: true,
|
||||
|
||||
@@ -45,6 +45,7 @@ export function CreateSegmentModal({
|
||||
isPrivate: false,
|
||||
filters: [],
|
||||
environmentId,
|
||||
workspaceId: null,
|
||||
id: "",
|
||||
surveys: [],
|
||||
createdAt: new Date(),
|
||||
|
||||
@@ -55,6 +55,7 @@ export const selectSegment = {
|
||||
title: true,
|
||||
description: true,
|
||||
environmentId: true,
|
||||
workspaceId: true,
|
||||
filters: true,
|
||||
isPrivate: true,
|
||||
surveys: {
|
||||
|
||||
@@ -47,7 +47,7 @@ export const updateWorkspaceBrandingAction = authenticatedActionClient
|
||||
const organization = await getOrganization(organizationId);
|
||||
|
||||
if (!organization) {
|
||||
throw new ResourceNotFoundError("Organization", organizationId);
|
||||
throw new Error("Organization not found");
|
||||
}
|
||||
const canRemoveBranding = await getRemoveBrandingPermission(organizationId);
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ describe("utils.ts", () => {
|
||||
|
||||
test("throws error if workspace not found", async () => {
|
||||
vi.mocked(getWorkspaceByEnvironmentId).mockResolvedValueOnce(null);
|
||||
await expect(getEnvironmentAuth("env123")).rejects.toThrow(ResourceNotFoundError);
|
||||
await expect(getEnvironmentAuth("env123")).rejects.toThrow("common.workspace_not_found");
|
||||
});
|
||||
|
||||
test("throws error if environment not found", async () => {
|
||||
|
||||
@@ -48,7 +48,7 @@ export const getEnvironmentAuth = reactCache(async (environmentId: string): Prom
|
||||
]);
|
||||
|
||||
if (!workspace) {
|
||||
throw new ResourceNotFoundError(t("common.workspace"), null);
|
||||
throw new Error(t("common.workspace_not_found"));
|
||||
}
|
||||
|
||||
if (!environment) {
|
||||
|
||||
@@ -27,6 +27,7 @@ export const WebhookTable = ({
|
||||
const { t } = useTranslation();
|
||||
const [activeWebhook, setActiveWebhook] = useState<Webhook>({
|
||||
environmentId: environment.id,
|
||||
workspaceId: null,
|
||||
id: "",
|
||||
name: "",
|
||||
url: "",
|
||||
|
||||
@@ -49,6 +49,7 @@ export const HowToSendCard = ({ localSurvey, setLocalSurvey, environment }: HowT
|
||||
isPrivate: true,
|
||||
title: localSurvey.id,
|
||||
environmentId: environment.id,
|
||||
workspaceId: null,
|
||||
surveys: [localSurvey.id],
|
||||
filters: [],
|
||||
createdAt: new Date(),
|
||||
|
||||
@@ -61,7 +61,7 @@ export const SurveyEditorPage = async (props: {
|
||||
]);
|
||||
|
||||
if (!workspaceWithTeamIds) {
|
||||
throw new ResourceNotFoundError(t("common.workspace"), null);
|
||||
throw new Error(t("common.workspace_not_found"));
|
||||
}
|
||||
|
||||
const organizationBilling = await getOrganizationBilling(workspaceWithTeamIds.organizationId);
|
||||
|
||||
@@ -14,6 +14,7 @@ export const selectSurvey = {
|
||||
name: true,
|
||||
type: true,
|
||||
environmentId: true,
|
||||
workspaceId: true,
|
||||
createdBy: true,
|
||||
status: true,
|
||||
welcomeCard: true,
|
||||
@@ -69,6 +70,7 @@ export const selectSurvey = {
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
environmentId: true,
|
||||
workspaceId: true,
|
||||
name: true,
|
||||
description: true,
|
||||
type: true,
|
||||
@@ -84,6 +86,7 @@ export const selectSurvey = {
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
environmentId: true,
|
||||
workspaceId: true,
|
||||
title: true,
|
||||
description: true,
|
||||
isPrivate: true,
|
||||
|
||||
@@ -15,6 +15,7 @@ export const surveySelect = {
|
||||
status: true,
|
||||
singleUse: true,
|
||||
environmentId: true,
|
||||
workspaceId: true,
|
||||
_count: {
|
||||
select: { responses: true },
|
||||
},
|
||||
|
||||
@@ -34,7 +34,7 @@ export const SurveysPage = async ({ params: paramsProps }: SurveyTemplateProps)
|
||||
const workspace = await getWorkspaceWithTeamIdsByEnvironmentId(params.environmentId);
|
||||
|
||||
if (!workspace) {
|
||||
throw new ResourceNotFoundError(t("common.workspace"), null);
|
||||
throw new Error(t("common.workspace_not_found"));
|
||||
}
|
||||
|
||||
const { session, isBilling, environment, isReadOnly } = await getEnvironmentAuth(params.environmentId);
|
||||
|
||||
@@ -9,6 +9,7 @@ export const getMinimalSurvey = (t: TFunction): TSurvey => ({
|
||||
name: "Minimal Survey",
|
||||
type: "app",
|
||||
environmentId: "someEnvId1",
|
||||
workspaceId: null,
|
||||
createdBy: null,
|
||||
status: "draft",
|
||||
displayOption: "displayOnce",
|
||||
|
||||
@@ -22,7 +22,7 @@ export const SurveyTemplatesPage = async (props: SurveyTemplateProps) => {
|
||||
const workspace = await getWorkspaceWithTeamIdsByEnvironmentId(environmentId);
|
||||
|
||||
if (!workspace) {
|
||||
throw new ResourceNotFoundError(t("common.workspace"), null);
|
||||
throw new Error(t("common.workspace_not_found"));
|
||||
}
|
||||
|
||||
if (isReadOnly) {
|
||||
|
||||
@@ -25,7 +25,7 @@ export const WorkspaceLookSettingsPage = async (props: { params: Promise<{ envir
|
||||
const workspace = await getWorkspaceByEnvironmentId(params.environmentId);
|
||||
|
||||
if (!workspace) {
|
||||
throw new ResourceNotFoundError(t("common.workspace"), null);
|
||||
throw new Error("Workspace not found");
|
||||
}
|
||||
|
||||
const canRemoveBranding = await getRemoveBrandingPermission(organization.id);
|
||||
|
||||
@@ -47,7 +47,12 @@ export const xmSegmentMigration: MigrationScript = {
|
||||
id: "s644oyyqccstfdeejc4fluye",
|
||||
name: "20241209110456_xm_segment_migration",
|
||||
run: async ({ tx }) => {
|
||||
const allSegments = await tx.segment.findMany();
|
||||
const allSegments = await tx.segment.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
filters: true,
|
||||
},
|
||||
});
|
||||
const updationPromises = [];
|
||||
for (const segment of allSegments) {
|
||||
updationPromises.push(
|
||||
@@ -56,6 +61,7 @@ export const xmSegmentMigration: MigrationScript = {
|
||||
data: {
|
||||
filters: findAndReplace(segment.filters),
|
||||
},
|
||||
select: { id: true },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
-- AlterTable: Add nullable workspaceId to all environment-owned models
|
||||
ALTER TABLE "Webhook" ADD COLUMN "workspaceId" TEXT;
|
||||
ALTER TABLE "ContactAttributeKey" ADD COLUMN "workspaceId" TEXT;
|
||||
ALTER TABLE "Contact" ADD COLUMN "workspaceId" TEXT;
|
||||
ALTER TABLE "Tag" ADD COLUMN "workspaceId" TEXT;
|
||||
ALTER TABLE "Survey" ADD COLUMN "workspaceId" TEXT;
|
||||
ALTER TABLE "ActionClass" ADD COLUMN "workspaceId" TEXT;
|
||||
ALTER TABLE "Integration" ADD COLUMN "workspaceId" TEXT;
|
||||
ALTER TABLE "ApiKeyEnvironment" ADD COLUMN "workspaceId" TEXT;
|
||||
ALTER TABLE "Segment" ADD COLUMN "workspaceId" TEXT;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Webhook" ADD CONSTRAINT "Webhook_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "ContactAttributeKey" ADD CONSTRAINT "ContactAttributeKey_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "Contact" ADD CONSTRAINT "Contact_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "Tag" ADD CONSTRAINT "Tag_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "Survey" ADD CONSTRAINT "Survey_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "ActionClass" ADD CONSTRAINT "ActionClass_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "Integration" ADD CONSTRAINT "Integration_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "ApiKeyEnvironment" ADD CONSTRAINT "ApiKeyEnvironment_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "Segment" ADD CONSTRAINT "Segment_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Webhook_workspaceId_idx" ON "Webhook"("workspaceId");
|
||||
CREATE INDEX "ContactAttributeKey_workspaceId_created_at_idx" ON "ContactAttributeKey"("workspaceId", "created_at");
|
||||
CREATE INDEX "Contact_workspaceId_idx" ON "Contact"("workspaceId");
|
||||
CREATE INDEX "Tag_workspaceId_idx" ON "Tag"("workspaceId");
|
||||
CREATE INDEX "Survey_workspaceId_updated_at_idx" ON "Survey"("workspaceId", "updated_at");
|
||||
CREATE INDEX "ActionClass_workspaceId_created_at_idx" ON "ActionClass"("workspaceId", "created_at");
|
||||
CREATE INDEX "Integration_workspaceId_idx" ON "Integration"("workspaceId");
|
||||
CREATE INDEX "ApiKeyEnvironment_workspaceId_idx" ON "ApiKeyEnvironment"("workspaceId");
|
||||
CREATE INDEX "Segment_workspaceId_idx" ON "Segment"("workspaceId");
|
||||
@@ -1,53 +0,0 @@
|
||||
import { logger } from "@formbricks/logger";
|
||||
import type { MigrationScript } from "../../src/scripts/migration-runner";
|
||||
|
||||
// Table names are from a hardcoded const array, not user input.
|
||||
// $executeRawUnsafe is required because Postgres does not support parameterized identifiers.
|
||||
const TABLES_TO_BACKFILL = [
|
||||
"Survey",
|
||||
"Contact",
|
||||
"ActionClass",
|
||||
"ContactAttributeKey",
|
||||
"Webhook",
|
||||
"Tag",
|
||||
"Segment",
|
||||
"Integration",
|
||||
"ApiKeyEnvironment",
|
||||
] as const;
|
||||
|
||||
export const backfillWorkspaceId: MigrationScript = {
|
||||
type: "data",
|
||||
id: "snae9apsx7e74yo9ncmhjl47",
|
||||
name: "20260401000001_backfill_workspace_id",
|
||||
run: async ({ tx }) => {
|
||||
for (const table of TABLES_TO_BACKFILL) {
|
||||
const updatedRows = await tx.$executeRawUnsafe(`
|
||||
UPDATE "${table}" t
|
||||
SET "workspaceId" = e."workspaceId"
|
||||
FROM "Environment" e
|
||||
WHERE t."environmentId" = e."id"
|
||||
AND t."workspaceId" IS NULL
|
||||
`);
|
||||
|
||||
logger.info(`Backfilled ${updatedRows.toString()} rows in ${table}`);
|
||||
}
|
||||
|
||||
// Verify no rows were missed.
|
||||
// Any remaining NULL workspaceId indicates orphaned rows (environmentId references a
|
||||
// non-existent Environment). The FK cascade should prevent this, but we check anyway.
|
||||
const failures: string[] = [];
|
||||
for (const table of TABLES_TO_BACKFILL) {
|
||||
const nullCount: [{ count: bigint }] = await tx.$queryRawUnsafe(`
|
||||
SELECT COUNT(*) as count FROM "${table}" WHERE "workspaceId" IS NULL
|
||||
`);
|
||||
|
||||
if (nullCount[0].count > 0n) {
|
||||
failures.push(`${table}: ${nullCount[0].count.toString()} rows with NULL workspaceId`);
|
||||
}
|
||||
}
|
||||
|
||||
if (failures.length > 0) {
|
||||
throw new Error(`Backfill verification failed:\n${failures.join("\n")}`);
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -49,11 +49,14 @@ model Webhook {
|
||||
source WebhookSource @default(user)
|
||||
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
|
||||
environmentId String
|
||||
workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
workspaceId String?
|
||||
triggers PipelineTriggers[]
|
||||
surveyIds String[]
|
||||
secret String?
|
||||
|
||||
@@index([environmentId])
|
||||
@@index([workspaceId])
|
||||
}
|
||||
|
||||
/// Represents an attribute value associated with a contact.
|
||||
@@ -116,11 +119,14 @@ model ContactAttributeKey {
|
||||
dataType ContactAttributeDataType @default(string)
|
||||
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
|
||||
environmentId String
|
||||
workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
workspaceId String?
|
||||
attributes ContactAttribute[]
|
||||
attributeFilters SurveyAttributeFilter[]
|
||||
|
||||
@@unique([key, environmentId])
|
||||
@@index([environmentId, createdAt])
|
||||
@@index([workspaceId, createdAt])
|
||||
}
|
||||
|
||||
/// Represents a person or user who can receive and respond to surveys.
|
||||
@@ -137,11 +143,14 @@ model Contact {
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
|
||||
environmentId String
|
||||
workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
workspaceId String?
|
||||
responses Response[]
|
||||
attributes ContactAttribute[]
|
||||
displays Display[]
|
||||
|
||||
@@index([environmentId])
|
||||
@@index([workspaceId])
|
||||
}
|
||||
|
||||
/// Stores a user's response to a survey, including their answers and metadata.
|
||||
@@ -204,8 +213,11 @@ model Tag {
|
||||
responses TagsOnResponses[]
|
||||
environmentId String
|
||||
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
|
||||
workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
workspaceId String?
|
||||
|
||||
@@unique([environmentId, name])
|
||||
@@index([workspaceId])
|
||||
}
|
||||
|
||||
/// Junction table linking tags to responses.
|
||||
@@ -350,6 +362,8 @@ model Survey {
|
||||
type SurveyType @default(app)
|
||||
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
|
||||
environmentId String
|
||||
workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
workspaceId String?
|
||||
creator User? @relation(fields: [createdBy], references: [id])
|
||||
createdBy String?
|
||||
status SurveyStatus @default(draft)
|
||||
@@ -413,6 +427,7 @@ model Survey {
|
||||
|
||||
@@index([environmentId, updatedAt])
|
||||
@@index([segmentId])
|
||||
@@index([workspaceId, updatedAt])
|
||||
}
|
||||
|
||||
/// Represents a quota configuration for a survey.
|
||||
@@ -507,11 +522,14 @@ model ActionClass {
|
||||
noCodeConfig Json?
|
||||
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
|
||||
environmentId String
|
||||
workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
workspaceId String?
|
||||
surveyTriggers SurveyTrigger[]
|
||||
|
||||
@@unique([key, environmentId])
|
||||
@@unique([name, environmentId])
|
||||
@@index([environmentId, createdAt])
|
||||
@@index([workspaceId, createdAt])
|
||||
}
|
||||
|
||||
enum EnvironmentType {
|
||||
@@ -540,9 +558,12 @@ model Integration {
|
||||
/// [IntegrationConfig]
|
||||
config Json
|
||||
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
|
||||
workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
workspaceId String?
|
||||
|
||||
@@unique([type, environmentId])
|
||||
@@index([environmentId])
|
||||
@@index([workspaceId])
|
||||
}
|
||||
|
||||
enum DataMigrationStatus {
|
||||
@@ -649,6 +670,17 @@ model Workspace {
|
||||
customHeadScripts String? // Custom HTML scripts for link surveys (self-hosted only)
|
||||
feedbackRecordDirectoryWorkspaces FeedbackRecordDirectoryWorkspace[]
|
||||
|
||||
// Direct resource relations (for environment deprecation migration)
|
||||
surveys Survey[]
|
||||
contacts Contact[]
|
||||
actionClasses ActionClass[]
|
||||
contactAttributeKeys ContactAttributeKey[]
|
||||
webhooks Webhook[]
|
||||
tags Tag[]
|
||||
segments Segment[]
|
||||
integrations Integration[]
|
||||
apiKeyEnvironments ApiKeyEnvironment[]
|
||||
|
||||
@@unique([organizationId, name])
|
||||
}
|
||||
|
||||
@@ -809,10 +841,13 @@ model ApiKeyEnvironment {
|
||||
apiKey ApiKey @relation(fields: [apiKeyId], references: [id], onDelete: Cascade)
|
||||
environmentId String
|
||||
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
|
||||
workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
workspaceId String?
|
||||
permission ApiKeyPermission
|
||||
|
||||
@@unique([apiKeyId, environmentId])
|
||||
@@index([environmentId])
|
||||
@@index([workspaceId])
|
||||
}
|
||||
|
||||
enum IdentityProvider {
|
||||
@@ -912,9 +947,12 @@ model Segment {
|
||||
filters Json @default("[]")
|
||||
environmentId String
|
||||
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
|
||||
workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
workspaceId String?
|
||||
surveys Survey[]
|
||||
|
||||
@@unique([environmentId, title])
|
||||
@@index([workspaceId])
|
||||
}
|
||||
|
||||
/// Represents a supported language in the system.
|
||||
|
||||
@@ -54,6 +54,7 @@ export const ZContactAttributeKey = z.object({
|
||||
})
|
||||
.describe("The data type of the attribute (string, number, date)"),
|
||||
environmentId: z.cuid2().describe("The ID of the environment this attribute belongs to"),
|
||||
workspaceId: z.string().nullable(),
|
||||
}) satisfies z.ZodType<ContactAttributeKey>;
|
||||
|
||||
ZContactAttributeKey.meta({
|
||||
|
||||
@@ -17,6 +17,7 @@ export const ZContact = z.object({
|
||||
})
|
||||
.describe("When the contact was last updated"),
|
||||
environmentId: z.string().describe("The environment this contact belongs to"),
|
||||
workspaceId: z.string().nullable(),
|
||||
}) satisfies z.ZodType<Contact>;
|
||||
|
||||
ZContact.meta({
|
||||
|
||||
@@ -72,6 +72,7 @@ const ZSurveyBase = z.object({
|
||||
pin: z.string().nullable().describe("The pin of the survey"),
|
||||
createdBy: z.string().nullable().describe("The user who created the survey"),
|
||||
environmentId: z.cuid2().describe("The environment ID of the survey"),
|
||||
workspaceId: z.string().nullable(),
|
||||
questions: z.array(ZSurveyQuestion).describe("The questions of the survey"),
|
||||
blocks: ZSurveyBlocks.prefault([]).describe("The blocks of the survey"),
|
||||
endings: z.array(ZSurveyEnding).prefault([]).describe("The endings of the survey"),
|
||||
|
||||
@@ -19,6 +19,7 @@ export const ZWebhook = z.object({
|
||||
url: z.url().describe("The URL of the webhook"),
|
||||
source: z.enum(["user", "zapier", "make", "n8n"]).describe("The source of the webhook"),
|
||||
environmentId: z.cuid2().describe("The ID of the environment"),
|
||||
workspaceId: z.string().nullable(),
|
||||
triggers: z
|
||||
.array(z.enum(["responseFinished", "responseCreated", "responseUpdated"]))
|
||||
.describe("The triggers of the webhook")
|
||||
|
||||
@@ -62,6 +62,7 @@ export const mockSurvey: TEnvironmentStateSurvey = {
|
||||
createdAt: new Date("2025-01-01T10:00:00Z"),
|
||||
updatedAt: new Date("2025-01-01T10:00:00Z"),
|
||||
environmentId: mockEnvironmentId,
|
||||
workspaceId: null,
|
||||
description: "Manual Trigger",
|
||||
noCodeConfig: {
|
||||
elementSelector: { cssSelector: ".btn", innerHtml: "Click me" },
|
||||
|
||||
@@ -135,6 +135,7 @@ export const ZActionClass = z.object({
|
||||
key: z.string().trim().min(1).nullable(),
|
||||
noCodeConfig: ZActionClassNoCodeConfig.nullable(),
|
||||
environmentId: z.string(),
|
||||
workspaceId: z.string().nullable(),
|
||||
createdAt: z.coerce.date(),
|
||||
updatedAt: z.coerce.date(),
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ export const ZContactAttributeKey = z.object({
|
||||
type: ZContactAttributeKeyType,
|
||||
dataType: ZContactAttributeDataType.prefault("string"),
|
||||
environmentId: z.string(),
|
||||
workspaceId: z.string().nullable(),
|
||||
});
|
||||
|
||||
export type TContactAttributeKey = z.infer<typeof ZContactAttributeKey>;
|
||||
|
||||
@@ -19,6 +19,7 @@ export type TIntegrationConfig = z.infer<typeof ZIntegrationConfig>;
|
||||
export const ZIntegrationBase = z.object({
|
||||
id: z.string(),
|
||||
environmentId: z.string(),
|
||||
workspaceId: z.string().nullable(),
|
||||
});
|
||||
|
||||
export const ZIntegration = ZIntegrationBase.extend({
|
||||
|
||||
@@ -3,6 +3,7 @@ import { z } from "zod";
|
||||
export const ZIntegrationBase = z.object({
|
||||
id: z.string(),
|
||||
environmentId: z.string(),
|
||||
workspaceId: z.string().nullable(),
|
||||
});
|
||||
|
||||
export const ZIntegrationBaseSurveyData = z.object({
|
||||
|
||||
@@ -344,6 +344,7 @@ export const ZSegment = z.object({
|
||||
isPrivate: z.boolean().prefault(true),
|
||||
filters: ZSegmentFilters,
|
||||
environmentId: z.string(),
|
||||
workspaceId: z.string().nullable(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
surveys: z.array(z.string()),
|
||||
|
||||
@@ -826,6 +826,7 @@ export const ZSurveyBase = z.object({
|
||||
name: z.string(),
|
||||
type: ZSurveyType,
|
||||
environmentId: z.string(),
|
||||
workspaceId: z.string().nullable(),
|
||||
createdBy: z.string().nullable(),
|
||||
status: ZSurveyStatus,
|
||||
displayOption: ZSurveyDisplayOption,
|
||||
|
||||
@@ -6,6 +6,7 @@ export const ZTag = z.object({
|
||||
updatedAt: z.date(),
|
||||
name: z.string(),
|
||||
environmentId: z.string(),
|
||||
workspaceId: z.string().nullable(),
|
||||
});
|
||||
export type TTag = z.infer<typeof ZTag>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user