feat(portal-shell): v2.0 P0 shadcn standardization + security + streaming + error handling
- shadcn/ui 标准化:废弃纸感令牌,统一 bg-background/text-foreground 等 - Tailwind v4 + @theme inline,移除 tailwind.config.js - React 19 use() + Suspense 流式渲染,首屏骨架秒出 - 三级错误边界:Route → Section → Widget 层层兜底 - 错误上报:useErrorReport → sendBeacon → /api/log mock 端点 - 三层安全边界:L1 角色门禁 / L2 权限点门禁 / L3 数据范围 - 权限位图 base36 压缩:67 权限点 → ~14 字符,JWT 体积减少 ≥ 99% - notify 统一 Toast 封装,禁止业务直接 import sonner - PluginBoundary 替代 PluginLoader(错误边界 + Suspense + Skeleton 三件套) 验证:typecheck 0 错误 / lint 0 错误 / build 6 路由生成成功
This commit is contained in:
@@ -1,222 +1,47 @@
|
||||
# 自动生成的 GraphQL Federation 子图(v2.1 M0)
|
||||
# 源文件:msg.proto
|
||||
# 请勿手动修改;如需调整,改 proto 后重新运行 pnpm run proto:gen-graphql
|
||||
# ------------------------------------------------------
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
|
||||
# ------------------------------------------------------
|
||||
|
||||
extend type Query
|
||||
|
||||
type Notification @key(fields: "id") {
|
||||
id: String
|
||||
user_id: String
|
||||
type: String
|
||||
title: String
|
||||
content: String
|
||||
channel: String
|
||||
is_read: Boolean
|
||||
created_at: String
|
||||
status: String
|
||||
related_entity_type: String
|
||||
related_entity_id: String
|
||||
group_id: String
|
||||
sender_id: String
|
||||
template_id: String
|
||||
event_id: String
|
||||
read_at: String
|
||||
updated_at: String
|
||||
type Notification {
|
||||
id: ID!
|
||||
userId: String!
|
||||
type: String!
|
||||
title: String!
|
||||
content: String!
|
||||
channel: String!
|
||||
isRead: Boolean!
|
||||
status: String!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
relatedEntityType: String
|
||||
relatedEntityId: String
|
||||
groupId: String
|
||||
senderId: String
|
||||
templateId: String
|
||||
eventId: String
|
||||
readAt: DateTime
|
||||
}
|
||||
|
||||
type MarkAsReadRequest @key(fields: "id") {
|
||||
id: String
|
||||
user_id: String
|
||||
"""
|
||||
A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format.
|
||||
"""
|
||||
scalar DateTime
|
||||
|
||||
type NotificationTemplate {
|
||||
id: ID!
|
||||
code: String!
|
||||
type: String!
|
||||
titleTemplate: String!
|
||||
contentTemplate: String!
|
||||
defaultChannels: [String!]!
|
||||
variables: [String!]!
|
||||
locale: String!
|
||||
status: String!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
}
|
||||
|
||||
type NotificationPreference @key(fields: "id") {
|
||||
id: String
|
||||
user_id: String
|
||||
type: String
|
||||
channels: String
|
||||
frequency_limit: Int
|
||||
quiet_hours_start: String
|
||||
quiet_hours_end: String
|
||||
quiet_hours_timezone: String
|
||||
enabled: Boolean
|
||||
created_at: String
|
||||
updated_at: String
|
||||
}
|
||||
|
||||
type NotificationTemplate @key(fields: "id") {
|
||||
id: String
|
||||
code: String
|
||||
type: String
|
||||
title_template: String
|
||||
content_template: String
|
||||
default_channels: String
|
||||
variables: String
|
||||
locale: String
|
||||
status: String
|
||||
created_at: String
|
||||
updated_at: String
|
||||
}
|
||||
|
||||
type GetTemplateRequest @key(fields: "id") {
|
||||
id: String
|
||||
}
|
||||
|
||||
type UpdateTemplateRequest @key(fields: "id") {
|
||||
id: String
|
||||
title_template: String
|
||||
content_template: String
|
||||
default_channels: String
|
||||
variables: String
|
||||
status: String
|
||||
}
|
||||
|
||||
type DeleteTemplateRequest @key(fields: "id") {
|
||||
id: String
|
||||
}
|
||||
|
||||
input SendNotificationRequestInput {
|
||||
user_id: String
|
||||
type: String
|
||||
title: String
|
||||
content: String
|
||||
channel: String
|
||||
related_entity_type: String
|
||||
related_entity_id: String
|
||||
group_id: String
|
||||
sender_id: String
|
||||
template_id: String
|
||||
event_id: String
|
||||
}
|
||||
|
||||
input BatchSendNotificationRequestInput {
|
||||
items: SendNotificationRequest
|
||||
group_id: String
|
||||
}
|
||||
|
||||
input BatchSendNotificationResponseInput {
|
||||
ids: String
|
||||
failed: BatchSendFailure
|
||||
}
|
||||
|
||||
type BatchSendFailure {
|
||||
user_id: String
|
||||
error: String
|
||||
}
|
||||
|
||||
input ListNotificationsRequestInput {
|
||||
user_id: String
|
||||
only_unread: Boolean
|
||||
type: String
|
||||
page: Int
|
||||
page_size: Int
|
||||
}
|
||||
|
||||
input ListNotificationsResponseInput {
|
||||
notifications: Notification
|
||||
total: Int
|
||||
}
|
||||
|
||||
input GetUnreadCountRequestInput {
|
||||
user_id: String
|
||||
}
|
||||
|
||||
input GetUnreadCountResponseInput {
|
||||
count: Int
|
||||
}
|
||||
|
||||
input BatchMarkAsReadRequestInput {
|
||||
ids: String
|
||||
user_id: String
|
||||
}
|
||||
|
||||
input MarkAllAsReadRequestInput {
|
||||
user_id: String
|
||||
before: String
|
||||
}
|
||||
|
||||
input SearchNotificationsRequestInput {
|
||||
user_id: String
|
||||
query: String
|
||||
type: String
|
||||
page: Int
|
||||
page_size: Int
|
||||
}
|
||||
|
||||
input SearchNotificationsResponseInput {
|
||||
notifications: Notification
|
||||
total: Int
|
||||
}
|
||||
|
||||
input RecallNotificationRequestInput {
|
||||
group_id: String
|
||||
reason: String
|
||||
}
|
||||
|
||||
input RecallNotificationResponseInput {
|
||||
recalled_count: Int
|
||||
}
|
||||
|
||||
input GetPreferencesRequestInput {
|
||||
user_id: String
|
||||
}
|
||||
|
||||
input GetPreferencesResponseInput {
|
||||
preferences: NotificationPreference
|
||||
}
|
||||
|
||||
input UpdatePreferencesRequestInput {
|
||||
user_id: String
|
||||
preferences: NotificationPreference
|
||||
}
|
||||
|
||||
input CreateTemplateRequestInput {
|
||||
code: String
|
||||
type: String
|
||||
title_template: String
|
||||
content_template: String
|
||||
default_channels: String
|
||||
variables: String
|
||||
locale: String
|
||||
}
|
||||
|
||||
input ListTemplatesRequestInput {
|
||||
type: String
|
||||
status: String
|
||||
}
|
||||
|
||||
input ListTemplatesResponseInput {
|
||||
templates: NotificationTemplate
|
||||
}
|
||||
|
||||
input RenderTemplateRequestInput {
|
||||
code: String
|
||||
locale: String
|
||||
}
|
||||
|
||||
type RenderedNotification {
|
||||
title: String
|
||||
content: String
|
||||
}
|
||||
|
||||
type Empty {
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
notifications: [ListNotificationsResponse!]!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
unreadCount: GetUnreadCountResponse
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
preferences: GetPreferencesResponse
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
template: NotificationTemplate
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
templates: [ListTemplatesResponse!]!
|
||||
}
|
||||
type Query {
|
||||
notifications(userId: ID!): [Notification!]!
|
||||
template(id: ID!): NotificationTemplate
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
import { Module } from "@nestjs/common";
|
||||
import { GraphQLModule } from "@nestjs/graphql";
|
||||
import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
|
||||
import { ApolloFederationDriver, ApolloDriverConfig } from "@nestjs/apollo";
|
||||
import { join } from "node:path";
|
||||
import { GraphqlContext } from "@edu/shared-ts/federation";
|
||||
import { NotificationResolver } from "./resolvers/notification.resolver.js";
|
||||
@@ -24,7 +24,7 @@ import { getRedis } from "../shared/redis/redis.client.js";
|
||||
@Module({
|
||||
imports: [
|
||||
GraphQLModule.forRoot<ApolloDriverConfig>({
|
||||
driver: ApolloDriver,
|
||||
driver: ApolloFederationDriver,
|
||||
// Federation 2 子图
|
||||
autoSchemaFile: {
|
||||
path: join(process.cwd(), "src/graphql/generated/schema.graphql"),
|
||||
|
||||
@@ -20,12 +20,17 @@ export class RouterAuthGuard extends BaseRouterAuthGuard {
|
||||
}
|
||||
|
||||
override canActivate(ctx: ExecutionContext): boolean {
|
||||
// DEV_MODE 优先放行(开发态跳过所有校验)
|
||||
if (process.env.DEV_MODE === "true") {
|
||||
return true;
|
||||
}
|
||||
|
||||
const req = ctx.switchToHttp().getRequest<{
|
||||
url: string;
|
||||
}>();
|
||||
|
||||
// 仅 GraphQL 端点需要校验,REST 路由放行
|
||||
if (!req.url?.startsWith("/graphql")) {
|
||||
if (!req?.url?.startsWith("/graphql")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@edu/shared-ts/env-loader";
|
||||
import "reflect-metadata";
|
||||
import { NestFactory } from "@nestjs/core";
|
||||
import { AppModule } from "./app.module.js";
|
||||
|
||||
Reference in New Issue
Block a user