Files
formbricks/apps/web/lib/connector/pipeline-handler.ts
T
Matti Nannt 8c7112e559 feat: remove FeedbackRecordDirectory entity, use workspace.id as Hub tenant_id
Drops FRD as a separate org-level entity in favour of using workspace.id
directly as the Hub tenant_id. This eliminates the dual-auth model, removes
the implicit cascade where workspace read granted access to XM data, and
simplifies the connector/chart/API-key permission surfaces.

Key changes:
- Schema: drop FeedbackRecordDirectory, FeedbackRecordDirectoryWorkspace and
  ApiKeyFeedbackRecordDirectory models; remove FKs from Chart/Connector/ApiKey
- Connector pipeline, CSV import and import now pass connector.workspaceId as
  tenant_id instead of feedbackRecordDirectoryId
- Chart actions: injectTenantFilter now receives workspaceId
- API key create/list: FRD permission section removed entirely
- Workspace create: no longer auto-creates/links a default FRD
- Feedback records page: single workspace-scoped Hub query replaces multi-FRD loop
- Delete entire modules/ee/feedback-record-directory module
- All tests updated; pnpm test (4570), build and lint pass clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 06:43:33 +02:00

133 lines
3.6 KiB
TypeScript

import "server-only";
import { logger } from "@formbricks/logger";
import { TConnectorWithMappings } from "@formbricks/types/connector";
import { TResponse } from "@formbricks/types/responses";
import { TSurvey } from "@formbricks/types/surveys/types";
import { createFeedbackRecordsBatch } from "@/modules/hub";
import { getConnectorsBySurveyId, updateConnector } from "./service";
import { transformResponseToFeedbackRecords } from "./transform";
const getErrorMessage = (error: unknown): string =>
error instanceof Error ? error.message : "Unknown error";
const logFailedRecords = (
connectorId: string,
results: Awaited<ReturnType<typeof createFeedbackRecordsBatch>>["results"]
): void => {
for (const [index, result] of results.entries()) {
if (!result.error) continue;
logger.error(
{
connectorId,
feedbackRecordIndex: index,
error: {
status: result.error.status,
message: result.error.message,
detail: result.error.detail,
},
},
"Failed to create FeedbackRecord"
);
}
};
const processConnector = async (
connector: TConnectorWithMappings,
response: TResponse,
survey: Pick<TSurvey, "id" | "name" | "blocks">,
workspaceId: string
): Promise<void> => {
const feedbackRecords = transformResponseToFeedbackRecords(
response,
survey,
connector.formbricksMappings,
connector.workspaceId
);
if (feedbackRecords.length === 0) {
return;
}
const { results } = await createFeedbackRecordsBatch(feedbackRecords);
const successes = results.filter((r) => r.data !== null).length;
const failures = results.filter((r) => r.error !== null).length;
if (failures > 0) {
logger.warn(
{
connectorId: connector.id,
surveyId: survey.id,
responseId: response.id,
successes,
failures,
},
`Connector pipeline: ${failures}/${feedbackRecords.length} FeedbackRecords failed to send`
);
logFailedRecords(connector.id, results);
} else {
logger.info(
{
connectorId: connector.id,
surveyId: survey.id,
responseId: response.id,
feedbackRecordsCreated: successes,
},
`Connector pipeline: Successfully sent ${successes} FeedbackRecords`
);
}
if (successes > 0) {
await updateConnector(connector.id, workspaceId, { lastSyncAt: new Date() });
}
};
/**
* Handle connector pipeline for a survey response
*
* This function is called from the pipeline when a response is created/finished.
* It looks up active connectors for the survey and sends the response data.
*
* @param response - The survey response
* @param survey - The survey
* @param workspaceId - The workspace ID (used as tenant_id)
*/
export const handleConnectorPipeline = async (
response: TResponse,
survey: Pick<TSurvey, "id" | "name" | "blocks">,
workspaceId: string
): Promise<void> => {
try {
const connectors = await getConnectorsBySurveyId(survey.id);
if (connectors.length === 0) {
return;
}
for (const connector of connectors) {
try {
await processConnector(connector, response, survey, workspaceId);
} catch (error) {
logger.error(
{
connectorId: connector.id,
surveyId: survey.id,
responseId: response.id,
error: getErrorMessage(error),
},
"Connector pipeline: Failed to process connector"
);
}
}
} catch (error) {
logger.error(
{
surveyId: survey.id,
responseId: response.id,
error: getErrorMessage(error),
},
"Connector pipeline: Failed to handle connectors"
);
}
};