Compare commits

...

1 Commits

Author SHA1 Message Date
Dhruwang
334f5e3f6a refactor: add environment resolution abstraction (Phase 1)
Create a single indirection point (resolver.ts) so all environment lookups
can later be swapped transparently during the environments-deprecation migration.
Update helper.ts to use the resolver instead of direct getEnvironment() calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 14:49:12 +05:30
2 changed files with 90 additions and 13 deletions

View File

@@ -0,0 +1,85 @@
import "server-only";
import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { logger } from "@formbricks/logger";
import { ZId } from "@formbricks/types/common";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
import { validateInputs } from "../utils/validate";
/**
* Resolves an environmentId or projectId to a projectId.
*
* Tries the Environment table first. If no environment is found,
* falls back to the Project table (the caller may already be passing a projectId).
* This is the single indirection point used during the environments-deprecation migration.
*/
export const resolveToProjectId = reactCache(async (environmentIdOrProjectId: string): Promise<string> => {
validateInputs([environmentIdOrProjectId, ZId]);
try {
// Try Environment table first
const environment = await prisma.environment.findUnique({
where: { id: environmentIdOrProjectId },
select: { projectId: true },
});
if (environment) {
return environment.projectId;
}
// Fall back to Project table — the input may already be a projectId
const project = await prisma.project.findUnique({
where: { id: environmentIdOrProjectId },
select: { id: true },
});
if (project) {
return project.id;
}
throw new ResourceNotFoundError("environment or project", environmentIdOrProjectId);
} catch (error) {
if (error instanceof ResourceNotFoundError) {
throw error;
}
if (error instanceof Prisma.PrismaClientKnownRequestError) {
logger.error(error, "Error resolving environment/project id");
throw new DatabaseError(error.message);
}
throw error;
}
});
/**
* Resolves an environmentId to both its own id and the parent projectId.
*
* Unlike `resolveToProjectId`, this requires the input to be a valid environmentId.
*/
export const resolveEnvironmentToProject = reactCache(
async (environmentId: string): Promise<{ projectId: string; environmentId: string }> => {
validateInputs([environmentId, ZId]);
try {
const environment = await prisma.environment.findUnique({
where: { id: environmentId },
select: { projectId: true },
});
if (!environment) {
throw new ResourceNotFoundError("environment", environmentId);
}
return { projectId: environment.projectId, environmentId };
} catch (error) {
if (error instanceof ResourceNotFoundError) {
throw error;
}
if (error instanceof Prisma.PrismaClientKnownRequestError) {
logger.error(error, "Error resolving environment to project");
throw new DatabaseError(error.message);
}
throw error;
}
}
);

View File

@@ -1,9 +1,9 @@
import { ResourceNotFoundError } from "@formbricks/types/errors";
import { resolveEnvironmentToProject } from "@/lib/environment/resolver";
import {
getActionClass,
getApiKey,
getContact,
getEnvironment,
getIntegration,
getInvite,
getLanguage,
@@ -62,12 +62,8 @@ export const getOrganizationIdFromProjectId = async (projectId: string) => {
};
export const getOrganizationIdFromEnvironmentId = async (environmentId: string) => {
const environment = await getEnvironment(environmentId);
if (!environment) {
throw new ResourceNotFoundError("environment", environmentId);
}
return await getOrganizationIdFromProjectId(environment.projectId);
const { projectId } = await resolveEnvironmentToProject(environmentId);
return await getOrganizationIdFromProjectId(projectId);
};
export const getOrganizationIdFromSurveyId = async (surveyId: string) => {
@@ -195,12 +191,8 @@ export const getOrganizationIdFromQuotaId = async (quotaId: string) => {
// project id helpers
export const getProjectIdFromEnvironmentId = async (environmentId: string) => {
const environment = await getEnvironment(environmentId);
if (!environment) {
throw new ResourceNotFoundError("environment", environmentId);
}
return environment.projectId;
const { projectId } = await resolveEnvironmentToProject(environmentId);
return projectId;
};
export const getProjectIdFromSurveyId = async (surveyId: string) => {