Skip to content

Commit

Permalink
feat: 钉钉/自定义邮件新增 配置定义 和 配置校验
Browse files Browse the repository at this point in the history
  • Loading branch information
CaoMeiYouRen committed Nov 19, 2024
1 parent 51baf2b commit 4f7d8c3
Show file tree
Hide file tree
Showing 6 changed files with 458 additions and 12 deletions.
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export * from './push/xi-zhi'
export * from './one'
export * from './interfaces/response'
export * from './interfaces/send'
export * from './interfaces/schema'
151 changes: 151 additions & 0 deletions src/interfaces/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// 是否是联合类型
type IsUnion<T, U = T> = T extends U ? ([U] extends [T] ? false : true) : never
/**
* 判断类型是否相同
*/
type Equal<Left, Right> =
(<U>() => U extends Left ? 1 : 0) extends (<U>() => U extends Right ? 1 : 0) ? true : false

/**
* 判断字段是否必填
*/
type IsRequired<T> = Equal<Required<T>, T>

export type Config = {
[key: string]: any
}

/**
* 配置 Schema
* 如果字段的类型是 string,则生成的 Schema 类型为 string
* 如果字段的类型是 number,则生成的 Schema 类型为 number
* 如果字段的类型是 boolean,则生成的 Schema 类型为 boolean
* 如果字段的类型是 object,则生成的 Schema 类型为 object
* 如果字段的类型是 array,则生成的 Schema 类型为 array
* 如果字段的类型是 联合 number 类型(1 | 2 | 3),则生成的 Schema 类型为 select
* 如果字段的类型是 联合 string 类型('text' | 'html'),则生成的 Schema 类型为 select
* (IsUnion<T[K]> extends true ? 'select' : never)
*/
export type ConfigSchema<T = Config> = {
[K in keyof T]: {
// 字段类型
type: IsUnion<T[K]> extends true ? 'select' : (
T[K] extends string ? 'string' : (
T[K] extends number ? 'number' : (
T[K] extends boolean ? 'boolean' : (
T[K] extends any[] ? 'array' : (
T[K] extends object ? 'object' : (
'select'
)
)
)
)
)
)

// 字段名称
title?: string
// 字段描述
description?: string
// 字段是否必填
required: IsRequired<Pick<T, K>>
// 字段默认值
default?: T[K]
// 字段选项,仅当字段类型为 select 时有效
options?: IsUnion<T[K]> extends true ? {
// 选项名称
label: string
// 选项值
value: T[K] // 选项值的类型跟字段的类型一致
}[] : never
}
}

// type ConfigA = {
// name: string
// age?: number
// isActive: boolean
// content?: 'text' | 'html'
// status: 1 | 2 | 3
// }

// type ConfigSchemaA = ConfigSchema<ConfigA>

// const a: ConfigSchemaA = {
// name: {
// type: 'string',
// title: '',
// description: '',
// required: true,
// default: '',
// },
// age: {
// type: 'number',
// title: '',
// description: '',
// required: false,
// default: 0,
// },
// isActive: {
// type: 'boolean',
// title: '',
// description: '',
// required: true,
// default: false,
// options: [
// {
// label: '是',
// value: true,
// },
// {
// label: '否',
// value: false,
// },
// ],
// },
// content: {
// type: 'string',
// title: '',
// description: '',
// required: false,
// default: 'text',
// options: [
// {
// label: '文本',
// value: 'text',
// },
// {
// label: 'HTML',
// value: 'html',
// },
// ],
// },
// status: {
// type: 'number',
// title: '',
// description: '',
// required: true,
// default: 1,
// options: [
// {
// label: '1',
// value: 1,
// },
// {
// label: '2',
// value: 2,
// },
// {
// label: '3',
// value: 3,
// },
// ],
// },
// }

export type Option = {
[key: string]: any
}

export type OptionSchema<T = Option> = ConfigSchema<T>

118 changes: 113 additions & 5 deletions src/push/custom-email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import SMTPTransport from 'nodemailer/lib/smtp-transport'
import Mail from 'nodemailer/lib/mailer'
import { Send } from '@/interfaces/send'
import { SendResponse } from '@/interfaces/response'
import { ConfigSchema, OptionSchema } from '@/interfaces/schema'
import { validate } from '@/utils/validate'

const Debugger = debug('push:custom-email')

Expand Down Expand Up @@ -35,8 +37,113 @@ export interface CustomEmailConfig {
EMAIL_PORT: number
}

export type CustomEmailConfigSchema = ConfigSchema<CustomEmailConfig>

export const customEmailConfigSchema: CustomEmailConfigSchema = {
EMAIL_TYPE: {
type: 'select',
title: '邮件类型',
description: '邮件类型',
required: true,
default: 'text',
options: [
{
label: '文本',
value: 'text',
},
{
label: 'HTML',
value: 'html',
},
],
},
EMAIL_TO_ADDRESS: {
type: 'string',
title: '收件邮箱',
description: '收件邮箱',
required: true,
default: '',
},
EMAIL_AUTH_USER: {
type: 'string',
title: '发件邮箱',
description: '发件邮箱',
required: true,
default: '',
},
EMAIL_AUTH_PASS: {
type: 'string',
title: '发件授权码(或密码)',
description: '发件授权码(或密码)',
required: true,
default: '',
},
EMAIL_HOST: {
type: 'string',
title: '发件域名',
description: '发件域名',
required: true,
default: '',
},
EMAIL_PORT: {
type: 'number',
title: '发件端口',
description: '发件端口',
required: true,
default: 465,
},
} as const

export type CustomEmailOption = Mail.Options

type OptionalCustomEmailOption = Pick<CustomEmailOption, 'to' | 'from' | 'subject' | 'text' | 'html'>

/**
* 由于 CustomEmailOption 的配置太多,所以不提供完整的 Schema,只提供部分配置 schema。
* 如需使用完整的配置,请查看官方文档
*/
export type CustomEmailOptionSchema = OptionSchema<{
[K in keyof OptionalCustomEmailOption]: string
}>

export const customEmailOptionSchema: CustomEmailOptionSchema = {
to: {
type: 'string',
title: '收件邮箱',
description: '收件邮箱',
required: false,
default: '',
},
from: {
type: 'string',
title: '发件邮箱',
description: '发件邮箱',
required: false,
default: '',
},
subject: {
type: 'string',
title: '邮件主题',
description: '邮件主题',
required: false,
default: '',
},
text: {
type: 'string',
title: '邮件内容',
description: '邮件内容',
required: false,
default: '',
},
html: {
type: 'string',
title: '邮件内容',
description: '邮件内容',
required: false,
default: '',
},
} as const

/**
* 自定义邮件。官方文档: https://github.com/nodemailer/nodemailer
*
Expand All @@ -47,18 +154,19 @@ export type CustomEmailOption = Mail.Options
*/
export class CustomEmail implements Send {

static configSchema = customEmailConfigSchema

static optionSchema = customEmailOptionSchema

private config: CustomEmailConfig

private transporter: nodemailer.Transporter<SMTPTransport.SentMessageInfo, SMTPTransport.Options>

constructor(config: CustomEmailConfig) {
this.config = config
Debugger('CustomEmailConfig: %o', config)
Object.entries(config).forEach(([key, value]) => {
if (!value) {
throw new Error(`CustomEmailConfig 的 "${key}" 字段是必须的!`)
}
})
// 根据 configSchema 验证 config
validate(config, CustomEmail.configSchema)
const { EMAIL_AUTH_USER, EMAIL_AUTH_PASS, EMAIL_HOST, EMAIL_PORT } = this.config
this.transporter = nodemailer.createTransport({
host: EMAIL_HOST,
Expand Down
Loading

0 comments on commit 4f7d8c3

Please sign in to comment.