Files
goodbuddy/src/shared/channel-settings-contracts.ts
T

229 lines
5.6 KiB
TypeScript

import { z } from 'zod'
import {
projectChannelSchema,
type ProjectChannel
} from './assistant-contracts'
import { settingsWarningsSchema } from './settings-warning-contracts'
export const CHANNEL_SETTINGS_LIMITS = {
maximumIdentifierLength: 256,
maximumSecretLength: 4_096,
maximumAllowedSenders: 100,
maximumStatusMessageLength: 500,
maximumWarningLength: 500
} as const
export const managedChannelSchema = projectChannelSchema
export type ManagedChannel = ProjectChannel
export const credentialChannelSchema = z.enum(['wecom', 'dingtalk'])
export type CredentialChannel = z.infer<
typeof credentialChannelSchema
>
const identifierSchema = z
.string()
.trim()
.max(CHANNEL_SETTINGS_LIMITS.maximumIdentifierLength)
const senderIdentifierSchema = identifierSchema.min(1)
export const allowedSenderIdsSchema = z
.array(senderIdentifierSchema)
.max(CHANNEL_SETTINGS_LIMITS.maximumAllowedSenders)
.transform((values) => [...new Set(values)])
export const channelSecretUpdateSchema = z.discriminatedUnion('action', [
z.object({ action: z.literal('keep') }).strict(),
z
.object({
action: z.literal('replace'),
value: z
.string()
.trim()
.min(1)
.max(CHANNEL_SETTINGS_LIMITS.maximumSecretLength)
})
.strict(),
z.object({ action: z.literal('clear') }).strict()
])
export type ChannelSecretUpdate = z.infer<
typeof channelSecretUpdateSchema
>
const editableChannelFields = {
enabled: z.boolean(),
secret: channelSecretUpdateSchema,
allowedSenderIds: allowedSenderIdsSchema,
allowGroupMessages: z.boolean()
} as const
export const weComChannelSettingsInputSchema = z
.object({
...editableChannelFields,
botId: identifierSchema
})
.strict()
export type WeComChannelSettingsInput = z.infer<
typeof weComChannelSettingsInputSchema
>
export const dingTalkChannelSettingsInputSchema = z
.object({
...editableChannelFields,
clientId: identifierSchema
})
.strict()
export type DingTalkChannelSettingsInput = z.infer<
typeof dingTalkChannelSettingsInputSchema
>
export const weixinChannelSettingsInputSchema = z
.object({
enabled: z.boolean()
})
.strict()
export type WeixinChannelSettingsInput = z.infer<
typeof weixinChannelSettingsInputSchema
>
export const channelSettingsApplySchema = z
.object({
weixin: weixinChannelSettingsInputSchema.optional(),
wecom: weComChannelSettingsInputSchema.optional(),
dingtalk: dingTalkChannelSettingsInputSchema.optional()
})
.strict()
.refine(
(input) =>
input.weixin !== undefined ||
input.wecom !== undefined ||
input.dingtalk !== undefined,
'至少需要提供一个通道设置'
)
export type ChannelSettingsApply = z.infer<
typeof channelSettingsApplySchema
>
export const channelCredentialSourceSchema = z.enum([
'none',
'encrypted',
'environment',
'unreadable'
])
export type ChannelCredentialSource = z.infer<
typeof channelCredentialSourceSchema
>
export const channelRuntimeStateSchema = z.enum([
'disabled',
'stopped',
'starting',
'running',
'error'
])
export type ChannelRuntimeState = z.infer<
typeof channelRuntimeStateSchema
>
export const channelRuntimeStatusSchema = z
.object({
state: channelRuntimeStateSchema,
lastError: z
.string()
.trim()
.min(1)
.max(CHANNEL_SETTINGS_LIMITS.maximumStatusMessageLength)
.optional()
})
.strict()
export type ChannelRuntimeStatus = z.infer<
typeof channelRuntimeStatusSchema
>
const publicChannelFields = {
enabled: z.boolean(),
secretConfigured: z.boolean(),
source: channelCredentialSourceSchema,
readOnly: z.boolean(),
allowedSenderIds: allowedSenderIdsSchema,
allowGroupMessages: z.boolean(),
status: channelRuntimeStatusSchema
} as const
export const weComChannelSettingsSchema = z
.object({
...publicChannelFields,
botId: identifierSchema
})
.strict()
export type WeComChannelSettings = z.infer<
typeof weComChannelSettingsSchema
>
export const dingTalkChannelSettingsSchema = z
.object({
...publicChannelFields,
clientId: identifierSchema
})
.strict()
export type DingTalkChannelSettings = z.infer<
typeof dingTalkChannelSettingsSchema
>
export const weixinChannelSettingsSchema = z
.object({
enabled: z.boolean(),
bindingConfigured: z.boolean(),
source: z.enum(['none', 'encrypted']),
accountDisplay: z.string().trim().min(1).max(64).optional(),
status: channelRuntimeStatusSchema
})
.strict()
export type WeixinChannelSettings = z.infer<
typeof weixinChannelSettingsSchema
>
export const channelSettingsSnapshotSchema = z
.object({
weixin: weixinChannelSettingsSchema,
wecom: weComChannelSettingsSchema,
dingtalk: dingTalkChannelSettingsSchema,
warnings: settingsWarningsSchema.optional()
})
.strict()
export type ChannelSettingsSnapshot = z.infer<
typeof channelSettingsSnapshotSchema
>
export const channelConnectionTestResultSchema = z
.object({
channel: managedChannelSchema,
ok: z.boolean(),
error: z
.string()
.trim()
.min(1)
.max(CHANNEL_SETTINGS_LIMITS.maximumStatusMessageLength)
.optional()
})
.strict()
.superRefine((result, context) => {
if (result.ok && result.error !== undefined) {
context.addIssue({
code: 'custom',
path: ['error'],
message: '成功结果不能包含错误'
})
}
if (!result.ok && result.error === undefined) {
context.addIssue({
code: 'custom',
path: ['error'],
message: '失败结果必须包含错误'
})
}
})
export type ChannelConnectionTestResult = z.infer<
typeof channelConnectionTestResultSchema
>