Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/modules/rows/routes/Rows_Edit.server.ts
T

164 lines
7.0 KiB
TypeScript

import { ActionFunction, LoaderFunctionArgs, redirect } from "react-router";
import type { TFunction } from "i18next";
import { MetaTagsDto } from "~/application/dtos/seo/MetaTagsDto";
import { createMetrics } from "~/modules/metrics/services/.server/MetricTracker";
import NotificationService from "~/modules/notifications/services/.server/NotificationService";
import { EntitiesApi } from "~/utils/api/.server/EntitiesApi";
import { RowsApi } from "~/utils/api/.server/RowsApi";
import UrlUtils from "~/utils/app/UrlUtils";
import { EntityWithDetails, getAllEntities } from "~/utils/db/entities/entities.db.server";
import { getUser } from "~/utils/db/users.db.server";
import EntityHelper from "~/utils/helpers/EntityHelper";
import { verifyUserHasEntityPermission } from "~/utils/helpers/.server/PermissionsService";
import RowHelper from "~/utils/helpers/RowHelper";
import RowsRequestUtils from "../utils/RowsRequestUtils";
import FormulaService from "~/modules/formulas/services/.server/FormulaService";
import { safeRedirectPathFromRequest } from "~/utils/app/RedirectSafety.server";
import { createScopedLogger } from "~/utils/logger.server";
import i18next from "i18next";
import { translateEntityTitlePlural } from "~/utils/i18n/translateLabel";
import { getRowsRouteErrorCode, getRowsRouteErrorStatus, translateRowsRouteError } from "./Rows_Error.server";
const rowsEditLogger = createScopedLogger("rows.edit");
export namespace Rows_Edit {
export type ActionData = {
updatedRow?: RowsApi.GetRowData;
deleted?: boolean;
error?: string;
errorCode?: import("~/utils/api/.server/RowsApi").RowApiErrorCode;
};
export type LoaderData = {
meta: MetaTagsDto;
rowData: RowsApi.GetRowData;
routes: EntitiesApi.Routes;
allEntities: EntityWithDetails[];
relationshipRows: RowsApi.GetRelationshipRowsData;
};
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const { time, getServerTimingHeader } = await createMetrics({ request, params }, `[Rows_Edit] ${params.entity}`);
const { t, userId, tenantId, entity } = await RowsRequestUtils.getLoader({ request, params });
const user = await time(getUser(userId), "getUser");
await time(verifyUserHasEntityPermission(request, entity, "update", tenantId), "verifyUserHasPermission");
if (!entity.isAutogenerated || entity.type === "system") {
throw redirect(tenantId ? UrlUtils.currentTenantUrl(params, "404") : "/404?entity=" + params.entity);
}
const rowData = await time(
RowsApi.get(params.id!, {
entity,
tenantId,
userId,
}),
"RowsApi.get"
);
if (!rowData.rowPermissions.canUpdate && !user?.admin) {
throw Error(t("shared.unauthorized"));
}
const entityPluralLabel = translateEntityTitlePlural(t, i18next, entity);
const data: LoaderData = {
meta: [
{
title: `${t("shared.edit")} | ${RowHelper.getTextDescription({ entity, item: rowData.item, t })} | ${entityPluralLabel} | ${
process.env.APP_NAME
}`,
},
],
rowData,
routes: EntitiesApi.getNoCodeRoutes({ request, params }),
allEntities: await time(getAllEntities({ tenantId }), "getAllEntities"),
relationshipRows: await time(RowsApi.getRelationshipRows({ entity, tenantId, userId }), "RowsApi.getRelationshipRows"),
};
return Response.json(data, { headers: getServerTimingHeader() });
};
export const action: ActionFunction = async ({ request, params }) => {
const { time, getServerTimingHeader } = await createMetrics({ request, params }, `[Rows_Edit] ${params.entity}`);
const { t, userId, tenantId, entity, form } = await RowsRequestUtils.getAction({ request, params });
const user = await getUser(userId);
const { item } = await time(
RowsApi.get(params.id!, {
entity,
tenantId,
userId,
}),
"RowsApi.get"
);
const action = form.get("action");
let rowValues: any = {};
if (action === "edit") {
try {
rowValues = RowHelper.getRowPropertiesFromForm({ t, entity, form, existing: item });
const updatedRow = await time(
RowsApi.update(params.id!, {
entity,
request,
tenantId,
userId,
rowValues,
}),
"RowsApi.update"
);
await time(
FormulaService.trigger({ trigger: "AFTER_UPDATED", rows: [updatedRow], entity: entity, session: { tenantId, userId }, t }),
"FormulaService.trigger.AFTER_UPDATED"
);
} catch (error: unknown) {
return rowActionError(t, error, getServerTimingHeader());
}
const updatedRow = await RowsApi.get(params.id!, { entity, tenantId, userId });
if (item.createdByUser) {
rowsEditLogger.info({ createdByUser: item.createdByUser }, "Sending notification");
await NotificationService.send({
channel: "my-rows",
to: item.createdByUser,
tenantId,
notification: {
from: { user },
message: t("notifications.messages.row.updated", {
email: user?.email ?? t("shared.unknown"),
row: RowHelper.getTextDescription({ entity, item, t }),
}),
action: {
title: t("shared.view"),
url: EntityHelper.getRoutes({ routes: EntitiesApi.getNoCodeRoutes({ request, params }), entity, item })?.overview ?? "",
},
},
});
}
const redirectTo = form.get("redirect")?.toString() || new URL(request.url).searchParams.get("redirect")?.toString();
if (redirectTo) {
return redirect(safeRedirectPathFromRequest(request, redirectTo), { headers: getServerTimingHeader() });
}
return Response.json({ updatedRow } satisfies ActionData, { headers: getServerTimingHeader() });
} else if (action === "delete") {
try {
await time(verifyUserHasEntityPermission(request, entity, "delete", tenantId), "verifyUserHasPermission");
await RowsApi.del(params.id!, {
entity,
request,
tenantId,
userId,
checkPermissions: !user?.admin,
});
} catch (error: unknown) {
return rowActionError(t, error, getServerTimingHeader());
}
const redirectTo = form.get("redirect")?.toString() || new URL(request.url).searchParams.get("redirect")?.toString();
if (redirectTo) {
return redirect(safeRedirectPathFromRequest(request, redirectTo), { headers: getServerTimingHeader() });
}
return Response.json({ deleted: true } satisfies ActionData, { headers: getServerTimingHeader() });
} else {
return Response.json({ error: t("shared.invalidForm") }, { status: 400, headers: getServerTimingHeader() });
}
};
}
function rowActionError(t: TFunction, error: unknown, headers: HeadersInit) {
return Response.json(
{ error: translateRowsRouteError(t, error), errorCode: getRowsRouteErrorCode(error) } satisfies Rows_Edit.ActionData,
{ status: getRowsRouteErrorStatus(error), headers }
);
}