mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-20 03:07:53 -05:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b77df0f36 |
@@ -46,6 +46,10 @@ vi.mock("@/modules/ee/billing/lib/organization-billing", () => ({
|
||||
cleanupStripeCustomer: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/hub/service", () => ({
|
||||
deleteFeedbackRecordsByTenant: vi.fn().mockResolvedValue({ data: { deletedCount: 0 }, error: null }),
|
||||
}));
|
||||
|
||||
describe("Organization Service", () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(ensureCloudStripeSetupForOrganization).mockResolvedValue(undefined);
|
||||
@@ -355,6 +359,7 @@ describe("Organization Service", () => {
|
||||
billing: { stripeCustomerId: "cus_123" },
|
||||
memberships: [],
|
||||
workspaces: [],
|
||||
feedbackDirectories: [],
|
||||
} as any);
|
||||
|
||||
await deleteOrganization("org1");
|
||||
@@ -363,5 +368,23 @@ describe("Organization Service", () => {
|
||||
expect(cleanupStripeCustomer).toHaveBeenCalledWith("cus_123");
|
||||
}
|
||||
});
|
||||
|
||||
test("should purge Hub feedback records for each feedback directory", async () => {
|
||||
const { deleteFeedbackRecordsByTenant } = await import("@/modules/hub/service");
|
||||
vi.mocked(prisma.organization.delete).mockResolvedValue({
|
||||
id: "org1",
|
||||
name: "Test Org",
|
||||
billing: null,
|
||||
memberships: [],
|
||||
workspaces: [],
|
||||
feedbackDirectories: [{ id: "frd_1" }, { id: "frd_2" }],
|
||||
} as any);
|
||||
|
||||
await deleteOrganization("org1");
|
||||
|
||||
expect(deleteFeedbackRecordsByTenant).toHaveBeenCalledTimes(2);
|
||||
expect(deleteFeedbackRecordsByTenant).toHaveBeenCalledWith("frd_1");
|
||||
expect(deleteFeedbackRecordsByTenant).toHaveBeenCalledWith("frd_2");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ import { updateUser } from "@/lib/user/service";
|
||||
import { getBillingUsageCycleWindow } from "@/lib/utils/billing";
|
||||
import { getWorkspaces } from "@/lib/workspace/service";
|
||||
import { cleanupStripeCustomer } from "@/modules/ee/billing/lib/organization-billing";
|
||||
import { deleteFeedbackRecordsByTenant } from "@/modules/hub/service";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
|
||||
export const select = {
|
||||
@@ -294,6 +295,11 @@ export const deleteOrganization = async (organizationId: string) => {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
feedbackDirectories: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -301,6 +307,12 @@ export const deleteOrganization = async (organizationId: string) => {
|
||||
if (IS_FORMBRICKS_CLOUD && stripeCustomerId) {
|
||||
await cleanupStripeCustomer(stripeCustomerId);
|
||||
}
|
||||
|
||||
// Best-effort: purge feedback records in the Hub for each directory tenant.
|
||||
// Failures are logged inside the gateway and do not roll back the local delete.
|
||||
for (const directory of deletedOrganization.feedbackDirectories) {
|
||||
await deleteFeedbackRecordsByTenant(directory.id);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
|
||||
@@ -129,6 +129,44 @@ export const deleteFeedbackRecord = async (id: string): Promise<HubFeedbackRecor
|
||||
}
|
||||
};
|
||||
|
||||
export type HubFeedbackRecordsByTenantDeleteResult = {
|
||||
data: { deletedCount: number } | null;
|
||||
error: HubError | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete all feedback records in the Hub for a given tenant.
|
||||
* Used when an organization (and its feedback directories) is deleted, so that
|
||||
* Hub-side records do not become orphaned.
|
||||
*
|
||||
* NOTE: depends on the Hub `bulkDelete` endpoint accepting a `tenant_id`-only
|
||||
* payload (no `user_id`). Until that ships, this call will fail with a 4xx and
|
||||
* be logged as a warning — caller treats this as best-effort.
|
||||
*/
|
||||
export const deleteFeedbackRecordsByTenant = async (
|
||||
tenantId: string
|
||||
): Promise<HubFeedbackRecordsByTenantDeleteResult> => {
|
||||
const client = getHubClient();
|
||||
if (!client) {
|
||||
return { data: null, error: { ...NO_CONFIG_ERROR } };
|
||||
}
|
||||
|
||||
try {
|
||||
// Cast: SDK currently requires `user_id`. Hub-side change will accept a
|
||||
// tenant-only payload; until the SDK types catch up we go through `unknown`.
|
||||
const bulkDelete = client.feedbackRecords.bulkDelete as unknown as (params: {
|
||||
tenant_id: string;
|
||||
}) => Promise<{ deleted_count: number }>;
|
||||
const data = await bulkDelete({ tenant_id: tenantId });
|
||||
return { data: { deletedCount: data.deleted_count }, error: null };
|
||||
} catch (err) {
|
||||
logger.warn({ err, tenantId }, "Hub: deleteFeedbackRecordsByTenant failed");
|
||||
const status = getErrorStatus(err);
|
||||
const message = getErrorMessage(err);
|
||||
return { data: null, error: { status, message, detail: message } };
|
||||
}
|
||||
};
|
||||
|
||||
export type ListFeedbackRecordsResult = {
|
||||
data: FeedbackRecordListResponse | null;
|
||||
error: HubError | null;
|
||||
|
||||
Reference in New Issue
Block a user