This repository has been archived on 2026-09-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BPOS-TMP-REPO/packages/contracts/src/api/mail.ts
T

179 lines
5.8 KiB
TypeScript

/**
* 邮件收发的路由契约(设计稿 §7.2;ADR-013)。
*
* 读标两端(设计中心要看通道对上了哪个信箱,运营中心管配置);写归运营中心。
* `/api/gov/*` 的写路由由 app 中间件自动广播 `{kind:'system', id:'gov'}`。
* 探测 / 试发 / 拉取的**厂商失败不是接口错误**:200 的 body 里带原话(脱敏)。
*/
import { z } from 'zod';
import type { RouteContract } from './http.ts';
import {
mailAccountInputSchema,
mailAccountViewSchema,
mailDeliveryViewSchema,
mailInboundDetailSchema,
mailInboundSummarySchema,
mailOutboundViewSchema,
mailPollOutcomeSchema,
mailProbeReportSchema,
mailSendResultSchema,
} from '../mail.ts';
const accountParams = z.object({ accountId: z.string().min(1) });
const mailParams = z.object({ mailId: z.string().min(1) });
const listQuery = z.object({
accountId: z.string().optional(),
/** 默认 50,上限 200 */
limit: z.coerce.number().int().min(1).max(200).optional(),
});
export const listMailAccountsRoute = {
method: 'GET',
path: '/api/gov/mail/accounts',
access: ['design', 'ops'],
output: z.object({ accounts: z.array(mailAccountViewSchema) }),
} as const satisfies RouteContract;
export const createMailAccountRoute = {
method: 'POST',
path: '/api/gov/mail/accounts',
access: 'ops',
body: mailAccountInputSchema,
output: mailAccountViewSchema,
} as const satisfies RouteContract;
/** 未存的配置探测 —— 表单上正填的那份,一个字节都不落库 */
export const probeMailDraftRoute = {
method: 'POST',
path: '/api/gov/mail/accounts/probe',
access: 'ops',
/** 改已存账号时带上 ?accountId=:表单没重填密码就借库里那份密文 */
query: z.object({ accountId: z.string().optional() }),
body: mailAccountInputSchema,
output: mailProbeReportSchema,
} as const satisfies RouteContract;
export const getMailAccountRoute = {
method: 'GET',
path: '/api/gov/mail/accounts/:accountId',
access: ['design', 'ops'],
params: accountParams,
output: mailAccountViewSchema,
} as const satisfies RouteContract;
/** secret 缺省 = 不改密文;kind / settings 变了游标清空重锚 */
export const updateMailAccountRoute = {
method: 'PUT',
path: '/api/gov/mail/accounts/:accountId',
access: 'ops',
params: accountParams,
body: mailAccountInputSchema,
output: mailAccountViewSchema,
} as const satisfies RouteContract;
export const deleteMailAccountRoute = {
method: 'DELETE',
path: '/api/gov/mail/accounts/:accountId',
access: 'ops',
params: accountParams,
output: z.object({ ok: z.literal(true) }),
} as const satisfies RouteContract;
/** 已存账号探测(用库里凭证);凭证空 412 */
export const probeMailAccountRoute = {
method: 'POST',
path: '/api/gov/mail/accounts/:accountId/probe',
access: 'ops',
params: accountParams,
output: mailProbeReportSchema,
} as const satisfies RouteContract;
/** 立即拉一轮;另一进程占着租约 409;停用 412 */
export const pollMailAccountRoute = {
method: 'POST',
path: '/api/gov/mail/accounts/:accountId/poll',
access: 'ops',
params: accountParams,
output: mailPollOutcomeSchema,
} as const satisfies RouteContract;
export const sendTestMailRoute = {
method: 'POST',
path: '/api/gov/mail/accounts/:accountId/send-test',
access: 'ops',
params: accountParams,
body: z.object({ to: z.email(), subject: z.string().min(1), text: z.string().min(1) }),
output: mailSendResultSchema,
} as const satisfies RouteContract;
export const listMailInboundRoute = {
method: 'GET',
path: '/api/gov/mail/inbound',
access: ['design', 'ops'],
query: listQuery,
output: z.object({ mails: z.array(mailInboundSummarySchema) }),
} as const satisfies RouteContract;
export const getMailInboundRoute = {
method: 'GET',
path: '/api/gov/mail/inbound/:mailId',
access: ['design', 'ops'],
params: mailParams,
output: mailInboundDetailSchema,
} as const satisfies RouteContract;
/** 附件原件(JSON + base64,与 /api/gov/upload/:id 同形);可带 ?ticket= 下载(ADR-012 §6) */
export const mailAttachmentRoute = {
method: 'GET',
path: '/api/gov/mail/inbound/:mailId/attachments/:index',
access: ['design', 'ops'],
params: z.object({ mailId: z.string().min(1), index: z.coerce.number().int().min(0) }),
output: z.object({ name: z.string(), contentType: z.string(), dataBase64: z.string() }),
} as const satisfies RouteContract;
/** 原件 .eml;超限没存 404 */
export const mailRawRoute = {
method: 'GET',
path: '/api/gov/mail/inbound/:mailId/raw',
access: ['design', 'ops'],
params: mailParams,
output: z.object({ name: z.string(), contentType: z.literal('message/rfc822'), dataBase64: z.string() }),
} as const satisfies RouteContract;
/** 人按的钮:重新投给订阅方(缺省全部);从此是至少一次 */
export const redeliverMailInboundRoute = {
method: 'POST',
path: '/api/gov/mail/inbound/:mailId/redeliver',
access: 'ops',
params: mailParams,
body: z.object({ subscriber: z.string().min(1).optional() }).default({}),
output: z.object({ deliveries: z.array(mailDeliveryViewSchema) }),
} as const satisfies RouteContract;
export const listMailOutboundRoute = {
method: 'GET',
path: '/api/gov/mail/outbound',
access: ['design', 'ops'],
query: listQuery,
output: z.object({ mails: z.array(mailOutboundViewSchema) }),
} as const satisfies RouteContract;
/** 全部 15 条 —— 文档总表与覆盖测试按它数 */
export const MAIL_ROUTES: readonly RouteContract[] = [
listMailAccountsRoute,
createMailAccountRoute,
probeMailDraftRoute,
getMailAccountRoute,
updateMailAccountRoute,
deleteMailAccountRoute,
probeMailAccountRoute,
pollMailAccountRoute,
sendTestMailRoute,
listMailInboundRoute,
getMailInboundRoute,
mailAttachmentRoute,
mailRawRoute,
redeliverMailInboundRoute,
listMailOutboundRoute,
];