mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-24 23:45:23 -06:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a2a8b74c8 | ||
|
|
43d5d3d719 | ||
|
|
5527f184b7 | ||
|
|
7dd5cf8b6e | ||
|
|
aec697f5b9 | ||
|
|
aa2588dd89 | ||
|
|
ed886e1794 | ||
|
|
452709dec7 | ||
|
|
a5cac35cfd | ||
|
|
3ee8485ef0 | ||
|
|
673f61be17 | ||
|
|
db86247510 | ||
|
|
090f6eef71 | ||
|
|
214d18616f | ||
|
|
3b126291a6 |
@@ -25,6 +25,9 @@ NEXTAUTH_SECRET=
|
||||
# You can use: `openssl rand -hex 32` to generate a secure one
|
||||
CRON_SECRET=
|
||||
|
||||
# Set the minimum log level(debug, info, warn, error, fatal)
|
||||
LOG_LEVEL=info
|
||||
|
||||
##############
|
||||
# DATABASE #
|
||||
##############
|
||||
|
||||
4
.github/workflows/sonarqube.yml
vendored
4
.github/workflows/sonarqube.yml
vendored
@@ -46,11 +46,7 @@ jobs:
|
||||
|
||||
- name: Run tests with coverage
|
||||
run: |
|
||||
cd apps/web
|
||||
pnpm test:coverage
|
||||
cd ../../
|
||||
# The Vitest coverage config is in your vite.config.mts
|
||||
|
||||
- name: SonarQube Scan
|
||||
uses: SonarSource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203
|
||||
env:
|
||||
|
||||
31
.github/workflows/terrafrom-plan-and-apply.yml
vendored
31
.github/workflows/terrafrom-plan-and-apply.yml
vendored
@@ -2,12 +2,13 @@ name: 'Terraform'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
# TODO: enable it back when migration is completed.
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - main
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
@@ -57,18 +58,18 @@ jobs:
|
||||
run: terraform plan -out .planfile
|
||||
working-directory: infra/terraform
|
||||
|
||||
- name: Post PR comment
|
||||
uses: borchero/terraform-plan-comment@3399d8dbae8b05185e815e02361ede2949cd99c4 # v2.4.0
|
||||
if: always() && github.ref != 'refs/heads/main' && (steps.validate.outcome == 'success' || steps.validate.outcome == 'failure')
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
planfile: .planfile
|
||||
working-directory: "infra/terraform"
|
||||
skip-comment: true
|
||||
# - name: Post PR comment
|
||||
# uses: borchero/terraform-plan-comment@3399d8dbae8b05185e815e02361ede2949cd99c4 # v2.4.0
|
||||
# if: always() && github.ref != 'refs/heads/main' && (steps.validate.outcome == 'success' || steps.validate.outcome == 'failure')
|
||||
# with:
|
||||
# token: ${{ github.token }}
|
||||
# planfile: .planfile
|
||||
# working-directory: "infra/terraform"
|
||||
# skip-comment: true
|
||||
|
||||
- name: Terraform Apply
|
||||
id: apply
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
# if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
run: terraform apply .planfile
|
||||
working-directory: "infra/terraform"
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"dependencies": {
|
||||
"@formbricks/js": "workspace:*",
|
||||
"lucide-react": "0.468.0",
|
||||
"next": "15.1.2",
|
||||
"next": "15.2.3",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
},
|
||||
|
||||
@@ -85,6 +85,8 @@ COPY --from=installer --chown=nextjs:nextjs /app/packages/database/schema.prisma
|
||||
COPY --from=installer --chown=nextjs:nextjs /app/packages/database/package.json ./packages/database/package.json
|
||||
COPY --from=installer --chown=nextjs:nextjs /app/packages/database/migration ./packages/database/migration
|
||||
COPY --from=installer --chown=nextjs:nextjs /app/packages/database/src ./packages/database/src
|
||||
COPY --from=installer --chown=nextjs:nextjs /app/packages/database/node_modules ./packages/database/node_modules
|
||||
COPY --from=installer --chown=nextjs:nextjs /app/packages/logger/dist ./packages/database/node_modules/@formbricks/logger/dist
|
||||
|
||||
# Copy Prisma-specific generated files
|
||||
COPY --from=installer --chown=nextjs:nextjs /app/node_modules/@prisma/client ./node_modules/@prisma/client
|
||||
@@ -93,14 +95,16 @@ COPY --from=installer --chown=nextjs:nextjs /app/node_modules/.prisma ./node_mod
|
||||
COPY --from=installer --chown=nextjs:nextjs /prisma_version.txt .
|
||||
COPY /docker/cronjobs /app/docker/cronjobs
|
||||
|
||||
# Copy only @paralleldrive/cuid2 and @noble/hashes
|
||||
# Copy required dependencies
|
||||
COPY --from=installer /app/node_modules/@paralleldrive/cuid2 ./node_modules/@paralleldrive/cuid2
|
||||
COPY --from=installer /app/node_modules/@noble/hashes ./node_modules/@noble/hashes
|
||||
COPY --from=installer /app/node_modules/zod ./node_modules/zod
|
||||
|
||||
RUN npm install -g tsx typescript prisma
|
||||
RUN npm install -g tsx typescript prisma pino-pretty
|
||||
|
||||
EXPOSE 3000
|
||||
ENV HOSTNAME "0.0.0.0"
|
||||
ENV NODE_ENV="production"
|
||||
# USER nextjs
|
||||
|
||||
# Prepare volume for uploads
|
||||
@@ -119,4 +123,4 @@ CMD if [ "${DOCKER_CRON_ENABLED:-1}" = "1" ]; then \
|
||||
fi; \
|
||||
(cd packages/database && npm run db:migrate:deploy) && \
|
||||
(cd packages/database && npm run db:create-saml-database:deploy) && \
|
||||
exec node apps/web/server.js
|
||||
exec node apps/web/server.js
|
||||
@@ -1,13 +1,10 @@
|
||||
import { getDefaultEndingCard } from "@/app/lib/templates";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { TFnType } from "@tolgee/react";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
|
||||
import { TXMTemplate } from "@formbricks/types/templates";
|
||||
|
||||
function logError(error: Error, context: string) {
|
||||
console.error(`Error in ${context}:`, error);
|
||||
}
|
||||
|
||||
export const getXMSurveyDefault = (t: TFnType): TXMTemplate => {
|
||||
try {
|
||||
return {
|
||||
@@ -19,7 +16,7 @@ export const getXMSurveyDefault = (t: TFnType): TXMTemplate => {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
logError(error, "getXMSurveyDefault");
|
||||
logger.error(error, "Failed to create default XM survey template");
|
||||
throw error; // Re-throw after logging
|
||||
}
|
||||
};
|
||||
@@ -449,7 +446,7 @@ export const getXMTemplates = (t: TFnType): TXMTemplate[] => {
|
||||
enpsSurvey(t),
|
||||
];
|
||||
} catch (error) {
|
||||
logError(error, "getXMTemplates");
|
||||
logger.error(error, "Unable to load XM templates, returning empty array");
|
||||
return []; // Return an empty array or handle as needed
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,21 +2,14 @@ import { ActionClassesTable } from "@/app/(app)/environments/[environmentId]/act
|
||||
import { ActionClassDataRow } from "@/app/(app)/environments/[environmentId]/actions/components/ActionRowData";
|
||||
import { ActionTableHeading } from "@/app/(app)/environments/[environmentId]/actions/components/ActionTableHeading";
|
||||
import { AddActionModal } from "@/app/(app)/environments/[environmentId]/actions/components/AddActionModal";
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
|
||||
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
|
||||
import { PageHeader } from "@/modules/ui/components/page-header";
|
||||
import { getTranslate } from "@/tolgee/server";
|
||||
import { Metadata } from "next";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getActionClasses } from "@formbricks/lib/actionClass/service";
|
||||
import { getEnvironments } from "@formbricks/lib/environment/service";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
import { getProjectByEnvironmentId } from "@formbricks/lib/project/service";
|
||||
import { findMatchingLocale } from "@formbricks/lib/utils/locale";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -25,51 +18,24 @@ export const metadata: Metadata = {
|
||||
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
const { isReadOnly, project, isBilling, environment } = await getEnvironmentAuth(params.environmentId);
|
||||
|
||||
const t = await getTranslate();
|
||||
const [actionClasses, organization, project] = await Promise.all([
|
||||
getActionClasses(params.environmentId),
|
||||
getOrganizationByEnvironmentId(params.environmentId),
|
||||
getProjectByEnvironmentId(params.environmentId),
|
||||
]);
|
||||
|
||||
const [actionClasses] = await Promise.all([getActionClasses(params.environmentId)]);
|
||||
|
||||
const locale = await findMatchingLocale();
|
||||
|
||||
if (!session) {
|
||||
throw new Error(t("common.session_not_found"));
|
||||
}
|
||||
|
||||
if (!organization) {
|
||||
throw new Error(t("common.organization_not_found"));
|
||||
}
|
||||
|
||||
if (!project) {
|
||||
throw new Error(t("common.project_not_found"));
|
||||
}
|
||||
|
||||
const environments = await getEnvironments(project.id);
|
||||
const currentEnvironment = environments.find((env) => env.id === params.environmentId);
|
||||
|
||||
if (!currentEnvironment) {
|
||||
throw new Error(t("common.environment_not_found"));
|
||||
}
|
||||
|
||||
const otherEnvironment = environments.filter((env) => env.id !== params.environmentId)[0];
|
||||
|
||||
const otherEnvActionClasses = await getActionClasses(otherEnvironment.id);
|
||||
|
||||
const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id);
|
||||
const { isMember, isBilling } = getAccessFlags(currentUserMembership?.role);
|
||||
|
||||
const projectPermission = await getProjectPermissionByUserId(session?.user.id, project.id);
|
||||
|
||||
const { hasReadAccess } = getTeamPermissionFlags(projectPermission);
|
||||
|
||||
if (isBilling) {
|
||||
return redirect(`/environments/${params.environmentId}/settings/billing`);
|
||||
}
|
||||
|
||||
const isReadOnly = isMember && hasReadAccess;
|
||||
|
||||
const renderAddActionButton = () => (
|
||||
<AddActionModal
|
||||
environmentId={params.environmentId}
|
||||
@@ -82,7 +48,7 @@ const Page = async (props) => {
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("common.actions")} cta={!isReadOnly ? renderAddActionButton() : undefined} />
|
||||
<ActionClassesTable
|
||||
environment={currentEnvironment}
|
||||
environment={environment}
|
||||
otherEnvironment={otherEnvironment}
|
||||
otherEnvActionClasses={otherEnvActionClasses}
|
||||
environmentId={params.environmentId}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { TIntegrationAirtableTables } from "@formbricks/types/integration/airtable";
|
||||
|
||||
export const fetchTables = async (environmentId: string, baseId: string) => {
|
||||
@@ -17,7 +18,8 @@ export const authorize = async (environmentId: string, apiHost: string): Promise
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
console.error(res.text);
|
||||
const errorText = await res.text();
|
||||
logger.error({ errorText }, "authorize: Could not fetch airtable config");
|
||||
throw new Error("Could not create response");
|
||||
}
|
||||
const resJSON = await res.json();
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
import { AirtableWrapper } from "@/app/(app)/environments/[environmentId]/integrations/airtable/components/AirtableWrapper";
|
||||
import { getSurveys } from "@/app/(app)/environments/[environmentId]/integrations/lib/surveys";
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
|
||||
import { GoBackButton } from "@/modules/ui/components/go-back-button";
|
||||
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
|
||||
import { PageHeader } from "@/modules/ui/components/page-header";
|
||||
import { getTranslate } from "@/tolgee/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getAirtableTables } from "@formbricks/lib/airtable/service";
|
||||
import { AIRTABLE_CLIENT_ID, WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { getIntegrations } from "@formbricks/lib/integration/service";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
import { getProjectByEnvironmentId } from "@formbricks/lib/project/service";
|
||||
import { findMatchingLocale } from "@formbricks/lib/utils/locale";
|
||||
import { TIntegrationItem } from "@formbricks/types/integration";
|
||||
import { TIntegrationAirtable } from "@formbricks/types/integration/airtable";
|
||||
@@ -24,48 +17,25 @@ const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslate();
|
||||
const isEnabled = !!AIRTABLE_CLIENT_ID;
|
||||
const [session, surveys, integrations, environment] = await Promise.all([
|
||||
getServerSession(authOptions),
|
||||
|
||||
const { isReadOnly, environment } = await getEnvironmentAuth(params.environmentId);
|
||||
|
||||
const [surveys, integrations] = await Promise.all([
|
||||
getSurveys(params.environmentId),
|
||||
getIntegrations(params.environmentId),
|
||||
getEnvironment(params.environmentId),
|
||||
]);
|
||||
|
||||
if (!session) {
|
||||
throw new Error(t("common.session_not_found"));
|
||||
}
|
||||
|
||||
if (!environment) {
|
||||
throw new Error(t("common.environment_not_found"));
|
||||
}
|
||||
const project = await getProjectByEnvironmentId(params.environmentId);
|
||||
if (!project) {
|
||||
throw new Error(t("common.project_not_found"));
|
||||
}
|
||||
|
||||
const airtableIntegration: TIntegrationAirtable | undefined = integrations?.find(
|
||||
(integration): integration is TIntegrationAirtable => integration.type === "airtable"
|
||||
);
|
||||
|
||||
let airtableArray: TIntegrationItem[] = [];
|
||||
if (airtableIntegration && airtableIntegration.config.key) {
|
||||
if (airtableIntegration?.config.key) {
|
||||
airtableArray = await getAirtableTables(params.environmentId);
|
||||
}
|
||||
|
||||
const locale = await findMatchingLocale();
|
||||
|
||||
const currentUserMembership = await getMembershipByUserIdOrganizationId(
|
||||
session?.user.id,
|
||||
project.organizationId
|
||||
);
|
||||
const { isMember } = getAccessFlags(currentUserMembership?.role);
|
||||
|
||||
const projectPermission = await getProjectPermissionByUserId(session?.user.id, environment?.projectId);
|
||||
|
||||
const { hasReadAccess } = getTeamPermissionFlags(projectPermission);
|
||||
|
||||
const isReadOnly = isMember && hasReadAccess;
|
||||
|
||||
if (isReadOnly) {
|
||||
redirect("./");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
export const authorize = async (environmentId: string, apiHost: string): Promise<string> => {
|
||||
const res = await fetch(`${apiHost}/api/google-sheet`, {
|
||||
method: "GET",
|
||||
@@ -5,7 +7,8 @@ export const authorize = async (environmentId: string, apiHost: string): Promise
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
console.error(res.text);
|
||||
const errorText = await res.text();
|
||||
logger.error({ errorText }, "authorize: Could not fetch google sheet config");
|
||||
throw new Error("Could not create response");
|
||||
}
|
||||
const resJSON = await res.json();
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { GoogleSheetWrapper } from "@/app/(app)/environments/[environmentId]/integrations/google-sheets/components/GoogleSheetWrapper";
|
||||
import { getSurveys } from "@/app/(app)/environments/[environmentId]/integrations/lib/surveys";
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
|
||||
import { GoBackButton } from "@/modules/ui/components/go-back-button";
|
||||
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
|
||||
import { PageHeader } from "@/modules/ui/components/page-header";
|
||||
import { getTranslate } from "@/tolgee/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { redirect } from "next/navigation";
|
||||
import {
|
||||
GOOGLE_SHEETS_CLIENT_ID,
|
||||
@@ -15,11 +12,7 @@ import {
|
||||
GOOGLE_SHEETS_REDIRECT_URL,
|
||||
WEBAPP_URL,
|
||||
} from "@formbricks/lib/constants";
|
||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { getIntegrations } from "@formbricks/lib/integration/service";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
import { getProjectByEnvironmentId } from "@formbricks/lib/project/service";
|
||||
import { findMatchingLocale } from "@formbricks/lib/utils/locale";
|
||||
import { TIntegrationGoogleSheets } from "@formbricks/types/integration/google-sheet";
|
||||
|
||||
@@ -27,43 +20,20 @@ const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslate();
|
||||
const isEnabled = !!(GOOGLE_SHEETS_CLIENT_ID && GOOGLE_SHEETS_CLIENT_SECRET && GOOGLE_SHEETS_REDIRECT_URL);
|
||||
const [session, surveys, integrations, environment] = await Promise.all([
|
||||
getServerSession(authOptions),
|
||||
|
||||
const { isReadOnly, environment } = await getEnvironmentAuth(params.environmentId);
|
||||
|
||||
const [surveys, integrations] = await Promise.all([
|
||||
getSurveys(params.environmentId),
|
||||
getIntegrations(params.environmentId),
|
||||
getEnvironment(params.environmentId),
|
||||
]);
|
||||
|
||||
if (!session) {
|
||||
throw new Error(t("common.session_not_found"));
|
||||
}
|
||||
|
||||
if (!environment) {
|
||||
throw new Error(t("common.environment_not_found"));
|
||||
}
|
||||
const project = await getProjectByEnvironmentId(params.environmentId);
|
||||
if (!project) {
|
||||
throw new Error(t("common.project_not_found"));
|
||||
}
|
||||
|
||||
const googleSheetIntegration: TIntegrationGoogleSheets | undefined = integrations?.find(
|
||||
(integration): integration is TIntegrationGoogleSheets => integration.type === "googleSheets"
|
||||
);
|
||||
|
||||
const locale = await findMatchingLocale();
|
||||
|
||||
const currentUserMembership = await getMembershipByUserIdOrganizationId(
|
||||
session?.user.id,
|
||||
project.organizationId
|
||||
);
|
||||
const { isMember } = getAccessFlags(currentUserMembership?.role);
|
||||
|
||||
const projectPermission = await getProjectPermissionByUserId(session?.user.id, environment?.projectId);
|
||||
|
||||
const { hasReadAccess } = getTeamPermissionFlags(projectPermission);
|
||||
|
||||
const isReadOnly = isMember && hasReadAccess;
|
||||
|
||||
if (isReadOnly) {
|
||||
redirect("./");
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { surveyCache } from "@formbricks/lib/survey/cache";
|
||||
import { selectSurvey } from "@formbricks/lib/survey/service";
|
||||
import { transformPrismaSurvey } from "@formbricks/lib/survey/utils";
|
||||
import { validateInputs } from "@formbricks/lib/utils/validate";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
@@ -34,7 +35,7 @@ export const getSurveys = reactCache(
|
||||
return surveysPrisma.map((surveyPrisma) => transformPrismaSurvey<TSurvey>(surveyPrisma));
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
console.error(error);
|
||||
logger.error({ error }, "getSurveys: Could not fetch surveys");
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
throw error;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
export const authorize = async (environmentId: string, apiHost: string): Promise<string> => {
|
||||
const res = await fetch(`${apiHost}/api/v1/integrations/notion`, {
|
||||
method: "GET",
|
||||
@@ -5,7 +7,8 @@ export const authorize = async (environmentId: string, apiHost: string): Promise
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
console.error(res.text);
|
||||
const errorText = await res.text();
|
||||
logger.error({ errorText }, "authorize: Could not fetch notion config");
|
||||
throw new Error("Could not create response");
|
||||
}
|
||||
const resJSON = await res.json();
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { getSurveys } from "@/app/(app)/environments/[environmentId]/integrations/lib/surveys";
|
||||
import { NotionWrapper } from "@/app/(app)/environments/[environmentId]/integrations/notion/components/NotionWrapper";
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
|
||||
import { GoBackButton } from "@/modules/ui/components/go-back-button";
|
||||
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
|
||||
import { PageHeader } from "@/modules/ui/components/page-header";
|
||||
import { getTranslate } from "@/tolgee/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { redirect } from "next/navigation";
|
||||
import {
|
||||
NOTION_AUTH_URL,
|
||||
@@ -16,12 +13,8 @@ import {
|
||||
NOTION_REDIRECT_URI,
|
||||
WEBAPP_URL,
|
||||
} from "@formbricks/lib/constants";
|
||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { getIntegrationByType } from "@formbricks/lib/integration/service";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
import { getNotionDatabases } from "@formbricks/lib/notion/service";
|
||||
import { getProjectByEnvironmentId } from "@formbricks/lib/project/service";
|
||||
import { findMatchingLocale } from "@formbricks/lib/utils/locale";
|
||||
import { TIntegrationNotion, TIntegrationNotionDatabase } from "@formbricks/types/integration/notion";
|
||||
|
||||
@@ -34,44 +27,20 @@ const Page = async (props) => {
|
||||
NOTION_AUTH_URL &&
|
||||
NOTION_REDIRECT_URI
|
||||
);
|
||||
const [session, surveys, notionIntegration, environment] = await Promise.all([
|
||||
getServerSession(authOptions),
|
||||
|
||||
const { isReadOnly, environment } = await getEnvironmentAuth(params.environmentId);
|
||||
|
||||
const [surveys, notionIntegration] = await Promise.all([
|
||||
getSurveys(params.environmentId),
|
||||
getIntegrationByType(params.environmentId, "notion"),
|
||||
getEnvironment(params.environmentId),
|
||||
]);
|
||||
|
||||
if (!session) {
|
||||
throw new Error(t("common.session_not_found"));
|
||||
}
|
||||
|
||||
if (!environment) {
|
||||
throw new Error(t("common.environment_not_found"));
|
||||
}
|
||||
|
||||
const project = await getProjectByEnvironmentId(params.environmentId);
|
||||
if (!project) {
|
||||
throw new Error(t("common.project_not_found"));
|
||||
}
|
||||
|
||||
let databasesArray: TIntegrationNotionDatabase[] = [];
|
||||
if (notionIntegration && (notionIntegration as TIntegrationNotion).config.key?.bot_id) {
|
||||
databasesArray = (await getNotionDatabases(environment.id)) ?? [];
|
||||
}
|
||||
const locale = await findMatchingLocale();
|
||||
|
||||
const currentUserMembership = await getMembershipByUserIdOrganizationId(
|
||||
session?.user.id,
|
||||
project.organizationId
|
||||
);
|
||||
const { isMember } = getAccessFlags(currentUserMembership?.role);
|
||||
|
||||
const projectPermission = await getProjectPermissionByUserId(session?.user.id, environment?.projectId);
|
||||
|
||||
const { hasReadAccess } = getTeamPermissionFlags(projectPermission);
|
||||
|
||||
const isReadOnly = isMember && hasReadAccess;
|
||||
|
||||
if (isReadOnly) {
|
||||
redirect("./");
|
||||
}
|
||||
|
||||
@@ -9,71 +9,40 @@ import notionLogo from "@/images/notion.png";
|
||||
import SlackLogo from "@/images/slacklogo.png";
|
||||
import WebhookLogo from "@/images/webhook.png";
|
||||
import ZapierLogo from "@/images/zapier-small.png";
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
|
||||
import { Card } from "@/modules/ui/components/integration-card";
|
||||
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
|
||||
import { PageHeader } from "@/modules/ui/components/page-header";
|
||||
import { getTranslate } from "@/tolgee/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import Image from "next/image";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { getIntegrations } from "@formbricks/lib/integration/service";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
import { TIntegrationType } from "@formbricks/types/integration";
|
||||
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const environmentId = params.environmentId;
|
||||
const t = await getTranslate();
|
||||
|
||||
const { isReadOnly, environment, isBilling } = await getEnvironmentAuth(params.environmentId);
|
||||
|
||||
const [
|
||||
environment,
|
||||
integrations,
|
||||
organization,
|
||||
session,
|
||||
userWebhookCount,
|
||||
zapierWebhookCount,
|
||||
makeWebhookCount,
|
||||
n8nwebhookCount,
|
||||
activePiecesWebhookCount,
|
||||
] = await Promise.all([
|
||||
getEnvironment(environmentId),
|
||||
getIntegrations(environmentId),
|
||||
getOrganizationByEnvironmentId(params.environmentId),
|
||||
getServerSession(authOptions),
|
||||
getWebhookCountBySource(environmentId, "user"),
|
||||
getWebhookCountBySource(environmentId, "zapier"),
|
||||
getWebhookCountBySource(environmentId, "make"),
|
||||
getWebhookCountBySource(environmentId, "n8n"),
|
||||
getWebhookCountBySource(environmentId, "activepieces"),
|
||||
getIntegrations(params.environmentId),
|
||||
getWebhookCountBySource(params.environmentId, "user"),
|
||||
getWebhookCountBySource(params.environmentId, "zapier"),
|
||||
getWebhookCountBySource(params.environmentId, "make"),
|
||||
getWebhookCountBySource(params.environmentId, "n8n"),
|
||||
getWebhookCountBySource(params.environmentId, "activepieces"),
|
||||
]);
|
||||
|
||||
const isIntegrationConnected = (type: TIntegrationType) =>
|
||||
integrations.some((integration) => integration.type === type);
|
||||
if (!session) {
|
||||
throw new Error(t("common.session_not_found"));
|
||||
}
|
||||
|
||||
if (!organization) {
|
||||
throw new Error(t("common.organization_not_found"));
|
||||
}
|
||||
|
||||
if (!environment) {
|
||||
throw new Error(t("common.environment_not_found"));
|
||||
}
|
||||
|
||||
const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id);
|
||||
const { isMember, isBilling } = getAccessFlags(currentUserMembership?.role);
|
||||
|
||||
const projectPermission = await getProjectPermissionByUserId(session?.user.id, environment?.projectId);
|
||||
|
||||
const { hasReadAccess } = getTeamPermissionFlags(projectPermission);
|
||||
|
||||
const isReadOnly = isMember && hasReadAccess;
|
||||
|
||||
if (isBilling) {
|
||||
return redirect(`/environments/${params.environmentId}/settings/billing`);
|
||||
@@ -244,7 +213,7 @@ const Page = async (props) => {
|
||||
docsHref: "https://formbricks.com/docs/app-surveys/quickstart",
|
||||
docsText: t("common.docs"),
|
||||
docsNewTab: true,
|
||||
connectHref: `/environments/${environmentId}/project/app-connection`,
|
||||
connectHref: `/environments/${params.environmentId}/project/app-connection`,
|
||||
connectText: t("common.connect"),
|
||||
connectNewTab: false,
|
||||
label: "Javascript SDK",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
export const authorize = async (environmentId: string, apiHost: string): Promise<string> => {
|
||||
const res = await fetch(`${apiHost}/api/v1/integrations/slack`, {
|
||||
method: "GET",
|
||||
@@ -5,7 +7,8 @@ export const authorize = async (environmentId: string, apiHost: string): Promise
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
console.error(res.text);
|
||||
const errorText = await res.text();
|
||||
logger.error({ errorText }, "authorize: Could not fetch slack config");
|
||||
throw new Error("Could not create response");
|
||||
}
|
||||
const resJSON = await res.json();
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
import { getSurveys } from "@/app/(app)/environments/[environmentId]/integrations/lib/surveys";
|
||||
import { SlackWrapper } from "@/app/(app)/environments/[environmentId]/integrations/slack/components/SlackWrapper";
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
|
||||
import { GoBackButton } from "@/modules/ui/components/go-back-button";
|
||||
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
|
||||
import { PageHeader } from "@/modules/ui/components/page-header";
|
||||
import { getTranslate } from "@/tolgee/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { redirect } from "next/navigation";
|
||||
import { SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { getIntegrationByType } from "@formbricks/lib/integration/service";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
import { getProjectByEnvironmentId } from "@formbricks/lib/project/service";
|
||||
import { findMatchingLocale } from "@formbricks/lib/utils/locale";
|
||||
import { TIntegrationSlack } from "@formbricks/types/integration/slack";
|
||||
|
||||
@@ -23,40 +16,16 @@ const Page = async (props) => {
|
||||
const isEnabled = !!(SLACK_CLIENT_ID && SLACK_CLIENT_SECRET);
|
||||
|
||||
const t = await getTranslate();
|
||||
const [session, surveys, slackIntegration, environment] = await Promise.all([
|
||||
getServerSession(authOptions),
|
||||
|
||||
const { isReadOnly, environment } = await getEnvironmentAuth(params.environmentId);
|
||||
|
||||
const [surveys, slackIntegration] = await Promise.all([
|
||||
getSurveys(params.environmentId),
|
||||
getIntegrationByType(params.environmentId, "slack"),
|
||||
getEnvironment(params.environmentId),
|
||||
]);
|
||||
|
||||
if (!session) {
|
||||
throw new Error(t("common.session_not_found"));
|
||||
}
|
||||
|
||||
if (!environment) {
|
||||
throw new Error(t("common.environment_not_found"));
|
||||
}
|
||||
|
||||
const project = await getProjectByEnvironmentId(params.environmentId);
|
||||
if (!project) {
|
||||
throw new Error(t("common.project_not_found"));
|
||||
}
|
||||
|
||||
const locale = await findMatchingLocale();
|
||||
|
||||
const currentUserMembership = await getMembershipByUserIdOrganizationId(
|
||||
session?.user.id,
|
||||
project.organizationId
|
||||
);
|
||||
const { isMember } = getAccessFlags(currentUserMembership?.role);
|
||||
|
||||
const projectPermission = await getProjectPermissionByUserId(session?.user.id, environment?.projectId);
|
||||
|
||||
const { hasReadAccess } = getTeamPermissionFlags(projectPermission);
|
||||
|
||||
const isReadOnly = isMember && hasReadAccess;
|
||||
|
||||
if (isReadOnly) {
|
||||
redirect("./");
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { ToasterClient } from "@/modules/ui/components/toaster-client";
|
||||
import { getTranslate } from "@/tolgee/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { redirect } from "next/navigation";
|
||||
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
@@ -49,7 +49,10 @@ const EnvLayout = async (props: {
|
||||
}
|
||||
|
||||
const membership = await getMembershipByUserIdOrganizationId(session.user.id, organization.id);
|
||||
if (!membership) return notFound();
|
||||
|
||||
if (!membership) {
|
||||
throw new Error(t("common.membership_not_found"));
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -157,6 +157,10 @@ const Page = async (props) => {
|
||||
throw new Error(t("common.user_not_found"));
|
||||
}
|
||||
|
||||
if (!memberships) {
|
||||
throw new Error(t("common.membership_not_found"));
|
||||
}
|
||||
|
||||
if (user?.notificationSettings) {
|
||||
user.notificationSettings = setCompleteNotificationSettings(user.notificationSettings, memberships);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import { AccountSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(account)/components/AccountSettingsNavbar";
|
||||
import { AccountSecurity } from "@/app/(app)/environments/[environmentId]/settings/(account)/profile/components/AccountSecurity";
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { getIsMultiOrgEnabled, getIsTwoFactorAuthEnabled } from "@/modules/ee/license-check/lib/utils";
|
||||
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
|
||||
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
|
||||
import { PageHeader } from "@/modules/ui/components/page-header";
|
||||
import { SettingsId } from "@/modules/ui/components/settings-id";
|
||||
import { UpgradePrompt } from "@/modules/ui/components/upgrade-prompt";
|
||||
import { getTranslate } from "@/tolgee/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import {
|
||||
getOrganizationByEnvironmentId,
|
||||
getOrganizationsWhereUserIsSingleOwner,
|
||||
} from "@formbricks/lib/organization/service";
|
||||
import { getOrganizationsWhereUserIsSingleOwner } from "@formbricks/lib/organization/service";
|
||||
import { getUser } from "@formbricks/lib/user/service";
|
||||
import { SettingsCard } from "../../components/SettingsCard";
|
||||
import { DeleteAccount } from "./components/DeleteAccount";
|
||||
@@ -25,20 +21,16 @@ const Page = async (props: { params: Promise<{ environmentId: string }> }) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslate();
|
||||
const { environmentId } = params;
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session) {
|
||||
throw new Error(t("common.session_not_found"));
|
||||
}
|
||||
|
||||
const organization = await getOrganizationByEnvironmentId(environmentId);
|
||||
|
||||
if (!organization) {
|
||||
throw new Error(t("common.organization_not_found"));
|
||||
}
|
||||
const { session } = await getEnvironmentAuth(params.environmentId);
|
||||
|
||||
const organizationsWithSingleOwner = await getOrganizationsWhereUserIsSingleOwner(session.user.id);
|
||||
|
||||
const user = session && session.user ? await getUser(session.user.id) : null;
|
||||
const user = session?.user ? await getUser(session.user.id) : null;
|
||||
|
||||
if (!user) {
|
||||
throw new Error(t("common.user_not_found"));
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar";
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { getEnterpriseLicense } from "@/modules/ee/license-check/lib/utils";
|
||||
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
|
||||
import { PageHeader } from "@/modules/ui/components/page-header";
|
||||
import { getTranslate } from "@/tolgee/server";
|
||||
import { CheckIcon } from "lucide-react";
|
||||
import { getServerSession } from "next-auth";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
@@ -21,20 +17,8 @@ const Page = async (props) => {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const session = await getServerSession(authOptions);
|
||||
const { isMember, currentUserMembership } = await getEnvironmentAuth(params.environmentId);
|
||||
|
||||
const organization = await getOrganizationByEnvironmentId(params.environmentId);
|
||||
|
||||
if (!session) {
|
||||
throw new Error(t("common.session_not_found"));
|
||||
}
|
||||
|
||||
if (!organization) {
|
||||
throw new Error(t("common.organization_not_found"));
|
||||
}
|
||||
|
||||
const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id);
|
||||
const { isMember } = getAccessFlags(currentUserMembership?.role);
|
||||
const isPricingDisabled = isMember;
|
||||
|
||||
if (isPricingDisabled) {
|
||||
|
||||
@@ -60,7 +60,7 @@ export const ShareEmbedSurvey = ({
|
||||
|
||||
const [activeId, setActiveId] = useState(survey.type === "link" ? tabs[0].id : tabs[3].id);
|
||||
const [showView, setShowView] = useState<"start" | "embed" | "panel">("start");
|
||||
const [surveyUrl, setSurveyUrl] = useState(webAppUrl + "/s/" + survey.id);
|
||||
const [surveyUrl, setSurveyUrl] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (survey.type !== "link") {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { getMetadataForLinkSurvey } from "@/modules/survey/link/metadata";
|
||||
import type { Metadata } from "next";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { getShortUrl } from "@formbricks/lib/shortUrl/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { TShortUrl, ZShortUrlId } from "@formbricks/types/short-url";
|
||||
|
||||
export const generateMetadata = async (props): Promise<Metadata> => {
|
||||
@@ -44,7 +45,7 @@ const Page = async (props) => {
|
||||
try {
|
||||
shortUrl = await getShortUrl(params.shortUrlId);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
logger.error(error, "Could not fetch short url");
|
||||
notFound();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { AsyncParser } from "@json2csv/node";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { NextRequest } from "next/server";
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
export const POST = async (request: NextRequest) => {
|
||||
const session = await getServerSession(authOptions);
|
||||
@@ -28,7 +29,7 @@ export const POST = async (request: NextRequest) => {
|
||||
try {
|
||||
csv = await parser.parse(json).promise();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
logger.error({ error: err, url: request.url }, "Failed to convert to CSV");
|
||||
throw new Error("Failed to convert to CSV");
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { surveyCache } from "@formbricks/lib/survey/cache";
|
||||
import { getSurvey, updateSurvey } from "@formbricks/lib/survey/service";
|
||||
import { doesSurveyHasOpenTextQuestion } from "@formbricks/lib/survey/utils";
|
||||
import { validateInputs } from "@formbricks/lib/utils/validate";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { TResponse } from "@formbricks/types/responses";
|
||||
@@ -80,7 +81,7 @@ export const generateInsightsEnabledForSurveyQuestions = async (
|
||||
|
||||
return { success: false };
|
||||
} catch (error) {
|
||||
console.error("Error generating insights for surveys:", error);
|
||||
logger.error(error, "Error generating insights for surveys");
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { headers } from "next/headers";
|
||||
import { z } from "zod";
|
||||
import { CRON_SECRET } from "@formbricks/lib/constants";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { generateInsightsEnabledForSurveyQuestions } from "./lib/utils";
|
||||
|
||||
export const maxDuration = 300; // This function can run for a maximum of 300 seconds
|
||||
@@ -25,7 +26,7 @@ export const POST = async (request: Request) => {
|
||||
const inputValidation = ZGenerateInsightsInput.safeParse(jsonInput);
|
||||
|
||||
if (!inputValidation.success) {
|
||||
console.error(inputValidation.error);
|
||||
logger.error({ error: inputValidation.error, url: request.url }, "Error in POST /api/insights");
|
||||
return responses.badRequestResponse(
|
||||
"Fields are missing or incorrectly formatted",
|
||||
transformErrorToDetails(inputValidation.error),
|
||||
|
||||
@@ -9,6 +9,7 @@ import { writeDataToSlack } from "@formbricks/lib/slack/service";
|
||||
import { getFormattedDateTimeString } from "@formbricks/lib/utils/datetime";
|
||||
import { parseRecallInfo } from "@formbricks/lib/utils/recall";
|
||||
import { truncateText } from "@formbricks/lib/utils/strings";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { Result } from "@formbricks/types/error-handlers";
|
||||
import { TIntegration, TIntegrationType } from "@formbricks/types/integration";
|
||||
import { TIntegrationAirtable } from "@formbricks/types/integration/airtable";
|
||||
@@ -83,13 +84,13 @@ export const handleIntegrations = async (
|
||||
survey
|
||||
);
|
||||
if (!googleResult.ok) {
|
||||
console.error("Error in google sheets integration: ", googleResult.error);
|
||||
logger.error(googleResult.error, "Error in google sheets integration");
|
||||
}
|
||||
break;
|
||||
case "slack":
|
||||
const slackResult = await handleSlackIntegration(integration as TIntegrationSlack, data, survey);
|
||||
if (!slackResult.ok) {
|
||||
console.error("Error in slack integration: ", slackResult.error);
|
||||
logger.error(slackResult.error, "Error in slack integration");
|
||||
}
|
||||
break;
|
||||
case "airtable":
|
||||
@@ -99,13 +100,13 @@ export const handleIntegrations = async (
|
||||
survey
|
||||
);
|
||||
if (!airtableResult.ok) {
|
||||
console.error("Error in airtable integration: ", airtableResult.error);
|
||||
logger.error(airtableResult.error, "Error in airtable integration");
|
||||
}
|
||||
break;
|
||||
case "notion":
|
||||
const notionResult = await handleNotionIntegration(integration as TIntegrationNotion, data, survey);
|
||||
if (!notionResult.ok) {
|
||||
console.error("Error in notion integration: ", notionResult.error);
|
||||
logger.error(notionResult.error, "Error in notion integration");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -418,7 +419,7 @@ const getValue = (colType: string, value: string | string[] | Date | number | Re
|
||||
return typeof value === "string" ? value : (value as string[]).join(", ");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
logger.error(error, "Payload build failed!");
|
||||
throw new Error("Payload build failed!");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { sendFollowUpEmail } from "@/modules/email";
|
||||
import { z } from "zod";
|
||||
import { TSurveyFollowUpAction } from "@formbricks/database/types/survey-follow-up";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { TOrganization } from "@formbricks/types/organizations";
|
||||
import { TResponse } from "@formbricks/types/responses";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
@@ -89,6 +90,6 @@ export const sendSurveyFollowUps = async (
|
||||
.map((result) => `FollowUp ${result.followUpId} failed: ${result.error}`);
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.error("Follow-up processing errors:", errors);
|
||||
logger.error(errors, "Follow-up processing errors");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@ import { getSurvey, updateSurvey } from "@formbricks/lib/survey/service";
|
||||
import { convertDatesInObject } from "@formbricks/lib/time";
|
||||
import { getPromptText } from "@formbricks/lib/utils/ai";
|
||||
import { parseRecallInfo } from "@formbricks/lib/utils/recall";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { handleIntegrations } from "./lib/handleIntegrations";
|
||||
|
||||
export const POST = async (request: Request) => {
|
||||
@@ -34,7 +35,10 @@ export const POST = async (request: Request) => {
|
||||
const inputValidation = ZPipelineInput.safeParse(convertedJsonInput);
|
||||
|
||||
if (!inputValidation.success) {
|
||||
console.error(inputValidation.error);
|
||||
logger.error(
|
||||
{ error: inputValidation.error, url: request.url },
|
||||
"Error in POST /api/(internal)/pipeline"
|
||||
);
|
||||
return responses.badRequestResponse(
|
||||
"Fields are missing or incorrectly formatted",
|
||||
transformErrorToDetails(inputValidation.error),
|
||||
@@ -87,7 +91,7 @@ export const POST = async (request: Request) => {
|
||||
data: response,
|
||||
}),
|
||||
}).catch((error) => {
|
||||
console.error(`Webhook call to ${webhook.url} failed:`, error);
|
||||
logger.error({ error, url: request.url }, `Webhook call to ${webhook.url} failed`);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -100,7 +104,7 @@ export const POST = async (request: Request) => {
|
||||
]);
|
||||
|
||||
if (!survey) {
|
||||
console.error(`Survey with id ${surveyId} not found`);
|
||||
logger.error({ url: request.url, surveyId }, `Survey with id ${surveyId} not found`);
|
||||
return new Response("Survey not found", { status: 404 });
|
||||
}
|
||||
|
||||
@@ -172,7 +176,10 @@ export const POST = async (request: Request) => {
|
||||
|
||||
const emailPromises = usersWithNotifications.map((user) =>
|
||||
sendResponseFinishedEmail(user.email, environmentId, survey, response, responseCount).catch((error) => {
|
||||
console.error(`Failed to send email to ${user.email}:`, error);
|
||||
logger.error(
|
||||
{ error, url: request.url, userEmail: user.email },
|
||||
`Failed to send email to ${user.email}`
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -188,7 +195,7 @@ export const POST = async (request: Request) => {
|
||||
const results = await Promise.allSettled([...webhookPromises, ...emailPromises]);
|
||||
results.forEach((result) => {
|
||||
if (result.status === "rejected") {
|
||||
console.error("Promise rejected:", result.reason);
|
||||
logger.error({ error: result.reason, url: request.url }, "Promise rejected");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -228,7 +235,7 @@ export const POST = async (request: Request) => {
|
||||
text,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
logger.error({ error: e, url: request.url }, "Error creating document and assigning insight");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,7 +247,7 @@ export const POST = async (request: Request) => {
|
||||
const results = await Promise.allSettled(webhookPromises);
|
||||
results.forEach((result) => {
|
||||
if (result.status === "rejected") {
|
||||
console.error("Promise rejected:", result.reason);
|
||||
logger.error({ error: result.reason, url: request.url }, "Promise rejected");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from "@formbricks/lib/posthogServer";
|
||||
import { getProjectByEnvironmentId } from "@formbricks/lib/project/service";
|
||||
import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ZJsPeopleUserIdInput } from "@formbricks/types/js";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
|
||||
@@ -103,7 +104,7 @@ export const GET = async (
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error sending plan limits reached event to Posthog: ${error}`);
|
||||
logger.error({ error, url: request.url }, `Error sending plan limits reached event to Posthog`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,7 +188,10 @@ export const GET = async (
|
||||
|
||||
return responses.successResponse({ ...state }, true);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
logger.error(
|
||||
{ error, url: request.url },
|
||||
"Error in GET /api/v1/client/[environmentId]/app/sync/[userId]"
|
||||
);
|
||||
return responses.internalServerErrorResponse("Unable to handle the request: " + error.message, true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ import { getSurveys } from "@formbricks/lib/survey/service";
|
||||
import { anySurveyHasFilters } from "@formbricks/lib/survey/utils";
|
||||
import { diffInDays } from "@formbricks/lib/utils/datetime";
|
||||
import { validateInputs } from "@formbricks/lib/utils/validate";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
@@ -150,7 +151,7 @@ export const getSyncSurveys = reactCache(
|
||||
return surveys;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { responses } from "@/app/lib/api/response";
|
||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils";
|
||||
import { capturePosthogEnvironmentEvent } from "@formbricks/lib/posthogServer";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ZDisplayCreateInput } from "@formbricks/types/displays";
|
||||
import { InvalidInputError } from "@formbricks/types/errors";
|
||||
import { createDisplay } from "./lib/display";
|
||||
@@ -48,7 +49,7 @@ export const POST = async (request: Request, context: Context): Promise<Response
|
||||
if (error instanceof InvalidInputError) {
|
||||
return responses.badRequestResponse(error.message);
|
||||
} else {
|
||||
console.error(error);
|
||||
logger.error({ error, url: request.url }, "Error in POST /api/v1/client/[environmentId]/displays");
|
||||
return responses.internalServerErrorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "@formbricks/lib/posthogServer";
|
||||
import { projectCache } from "@formbricks/lib/project/cache";
|
||||
import { surveyCache } from "@formbricks/lib/survey/cache";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { TJsEnvironmentState } from "@formbricks/types/js";
|
||||
import { getActionClassesForEnvironmentState } from "./actionClass";
|
||||
@@ -89,7 +90,7 @@ export const getEnvironmentState = async (
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Error sending plan limits reached event to Posthog: ${err}`);
|
||||
logger.error(err, "Error sending plan limits reached event to Posthog");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { prisma } from "@formbricks/database";
|
||||
import { cache } from "@formbricks/lib/cache";
|
||||
import { projectCache } from "@formbricks/lib/project/cache";
|
||||
import { validateInputs } from "@formbricks/lib/utils/validate";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
import { TJsEnvironmentStateProject } from "@formbricks/types/js";
|
||||
@@ -35,7 +36,7 @@ export const getProjectForEnvironmentState = reactCache(
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
console.error(error);
|
||||
logger.error(error, "Error getting project for environment state");
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
throw error;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { prisma } from "@formbricks/database";
|
||||
import { cache } from "@formbricks/lib/cache";
|
||||
import { surveyCache } from "@formbricks/lib/survey/cache";
|
||||
import { validateInputs } from "@formbricks/lib/utils/validate";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
import { TJsEnvironmentStateSurvey } from "@formbricks/types/js";
|
||||
@@ -80,7 +81,7 @@ export const getSurveysForEnvironmentState = reactCache(
|
||||
return surveysPrisma.map((survey) => transformPrismaSurvey<TJsEnvironmentStateSurvey>(survey));
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
console.error(error);
|
||||
logger.error(error, "Error getting surveys for environment state");
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
throw error;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { responses } from "@/app/lib/api/response";
|
||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { NextRequest } from "next/server";
|
||||
import { environmentCache } from "@formbricks/lib/environment/cache";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { ZJsSyncInput } from "@formbricks/types/js";
|
||||
|
||||
@@ -11,7 +12,7 @@ export const OPTIONS = async (): Promise<Response> => {
|
||||
};
|
||||
|
||||
export const GET = async (
|
||||
_: NextRequest,
|
||||
request: NextRequest,
|
||||
props: {
|
||||
params: Promise<{
|
||||
environmentId: string;
|
||||
@@ -58,11 +59,14 @@ export const GET = async (
|
||||
return responses.notFoundResponse(err.resourceType, err.resourceId);
|
||||
}
|
||||
|
||||
console.error(err);
|
||||
logger.error(
|
||||
{ error: err, url: request.url },
|
||||
"Error in GET /api/v1/client/[environmentId]/environment"
|
||||
);
|
||||
return responses.internalServerErrorResponse(err.message, true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
logger.error({ error, url: request.url }, "Error in GET /api/v1/client/[environmentId]/environment");
|
||||
return responses.internalServerErrorResponse("Unable to handle the request: " + error.message, true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { sendToPipeline } from "@/app/lib/pipelines";
|
||||
import { updateResponse } from "@formbricks/lib/response/service";
|
||||
import { getSurvey } from "@formbricks/lib/survey/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { DatabaseError, InvalidInputError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { ZResponseUpdateInput } from "@formbricks/types/responses";
|
||||
|
||||
@@ -45,7 +46,10 @@ export const PUT = async (
|
||||
return responses.badRequestResponse(error.message);
|
||||
}
|
||||
if (error instanceof DatabaseError) {
|
||||
console.error(error);
|
||||
logger.error(
|
||||
{ error, url: request.url },
|
||||
"Error in PUT /api/v1/client/[environmentId]/responses/[responseId]"
|
||||
);
|
||||
return responses.internalServerErrorResponse(error.message);
|
||||
}
|
||||
}
|
||||
@@ -59,7 +63,10 @@ export const PUT = async (
|
||||
return responses.badRequestResponse(error.message);
|
||||
}
|
||||
if (error instanceof DatabaseError) {
|
||||
console.error(error);
|
||||
logger.error(
|
||||
{ error, url: request.url },
|
||||
"Error in PUT /api/v1/client/[environmentId]/responses/[responseId]"
|
||||
);
|
||||
return responses.internalServerErrorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { calculateTtcTotal } from "@formbricks/lib/response/utils";
|
||||
import { responseNoteCache } from "@formbricks/lib/responseNote/cache";
|
||||
import { captureTelemetry } from "@formbricks/lib/telemetry";
|
||||
import { validateInputs } from "@formbricks/lib/utils/validate";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { TContactAttributes } from "@formbricks/types/contact-attribute";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { TResponse, TResponseInput, ZResponseInput } from "@formbricks/types/responses";
|
||||
@@ -178,7 +179,7 @@ export const createResponse = async (responseInput: TResponseInput): Promise<TRe
|
||||
});
|
||||
} catch (err) {
|
||||
// Log error but do not throw
|
||||
console.error(`Error sending plan limits reached event to Posthog: ${err}`);
|
||||
logger.error(err, "Error sending plan limits reached event to Posthog");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { headers } from "next/headers";
|
||||
import { UAParser } from "ua-parser-js";
|
||||
import { capturePosthogEnvironmentEvent } from "@formbricks/lib/posthogServer";
|
||||
import { getSurvey } from "@formbricks/lib/survey/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { InvalidInputError } from "@formbricks/types/errors";
|
||||
import { TResponse, TResponseInput, ZResponseInput } from "@formbricks/types/responses";
|
||||
@@ -107,7 +108,7 @@ export const POST = async (request: Request, context: Context): Promise<Response
|
||||
if (error instanceof InvalidInputError) {
|
||||
return responses.badRequestResponse(error.message);
|
||||
} else {
|
||||
console.error(error);
|
||||
logger.error({ error, url: request.url }, "Error creating response");
|
||||
return responses.internalServerErrorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { validateLocalSignedUrl } from "@formbricks/lib/crypto";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
import { putFileToLocalStorage } from "@formbricks/lib/storage/service";
|
||||
import { getSurvey } from "@formbricks/lib/survey/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
interface Context {
|
||||
params: Promise<{
|
||||
@@ -125,7 +126,7 @@ export const POST = async (req: NextRequest, context: Context): Promise<Response
|
||||
message: "File uploaded successfully",
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("err: ", err);
|
||||
logger.error({ error: err, url: req.url }, "Error in POST /api/v1/client/[environmentId]/upload");
|
||||
if (err.name === "FileTooLargeError") {
|
||||
return responses.badRequestResponse(err.message);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { fetchAirtableAuthToken } from "@formbricks/lib/airtable/service";
|
||||
import { AIRTABLE_CLIENT_ID, WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
|
||||
import { createOrUpdateIntegration } from "@formbricks/lib/integration/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
const getEmail = async (token: string) => {
|
||||
const req_ = await fetch("https://api.airtable.com/v0/meta/whoami", {
|
||||
@@ -77,7 +78,7 @@ export const GET = async (req: NextRequest) => {
|
||||
await createOrUpdateIntegration(environmentId, airtableIntegrationInput);
|
||||
return Response.redirect(`${WEBAPP_URL}/environments/${environmentId}/integrations/airtable`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
logger.error({ error, url: req.url }, "Error in GET /api/v1/integrations/airtable/callback");
|
||||
responses.internalServerErrorResponse(error);
|
||||
}
|
||||
responses.badRequestResponse("unknown error occurred");
|
||||
|
||||
@@ -2,6 +2,7 @@ import { authenticateRequest, handleErrorResponse } from "@/app/api/v1/auth";
|
||||
import { responses } from "@/app/lib/api/response";
|
||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { deleteActionClass, getActionClass, updateActionClass } from "@formbricks/lib/actionClass/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { TActionClass, ZActionClassInput } from "@formbricks/types/action-classes";
|
||||
import { TAuthenticationApiKey } from "@formbricks/types/auth";
|
||||
|
||||
@@ -54,7 +55,7 @@ export const PUT = async (
|
||||
try {
|
||||
actionClassUpdate = await request.json();
|
||||
} catch (error) {
|
||||
console.error(`Error parsing JSON: ${error}`);
|
||||
logger.error({ error, url: request.url }, "Error parsing JSON");
|
||||
return responses.badRequestResponse("Malformed JSON input, please check your request body");
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { authenticateRequest } from "@/app/api/v1/auth";
|
||||
import { responses } from "@/app/lib/api/response";
|
||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { createActionClass, getActionClasses } from "@formbricks/lib/actionClass/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { TActionClass, ZActionClassInput } from "@formbricks/types/action-classes";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
|
||||
@@ -28,7 +29,7 @@ export const POST = async (request: Request): Promise<Response> => {
|
||||
try {
|
||||
actionClassInput = await request.json();
|
||||
} catch (error) {
|
||||
console.error(`Error parsing JSON input: ${error}`);
|
||||
logger.error({ error, url: request.url }, "Error parsing JSON input");
|
||||
return responses.badRequestResponse("Malformed JSON input, please check your request body");
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
|
||||
import { deleteResponse, getResponse, updateResponse } from "@formbricks/lib/response/service";
|
||||
import { getSurvey } from "@formbricks/lib/survey/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { TResponse, ZResponseUpdateInput } from "@formbricks/types/responses";
|
||||
|
||||
const fetchAndValidateResponse = async (authentication: any, responseId: string): Promise<TResponse> => {
|
||||
@@ -77,7 +78,7 @@ export const PUT = async (
|
||||
try {
|
||||
responseUpdate = await request.json();
|
||||
} catch (error) {
|
||||
console.error(`Error parsing JSON: ${error}`);
|
||||
logger.error({ error, url: request.url }, "Error parsing JSON");
|
||||
return responses.badRequestResponse("Malformed JSON input, please check your request body");
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { calculateTtcTotal } from "@formbricks/lib/response/utils";
|
||||
import { responseNoteCache } from "@formbricks/lib/responseNote/cache";
|
||||
import { captureTelemetry } from "@formbricks/lib/telemetry";
|
||||
import { validateInputs } from "@formbricks/lib/utils/validate";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { TContactAttributes } from "@formbricks/types/contact-attribute";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { TResponse, TResponseInput, ZResponseInput } from "@formbricks/types/responses";
|
||||
@@ -178,7 +179,7 @@ export const createResponse = async (responseInput: TResponseInput): Promise<TRe
|
||||
});
|
||||
} catch (err) {
|
||||
// Log error but do not throw
|
||||
console.error(`Error sending plan limits reached event to Posthog: ${err}`);
|
||||
logger.error(err, "Error sending plan limits reached event to Posthog");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { NextRequest } from "next/server";
|
||||
import { getResponses, getResponsesByEnvironmentId } from "@formbricks/lib/response/service";
|
||||
import { getSurvey } from "@formbricks/lib/survey/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { DatabaseError, InvalidInputError } from "@formbricks/types/errors";
|
||||
import { TResponse, ZResponseInput } from "@formbricks/types/responses";
|
||||
import { createResponse } from "./lib/response";
|
||||
@@ -45,7 +46,7 @@ export const POST = async (request: Request): Promise<Response> => {
|
||||
try {
|
||||
jsonInput = await request.json();
|
||||
} catch (err) {
|
||||
console.error(`Error parsing JSON input: ${err}`);
|
||||
logger.error({ error: err, url: request.url }, "Error parsing JSON input");
|
||||
return responses.badRequestResponse("Malformed JSON input, please check your request body");
|
||||
}
|
||||
|
||||
@@ -92,7 +93,7 @@ export const POST = async (request: Request): Promise<Response> => {
|
||||
if (error instanceof InvalidInputError) {
|
||||
return responses.badRequestResponse(error.message);
|
||||
} else {
|
||||
console.error(error);
|
||||
logger.error({ error, url: request.url }, "Error in POST /api/v1/management/responses");
|
||||
return responses.internalServerErrorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { NextRequest } from "next/server";
|
||||
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { getSignedUrlForPublicFile } from "./lib/getSignedUrl";
|
||||
|
||||
// api endpoint for uploading public files
|
||||
@@ -17,7 +18,7 @@ export const POST = async (req: NextRequest): Promise<Response> => {
|
||||
try {
|
||||
storageInput = await req.json();
|
||||
} catch (error) {
|
||||
console.error(`Error parsing JSON input: ${error}`);
|
||||
logger.error({ error, url: req.url }, "Error parsing JSON input");
|
||||
return responses.badRequestResponse("Malformed JSON input, please check your request body");
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { segmentCache } from "@formbricks/lib/cache/segment";
|
||||
import { responseCache } from "@formbricks/lib/response/cache";
|
||||
import { surveyCache } from "@formbricks/lib/survey/cache";
|
||||
import { validateInputs } from "@formbricks/lib/utils/validate";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
|
||||
export const deleteSurvey = async (surveyId: string) => {
|
||||
@@ -67,7 +68,7 @@ export const deleteSurvey = async (surveyId: string) => {
|
||||
return deletedSurvey;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
console.error(error);
|
||||
logger.error({ error, surveyId }, "Error deleting survey");
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getMultiLanguagePermission } from "@/modules/ee/license-check/lib/utils
|
||||
import { getSurveyFollowUpsPermission } from "@/modules/survey/follow-ups/lib/utils";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
import { getSurvey, updateSurvey } from "@formbricks/lib/survey/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { TSurvey, ZSurveyUpdateInput } from "@formbricks/types/surveys/types";
|
||||
|
||||
const fetchAndAuthorizeSurvey = async (authentication: any, surveyId: string): Promise<TSurvey | null> => {
|
||||
@@ -79,7 +80,7 @@ export const PUT = async (
|
||||
try {
|
||||
surveyUpdate = await request.json();
|
||||
} catch (error) {
|
||||
console.error(`Error parsing JSON input: ${error}`);
|
||||
logger.error({ error, url: request.url }, "Error parsing JSON input");
|
||||
return responses.badRequestResponse("Malformed JSON input, please check your request body");
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { getMultiLanguagePermission } from "@/modules/ee/license-check/lib/utils
|
||||
import { getSurveyFollowUpsPermission } from "@/modules/survey/follow-ups/lib/utils";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
import { createSurvey, getSurveys } from "@formbricks/lib/survey/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
import { ZSurveyCreateInput } from "@formbricks/types/surveys/types";
|
||||
|
||||
@@ -41,7 +42,7 @@ export const POST = async (request: Request): Promise<Response> => {
|
||||
try {
|
||||
surveyInput = await request.json();
|
||||
} catch (error) {
|
||||
console.error(`Error parsing JSON: ${error}`);
|
||||
logger.error({ error, url: request.url }, "Error parsing JSON");
|
||||
return responses.badRequestResponse("Malformed JSON input, please check your request body");
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { getEnvironmentIdFromApiKey } from "@/app/api/v1/lib/api-key";
|
||||
import { deleteWebhook, getWebhook } from "@/app/api/v1/webhooks/[webhookId]/lib/webhook";
|
||||
import { responses } from "@/app/lib/api/response";
|
||||
import { headers } from "next/headers";
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
export const GET = async (_: Request, props: { params: Promise<{ webhookId: string }> }) => {
|
||||
const params = await props.params;
|
||||
@@ -26,7 +27,7 @@ export const GET = async (_: Request, props: { params: Promise<{ webhookId: stri
|
||||
return responses.successResponse(webhook);
|
||||
};
|
||||
|
||||
export const DELETE = async (_: Request, props: { params: Promise<{ webhookId: string }> }) => {
|
||||
export const DELETE = async (request: Request, props: { params: Promise<{ webhookId: string }> }) => {
|
||||
const params = await props.params;
|
||||
const headersList = await headers();
|
||||
const apiKey = headersList.get("x-api-key");
|
||||
@@ -52,7 +53,7 @@ export const DELETE = async (_: Request, props: { params: Promise<{ webhookId: s
|
||||
const webhook = await deleteWebhook(params.webhookId);
|
||||
return responses.successResponse(webhook);
|
||||
} catch (e) {
|
||||
console.error(e.message);
|
||||
logger.error({ error: e, url: request.url }, "Error deleting webhook");
|
||||
return responses.notFoundResponse("Webhook", params.webhookId);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { responses } from "@/app/lib/api/response";
|
||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils";
|
||||
import { capturePosthogEnvironmentEvent } from "@formbricks/lib/posthogServer";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { InvalidInputError } from "@formbricks/types/errors";
|
||||
import { createDisplay } from "./lib/display";
|
||||
|
||||
@@ -48,7 +49,7 @@ export const POST = async (request: Request, context: Context): Promise<Response
|
||||
if (error instanceof InvalidInputError) {
|
||||
return responses.badRequestResponse(error.message);
|
||||
} else {
|
||||
console.error(error);
|
||||
logger.error({ error, url: request.url }, "Error creating display");
|
||||
return responses.internalServerErrorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { calculateTtcTotal } from "@formbricks/lib/response/utils";
|
||||
import { responseNoteCache } from "@formbricks/lib/responseNote/cache";
|
||||
import { captureTelemetry } from "@formbricks/lib/telemetry";
|
||||
import { validateInputs } from "@formbricks/lib/utils/validate";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { TContactAttributes } from "@formbricks/types/contact-attribute";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { TResponse, ZResponseInput } from "@formbricks/types/responses";
|
||||
@@ -129,7 +130,7 @@ export const createResponse = async (responseInput: TResponseInputV2): Promise<T
|
||||
});
|
||||
} catch (err) {
|
||||
// Log error but do not throw
|
||||
console.error(`Error sending plan limits reached event to Posthog: ${err}`);
|
||||
logger.error(err, "Error sending plan limits reached event to Posthog");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { headers } from "next/headers";
|
||||
import { UAParser } from "ua-parser-js";
|
||||
import { capturePosthogEnvironmentEvent } from "@formbricks/lib/posthogServer";
|
||||
import { getSurvey } from "@formbricks/lib/survey/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { InvalidInputError } from "@formbricks/types/errors";
|
||||
import { TResponse } from "@formbricks/types/responses";
|
||||
@@ -108,7 +109,7 @@ export const POST = async (request: Request, context: Context): Promise<Response
|
||||
if (error instanceof InvalidInputError) {
|
||||
return responses.badRequestResponse(error.message);
|
||||
} else {
|
||||
console.error(error);
|
||||
logger.error({ error, url: request.url }, "Error creating response");
|
||||
return responses.internalServerErrorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { GET } from "@/modules/api/v2/management/surveys/[surveyId]/contact-links/contacts/[contactId]/route";
|
||||
|
||||
export { GET };
|
||||
4
apps/web/app/c/[jwt]/page.tsx
Normal file
4
apps/web/app/c/[jwt]/page.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
import { ContactSurveyPage, generateMetadata } from "@/modules/survey/link/contact-survey/page";
|
||||
|
||||
export { generateMetadata };
|
||||
export default ContactSurveyPage;
|
||||
@@ -1,5 +1,6 @@
|
||||
import { TPipelineInput } from "@/app/lib/types/pipelines";
|
||||
import { CRON_SECRET, WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
export const sendToPipeline = async ({ event, surveyId, environmentId, response }: TPipelineInput) => {
|
||||
return fetch(`${WEBAPP_URL}/api/pipeline`, {
|
||||
@@ -15,6 +16,6 @@ export const sendToPipeline = async ({ event, surveyId, environmentId, response
|
||||
response,
|
||||
}),
|
||||
}).catch((error) => {
|
||||
console.error(`Error sending event to pipeline: ${error}`);
|
||||
logger.error(error, "Error sending event to pipeline");
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { LRUCache } from "lru-cache";
|
||||
import { ENTERPRISE_LICENSE_KEY, REDIS_HTTP_URL } from "@formbricks/lib/constants";
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
interface Options {
|
||||
interval: number;
|
||||
@@ -28,8 +29,7 @@ const redisRateLimiter = (options: Options) => async (token: string) => {
|
||||
}
|
||||
const tokenCountResponse = await fetch(`${REDIS_HTTP_URL}/INCR/${token}`);
|
||||
if (!tokenCountResponse.ok) {
|
||||
// eslint-disable-next-line no-console -- need for debugging
|
||||
console.error("Failed to increment token count in Redis", tokenCountResponse);
|
||||
logger.error({ tokenCountResponse }, "Failed to increment token count in Redis");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from "@opentelemetry/resources";
|
||||
import { MeterProvider } from "@opentelemetry/sdk-metrics";
|
||||
import { env } from "@formbricks/lib/env";
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
const exporter = new PrometheusExporter({
|
||||
port: env.PROMETHEUS_EXPORTER_PORT ? parseInt(env.PROMETHEUS_EXPORTER_PORT) : 9464,
|
||||
@@ -51,7 +52,7 @@ process.on("SIGTERM", async () => {
|
||||
await meterProvider.shutdown();
|
||||
// Possibly close other instrumentation resources
|
||||
} catch (e) {
|
||||
console.error("Error during graceful shutdown:", e);
|
||||
logger.error(e, "Error during graceful shutdown");
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { DEFAULT_SERVER_ERROR_MESSAGE, createSafeActionClient } from "next-safe-action";
|
||||
import { getUser } from "@formbricks/lib/user/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import {
|
||||
AuthenticationError,
|
||||
AuthorizationError,
|
||||
@@ -25,7 +26,7 @@ export const actionClient = createSafeActionClient({
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console -- This error needs to be logged for debugging server-side errors
|
||||
console.error("SERVER ERROR: ", e);
|
||||
logger.error(e, "SERVER ERROR");
|
||||
return DEFAULT_SERVER_ERROR_MESSAGE;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -85,6 +85,7 @@ export const middleware = async (originalRequest: NextRequest) => {
|
||||
});
|
||||
|
||||
request.headers.set("x-request-id", uuidv4());
|
||||
request.headers.set("x-start-time", Date.now().toString());
|
||||
|
||||
// Create a new NextResponse object to forward the new request with headers
|
||||
const nextResponseWithCustomHeader = NextResponse.next({
|
||||
|
||||
@@ -6,10 +6,17 @@ interface SurveyLinkDisplayProps {
|
||||
|
||||
export const SurveyLinkDisplay = ({ surveyUrl }: SurveyLinkDisplayProps) => {
|
||||
return (
|
||||
<Input
|
||||
autoFocus={true}
|
||||
className="mt-2 w-full min-w-96 text-ellipsis rounded-lg border bg-white px-4 py-2 text-slate-800 caret-transparent"
|
||||
defaultValue={surveyUrl}
|
||||
/>
|
||||
<>
|
||||
{surveyUrl ? (
|
||||
<Input
|
||||
autoFocus={true}
|
||||
className="mt-2 w-full min-w-96 text-ellipsis rounded-lg border bg-white px-4 py-2 text-slate-800 caret-transparent"
|
||||
value={surveyUrl}
|
||||
/>
|
||||
) : (
|
||||
//loading state
|
||||
<div className="mt-2 h-10 w-full min-w-96 animate-pulse rounded-lg bg-slate-100 px-4 py-2 text-slate-800 caret-transparent"></div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -80,6 +80,7 @@ export const ShareSurveyLink = ({
|
||||
<Button
|
||||
title={t("environments.surveys.preview_survey_in_a_new_tab")}
|
||||
aria-label={t("environments.surveys.preview_survey_in_a_new_tab")}
|
||||
disabled={!surveyUrl}
|
||||
onClick={() => {
|
||||
let previewUrl = surveyUrl;
|
||||
if (previewUrl.includes("?")) {
|
||||
@@ -93,6 +94,7 @@ export const ShareSurveyLink = ({
|
||||
<SquareArrowOutUpRight />
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!surveyUrl}
|
||||
variant="secondary"
|
||||
title={t("environments.surveys.copy_survey_link_to_clipboard")}
|
||||
aria-label={t("environments.surveys.copy_survey_link_to_clipboard")}
|
||||
@@ -108,11 +110,13 @@ export const ShareSurveyLink = ({
|
||||
title={t("environments.surveys.summary.download_qr_code")}
|
||||
aria-label={t("environments.surveys.summary.download_qr_code")}
|
||||
size={"icon"}
|
||||
disabled={!surveyUrl}
|
||||
onClick={downloadQRCode}>
|
||||
<QrCode style={{ width: "24px", height: "24px" }} />
|
||||
</Button>
|
||||
{survey.singleUse?.enabled && (
|
||||
<Button
|
||||
disabled={!surveyUrl}
|
||||
title="Regenerate single use survey link"
|
||||
aria-label="Regenerate single use survey link"
|
||||
onClick={generateNewSingleUseLink}>
|
||||
|
||||
@@ -105,7 +105,7 @@ export const ResponseNotes = ({
|
||||
!isOpen && unresolvedNotes.length && "group/hint cursor-pointer bg-white hover:-right-3",
|
||||
!isOpen && !unresolvedNotes.length && "cursor-pointer bg-slate-50",
|
||||
isOpen
|
||||
? "-right-5 top-0 h-5/6 max-h-[600px] w-1/4 bg-white"
|
||||
? "-right-2 top-0 h-5/6 max-h-[600px] w-1/4 bg-white"
|
||||
: unresolvedNotes.length
|
||||
? "right-0 top-[8.33%] h-5/6 max-h-[600px] w-1/12"
|
||||
: "right-[120px] top-[8.333%] h-5/6 max-h-[600px] w-1/12 group-hover:right-[0]"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error";
|
||||
import { type LimitOptions, Ratelimit, type RatelimitResponse } from "@unkey/ratelimit";
|
||||
import { MANAGEMENT_API_RATE_LIMIT, RATE_LIMITING_DISABLED, UNKEY_ROOT_KEY } from "@formbricks/lib/constants";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { Result, err, okVoid } from "@formbricks/types/error-handlers";
|
||||
|
||||
export type RateLimitHelper = {
|
||||
@@ -18,7 +19,7 @@ let warningDisplayed = false;
|
||||
/** Prevent flooding the logs while testing/building */
|
||||
function logOnce(message: string) {
|
||||
if (warningDisplayed) return;
|
||||
console.warn(message);
|
||||
logger.warn(message);
|
||||
warningDisplayed = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
vi.mock("@formbricks/logger", () => ({
|
||||
logger: {
|
||||
warn: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@unkey/ratelimit", () => ({
|
||||
Ratelimit: vi.fn(),
|
||||
@@ -16,18 +23,18 @@ describe("when rate limiting is disabled", () => {
|
||||
});
|
||||
|
||||
test("should log a warning once and return a stubbed response", async () => {
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
const loggerSpy = vi.spyOn(logger, "warn");
|
||||
const { rateLimiter } = await import("@/modules/api/v2/lib/rate-limit");
|
||||
|
||||
const res1 = await rateLimiter()({ identifier: "test-id" });
|
||||
expect(res1).toEqual({ success: true, limit: 10, remaining: 999, reset: 0 });
|
||||
expect(warnSpy).toHaveBeenCalledWith("Rate limiting disabled");
|
||||
expect(loggerSpy).toHaveBeenCalled();
|
||||
|
||||
// Subsequent calls won't log again.
|
||||
await rateLimiter()({ identifier: "another-id" });
|
||||
|
||||
expect(warnSpy).toHaveBeenCalledTimes(1);
|
||||
warnSpy.mockRestore();
|
||||
expect(loggerSpy).toHaveBeenCalledTimes(1);
|
||||
loggerSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,14 +51,14 @@ describe("when UNKEY_ROOT_KEY is missing", () => {
|
||||
});
|
||||
|
||||
test("should log a warning about missing UNKEY_ROOT_KEY and return stub response", async () => {
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
const loggerSpy = vi.spyOn(logger, "warn");
|
||||
const { rateLimiter } = await import("@/modules/api/v2/lib/rate-limit");
|
||||
const limiterFunc = rateLimiter();
|
||||
|
||||
const res = await limiterFunc({ identifier: "test-id" });
|
||||
expect(res).toEqual({ success: true, limit: 10, remaining: 999, reset: 0 });
|
||||
expect(warnSpy).toHaveBeenCalledWith("Disabled due to not finding UNKEY_ROOT_KEY env variable");
|
||||
warnSpy.mockRestore();
|
||||
expect(loggerSpy).toHaveBeenCalled();
|
||||
loggerSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { ZodError } from "zod";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { formatZodError, handleApiError, logApiError, logApiRequest } from "../utils";
|
||||
|
||||
const mockRequest = new Request("http://localhost");
|
||||
@@ -128,38 +129,77 @@ describe("utils", () => {
|
||||
|
||||
describe("logApiRequest", () => {
|
||||
test("logs API request details", () => {
|
||||
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
||||
// Mock the withContext method and its returned info method
|
||||
const infoMock = vi.fn();
|
||||
const withContextMock = vi.fn().mockReturnValue({
|
||||
info: infoMock,
|
||||
});
|
||||
|
||||
// Replace the original withContext with our mock
|
||||
const originalWithContext = logger.withContext;
|
||||
logger.withContext = withContextMock;
|
||||
|
||||
const mockRequest = new Request("http://localhost/api/test?apikey=123&token=abc&safeParam=value");
|
||||
mockRequest.headers.set("x-request-id", "123");
|
||||
mockRequest.headers.set("x-start-time", Date.now().toString());
|
||||
|
||||
logApiRequest(mockRequest, 200, 100);
|
||||
logApiRequest(mockRequest, 200);
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
`[API REQUEST DETAILS] GET /api/test - 200 - 100ms\n correlationId: 123\n queryParams: {"safeParam":"value"}`
|
||||
);
|
||||
// Verify withContext was called
|
||||
expect(withContextMock).toHaveBeenCalled();
|
||||
// Verify info was called on the child logger
|
||||
expect(infoMock).toHaveBeenCalledWith("API Request Details");
|
||||
|
||||
consoleLogSpy.mockRestore();
|
||||
// Restore the original method
|
||||
logger.withContext = originalWithContext;
|
||||
});
|
||||
|
||||
test("logs API request details without correlationId and without safe query params", () => {
|
||||
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
||||
// Mock the withContext method and its returned info method
|
||||
const infoMock = vi.fn();
|
||||
const withContextMock = vi.fn().mockReturnValue({
|
||||
info: infoMock,
|
||||
});
|
||||
|
||||
// Replace the original withContext with our mock
|
||||
const originalWithContext = logger.withContext;
|
||||
logger.withContext = withContextMock;
|
||||
|
||||
const mockRequest = new Request("http://localhost/api/test?apikey=123&token=abc");
|
||||
mockRequest.headers.delete("x-request-id");
|
||||
mockRequest.headers.set("x-start-time", (Date.now() - 100).toString());
|
||||
|
||||
logApiRequest(mockRequest, 200, 100);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
`[API REQUEST DETAILS] GET /api/test - 200 - 100ms\n queryParams: {}`
|
||||
logApiRequest(mockRequest, 200);
|
||||
|
||||
// Verify withContext was called with the expected context
|
||||
expect(withContextMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
method: "GET",
|
||||
path: "/api/test",
|
||||
responseStatus: 200,
|
||||
queryParams: {},
|
||||
})
|
||||
);
|
||||
|
||||
consoleLogSpy.mockRestore();
|
||||
// Verify info was called on the child logger
|
||||
expect(infoMock).toHaveBeenCalledWith("API Request Details");
|
||||
|
||||
// Restore the original method
|
||||
logger.withContext = originalWithContext;
|
||||
});
|
||||
});
|
||||
|
||||
describe("logApiError", () => {
|
||||
test("logs API error details", () => {
|
||||
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
// Mock the withContext method and its returned error method
|
||||
const errorMock = vi.fn();
|
||||
const withContextMock = vi.fn().mockReturnValue({
|
||||
error: errorMock,
|
||||
});
|
||||
|
||||
// Replace the original withContext with our mock
|
||||
const originalWithContext = logger.withContext;
|
||||
logger.withContext = withContextMock;
|
||||
|
||||
const mockRequest = new Request("http://localhost/api/test");
|
||||
mockRequest.headers.set("x-request-id", "123");
|
||||
@@ -171,15 +211,29 @@ describe("utils", () => {
|
||||
|
||||
logApiError(mockRequest, error);
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
`[API ERROR DETAILS]\n correlationId: 123\n error: ${JSON.stringify(error, null, 2)}`
|
||||
);
|
||||
// Verify withContext was called with the expected context
|
||||
expect(withContextMock).toHaveBeenCalledWith({
|
||||
correlationId: "123",
|
||||
error,
|
||||
});
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
// Verify error was called on the child logger
|
||||
expect(errorMock).toHaveBeenCalledWith("API Error Details");
|
||||
|
||||
// Restore the original method
|
||||
logger.withContext = originalWithContext;
|
||||
});
|
||||
|
||||
test("logs API error details without correlationId", () => {
|
||||
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
// Mock the withContext method and its returned error method
|
||||
const errorMock = vi.fn();
|
||||
const withContextMock = vi.fn().mockReturnValue({
|
||||
error: errorMock,
|
||||
});
|
||||
|
||||
// Replace the original withContext with our mock
|
||||
const originalWithContext = logger.withContext;
|
||||
logger.withContext = withContextMock;
|
||||
|
||||
const mockRequest = new Request("http://localhost/api/test");
|
||||
mockRequest.headers.delete("x-request-id");
|
||||
@@ -191,11 +245,17 @@ describe("utils", () => {
|
||||
|
||||
logApiError(mockRequest, error);
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
`[API ERROR DETAILS]\n error: ${JSON.stringify(error, null, 2)}`
|
||||
);
|
||||
// Verify withContext was called with the expected context
|
||||
expect(withContextMock).toHaveBeenCalledWith({
|
||||
correlationId: "",
|
||||
error,
|
||||
});
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
// Verify error was called on the child logger
|
||||
expect(errorMock).toHaveBeenCalledWith("API Error Details");
|
||||
|
||||
// Restore the original method
|
||||
logger.withContext = originalWithContext;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { responses } from "@/modules/api/v2/lib/response";
|
||||
import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error";
|
||||
import { ZodError } from "zod";
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
export const handleApiError = (request: Request, err: ApiErrorResponseV2): Response => {
|
||||
logApiError(request, err);
|
||||
@@ -40,11 +41,12 @@ export const formatZodError = (error: ZodError) => {
|
||||
}));
|
||||
};
|
||||
|
||||
export const logApiRequest = (request: Request, responseStatus: number, duration: number): void => {
|
||||
export const logApiRequest = (request: Request, responseStatus: number): void => {
|
||||
const method = request.method;
|
||||
const url = new URL(request.url);
|
||||
const path = url.pathname;
|
||||
const correlationId = request.headers.get("x-request-id") || "";
|
||||
const startTime = request.headers.get("x-start-time") || "";
|
||||
const queryParams = Object.fromEntries(url.searchParams.entries());
|
||||
|
||||
const sensitiveParams = ["apikey", "token", "secret"];
|
||||
@@ -52,14 +54,25 @@ export const logApiRequest = (request: Request, responseStatus: number, duration
|
||||
Object.entries(queryParams).filter(([key]) => !sensitiveParams.includes(key.toLowerCase()))
|
||||
);
|
||||
|
||||
console.log(
|
||||
`[API REQUEST DETAILS] ${method} ${path} - ${responseStatus} - ${duration}ms${correlationId ? `\n correlationId: ${correlationId}` : ""}\n queryParams: ${JSON.stringify(safeQueryParams)}`
|
||||
);
|
||||
// Info: Conveys general, operational messages about system progress and state.
|
||||
logger
|
||||
.withContext({
|
||||
method,
|
||||
path,
|
||||
responseStatus,
|
||||
duration: `${Date.now() - parseInt(startTime)} ms`,
|
||||
correlationId,
|
||||
queryParams: safeQueryParams,
|
||||
})
|
||||
.info("API Request Details");
|
||||
};
|
||||
|
||||
export const logApiError = (request: Request, error: ApiErrorResponseV2): void => {
|
||||
const correlationId = request.headers.get("x-request-id") || "";
|
||||
console.error(
|
||||
`[API ERROR DETAILS]${correlationId ? `\n correlationId: ${correlationId}` : ""}\n error: ${JSON.stringify(error, null, 2)}`
|
||||
);
|
||||
logger
|
||||
.withContext({
|
||||
correlationId,
|
||||
error,
|
||||
})
|
||||
.error("API Error Details");
|
||||
};
|
||||
|
||||
@@ -75,7 +75,6 @@ export const apiWrapper = async <S extends ExtendedSchemas>({
|
||||
|
||||
if (schemas?.params) {
|
||||
const paramsObject = (await externalParams) || {};
|
||||
console.log("paramsObject: ", paramsObject);
|
||||
const paramsResult = schemas.params.safeParse(paramsObject);
|
||||
if (!paramsResult.success) {
|
||||
throw err({
|
||||
|
||||
@@ -14,8 +14,6 @@ export const authenticatedApiClient = async <S extends ExtendedSchemas>({
|
||||
rateLimit?: boolean;
|
||||
handler: HandlerFn<ParsedSchemas<S>>;
|
||||
}): Promise<Response> => {
|
||||
const startTime = Date.now();
|
||||
|
||||
const response = await apiWrapper({
|
||||
request,
|
||||
schemas,
|
||||
@@ -23,10 +21,9 @@ export const authenticatedApiClient = async <S extends ExtendedSchemas>({
|
||||
rateLimit,
|
||||
handler,
|
||||
});
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
logApiRequest(request, response.status, duration);
|
||||
if (response.ok) {
|
||||
logApiRequest(request, response.status);
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { environmentId, fileUploadQuestion, openTextQuestion, responseData } from "./__mocks__/utils.mock";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { deleteFile } from "@formbricks/lib/storage/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { okVoid } from "@formbricks/types/error-handlers";
|
||||
import { findAndDeleteUploadedFilesInResponse } from "../utils";
|
||||
|
||||
vi.mock("@formbricks/logger", () => ({
|
||||
logger: {
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@formbricks/lib/storage/service", () => ({
|
||||
deleteFile: vi.fn(),
|
||||
}));
|
||||
@@ -37,15 +44,15 @@ describe("findAndDeleteUploadedFilesInResponse", () => {
|
||||
[fileUploadQuestion.id]: [invalidFileUrl],
|
||||
};
|
||||
|
||||
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
const loggerSpy = vi.spyOn(logger, "error");
|
||||
|
||||
const result = await findAndDeleteUploadedFilesInResponse(responseData, [fileUploadQuestion]);
|
||||
|
||||
expect(deleteFile).not.toHaveBeenCalled();
|
||||
expect(consoleErrorSpy).toHaveBeenCalled();
|
||||
expect(loggerSpy).toHaveBeenCalled();
|
||||
expect(result).toEqual(okVoid());
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
loggerSpy.mockRestore();
|
||||
});
|
||||
|
||||
test("process multiple file URLs", async () => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error";
|
||||
import { Response, Survey } from "@prisma/client";
|
||||
import { deleteFile } from "@formbricks/lib/storage/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { Result, okVoid } from "@formbricks/types/error-handlers";
|
||||
import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
|
||||
|
||||
@@ -26,7 +27,7 @@ export const findAndDeleteUploadedFilesInResponse = async (
|
||||
}
|
||||
return deleteFile(environmentId, accessType as "private" | "public", fileName);
|
||||
} catch (error) {
|
||||
console.error(`Failed to delete file ${fileUrl}:`, error);
|
||||
logger.error({ error, fileUrl }, "Failed to delete file");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import { responseCache } from "@formbricks/lib/response/cache";
|
||||
import { calculateTtcTotal } from "@formbricks/lib/response/utils";
|
||||
import { responseNoteCache } from "@formbricks/lib/responseNote/cache";
|
||||
import { captureTelemetry } from "@formbricks/lib/telemetry";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { Result, err, ok } from "@formbricks/types/error-handlers";
|
||||
|
||||
export const createResponse = async (
|
||||
@@ -110,7 +111,7 @@ export const createResponse = async (
|
||||
});
|
||||
} catch (err) {
|
||||
// Log error but do not throw it
|
||||
console.error(`Error sending plan limits reached event to Posthog: ${err}`);
|
||||
logger.error(err, "Error sending plan limits reached event to Posthog");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { getContact } from "./contacts";
|
||||
|
||||
vi.mock("@formbricks/database", () => ({
|
||||
prisma: {
|
||||
contact: {
|
||||
findUnique: vi.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe("getContact", () => {
|
||||
const mockContactId = "cm8fj8ry6000008l5daam88nc";
|
||||
const mockEnvironmentId = "cm8fj8xt3000108l5art7594h";
|
||||
const mockContact = {
|
||||
id: mockContactId,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("returns contact when found", async () => {
|
||||
vi.mocked(prisma.contact.findUnique).mockResolvedValue(mockContact);
|
||||
|
||||
const result = await getContact(mockContactId, mockEnvironmentId);
|
||||
|
||||
expect(prisma.contact.findUnique).toHaveBeenCalledWith({
|
||||
where: {
|
||||
id: mockContactId,
|
||||
environmentId: mockEnvironmentId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
if (result.ok) {
|
||||
expect(result.data).toEqual(mockContact);
|
||||
}
|
||||
});
|
||||
|
||||
test("returns null when contact not found", async () => {
|
||||
vi.mocked(prisma.contact.findUnique).mockResolvedValue(null);
|
||||
|
||||
const result = await getContact(mockContactId, mockEnvironmentId);
|
||||
|
||||
expect(prisma.contact.findUnique).toHaveBeenCalled();
|
||||
if (!result.ok) {
|
||||
expect(result.error).toEqual({
|
||||
details: [
|
||||
{
|
||||
field: "contact",
|
||||
issue: "not found",
|
||||
},
|
||||
],
|
||||
type: "not_found",
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import { contactCache } from "@/lib/cache/contact";
|
||||
import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error";
|
||||
import { Contact } from "@prisma/client";
|
||||
import { cache as reactCache } from "react";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { cache } from "@formbricks/lib/cache";
|
||||
import { Result, err, ok } from "@formbricks/types/error-handlers";
|
||||
|
||||
export const getContact = reactCache(async (contactId: string, environmentId: string) =>
|
||||
cache(
|
||||
async (): Promise<Result<Pick<Contact, "id">, ApiErrorResponseV2>> => {
|
||||
try {
|
||||
const contact = await prisma.contact.findUnique({
|
||||
where: {
|
||||
id: contactId,
|
||||
environmentId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!contact) {
|
||||
return err({ type: "not_found", details: [{ field: "contact", issue: "not found" }] });
|
||||
}
|
||||
|
||||
return ok(contact);
|
||||
} catch (error) {
|
||||
return err({ type: "internal_server_error", details: [{ field: "contact", issue: error.message }] });
|
||||
}
|
||||
},
|
||||
[`contact-link-getContact-${contactId}-${environmentId}`],
|
||||
{
|
||||
tags: [contactCache.tag.byId(contactId), contactCache.tag.byEnvironmentId(environmentId)],
|
||||
}
|
||||
)()
|
||||
);
|
||||
@@ -0,0 +1,61 @@
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { getResponse } from "./response";
|
||||
|
||||
vi.mock("@formbricks/database", () => ({
|
||||
prisma: {
|
||||
response: {
|
||||
findFirst: vi.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe("getResponse", () => {
|
||||
const mockContactId = "cm8fj8xt3000108l5art7594h";
|
||||
const mockSurveyId = "cm8fj9962000208l56jcu94i5";
|
||||
const mockResponse = {
|
||||
id: "cm8fj9gqp000308l5ab7y800j",
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("returns response when found", async () => {
|
||||
vi.mocked(prisma.response.findFirst).mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await getResponse(mockContactId, mockSurveyId);
|
||||
|
||||
expect(prisma.response.findFirst).toHaveBeenCalledWith({
|
||||
where: {
|
||||
contactId: mockContactId,
|
||||
surveyId: mockSurveyId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
if (result.ok) {
|
||||
expect(result.data).toEqual(mockResponse);
|
||||
}
|
||||
});
|
||||
|
||||
test("returns null when response not found", async () => {
|
||||
vi.mocked(prisma.response.findFirst).mockResolvedValue(null);
|
||||
|
||||
const result = await getResponse(mockContactId, mockSurveyId);
|
||||
|
||||
expect(prisma.response.findFirst).toHaveBeenCalled();
|
||||
if (!result.ok) {
|
||||
expect(result.error).toEqual({
|
||||
details: [
|
||||
{
|
||||
field: "response",
|
||||
issue: "not found",
|
||||
},
|
||||
],
|
||||
type: "not_found",
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error";
|
||||
import { Response } from "@prisma/client";
|
||||
import { cache as reactCache } from "react";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { cache } from "@formbricks/lib/cache";
|
||||
import { responseCache } from "@formbricks/lib/response/cache";
|
||||
import { Result, err, ok } from "@formbricks/types/error-handlers";
|
||||
|
||||
export const getResponse = reactCache(async (contactId: string, surveyId: string) =>
|
||||
cache(
|
||||
async (): Promise<Result<Pick<Response, "id">, ApiErrorResponseV2>> => {
|
||||
try {
|
||||
const response = await prisma.response.findFirst({
|
||||
where: {
|
||||
contactId,
|
||||
surveyId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response) {
|
||||
return err({ type: "not_found", details: [{ field: "response", issue: "not found" }] });
|
||||
}
|
||||
|
||||
return ok(response);
|
||||
} catch (error) {
|
||||
return err({ type: "internal_server_error", details: [{ field: "response", issue: error.message }] });
|
||||
}
|
||||
},
|
||||
[`contact-link-getResponse-${contactId}-${surveyId}`],
|
||||
{
|
||||
tags: [responseCache.tag.byId(contactId), responseCache.tag.bySurveyId(surveyId)],
|
||||
}
|
||||
)()
|
||||
);
|
||||
@@ -0,0 +1,61 @@
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { getSurvey } from "./surveys";
|
||||
|
||||
vi.mock("@formbricks/database", () => ({
|
||||
prisma: {
|
||||
survey: {
|
||||
findUnique: vi.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe("getSurvey", () => {
|
||||
const mockSurveyId = "cm8fj9psb000408l50e1x4c6f";
|
||||
const mockSurvey = {
|
||||
id: mockSurveyId,
|
||||
type: "web",
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("returns survey when found", async () => {
|
||||
vi.mocked(prisma.survey.findUnique).mockResolvedValue(mockSurvey);
|
||||
|
||||
const result = await getSurvey(mockSurveyId);
|
||||
|
||||
expect(prisma.survey.findUnique).toHaveBeenCalledWith({
|
||||
where: {
|
||||
id: mockSurveyId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
type: true,
|
||||
},
|
||||
});
|
||||
if (result.ok) {
|
||||
expect(result.data).toEqual(mockSurvey);
|
||||
}
|
||||
});
|
||||
|
||||
test("returns null when survey not found", async () => {
|
||||
vi.mocked(prisma.survey.findUnique).mockResolvedValue(null);
|
||||
|
||||
const result = await getSurvey(mockSurveyId);
|
||||
|
||||
expect(prisma.survey.findUnique).toHaveBeenCalled();
|
||||
if (!result.ok) {
|
||||
expect(result.error).toEqual({
|
||||
details: [
|
||||
{
|
||||
field: "survey",
|
||||
issue: "not found",
|
||||
},
|
||||
],
|
||||
type: "not_found",
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error";
|
||||
import { Survey } from "@prisma/client";
|
||||
import { cache as reactCache } from "react";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { cache } from "@formbricks/lib/cache";
|
||||
import { surveyCache } from "@formbricks/lib/survey/cache";
|
||||
import { Result, err, ok } from "@formbricks/types/error-handlers";
|
||||
|
||||
export const getSurvey = reactCache(async (surveyId: string) =>
|
||||
cache(
|
||||
async (): Promise<Result<Pick<Survey, "id" | "type">, ApiErrorResponseV2>> => {
|
||||
try {
|
||||
const survey = await prisma.survey.findUnique({
|
||||
where: { id: surveyId },
|
||||
select: {
|
||||
id: true,
|
||||
type: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!survey) {
|
||||
return err({ type: "not_found", details: [{ field: "survey", issue: "not found" }] });
|
||||
}
|
||||
|
||||
return ok(survey);
|
||||
} catch (error) {
|
||||
return err({ type: "internal_server_error", details: [{ field: "survey", issue: error.message }] });
|
||||
}
|
||||
},
|
||||
[`contact-link-getSurvey-${surveyId}`],
|
||||
{
|
||||
tags: [surveyCache.tag.byId(surveyId)],
|
||||
}
|
||||
)()
|
||||
);
|
||||
@@ -0,0 +1,111 @@
|
||||
import { responses } from "@/modules/api/v2/lib/response";
|
||||
import { handleApiError } from "@/modules/api/v2/lib/utils";
|
||||
import { authenticatedApiClient } from "@/modules/api/v2/management/auth/authenticated-api-client";
|
||||
import { checkAuthorization } from "@/modules/api/v2/management/auth/check-authorization";
|
||||
import { getEnvironmentId } from "@/modules/api/v2/management/lib/helper";
|
||||
import { getContact } from "@/modules/api/v2/management/surveys/[surveyId]/contact-links/contacts/[contactId]/lib/contacts";
|
||||
import { getResponse } from "@/modules/api/v2/management/surveys/[surveyId]/contact-links/contacts/[contactId]/lib/response";
|
||||
import { getSurvey } from "@/modules/api/v2/management/surveys/[surveyId]/contact-links/contacts/[contactId]/lib/surveys";
|
||||
import { getContactSurveyLink } from "@/modules/ee/contacts/lib/contact-survey-link";
|
||||
import { z } from "zod";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
|
||||
const ZContactLinkParams = z.object({
|
||||
surveyId: ZId,
|
||||
contactId: ZId,
|
||||
});
|
||||
|
||||
export const GET = async (
|
||||
request: Request,
|
||||
props: { params: Promise<{ surveyId: string; contactId: string }> }
|
||||
) =>
|
||||
authenticatedApiClient({
|
||||
request,
|
||||
externalParams: props.params,
|
||||
schemas: {
|
||||
params: ZContactLinkParams,
|
||||
},
|
||||
handler: async ({ authentication, parsedInput }) => {
|
||||
const { params } = parsedInput;
|
||||
|
||||
if (!params) {
|
||||
return handleApiError(request, {
|
||||
type: "bad_request",
|
||||
details: [{ field: "params", issue: "missing" }],
|
||||
});
|
||||
}
|
||||
|
||||
const environmentIdResult = await getEnvironmentId(params.surveyId, false);
|
||||
|
||||
if (!environmentIdResult.ok) {
|
||||
return handleApiError(request, environmentIdResult.error);
|
||||
}
|
||||
|
||||
const environmentId = environmentIdResult.data;
|
||||
|
||||
const checkAuthorizationResult = await checkAuthorization({
|
||||
authentication,
|
||||
environmentId,
|
||||
});
|
||||
|
||||
if (!checkAuthorizationResult.ok) {
|
||||
return handleApiError(request, checkAuthorizationResult.error);
|
||||
}
|
||||
|
||||
const surveyResult = await getSurvey(params.surveyId);
|
||||
|
||||
if (!surveyResult.ok) {
|
||||
return handleApiError(request, surveyResult.error);
|
||||
}
|
||||
|
||||
const survey = surveyResult.data;
|
||||
|
||||
if (!survey) {
|
||||
return handleApiError(request, {
|
||||
type: "not_found",
|
||||
details: [{ field: "surveyId", issue: "Not found" }],
|
||||
});
|
||||
}
|
||||
|
||||
if (survey.type !== "link") {
|
||||
return handleApiError(request, {
|
||||
type: "bad_request",
|
||||
details: [{ field: "surveyId", issue: "Not a link survey" }],
|
||||
});
|
||||
}
|
||||
|
||||
// Check if contact exists and belongs to the environment
|
||||
const contactResult = await getContact(params.contactId, environmentId);
|
||||
|
||||
if (!contactResult.ok) {
|
||||
return handleApiError(request, contactResult.error);
|
||||
}
|
||||
|
||||
const contact = contactResult.data;
|
||||
|
||||
if (!contact) {
|
||||
return handleApiError(request, {
|
||||
type: "not_found",
|
||||
details: [{ field: "contactId", issue: "Not found" }],
|
||||
});
|
||||
}
|
||||
|
||||
// Check if contact has already responded to this survey
|
||||
const existingResponseResult = await getResponse(params.contactId, params.surveyId);
|
||||
|
||||
if (existingResponseResult.ok) {
|
||||
return handleApiError(request, {
|
||||
type: "bad_request",
|
||||
details: [{ field: "contactId", issue: "Already responded" }],
|
||||
});
|
||||
}
|
||||
|
||||
const surveyUrlResult = getContactSurveyLink(params.contactId, params.surveyId, 7);
|
||||
|
||||
if (!surveyUrlResult.ok) {
|
||||
return handleApiError(request, surveyUrlResult.error);
|
||||
}
|
||||
|
||||
return responses.successResponse({ data: { surveyUrl: surveyUrlResult.data } });
|
||||
},
|
||||
});
|
||||
@@ -80,4 +80,5 @@ const document = createDocument({
|
||||
],
|
||||
});
|
||||
|
||||
// do not replace this with logger.info
|
||||
console.log(yaml.stringify(document));
|
||||
|
||||
@@ -11,6 +11,7 @@ import { WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import { verifyInviteToken } from "@formbricks/lib/jwt";
|
||||
import { createMembership } from "@formbricks/lib/membership/service";
|
||||
import { getUser, updateUser } from "@formbricks/lib/user/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ContentLayout } from "./components/content-layout";
|
||||
|
||||
interface InvitePageProps {
|
||||
@@ -131,7 +132,7 @@ export const InvitePage = async (props: InvitePageProps) => {
|
||||
</ContentLayout>
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
logger.error(e, "Error in InvitePage");
|
||||
return (
|
||||
<ContentLayout
|
||||
headline={t("auth.invite.invite_not_found")}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from "@formbricks/lib/constants";
|
||||
import { symmetricDecrypt, symmetricEncrypt } from "@formbricks/lib/crypto";
|
||||
import { verifyToken } from "@formbricks/lib/jwt";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { TUser } from "@formbricks/types/user";
|
||||
import { createBrevoCustomer } from "./brevo";
|
||||
|
||||
@@ -51,7 +52,7 @@ export const authOptions: NextAuthOptions = {
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
logger.error(e, "Error in CredentialsProvider authorize");
|
||||
throw Error("Internal server error. Please try again later");
|
||||
}
|
||||
if (!user) {
|
||||
@@ -69,7 +70,7 @@ export const authOptions: NextAuthOptions = {
|
||||
|
||||
if (user.twoFactorEnabled && credentials.backupCode) {
|
||||
if (!ENCRYPTION_KEY) {
|
||||
console.error("Missing encryption key; cannot proceed with backup code login.");
|
||||
logger.error("Missing encryption key; cannot proceed with backup code login.");
|
||||
throw new Error("Internal Server Error");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Response } from "node-fetch";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { validateInputs } from "@formbricks/lib/utils/validate";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { createBrevoCustomer } from "./brevo";
|
||||
|
||||
vi.mock("@formbricks/lib/constants", () => ({
|
||||
@@ -35,17 +36,17 @@ describe("createBrevoCustomer", () => {
|
||||
});
|
||||
|
||||
it("should log an error if fetch fails", async () => {
|
||||
const consoleSpy = vi.spyOn(console, "error");
|
||||
const loggerSpy = vi.spyOn(logger, "error");
|
||||
|
||||
vi.mocked(global.fetch).mockRejectedValueOnce(new Error("Fetch failed"));
|
||||
|
||||
await createBrevoCustomer({ id: "123", email: "test@example.com" });
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith("Error sending user to Brevo:", expect.any(Error));
|
||||
expect(loggerSpy).toHaveBeenCalledWith(expect.any(Error), "Error sending user to Brevo");
|
||||
});
|
||||
|
||||
it("should log the error response if fetch status is not 200", async () => {
|
||||
const consoleSpy = vi.spyOn(console, "error");
|
||||
const loggerSpy = vi.spyOn(logger, "error");
|
||||
|
||||
vi.mocked(global.fetch).mockResolvedValueOnce(
|
||||
new Response("Bad Request", { status: 400, statusText: "Bad Request" })
|
||||
@@ -53,6 +54,6 @@ describe("createBrevoCustomer", () => {
|
||||
|
||||
await createBrevoCustomer({ id: "123", email: "test@example.com" });
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith("Error sending user to Brevo:", "Bad Request");
|
||||
expect(loggerSpy).toHaveBeenCalledWith({ errorText: "Bad Request" }, "Error sending user to Brevo");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { BREVO_API_KEY, BREVO_LIST_ID } from "@formbricks/lib/constants";
|
||||
import { validateInputs } from "@formbricks/lib/utils/validate";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { TUserEmail, ZUserEmail } from "@formbricks/types/user";
|
||||
|
||||
@@ -34,9 +35,10 @@ export const createBrevoCustomer = async ({ id, email }: { id: string; email: TU
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
console.error("Error sending user to Brevo:", await res.text());
|
||||
const errorText = await res.text();
|
||||
logger.error({ errorText }, "Error sending user to Brevo");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error sending user to Brevo:", error);
|
||||
logger.error(error, "Error sending user to Brevo");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,3 +3,9 @@ export type TOidcNameFields = {
|
||||
family_name?: string;
|
||||
preferred_username?: string;
|
||||
};
|
||||
|
||||
export type TSamlNameFields = {
|
||||
name?: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ConnectionAPIController } from "@boxyhq/saml-jackson/dist/controller/ap
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { SAML_PRODUCT, SAML_TENANT, SAML_XML_DIR, WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
const getPreloadedConnectionFile = async () => {
|
||||
const preloadedConnections = await fs.readdir(path.join(SAML_XML_DIR));
|
||||
@@ -41,7 +42,7 @@ export const preloadConnection = async (connectionController: ConnectionAPIContr
|
||||
const preloadedConnectionMetadata = await getPreloadedConnectionMetadata();
|
||||
|
||||
if (!preloadedConnectionMetadata) {
|
||||
console.log("No preloaded connection metadata found");
|
||||
logger.info("No preloaded connection metadata found");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -68,6 +69,6 @@ export const preloadConnection = async (connectionController: ConnectionAPIContr
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error preloading connection:", error.message);
|
||||
logger.error(error, "Error preloading connection");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { SAML_PRODUCT, SAML_TENANT, SAML_XML_DIR, WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { preloadConnection } from "../preload-connection";
|
||||
|
||||
vi.mock("@formbricks/lib/constants", () => ({
|
||||
@@ -114,14 +115,11 @@ describe("SAML Preload Connection", () => {
|
||||
test("handle case when no XML files are found", async () => {
|
||||
vi.mocked(fs.readdir).mockResolvedValue(["other-file.txt"] as any);
|
||||
|
||||
const consoleErrorSpy = vi.spyOn(console, "error");
|
||||
const loggerSpy = vi.spyOn(logger, "error");
|
||||
|
||||
await preloadConnection(mockConnectionController as any);
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
"Error preloading connection:",
|
||||
expect.stringContaining("No preloaded connection file found")
|
||||
);
|
||||
expect(loggerSpy).toHaveBeenCalledWith(expect.any(Error), "Error preloading connection");
|
||||
|
||||
expect(mockConnectionController.createSAMLConnection).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -130,13 +128,10 @@ describe("SAML Preload Connection", () => {
|
||||
const errorMessage = "Invalid metadata";
|
||||
mockConnectionController.createSAMLConnection.mockRejectedValue(new Error(errorMessage));
|
||||
|
||||
const consoleErrorSpy = vi.spyOn(console, "error");
|
||||
const loggerSpy = vi.spyOn(logger, "error");
|
||||
|
||||
await preloadConnection(mockConnectionController as any);
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
"Error preloading connection:",
|
||||
expect.stringContaining(errorMessage)
|
||||
);
|
||||
expect(loggerSpy).toHaveBeenCalledWith(expect.any(Error), "Error preloading connection");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import { STRIPE_API_VERSION, WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import { STRIPE_PRICE_LOOKUP_KEYS } from "@formbricks/lib/constants";
|
||||
import { env } from "@formbricks/lib/env";
|
||||
import { getOrganization } from "@formbricks/lib/organization/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
|
||||
apiVersion: STRIPE_API_VERSION,
|
||||
@@ -96,7 +97,7 @@ export const createSubscription = async (
|
||||
url: "",
|
||||
};
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
logger.error(err, "Error creating subscription");
|
||||
return {
|
||||
status: 500,
|
||||
newPlan: true,
|
||||
|
||||
@@ -2,6 +2,7 @@ import Stripe from "stripe";
|
||||
import { STRIPE_API_VERSION } from "@formbricks/lib/constants";
|
||||
import { env } from "@formbricks/lib/env";
|
||||
import { getOrganization } from "@formbricks/lib/organization/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
|
||||
apiVersion: STRIPE_API_VERSION,
|
||||
@@ -44,7 +45,7 @@ export const isSubscriptionCancelled = async (
|
||||
date: null,
|
||||
};
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
logger.error(err, "Error checking if subscription is cancelled");
|
||||
return {
|
||||
cancelled: false,
|
||||
date: null,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { handleSubscriptionDeleted } from "@/modules/ee/billing/api/lib/subscrip
|
||||
import Stripe from "stripe";
|
||||
import { STRIPE_API_VERSION } from "@formbricks/lib/constants";
|
||||
import { env } from "@formbricks/lib/env";
|
||||
import { logger } from "@formbricks/logger";
|
||||
|
||||
const stripe = new Stripe(env.STRIPE_SECRET_KEY!, {
|
||||
apiVersion: STRIPE_API_VERSION,
|
||||
@@ -19,7 +20,7 @@ export const webhookHandler = async (requestBody: string, stripeSignature: strin
|
||||
event = stripe.webhooks.constructEvent(requestBody, stripeSignature, webhookSecret);
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : "Unknown error";
|
||||
if (err! instanceof Error) console.error(err);
|
||||
if (err! instanceof Error) logger.error(err, "Error in Stripe webhook handler");
|
||||
return { status: 400, message: `Webhook Error: ${errorMessage}` };
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import Stripe from "stripe";
|
||||
import { PROJECT_FEATURE_KEYS, STRIPE_API_VERSION } from "@formbricks/lib/constants";
|
||||
import { env } from "@formbricks/lib/env";
|
||||
import { getOrganization, updateOrganization } from "@formbricks/lib/organization/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import {
|
||||
TOrganizationBillingPeriod,
|
||||
@@ -27,7 +28,7 @@ export const handleSubscriptionCreatedOrUpdated = async (event: Stripe.Event) =>
|
||||
}
|
||||
|
||||
if (!organizationId) {
|
||||
console.error("No organizationId found in subscription");
|
||||
logger.error({ event, organizationId }, "No organizationId found in subscription");
|
||||
return { status: 400, message: "skipping, no organizationId found" };
|
||||
}
|
||||
|
||||
@@ -60,7 +61,7 @@ export const handleSubscriptionCreatedOrUpdated = async (event: Stripe.Event) =>
|
||||
} else if (parseInt(product.metadata.responses) > 0) {
|
||||
responses = parseInt(product.metadata.responses);
|
||||
} else {
|
||||
console.error("Invalid responses metadata in product: ", product.metadata.responses);
|
||||
logger.error({ responses: product.metadata.responses }, "Invalid responses metadata in product");
|
||||
throw new Error("Invalid responses metadata in product");
|
||||
}
|
||||
|
||||
@@ -69,7 +70,7 @@ export const handleSubscriptionCreatedOrUpdated = async (event: Stripe.Event) =>
|
||||
} else if (parseInt(product.metadata.miu) > 0) {
|
||||
miu = parseInt(product.metadata.miu);
|
||||
} else {
|
||||
console.error("Invalid miu metadata in product: ", product.metadata.miu);
|
||||
logger.error({ miu: product.metadata.miu }, "Invalid miu metadata in product");
|
||||
throw new Error("Invalid miu metadata in product");
|
||||
}
|
||||
|
||||
@@ -78,7 +79,7 @@ export const handleSubscriptionCreatedOrUpdated = async (event: Stripe.Event) =>
|
||||
} else if (parseInt(product.metadata.projects) > 0) {
|
||||
projects = parseInt(product.metadata.projects);
|
||||
} else {
|
||||
console.error("Invalid projects metadata in product: ", product.metadata.projects);
|
||||
logger.error({ projects: product.metadata.projects }, "Invalid projects metadata in product");
|
||||
throw new Error("Invalid projects metadata in product");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import Stripe from "stripe";
|
||||
import { BILLING_LIMITS, PROJECT_FEATURE_KEYS } from "@formbricks/lib/constants";
|
||||
import { getOrganization, updateOrganization } from "@formbricks/lib/organization/service";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
|
||||
export const handleSubscriptionDeleted = async (event: Stripe.Event) => {
|
||||
const stripeSubscriptionObject = event.data.object as Stripe.Subscription;
|
||||
const organizationId = stripeSubscriptionObject.metadata.organizationId;
|
||||
if (!organizationId) {
|
||||
console.error("No organizationId found in subscription");
|
||||
logger.error({ event, organizationId }, "No organizationId found in subscription");
|
||||
return { status: 400, message: "skipping, no organizationId found" };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar";
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
|
||||
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
|
||||
import { PageHeader } from "@/modules/ui/components/page-header";
|
||||
import { getTranslate } from "@/tolgee/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { notFound } from "next/navigation";
|
||||
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import { PROJECT_FEATURE_KEYS, STRIPE_PRICE_LOOKUP_KEYS } from "@formbricks/lib/constants";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
import {
|
||||
getMonthlyActiveOrganizationPeopleCount,
|
||||
getMonthlyOrganizationResponseCount,
|
||||
getOrganizationByEnvironmentId,
|
||||
} from "@formbricks/lib/organization/service";
|
||||
import { getOrganizationProjectsCount } from "@formbricks/lib/project/service";
|
||||
import { PricingTable } from "./components/pricing-table";
|
||||
@@ -20,29 +16,19 @@ import { PricingTable } from "./components/pricing-table";
|
||||
export const PricingPage = async (props) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslate();
|
||||
const organization = await getOrganizationByEnvironmentId(params.environmentId);
|
||||
|
||||
const { organization, isMember, currentUserMembership } = await getEnvironmentAuth(params.environmentId);
|
||||
|
||||
if (!IS_FORMBRICKS_CLOUD) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
if (!organization) {
|
||||
throw new Error(t("common.organization_not_found"));
|
||||
}
|
||||
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session) {
|
||||
throw new Error(t("common.not_authorized"));
|
||||
}
|
||||
|
||||
const [peopleCount, responseCount, projectCount] = await Promise.all([
|
||||
getMonthlyActiveOrganizationPeopleCount(organization.id),
|
||||
getMonthlyOrganizationResponseCount(organization.id),
|
||||
getOrganizationProjectsCount(organization.id),
|
||||
]);
|
||||
|
||||
const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id);
|
||||
const { isMember } = getAccessFlags(currentUserMembership?.role);
|
||||
const hasBillingRights = !isMember;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,20 +1,12 @@
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import { AttributesSection } from "@/modules/ee/contacts/[contactId]/components/attributes-section";
|
||||
import { DeleteContactButton } from "@/modules/ee/contacts/[contactId]/components/delete-contact-button";
|
||||
import { getContactAttributes } from "@/modules/ee/contacts/lib/contact-attributes";
|
||||
import { getContact } from "@/modules/ee/contacts/lib/contacts";
|
||||
import { getContactIdentifier } from "@/modules/ee/contacts/lib/utils";
|
||||
import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
|
||||
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
|
||||
import { PageHeader } from "@/modules/ui/components/page-header";
|
||||
import { getTranslate } from "@/tolgee/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
import { getProjectByEnvironmentId } from "@formbricks/lib/project/service";
|
||||
import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service";
|
||||
import { ResponseSection } from "./components/response-section";
|
||||
|
||||
@@ -23,45 +15,19 @@ export const SingleContactPage = async (props: {
|
||||
}) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslate();
|
||||
const [environment, environmentTags, project, session, organization, contact, contactAttributes] =
|
||||
await Promise.all([
|
||||
getEnvironment(params.environmentId),
|
||||
getTagsByEnvironmentId(params.environmentId),
|
||||
getProjectByEnvironmentId(params.environmentId),
|
||||
getServerSession(authOptions),
|
||||
getOrganizationByEnvironmentId(params.environmentId),
|
||||
getContact(params.contactId),
|
||||
getContactAttributes(params.contactId),
|
||||
]);
|
||||
|
||||
if (!project) {
|
||||
throw new Error(t("common.project_not_found"));
|
||||
}
|
||||
const { environment, isReadOnly } = await getEnvironmentAuth(params.environmentId);
|
||||
|
||||
if (!environment) {
|
||||
throw new Error(t("common.environment_not_found"));
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
throw new Error(t("common.session_not_found"));
|
||||
}
|
||||
|
||||
if (!organization) {
|
||||
throw new Error(t("common.organization_not_found"));
|
||||
}
|
||||
const [environmentTags, contact, contactAttributes] = await Promise.all([
|
||||
getTagsByEnvironmentId(params.environmentId),
|
||||
getContact(params.contactId),
|
||||
getContactAttributes(params.contactId),
|
||||
]);
|
||||
|
||||
if (!contact) {
|
||||
throw new Error(t("environments.contacts.contact_not_found"));
|
||||
}
|
||||
|
||||
const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id);
|
||||
const { isMember } = getAccessFlags(currentUserMembership?.role);
|
||||
|
||||
const projectPermission = await getProjectPermissionByUserId(session.user.id, project.id);
|
||||
const { hasReadAccess } = getTeamPermissionFlags(projectPermission);
|
||||
|
||||
const isReadOnly = isMember && hasReadAccess;
|
||||
|
||||
const getDeletePersonButton = () => {
|
||||
return (
|
||||
<DeleteContactButton
|
||||
|
||||
@@ -3,6 +3,7 @@ import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { updateAttributes } from "@/modules/ee/contacts/lib/attributes";
|
||||
import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils";
|
||||
import { NextRequest } from "next/server";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { ZJsContactsUpdateAttributeInput } from "@formbricks/types/js";
|
||||
import { getContactByUserIdWithAttributes } from "./lib/contact";
|
||||
@@ -89,7 +90,7 @@ export const PUT = async (
|
||||
true
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
logger.error({ err, url: req.url }, "Error updating attributes");
|
||||
if (err.statusCode === 403) {
|
||||
return responses.forbiddenResponse(err.message || "Forbidden", true, { ignore: true });
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { contactCache } from "@/lib/cache/contact";
|
||||
import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils";
|
||||
import { NextRequest, userAgent } from "next/server";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { ZJsUserIdentifyInput } from "@formbricks/types/js";
|
||||
import { getPersonState } from "./lib/personState";
|
||||
@@ -62,11 +63,11 @@ export const GET = async (
|
||||
return responses.notFoundResponse(err.resourceType, err.resourceId);
|
||||
}
|
||||
|
||||
console.error(err);
|
||||
logger.error({ err, url: request.url }, "Error fetching person state");
|
||||
return responses.internalServerErrorResponse(err.message ?? "Unable to fetch person state", true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
logger.error({ error, url: request.url }, "Error fetching person state");
|
||||
return responses.internalServerErrorResponse(`Unable to complete response: ${error.message}`, true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { responses } from "@/app/lib/api/response";
|
||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils";
|
||||
import { NextRequest, userAgent } from "next/server";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { TContactAttributes } from "@formbricks/types/contact-attribute";
|
||||
import { ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { TJsPersonState, ZJsUserIdentifyInput, ZJsUserUpdateInput } from "@formbricks/types/js";
|
||||
@@ -94,11 +95,11 @@ export const POST = async (
|
||||
return responses.notFoundResponse(err.resourceType, err.resourceId);
|
||||
}
|
||||
|
||||
console.error(err);
|
||||
logger.error({ err, url: request.url }, "Error in POST /api/v1/client/[environmentId]/user");
|
||||
return responses.internalServerErrorResponse(err.message ?? "Unable to fetch person state", true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
logger.error({ error, url: request.url }, "Error in POST /api/v1/client/[environmentId]/user");
|
||||
return responses.internalServerErrorResponse(`Unable to complete response: ${error.message}`, true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { authenticateRequest, handleErrorResponse } from "@/app/api/v1/auth";
|
||||
import { responses } from "@/app/lib/api/response";
|
||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { TAuthenticationApiKey } from "@formbricks/types/auth";
|
||||
import { TContactAttributeKey } from "@formbricks/types/contact-attribute-key";
|
||||
import {
|
||||
@@ -109,7 +110,7 @@ export const PUT = async (
|
||||
try {
|
||||
contactAttributeKeyUpdate = await request.json();
|
||||
} catch (error) {
|
||||
console.error(`Error parsing JSON input: ${error}`);
|
||||
logger.error({ error, url: request.url }, "Error parsing JSON input");
|
||||
return responses.badRequestResponse("Malformed JSON input, please check your request body");
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { authenticateRequest } from "@/app/api/v1/auth";
|
||||
import { responses } from "@/app/lib/api/response";
|
||||
import { transformErrorToDetails } from "@/app/lib/api/validator";
|
||||
import { getIsContactsEnabled } from "@/modules/ee/license-check/lib/utils";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
import { ZContactAttributeKeyCreateInput } from "./[contactAttributeKeyId]/types/contact-attribute-keys";
|
||||
import { createContactAttributeKey, getContactAttributeKeys } from "./lib/contact-attribute-keys";
|
||||
@@ -40,7 +41,7 @@ export const POST = async (request: Request): Promise<Response> => {
|
||||
try {
|
||||
contactAttibuteKeyInput = await request.json();
|
||||
} catch (error) {
|
||||
console.error(`Error parsing JSON input: ${error}`);
|
||||
logger.error({ error, url: request.url }, "Error parsing JSON input");
|
||||
return responses.badRequestResponse("Malformed JSON input, please check your request body");
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user