Files
formbricks/apps/web/modules/ee/analysis/dashboards/actions.ts
T
2026-02-19 19:33:40 +07:00

301 lines
8.2 KiB
TypeScript

"use server";
import { z } from "zod";
import { prisma } from "@formbricks/database";
import { ZId } from "@formbricks/types/common";
import { ZWidgetLayout } from "@formbricks/types/dashboard";
import { authenticatedActionClient } from "@/lib/utils/action-client";
import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware";
import { AuthenticatedActionClientCtx } from "@/lib/utils/action-client/types/context";
import { getOrganizationIdFromEnvironmentId, getProjectIdFromEnvironmentId } from "@/lib/utils/helper";
import { withAuditLogging } from "@/modules/ee/audit-logs/lib/handler";
const checkProjectAccess = async (
userId: string,
environmentId: string,
minPermission: "read" | "readWrite" | "manage"
) => {
const organizationId = await getOrganizationIdFromEnvironmentId(environmentId);
const projectId = await getProjectIdFromEnvironmentId(environmentId);
await checkAuthorizationUpdated({
userId,
organizationId,
access: [
{ type: "organization", roles: ["owner", "manager"] },
{ type: "projectTeam", minPermission, projectId },
],
});
return { organizationId, projectId };
};
const ZCreateDashboardAction = z.object({
environmentId: ZId,
name: z.string().min(1),
description: z.string().optional(),
});
export const createDashboardAction = authenticatedActionClient.schema(ZCreateDashboardAction).action(
withAuditLogging(
"created",
"dashboard",
async ({
ctx,
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer<typeof ZCreateDashboardAction>;
}) => {
const { organizationId, projectId } = await checkProjectAccess(
ctx.user.id,
parsedInput.environmentId,
"readWrite"
);
const dashboard = await prisma.dashboard.create({
data: {
name: parsedInput.name,
description: parsedInput.description,
projectId,
createdBy: ctx.user.id,
},
});
ctx.auditLoggingCtx.organizationId = organizationId;
ctx.auditLoggingCtx.projectId = projectId;
ctx.auditLoggingCtx.newObject = dashboard;
return dashboard;
}
)
);
const ZUpdateDashboardAction = z.object({
environmentId: ZId,
dashboardId: ZId,
name: z.string().min(1).optional(),
description: z.string().optional().nullable(),
});
export const updateDashboardAction = authenticatedActionClient.schema(ZUpdateDashboardAction).action(
withAuditLogging(
"updated",
"dashboard",
async ({
ctx,
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer<typeof ZUpdateDashboardAction>;
}) => {
const { organizationId, projectId } = await checkProjectAccess(
ctx.user.id,
parsedInput.environmentId,
"readWrite"
);
const dashboard = await prisma.dashboard.findFirst({
where: { id: parsedInput.dashboardId, projectId },
});
if (!dashboard) {
throw new Error("Dashboard not found");
}
const updatedDashboard = await prisma.dashboard.update({
where: { id: parsedInput.dashboardId },
data: {
...(parsedInput.name !== undefined && { name: parsedInput.name }),
...(parsedInput.description !== undefined && { description: parsedInput.description }),
},
});
ctx.auditLoggingCtx.organizationId = organizationId;
ctx.auditLoggingCtx.projectId = projectId;
ctx.auditLoggingCtx.oldObject = dashboard;
ctx.auditLoggingCtx.newObject = updatedDashboard;
return updatedDashboard;
}
)
);
const ZDeleteDashboardAction = z.object({
environmentId: ZId,
dashboardId: ZId,
});
export const deleteDashboardAction = authenticatedActionClient.schema(ZDeleteDashboardAction).action(
withAuditLogging(
"deleted",
"dashboard",
async ({
ctx,
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer<typeof ZDeleteDashboardAction>;
}) => {
const { organizationId, projectId } = await checkProjectAccess(
ctx.user.id,
parsedInput.environmentId,
"readWrite"
);
const dashboard = await prisma.dashboard.findFirst({
where: { id: parsedInput.dashboardId, projectId },
});
if (!dashboard) {
throw new Error("Dashboard not found");
}
await prisma.dashboard.delete({
where: { id: parsedInput.dashboardId },
});
ctx.auditLoggingCtx.organizationId = organizationId;
ctx.auditLoggingCtx.projectId = projectId;
ctx.auditLoggingCtx.oldObject = dashboard;
return { success: true };
}
)
);
const ZGetDashboardsAction = z.object({
environmentId: ZId,
});
export const getDashboardsAction = authenticatedActionClient
.schema(ZGetDashboardsAction)
.action(
async ({
ctx,
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer<typeof ZGetDashboardsAction>;
}) => {
const { projectId } = await checkProjectAccess(ctx.user.id, parsedInput.environmentId, "read");
return prisma.dashboard.findMany({
where: { projectId },
orderBy: { createdAt: "desc" },
select: {
id: true,
name: true,
description: true,
createdAt: true,
updatedAt: true,
_count: { select: { widgets: true } },
},
});
}
);
const ZGetDashboardAction = z.object({
environmentId: ZId,
dashboardId: ZId,
});
export const getDashboardAction = authenticatedActionClient
.schema(ZGetDashboardAction)
.action(
async ({
ctx,
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer<typeof ZGetDashboardAction>;
}) => {
const { projectId } = await checkProjectAccess(ctx.user.id, parsedInput.environmentId, "read");
const dashboard = await prisma.dashboard.findFirst({
where: { id: parsedInput.dashboardId, projectId },
include: {
widgets: {
orderBy: { order: "asc" },
include: {
chart: {
select: {
id: true,
name: true,
type: true,
query: true,
config: true,
},
},
},
},
},
});
if (!dashboard) {
throw new Error("Dashboard not found");
}
return dashboard;
}
);
const ZAddChartToDashboardAction = z.object({
environmentId: ZId,
dashboardId: ZId,
chartId: ZId,
title: z.string().optional(),
layout: ZWidgetLayout.optional().default({ x: 0, y: 0, w: 4, h: 3 }),
});
export const addChartToDashboardAction = authenticatedActionClient
.schema(ZAddChartToDashboardAction)
.action(
withAuditLogging(
"created",
"dashboardWidget",
async ({
ctx,
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer<typeof ZAddChartToDashboardAction>;
}) => {
const { organizationId, projectId } = await checkProjectAccess(
ctx.user.id,
parsedInput.environmentId,
"readWrite"
);
const [chart, dashboard] = await Promise.all([
prisma.chart.findFirst({ where: { id: parsedInput.chartId, projectId } }),
prisma.dashboard.findFirst({ where: { id: parsedInput.dashboardId, projectId } }),
]);
if (!chart) {
throw new Error("Chart not found");
}
if (!dashboard) {
throw new Error("Dashboard not found");
}
const maxOrder = await prisma.dashboardWidget.aggregate({
where: { dashboardId: parsedInput.dashboardId },
_max: { order: true },
});
const widget = await prisma.dashboardWidget.create({
data: {
dashboardId: parsedInput.dashboardId,
chartId: parsedInput.chartId,
title: parsedInput.title,
layout: parsedInput.layout,
order: (maxOrder._max.order ?? -1) + 1,
},
});
ctx.auditLoggingCtx.organizationId = organizationId;
ctx.auditLoggingCtx.projectId = projectId;
ctx.auditLoggingCtx.newObject = widget;
return widget;
}
)
);