Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/modules/events/utils/EventUtils.ts
T

456 lines
22 KiB
TypeScript

import { AccountCreatedDto } from "../dtos/AccountCreatedDto";
import { AccountDeletedDto } from "../dtos/AccountDeletedDto";
import { AccountUpdatedDto } from "../dtos/AccountUpdatedDto";
import { ApiKeyCreatedDto } from "../dtos/ApiKeyCreatedDto";
import { ApiKeyDeletedDto } from "../dtos/ApiKeyDeletedDto";
import { ApiKeyUpdatedDto } from "../dtos/ApiKeyUpdatedDto";
import { EmailReceivedDto } from "../dtos/EmailReceivedDto";
import { MemberDeletedDto } from "../dtos/MemberDeletedDto";
import { MemberInvitationAcceptedDto } from "../dtos/MemberInvitationAcceptedDto";
import { MemberInvitationCreatedDto } from "../dtos/MemberInvitationCreatedDto";
import { RoleAssignedDto } from "../dtos/RoleAssignedDto";
import { RoleUnassignedDto } from "../dtos/RoleUnassignedDto";
import { RowCreatedDto } from "../dtos/RowCreatedDto";
import { RowDeletedDto } from "../dtos/RowDeletedDto";
import { RowSharedDto } from "../dtos/RowSharedDto";
import { RowUpdatedDto } from "../dtos/RowUpdatedDto";
import { SubscriptionCancelledDto } from "../dtos/SubscriptionCancelledDto";
import { SubscriptionEndedDto } from "../dtos/SubscriptionEndedDto";
import { SubscriptionSubscribedDto } from "../dtos/SubscriptionSubscribedDto";
import { SubscriptionUpgradedDto } from "../dtos/SubscriptionUpgradedDto";
import { UserPasswordUpdatedDto } from "../dtos/UserPasswordUpdatedDto";
import { UserPreferencesUpdatedDto } from "../dtos/UserPreferencesUpdatedDto";
import { UserProfileDeletedDto } from "../dtos/UserProfileDeletedDto";
import { UserProfileUpdatedDto } from "../dtos/UserProfileUpdatedDto";
import { ApplicationEvent, getApplicationEventNameKey, isApplicationEvent } from "../types/ApplicationEvent";
import { Event } from "@toolctrl/db/prisma-browser";
import { RowTasksCreatedDto } from "../dtos/RowTasksCreatedDto";
import { RowTasksUpdatedDto } from "../dtos/RowTasksUpdatedDto";
import { RowTasksDeletedDto } from "../dtos/RowTasksDeletedDto";
import { RowCommentsCreatedDto } from "../dtos/RowCommentsCreatedDto";
import { RowCommentsDeletedDto } from "../dtos/RowCommentsDeletedDto";
import { RowCommentsReactedDto } from "../dtos/RowCommentsReactedDto";
import { OnboardingStartedDto } from "../dtos/OnboardingStartedDto";
import { OnboardingDismissedDto } from "../dtos/OnboardingDismissedDto";
import { OnboardingCompletedDto } from "../dtos/OnboardingCompletedDto";
import { SubscriptionDowngradedDto } from "../dtos/SubscriptionDowngradedDto";
import type { TFunction } from "i18next";
type EventDescriptionFormatters = {
dateTime?: (value: Date | number | string, options?: Intl.DateTimeFormatOptions) => string;
};
function parseDescription(event: Event, t?: TFunction, formatters?: EventDescriptionFormatters) {
if (event.description && !t) {
return event.description;
}
try {
const data = JSON.parse(event.data);
return getDescription(event.name as ApplicationEvent, data, t, formatters);
} catch {
if (event.description) {
return event.description;
}
return translateEventDescription(t, "events.descriptions.unknownWithName", `Unknown event: ${event.name}`, { name: event.name });
}
}
function parseName(eventName: string, t?: TFunction) {
if (!isApplicationEvent(eventName)) {
return translateEventDescription(t, "events.names.unmapped", eventName, { name: eventName });
}
return t ? t(getApplicationEventNameKey(eventName)) : eventName;
}
function translateEventDescription(
t: TFunction | undefined,
key: string,
defaultValue: string,
values: Record<string, string | number | boolean | null | undefined> = {}
) {
return t ? t(key, { defaultValue, ...values }) : defaultValue;
}
function translateEventValue(t: TFunction | undefined, key: string, defaultValue: string) {
return t ? t(key, { defaultValue }) : defaultValue;
}
function getDescription(event: ApplicationEvent, data: any, t?: TFunction, formatters?: EventDescriptionFormatters) {
if (!isApplicationEvent(event)) {
return translateEventDescription(t, "events.descriptions.unknown", "Unknown event");
}
try {
switch (event) {
case "user.profile.updated": {
const payload = data as UserProfileUpdatedDto;
return translateEventDescription(t, "events.descriptions.userProfile.updated", `${payload.email} updated their profile`, { email: payload.email });
}
case "user.profile.deleted": {
const payload = data as UserProfileDeletedDto;
return translateEventDescription(t, "events.descriptions.userProfile.deleted", `${payload.email} deleted their profile`, { email: payload.email });
}
case "user.password.updated": {
const payload = data as UserPasswordUpdatedDto;
if (payload.fromUser) {
return translateEventDescription(
t,
"events.descriptions.userPassword.updatedByUser",
`${payload.fromUser.email} updated ${payload.user.email}'s password`,
{ fromEmail: payload.fromUser.email, email: payload.user.email }
);
}
return translateEventDescription(t, "events.descriptions.userPassword.updated", `${payload.user.email} updated their password`, { email: payload.user.email });
}
case "user.mfa.totp.enrollment.started": {
return translateEventDescription(t, "events.descriptions.userMfa.enrollmentStarted", `${data.user.email} started authenticator enrollment`, { email: data.user.email });
}
case "user.mfa.totp.enabled": {
return translateEventDescription(t, "events.descriptions.userMfa.enabled", `${data.user.email} enabled authenticator MFA`, { email: data.user.email });
}
case "user.mfa.totp.disabled": {
return translateEventDescription(t, "events.descriptions.userMfa.disabled", `${data.user.email} disabled authenticator MFA`, { email: data.user.email });
}
case "user.preferences.updated": {
const payload = data as UserPreferencesUpdatedDto;
if (payload.old.locale !== undefined || payload.new.locale !== undefined) {
const oldLocale = payload.old.locale ?? "";
const newLocale = payload.new.locale ?? "";
const oldLocaleLabel = oldLocale ? translateEventValue(t, `shared.locales.${oldLocale}`, oldLocale) : "";
const newLocaleLabel = newLocale ? translateEventValue(t, `shared.locales.${newLocale}`, newLocale) : "";
return translateEventDescription(
t,
"events.descriptions.userPreferences.localeUpdated",
`${payload.user.email} updated their language: ${oldLocale} -> ${newLocale}`,
{
email: payload.user.email,
oldLocale: oldLocaleLabel,
newLocale: newLocaleLabel,
}
);
}
if (payload.old.lightOrDarkMode !== undefined || payload.new.lightOrDarkMode !== undefined) {
const oldTheme = payload.old.lightOrDarkMode ?? "light";
const newTheme = payload.new.lightOrDarkMode ?? "light";
const oldThemeLabel = translateEventValue(t, `shared.${oldTheme}`, oldTheme);
const newThemeLabel = translateEventValue(t, `shared.${newTheme}`, newTheme);
return translateEventDescription(
t,
"events.descriptions.userPreferences.themeUpdated",
`${payload.user.email} updated their theme mode: ${oldTheme} -> ${newTheme}`,
{
email: payload.user.email,
oldTheme: oldThemeLabel,
newTheme: newThemeLabel,
}
);
}
return translateEventDescription(t, "events.descriptions.userPreferences.updated", `${payload.user.email} updated their preferences`, {
email: payload.user.email,
});
}
case "account.created": {
const payload = data as AccountCreatedDto;
return translateEventDescription(t, "events.descriptions.account.created", `Account created: ${payload.tenant.name}`, { tenant: payload.tenant.name });
}
case "account.updated": {
const payload = data as AccountUpdatedDto;
return translateEventDescription(
t,
"events.descriptions.account.updated",
`${payload.user.email} updated account: ${payload.old.name} (/${payload.old.slug}) -> ${payload.new.name} (/${payload.new.slug})`,
{ email: payload.user.email, oldName: payload.old.name, oldSlug: payload.old.slug, newName: payload.new.name, newSlug: payload.new.slug }
);
}
case "account.deleted": {
const payload = data as AccountDeletedDto;
let subscription = "";
if (payload.subscription) {
subscription = `, ${payload.subscription.product.title} - $${payload.subscription.price.amount}`;
}
return translateEventDescription(t, "events.descriptions.account.deleted", `${payload.user.email} deleted the account '${payload.tenant.name}'${subscription}.`, {
email: payload.user.email,
tenant: payload.tenant.name,
subscription,
});
}
case "member.invitation.created": {
const payload = data as MemberInvitationCreatedDto;
return translateEventDescription(t, "events.descriptions.memberInvitation.created", `${payload.fromUser.email} invited ${payload.user.email} to ${payload.tenant.name}`, {
fromEmail: payload.fromUser.email,
email: payload.user.email,
tenant: payload.tenant.name,
});
}
case "member.invitation.accepted": {
const payload = data as MemberInvitationAcceptedDto;
const defaultPrefix = payload.newUser ? "(New user) " : "";
const prefix = payload.newUser ? translateEventDescription(t, "events.descriptions.memberInvitation.newUserPrefix", defaultPrefix) : "";
if (payload.fromUser) {
return translateEventDescription(
t,
"events.descriptions.memberInvitation.acceptedFromUser",
`${defaultPrefix}${payload.user.email} accepted the invitation to ${payload.tenant.name} from user ${payload.fromUser.email}`,
{ prefix, email: payload.user.email, tenant: payload.tenant.name, fromEmail: payload.fromUser.email }
);
}
return translateEventDescription(t, "events.descriptions.memberInvitation.accepted", `${defaultPrefix}${payload.user.email} accepted the invitation to ${payload.tenant.name}`, {
prefix,
email: payload.user.email,
tenant: payload.tenant.name,
});
}
case "member.deleted": {
const payload = data as MemberDeletedDto;
return translateEventDescription(t, "events.descriptions.member.deleted", `${payload.fromUser.email} deleted ${payload.user.email} from ${payload.tenant.name}`, {
fromEmail: payload.fromUser.email,
email: payload.user.email,
tenant: payload.tenant.name,
});
}
case "role.assigned": {
const payload = data as RoleAssignedDto;
return translateEventDescription(t, "events.descriptions.role.assigned", `${payload.fromUser.email} assigned the role "${payload.role.name}" to ${payload.toUser.email}`, {
fromEmail: payload.fromUser.email,
role: payload.role.name,
email: payload.toUser.email,
});
}
case "role.unassigned": {
const payload = data as RoleUnassignedDto;
return translateEventDescription(t, "events.descriptions.role.unassigned", `${payload.fromUser.email} unassigned the role "${payload.role.name}" from ${payload.toUser.email}`, {
fromEmail: payload.fromUser.email,
role: payload.role.name,
email: payload.toUser.email,
});
}
case "subscription.subscribed": {
const payload = data as SubscriptionSubscribedDto;
if (payload.user) {
return translateEventDescription(t, "events.descriptions.subscription.subscribedByUser", `${payload.user.email} subscribed ${payload.tenant.name} to ${payload.subscription.product.title}`, {
email: payload.user.email,
tenant: payload.tenant.name,
product: payload.subscription.product.title,
});
}
return translateEventDescription(t, "events.descriptions.subscription.subscribed", `${payload.tenant.name} subscribed to ${payload.subscription.product.title}`, {
tenant: payload.tenant.name,
product: payload.subscription.product.title,
});
}
case "subscription.upgraded": {
const payload = data as SubscriptionUpgradedDto;
const product = payload.subscription?.product.title ?? "";
if (payload.user) {
return translateEventDescription(t, "events.descriptions.subscription.upgradedByUser", `Subscription upgraded: ${payload.tenant.name} (${product}), by ${payload.user.email}`, {
tenant: payload.tenant.name,
product,
email: payload.user.email,
});
}
return translateEventDescription(t, "events.descriptions.subscription.upgraded", `Subscription upgraded: ${payload.tenant.name} (${product})`, { tenant: payload.tenant.name, product });
}
case "subscription.downgraded": {
const payload = data as SubscriptionDowngradedDto;
const product = payload.subscription?.product.title ?? "";
if (payload.user) {
return translateEventDescription(t, "events.descriptions.subscription.downgradedByUser", `Subscription downgraded: ${payload.tenant.name} (${product}), by ${payload.user.email}`, {
tenant: payload.tenant.name,
product,
email: payload.user.email,
});
}
return translateEventDescription(t, "events.descriptions.subscription.downgraded", `Subscription downgraded: ${payload.tenant.name} (${product})`, { tenant: payload.tenant.name, product });
}
case "subscription.cancelled": {
const payload = data as SubscriptionCancelledDto;
const product = payload.subscription?.product.title ?? "";
if (payload.user) {
return translateEventDescription(t, "events.descriptions.subscription.cancelledByUser", `Subscription cancelled: ${payload.tenant.name} (${product}), by ${payload.user.email}`, {
tenant: payload.tenant.name,
product,
email: payload.user.email,
});
}
return translateEventDescription(t, "events.descriptions.subscription.cancelled", `Subscription cancelled: ${payload.tenant.name} (${product})`, { tenant: payload.tenant.name, product });
}
case "subscription.ended": {
const payload = data as SubscriptionEndedDto;
const product = payload.subscription?.product.title ?? "";
return translateEventDescription(t, "events.descriptions.subscription.ended", `Subscription ended: ${payload.tenant.name} (${product})`, { tenant: payload.tenant.name, product });
}
case "api_key.created": {
const payload = data as ApiKeyCreatedDto;
return translateEventDescription(t, "events.descriptions.apiKey.created", `${payload.user.email} created API key: ${payload.alias}`, { email: payload.user.email, alias: payload.alias });
}
case "api_key.updated": {
const payload = data as ApiKeyUpdatedDto;
return translateEventDescription(t, "events.descriptions.apiKey.updated", `${payload.user.email} updated API key: ${payload.old.alias} -> ${payload.new.alias}`, {
email: payload.user.email,
oldAlias: payload.old.alias,
newAlias: payload.new.alias,
});
}
case "api_key.deleted": {
const payload = data as ApiKeyDeletedDto;
return translateEventDescription(t, "events.descriptions.apiKey.deleted", `${payload.user.email} deleted API key: ${payload.alias}`, { email: payload.user.email, alias: payload.alias });
}
case "email.received": {
const payload = data as EmailReceivedDto;
return translateEventDescription(t, "events.descriptions.email.received", `New email from ${payload.fromEmail}: ${payload.subject}`, {
fromEmail: payload.fromEmail,
subject: payload.subject,
});
}
case "row.created": {
const payload = data as RowCreatedDto;
const actor = getUserOrApiKey(payload, t);
return translateEventDescription(t, "events.descriptions.row.created", `New ${payload.entity.name}: ${payload.title}, by ${actor}`, {
entity: payload.entity.name,
title: payload.title,
actor,
});
}
case "row.updated": {
const payload = data as RowUpdatedDto;
const actor = getUserOrApiKey(payload, t);
return translateEventDescription(t, "events.descriptions.row.updated", `Updated ${payload.entity.name}: ${payload.title}, by ${actor}`, {
entity: payload.entity.name,
title: payload.title,
actor,
});
}
case "row.deleted": {
const payload = data as RowDeletedDto;
const actor = getUserOrApiKey(payload, t);
return translateEventDescription(t, "events.descriptions.row.deleted", `Deleted ${payload.entity.name}: ${payload.title}, by ${actor}`, {
entity: payload.entity.name,
title: payload.title,
actor,
});
}
case "row.shared": {
const payload = data as RowSharedDto;
const actor = getUserOrApiKey(payload, t);
return translateEventDescription(
t,
"events.descriptions.row.shared",
`${payload.entity.name}: ${payload.title} shared with ${payload.type} "${payload.to}" (${payload.access}) by ${actor}`,
{ entity: payload.entity.name, title: payload.title, type: payload.type, to: payload.to, access: payload.access, actor }
);
}
case "row.tasks.created": {
const payload = data as RowTasksCreatedDto;
const actor = getUserOrApiKey(payload, t);
return translateEventDescription(t, "events.descriptions.rowTasks.created", `Row ${payload.rowId} task created '${payload.task.name}' by ${actor}`, {
rowId: payload.rowId,
task: payload.task.name,
actor,
});
}
case "row.tasks.updated": {
const payload = data as RowTasksUpdatedDto;
let change = "";
if (payload.new.completedAt) {
const completedAt = formatters?.dateTime ? formatters.dateTime(payload.new.completedAt) : String(payload.new.completedAt);
change = translateEventDescription(
t,
"events.descriptions.rowTasks.changes.completedAt",
`completed at ${completedAt}`,
{ date: completedAt }
);
} else {
change = translateEventDescription(t, "events.descriptions.rowTasks.changes.incomplete", "set as incomplete");
}
const actor = getUserOrApiKey(payload, t);
return translateEventDescription(t, "events.descriptions.rowTasks.updated", `Row ${payload.rowId} task updated '${payload.new.name}', ${change}, by ${actor}`, {
rowId: payload.rowId,
task: payload.new.name,
change,
actor,
});
}
case "row.tasks.deleted": {
const payload = data as RowTasksDeletedDto;
const actor = getUserOrApiKey(payload, t);
return translateEventDescription(t, "events.descriptions.rowTasks.deleted", `Row ${payload.rowId} task deleted '${payload.task.name}' by ${actor}`, {
rowId: payload.rowId,
task: payload.task.name,
actor,
});
}
case "row.comments.created": {
const payload = data as RowCommentsCreatedDto;
const actor = payload.user ? payload.user.email : "";
return translateEventDescription(t, "events.descriptions.rowComments.created", `Row ${payload.rowId} commented '${payload.comment.text}' by ${actor}`, {
rowId: payload.rowId,
comment: payload.comment.text,
actor,
});
}
case "row.comments.deleted": {
const payload = data as RowCommentsDeletedDto;
const actor = payload.user ? payload.user.email : "";
return translateEventDescription(t, "events.descriptions.rowComments.deleted", `Row ${payload.rowId} comment deleted '${payload.comment.text}' by ${actor}`, {
rowId: payload.rowId,
comment: payload.comment.text,
actor,
});
}
case "row.comments.reacted": {
const payload = data as RowCommentsReactedDto;
const actor = payload.user ? payload.user.email : "";
return translateEventDescription(t, "events.descriptions.rowComments.reacted", `Row ${payload.rowId} comment reaction (${payload.reaction}) '${payload.comment.text}' by ${actor}`, {
rowId: payload.rowId,
reaction: payload.reaction,
comment: payload.comment.text,
actor,
});
}
case "onboarding.started": {
const payload = data as OnboardingStartedDto;
return translateEventDescription(t, "events.descriptions.onboarding.started", `Onboarding started by ${payload.user.email}: ${payload.onboarding.title}`, {
email: payload.user.email,
title: payload.onboarding.title,
});
}
case "onboarding.dismissed": {
const payload = data as OnboardingDismissedDto;
return translateEventDescription(t, "events.descriptions.onboarding.dismissed", `Onboarding dismissed by ${payload.user.email}: ${payload.onboarding.title}`, {
email: payload.user.email,
title: payload.onboarding.title,
});
}
case "onboarding.completed": {
const payload = data as OnboardingCompletedDto;
return translateEventDescription(t, "events.descriptions.onboarding.completed", `Onboarding completed by ${payload.user.email}: ${payload.onboarding.title}`, {
email: payload.user.email,
title: payload.onboarding.title,
});
}
default:
return assertNever(event);
}
} catch {
return parseName(event, t);
}
}
function assertNever(x: never): never {
throw new Error("Unexpected event: " + x);
}
function getUserOrApiKey(item: { user?: { id: string; email: string } | null; apiKey?: { id: string; alias: string } | null }, t?: TFunction) {
if (item.user) {
return item.user.email;
} else if (item.apiKey) {
return translateEventDescription(t, "events.actors.apiKey", "API Key {{alias}}", { alias: item.apiKey.alias });
}
return "";
}
export default {
parseName,
parseDescription,
getDescription,
};