Files
Edu/services/msg/src/notifications/notifications.schema.ts
SpecialX 7b7abbb309 feat(msg): 完整实现 msg 消息服务
包含 channels/preferences/templates/grpc/kafka/outbox/push/redis 等完整实现
2026-07-10 19:09:52 +08:00

164 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
boolean,
int,
json,
mysqlTable,
text,
timestamp,
varchar,
} from "drizzle-orm/mysql-core";
/**
* msg 服务数据库 Schema。
*
* 仲裁依据:
* - 02-architecture-design.md §3.1(表清单 + 字段 + 索引)
* - G11ID 用 cuid2varchar(32)
* - proto msg.proto 字段对齐Notification / NotificationPreference / NotificationTemplate
*
* 表清单:
* - msg_notifications通知主表扩展字段
* - msg_notification_preferences用户偏好扩展字段
* - msg_notification_templates通知模板新增
* - msg_notification_deliveries投递记录新增
*
* 注msg_outbox_events / processed_events 在 shared/outbox/outbox.schema.ts
*/
// ============================================================
// 通知状态 / 渠道 / 类型 枚举字符串枚举DB 存 varchar
// ============================================================
export type NotificationStatus =
"pending" | "sent" | "delivered" | "read" | "recalled" | "failed";
export type NotificationChannel =
"in_app" | "email" | "sms" | "push" | "wechat";
export type NotificationType =
"system" | "exam" | "homework" | "grade" | "attendance" | "mastery";
export type DeliveryStatus =
"pending" | "sent" | "delivered" | "failed" | "retrying";
export type TemplateStatus = "draft" | "active" | "archived";
// ============================================================
// msg_notifications通知主表 · 扩展)
// ============================================================
export const notifications = mysqlTable("msg_notifications", {
id: varchar("id", { length: 32 }).notNull().primaryKey(),
userId: varchar("user_id", { length: 32 }).notNull(),
type: varchar("type", { length: 32 }).notNull().$type<NotificationType>(),
title: varchar("title", { length: 255 }).notNull(),
content: text("content").notNull(),
channel: varchar("channel", { length: 32 })
.notNull()
.$type<NotificationChannel>(),
isRead: boolean("is_read").notNull().default(false),
createdAt: timestamp("created_at").notNull().defaultNow(),
// 扩展字段P5
status: varchar("status", { length: 32 })
.notNull()
.default("pending")
.$type<NotificationStatus>(),
metadata: json("metadata").$type<Record<string, string> | null>(),
relatedEntityType: varchar("related_entity_type", { length: 64 }),
relatedEntityId: varchar("related_entity_id", { length: 32 }),
groupId: varchar("group_id", { length: 32 }),
senderId: varchar("sender_id", { length: 32 }),
templateId: varchar("template_id", { length: 32 }),
eventId: varchar("event_id", { length: 64 }),
readAt: timestamp("read_at"),
updatedAt: timestamp("updated_at").notNull().defaultNow().onUpdateNow(),
});
export type Notification = typeof notifications.$inferSelect;
export type NewNotification = typeof notifications.$inferInsert;
// ============================================================
// msg_notification_preferences用户偏好 · 扩展)
// ============================================================
export const notificationPreferences = mysqlTable(
"msg_notification_preferences",
{
id: varchar("id", { length: 32 }).notNull().primaryKey(),
userId: varchar("user_id", { length: 32 }).notNull(),
type: varchar("type", { length: 32 }).notNull().$type<NotificationType>(),
channels: json("channels").notNull().$type<NotificationChannel[]>(),
frequencyLimit: int("frequency_limit"),
quietHoursStart: varchar("quiet_hours_start", { length: 8 }),
quietHoursEnd: varchar("quiet_hours_end", { length: 8 }),
quietHoursTimezone: varchar("quiet_hours_timezone", { length: 64 }).default(
"Asia/Shanghai",
),
enabled: boolean("enabled").notNull().default(true),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at").notNull().defaultNow().onUpdateNow(),
},
);
export type NotificationPreference =
typeof notificationPreferences.$inferSelect;
export type NewNotificationPreference =
typeof notificationPreferences.$inferInsert;
// ============================================================
// msg_notification_templates通知模板 · 新增)
// ============================================================
export const notificationTemplates = mysqlTable("msg_notification_templates", {
id: varchar("id", { length: 32 }).notNull().primaryKey(),
code: varchar("code", { length: 64 }).notNull(),
type: varchar("type", { length: 32 }).notNull().$type<NotificationType>(),
titleTemplate: varchar("title_template", { length: 255 }).notNull(),
contentTemplate: text("content_template").notNull(),
defaultChannels: json("default_channels")
.notNull()
.$type<NotificationChannel[]>(),
variables: json("variables").notNull().$type<string[]>(),
locale: varchar("locale", { length: 16 }).notNull().default("zh-CN"),
status: varchar("status", { length: 32 })
.notNull()
.default("draft")
.$type<TemplateStatus>(),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at").notNull().defaultNow().onUpdateNow(),
});
export type NotificationTemplate = typeof notificationTemplates.$inferSelect;
export type NewNotificationTemplate = typeof notificationTemplates.$inferInsert;
// ============================================================
// msg_notification_deliveries投递记录 · 新增)
// ============================================================
export const notificationDeliveries = mysqlTable(
"msg_notification_deliveries",
{
id: varchar("id", { length: 32 }).notNull().primaryKey(),
notificationId: varchar("notification_id", { length: 32 }).notNull(),
channel: varchar("channel", { length: 32 })
.notNull()
.$type<NotificationChannel>(),
status: varchar("status", { length: 32 })
.notNull()
.default("pending")
.$type<DeliveryStatus>(),
externalId: varchar("external_id", { length: 128 }),
attemptCount: int("attempt_count").notNull().default(0),
maxRetry: int("max_retry").notNull().default(3),
lastError: text("last_error"),
nextRetryAt: timestamp("next_retry_at"),
deliveredAt: timestamp("delivered_at"),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at").notNull().defaultNow().onUpdateNow(),
},
);
export type NotificationDelivery = typeof notificationDeliveries.$inferSelect;
export type NewNotificationDelivery =
typeof notificationDeliveries.$inferInsert;