Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
135 lines
4.4 KiB
TypeScript
135 lines
4.4 KiB
TypeScript
import { z } from 'zod';
|
|
import { MAX_REGEX_PATTERN_LENGTH } from './evaluator';
|
|
|
|
const CONDITION_VALUE_MAX_LENGTH = 2_000;
|
|
|
|
const regexPatternSchema = z
|
|
.string()
|
|
.min(1)
|
|
.max(MAX_REGEX_PATTERN_LENGTH)
|
|
.superRefine((pattern, ctx) => {
|
|
try {
|
|
// eslint-disable-next-line no-new
|
|
new RegExp(pattern);
|
|
} catch {
|
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Invalid regular expression' });
|
|
}
|
|
});
|
|
|
|
export const inboundEmailRuleConditionSchema = z
|
|
.object({
|
|
field: z.enum(['from_address', 'from_domain', 'to_address', 'subject', 'body_text']),
|
|
operator: z.enum(['equals', 'contains', 'starts_with', 'ends_with', 'matches_regex']),
|
|
value: z.string().min(1).max(CONDITION_VALUE_MAX_LENGTH),
|
|
})
|
|
.superRefine((condition, ctx) => {
|
|
if (condition.operator === 'matches_regex') {
|
|
const result = regexPatternSchema.safeParse(condition.value);
|
|
if (!result.success) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ['value'],
|
|
message: result.error.issues[0]?.message ?? 'Invalid regular expression',
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
const occurrenceSchema = z.enum(['first', 'last']).optional();
|
|
|
|
export const inboundEmailExtractionSchema = z.discriminatedUnion('type', [
|
|
z.object({
|
|
type: z.literal('between'),
|
|
start: z.string().min(1).max(200),
|
|
end: z.string().min(1).max(200),
|
|
occurrence: occurrenceSchema,
|
|
}),
|
|
z.object({
|
|
type: z.literal('after'),
|
|
marker: z.string().min(1).max(200),
|
|
occurrence: occurrenceSchema,
|
|
}),
|
|
z.object({
|
|
type: z.literal('before'),
|
|
marker: z.string().min(1).max(200),
|
|
occurrence: occurrenceSchema,
|
|
}),
|
|
z.object({
|
|
type: z.literal('regex'),
|
|
pattern: regexPatternSchema,
|
|
}),
|
|
]);
|
|
|
|
export const extractAssignClientConfigSchema = z.object({
|
|
source: z.enum(['subject', 'body_text']),
|
|
extraction: inboundEmailExtractionSchema,
|
|
});
|
|
|
|
export const setDestinationConfigSchema = z.object({
|
|
inbound_ticket_defaults_id: z.string().uuid(),
|
|
});
|
|
|
|
export const aiClassifyConfigSchema = z.object({
|
|
instruction: z.string().min(1).max(4_000),
|
|
allowed_outcomes: z.array(z.enum(['skip', 'assign_client'])).min(1),
|
|
});
|
|
|
|
const ACTION_CONFIG_SCHEMAS = {
|
|
skip: z.object({ note: z.string().max(500).optional() }).strict(),
|
|
extract_assign_client: extractAssignClientConfigSchema,
|
|
set_destination: setDestinationConfigSchema,
|
|
ai_classify: aiClassifyConfigSchema,
|
|
} as const;
|
|
|
|
export const inboundEmailRuleInputSchema = z
|
|
.object({
|
|
name: z.string().min(1).max(200),
|
|
is_active: z.boolean().default(true),
|
|
provider_ids: z.array(z.string().uuid()).min(1).nullable().default(null),
|
|
conditions: z.array(inboundEmailRuleConditionSchema).min(1).max(20),
|
|
action_type: z.enum(['skip', 'extract_assign_client', 'set_destination', 'ai_classify']),
|
|
action_config: z.record(z.unknown()).default({}),
|
|
on_no_match: z.enum(['proceed', 'fallback_destination', 'skip']).default('proceed'),
|
|
fallback_inbound_ticket_defaults_id: z.string().uuid().nullable().default(null),
|
|
})
|
|
.superRefine((rule, ctx) => {
|
|
const configSchema = ACTION_CONFIG_SCHEMAS[rule.action_type];
|
|
const result = configSchema.safeParse(rule.action_config);
|
|
if (!result.success) {
|
|
for (const issue of result.error.issues) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ['action_config', ...issue.path],
|
|
message: issue.message,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (rule.on_no_match === 'fallback_destination' && !rule.fallback_inbound_ticket_defaults_id) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ['fallback_inbound_ticket_defaults_id'],
|
|
message: 'A fallback destination is required when non-match behavior is "fallback_destination"',
|
|
});
|
|
}
|
|
});
|
|
|
|
export type InboundEmailRuleInput = z.infer<typeof inboundEmailRuleInputSchema>;
|
|
|
|
export const clientNameAliasInputSchema = z.object({
|
|
client_id: z.string().uuid(),
|
|
alias: z.string().min(1).max(255),
|
|
});
|
|
|
|
export type ClientNameAliasInput = z.infer<typeof clientNameAliasInputSchema>;
|
|
|
|
/** Sample email accepted by the rule tester action. */
|
|
export const inboundEmailRuleTestSampleSchema = z.object({
|
|
from: z.string().max(320).default(''),
|
|
to: z.string().max(320).optional().default(''),
|
|
subject: z.string().max(1_000).default(''),
|
|
bodyText: z.string().max(100_000).optional().default(''),
|
|
});
|
|
|
|
export type InboundEmailRuleTestSample = z.infer<typeof inboundEmailRuleTestSampleSchema>;
|