From babc0200850cd802b3f715bb58461b926ba31b47 Mon Sep 17 00:00:00 2001
From: Dhruwang Jariwala <67850763+Dhruwang@users.noreply.github.com>
Date: Mon, 11 Aug 2025 13:25:52 +0530
Subject: [PATCH] chore: short url legacy removal (#6391)
---
apps/web/app/[shortUrlId]/loading.tsx | 12 ----
apps/web/app/[shortUrlId]/page.tsx | 59 -------------------
apps/web/lib/shortUrl/service.ts | 44 --------------
apps/web/vite.config.mts | 2 -
.../technical-handbook/database-model.mdx | 1 -
.../migration.sql | 2 +
packages/database/schema.prisma | 12 ----
packages/types/short-url.ts | 14 -----
sonar-project.properties | 4 +-
9 files changed, 4 insertions(+), 146 deletions(-)
delete mode 100644 apps/web/app/[shortUrlId]/loading.tsx
delete mode 100644 apps/web/app/[shortUrlId]/page.tsx
delete mode 100644 apps/web/lib/shortUrl/service.ts
create mode 100644 packages/database/migration/20250811123224_remove_short_url/migration.sql
delete mode 100644 packages/types/short-url.ts
diff --git a/apps/web/app/[shortUrlId]/loading.tsx b/apps/web/app/[shortUrlId]/loading.tsx
deleted file mode 100644
index f49a67b31c..0000000000
--- a/apps/web/app/[shortUrlId]/loading.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-const Loading = () => {
- return (
-
- );
-};
-
-export default Loading;
diff --git a/apps/web/app/[shortUrlId]/page.tsx b/apps/web/app/[shortUrlId]/page.tsx
deleted file mode 100644
index 8a6a824d27..0000000000
--- a/apps/web/app/[shortUrlId]/page.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { getShortUrl } from "@/lib/shortUrl/service";
-import { getMetadataForLinkSurvey } from "@/modules/survey/link/metadata";
-import type { Metadata } from "next";
-import { notFound, redirect } from "next/navigation";
-import { logger } from "@formbricks/logger";
-import { TShortUrl, ZShortUrlId } from "@formbricks/types/short-url";
-
-export const generateMetadata = async (props): Promise => {
- const params = await props.params;
- if (!params.shortUrlId) {
- notFound();
- }
-
- if (ZShortUrlId.safeParse(params.shortUrlId).success !== true) {
- notFound();
- }
-
- try {
- const shortUrl = await getShortUrl(params.shortUrlId);
-
- if (!shortUrl) {
- notFound();
- }
-
- const surveyId = shortUrl.url.substring(shortUrl.url.lastIndexOf("/") + 1);
- return getMetadataForLinkSurvey(surveyId);
- } catch (error) {
- notFound();
- }
-};
-
-const Page = async (props) => {
- const params = await props.params;
- if (!params.shortUrlId) {
- notFound();
- }
-
- if (ZShortUrlId.safeParse(params.shortUrlId).success !== true) {
- // return not found if unable to parse short url id
- notFound();
- }
-
- let shortUrl: TShortUrl | null = null;
-
- try {
- shortUrl = await getShortUrl(params.shortUrlId);
- } catch (error) {
- logger.error(error, "Could not fetch short url");
- notFound();
- }
-
- if (shortUrl) {
- redirect(shortUrl.url);
- }
-
- notFound();
-};
-
-export default Page;
diff --git a/apps/web/lib/shortUrl/service.ts b/apps/web/lib/shortUrl/service.ts
deleted file mode 100644
index 8c1df269d5..0000000000
--- a/apps/web/lib/shortUrl/service.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-// DEPRECATED
-// The ShortUrl feature is deprecated and only available for backward compatibility.
-import { Prisma } from "@prisma/client";
-import { cache as reactCache } from "react";
-import { z } from "zod";
-import { prisma } from "@formbricks/database";
-import { DatabaseError } from "@formbricks/types/errors";
-import { TShortUrl, ZShortUrlId } from "@formbricks/types/short-url";
-import { validateInputs } from "../utils/validate";
-
-// Get the full url from short url and return it
-export const getShortUrl = reactCache(async (id: string): Promise => {
- validateInputs([id, ZShortUrlId]);
- try {
- return await prisma.shortUrl.findUnique({
- where: {
- id,
- },
- });
- } catch (error) {
- if (error instanceof Prisma.PrismaClientKnownRequestError) {
- throw new DatabaseError(error.message);
- }
-
- throw error;
- }
-});
-
-export const getShortUrlByUrl = reactCache(async (url: string): Promise => {
- validateInputs([url, z.string().url()]);
- try {
- return await prisma.shortUrl.findUnique({
- where: {
- url,
- },
- });
- } catch (error) {
- if (error instanceof Prisma.PrismaClientKnownRequestError) {
- throw new DatabaseError(error.message);
- }
-
- throw error;
- }
-});
diff --git a/apps/web/vite.config.mts b/apps/web/vite.config.mts
index 10dcd8544e..6e3bda203e 100644
--- a/apps/web/vite.config.mts
+++ b/apps/web/vite.config.mts
@@ -73,8 +73,6 @@ export default defineConfig({
"modules/setup/**/intro/**", // Setup intro pages
"modules/setup/**/signup/**", // Setup signup pages
"modules/setup/**/layout.tsx", // Setup layouts
- "lib/shortUrl/**", // Short URL functionality
- "app/[shortUrlId]", // Short URL pages
"modules/ee/contacts/components/**", // Contact components
// Third-party integrations
diff --git a/docs/development/technical-handbook/database-model.mdx b/docs/development/technical-handbook/database-model.mdx
index dd4f6625cd..2bf81d4e51 100644
--- a/docs/development/technical-handbook/database-model.mdx
+++ b/docs/development/technical-handbook/database-model.mdx
@@ -175,7 +175,6 @@ Formbricks stores all data in PostgreSQL tables. Here's a comprehensive list of
| Response | Stores survey responses and associated metadata |
| ResponseNote | Contains team member comments on survey responses |
| Segment | Defines groups of contacts based on attributes |
-| ShortUrl | Maps shortened URLs to their full destinations |
| Survey | Stores survey configurations, questions, and display rules |
| SurveyAttributeFilter | Defines targeting rules for surveys based on contact attributes |
| SurveyFollowUp | Configures automated actions based on survey responses |
diff --git a/packages/database/migration/20250811123224_remove_short_url/migration.sql b/packages/database/migration/20250811123224_remove_short_url/migration.sql
new file mode 100644
index 0000000000..4800203aaf
--- /dev/null
+++ b/packages/database/migration/20250811123224_remove_short_url/migration.sql
@@ -0,0 +1,2 @@
+-- Drop ShortUrl table
+DROP TABLE IF EXISTS "ShortUrl";
diff --git a/packages/database/schema.prisma b/packages/database/schema.prisma
index 4a075673ba..9429326367 100644
--- a/packages/database/schema.prisma
+++ b/packages/database/schema.prisma
@@ -874,18 +874,6 @@ model User {
@@index([email])
}
-/// Maps a short URL to its full destination.
-/// Used for creating memorable, shortened URLs for surveys.
-///
-/// @property id - Short identifier/slug for the URL
-/// @property url - The full destination URL
-model ShortUrl {
- id String @id // generate nanoId in service
- createdAt DateTime @default(now()) @map(name: "created_at")
- updatedAt DateTime @updatedAt @map(name: "updated_at")
- url String @unique
-}
-
/// Defines a segment of contacts based on attributes.
/// Used for targeting surveys to specific user groups.
///
diff --git a/packages/types/short-url.ts b/packages/types/short-url.ts
deleted file mode 100644
index 23ccd12620..0000000000
--- a/packages/types/short-url.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { z } from "zod";
-
-export const ZShortUrlId = z.string().length(10);
-
-export type TShortUrlId = z.infer;
-
-export const ZShortUrl = z.object({
- id: ZShortUrlId,
- createdAt: z.date(),
- updatedAt: z.date(),
- url: z.string().url(),
-});
-
-export type TShortUrl = z.infer;
diff --git a/sonar-project.properties b/sonar-project.properties
index 31aca3d5c7..b2fbde521e 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -21,5 +21,5 @@ sonar.scm.exclusions.disabled=false
sonar.sourceEncoding=UTF-8
# Coverage
-sonar.coverage.exclusions=**/*.test.*,**/*.spec.*,**/*.mdx,**/*.config.mts,**/*.config.ts,**/constants.ts,**/route.ts,**/route.tsx,**/types/**,**/types.ts,**/stories.*,**/*.mock.*,**/mocks/**,**/__mocks__/**,**/openapi.ts,**/openapi-document.ts,**/instrumentation.ts,scripts/openapi/merge-client-endpoints.ts,**/playwright/**,**/Dockerfile,**/*.config.cjs,**/*.css,**/templates.ts,**/actions.ts,apps/web/modules/ui/components/icons/*,**/*.json,apps/web/vitestSetup.ts,packages/js-core/src/index.ts,apps/web/tailwind.config.js,apps/web/postcss.config.js,apps/web/next.config.mjs,apps/web/scripts/**,packages/js-core/vitest.setup.ts,**/*.mjs,apps/web/modules/auth/lib/mock-data.ts,apps/web/modules/analysis/components/SingleResponseCard/components/Smileys.tsx,packages/surveys/src/components/general/smileys.tsx,**/cache.ts,apps/web/app/**/billing-confirmation/**,apps/web/modules/ee/billing/**,apps/web/modules/ee/multi-language-surveys/**,apps/web/modules/email/**,apps/web/modules/integrations/**,apps/web/modules/setup/**/intro/**,apps/web/modules/setup/**/signup/**,apps/web/modules/setup/**/layout.tsx,apps/web/modules/survey/follow-ups/**,apps/web/app/share/**,apps/web/lib/shortUrl/**,apps/web/modules/ee/contacts/[contactId]/**,apps/web/modules/ee/contacts/components/**,apps/web/modules/ee/two-factor-auth/**,apps/web/lib/posthogServer.ts,apps/web/lib/slack/**,apps/web/lib/notion/**,apps/web/lib/googleSheet/**,apps/web/app/api/google-sheet/**,apps/web/app/api/billing/**,apps/web/lib/airtable/**,apps/web/app/api/v1/integrations/**,apps/web/lib/env.ts,**/instrumentation-node.ts,**/cache/**,**/*.svg,apps/web/modules/ui/components/icons/**,apps/web/modules/ui/components/table/**
-sonar.cpd.exclusions=**/*.test.*,**/*.spec.*,**/*.mdx,**/*.config.mts,**/*.config.ts,**/constants.ts,**/route.ts,**/route.tsx,**/types/**,**/types.ts,**/stories.*,**/*.mock.*,**/mocks/**,**/__mocks__/**,**/openapi.ts,**/openapi-document.ts,**/instrumentation.ts,scripts/openapi/merge-client-endpoints.ts,**/playwright/**,**/Dockerfile,**/*.config.cjs,**/*.css,**/templates.ts,**/actions.ts,apps/web/modules/ui/components/icons/*,**/*.json,apps/web/vitestSetup.ts,apps/web/tailwind.config.js,apps/web/postcss.config.js,apps/web/next.config.mjs,apps/web/scripts/**,packages/js-core/vitest.setup.ts,packages/js-core/src/index.ts,**/*.mjs,apps/web/modules/auth/lib/mock-data.ts,apps/web/modules/analysis/components/SingleResponseCard/components/Smileys.tsx,packages/surveys/src/components/general/smileys.tsx,**/cache.ts,apps/web/app/**/billing-confirmation/**,apps/web/modules/ee/billing/**,apps/web/modules/ee/multi-language-surveys/**,apps/web/modules/email/**,apps/web/modules/integrations/**,apps/web/modules/setup/**/intro/**,apps/web/modules/setup/**/signup/**,apps/web/modules/setup/**/layout.tsx,apps/web/modules/survey/follow-ups/**,apps/web/app/share/**,apps/web/lib/shortUrl/**,apps/web/modules/ee/contacts/[contactId]/**,apps/web/modules/ee/contacts/components/**,apps/web/modules/ee/two-factor-auth/**,apps/web/lib/posthogServer.ts,apps/web/lib/slack/**,apps/web/lib/notion/**,apps/web/lib/googleSheet/**,apps/web/app/api/google-sheet/**,apps/web/app/api/billing/**,apps/web/lib/airtable/**,apps/web/app/api/v1/integrations/**,apps/web/lib/env.ts,**/instrumentation-node.ts,**/cache/**,**/*.svg,apps/web/modules/ui/components/icons/**,apps/web/modules/ui/components/table/**
+sonar.coverage.exclusions=**/*.test.*,**/*.spec.*,**/*.mdx,**/*.config.mts,**/*.config.ts,**/constants.ts,**/route.ts,**/route.tsx,**/types/**,**/types.ts,**/stories.*,**/*.mock.*,**/mocks/**,**/__mocks__/**,**/openapi.ts,**/openapi-document.ts,**/instrumentation.ts,scripts/openapi/merge-client-endpoints.ts,**/playwright/**,**/Dockerfile,**/*.config.cjs,**/*.css,**/templates.ts,**/actions.ts,apps/web/modules/ui/components/icons/*,**/*.json,apps/web/vitestSetup.ts,packages/js-core/src/index.ts,apps/web/tailwind.config.js,apps/web/postcss.config.js,apps/web/next.config.mjs,apps/web/scripts/**,packages/js-core/vitest.setup.ts,**/*.mjs,apps/web/modules/auth/lib/mock-data.ts,apps/web/modules/analysis/components/SingleResponseCard/components/Smileys.tsx,packages/surveys/src/components/general/smileys.tsx,**/cache.ts,apps/web/app/**/billing-confirmation/**,apps/web/modules/ee/billing/**,apps/web/modules/ee/multi-language-surveys/**,apps/web/modules/email/**,apps/web/modules/integrations/**,apps/web/modules/setup/**/intro/**,apps/web/modules/setup/**/signup/**,apps/web/modules/setup/**/layout.tsx,apps/web/modules/survey/follow-ups/**,apps/web/app/share/**,apps/web/modules/ee/contacts/[contactId]/**,apps/web/modules/ee/contacts/components/**,apps/web/modules/ee/two-factor-auth/**,apps/web/lib/posthogServer.ts,apps/web/lib/slack/**,apps/web/lib/notion/**,apps/web/lib/googleSheet/**,apps/web/app/api/google-sheet/**,apps/web/app/api/billing/**,apps/web/lib/airtable/**,apps/web/app/api/v1/integrations/**,apps/web/lib/env.ts,**/instrumentation-node.ts,**/cache/**,**/*.svg,apps/web/modules/ui/components/icons/**,apps/web/modules/ui/components/table/**
+sonar.cpd.exclusions=**/*.test.*,**/*.spec.*,**/*.mdx,**/*.config.mts,**/*.config.ts,**/constants.ts,**/route.ts,**/route.tsx,**/types/**,**/types.ts,**/stories.*,**/*.mock.*,**/mocks/**,**/__mocks__/**,**/openapi.ts,**/openapi-document.ts,**/instrumentation.ts,scripts/openapi/merge-client-endpoints.ts,**/playwright/**,**/Dockerfile,**/*.config.cjs,**/*.css,**/templates.ts,**/actions.ts,apps/web/modules/ui/components/icons/*,**/*.json,apps/web/vitestSetup.ts,apps/web/tailwind.config.js,apps/web/postcss.config.js,apps/web/next.config.mjs,apps/web/scripts/**,packages/js-core/vitest.setup.ts,packages/js-core/src/index.ts,**/*.mjs,apps/web/modules/auth/lib/mock-data.ts,apps/web/modules/analysis/components/SingleResponseCard/components/Smileys.tsx,packages/surveys/src/components/general/smileys.tsx,**/cache.ts,apps/web/app/**/billing-confirmation/**,apps/web/modules/ee/billing/**,apps/web/modules/ee/multi-language-surveys/**,apps/web/modules/email/**,apps/web/modules/integrations/**,apps/web/modules/setup/**/intro/**,apps/web/modules/setup/**/signup/**,apps/web/modules/setup/**/layout.tsx,apps/web/modules/survey/follow-ups/**,apps/web/app/share/**,apps/web/modules/ee/contacts/[contactId]/**,apps/web/modules/ee/contacts/components/**,apps/web/modules/ee/two-factor-auth/**,apps/web/lib/posthogServer.ts,apps/web/lib/slack/**,apps/web/lib/notion/**,apps/web/lib/googleSheet/**,apps/web/app/api/google-sheet/**,apps/web/app/api/billing/**,apps/web/lib/airtable/**,apps/web/app/api/v1/integrations/**,apps/web/lib/env.ts,**/instrumentation-node.ts,**/cache/**,**/*.svg,apps/web/modules/ui/components/icons/**,apps/web/modules/ui/components/table/**