fix: add missing @RequirePermission decorators

Adds @RequirePermission to 19 TS GraphQL resolvers across 5
subgraphs (iam, config-service, core-edu, content, msg) per
audit report §6.1. Maps: iam user/role -> IAM_USER_READ;
config-service 5 queries -> CONFIG_USER; core-edu classInfo ->
CLASS_READ, exam -> EXAM_READ, grade -> GRADE_READ, homework
-> HOMEWORK_READ, datascope visibleGrades/visibleExams ->
GRADE_READ/EXAM_READ; content chapter/knowledgePoint/question/
textbook -> CONTENT_*_READ; msg notifications ->
MSG_NOTIFICATION_READ, template -> MSG_NOTIFICATION_MANAGE.
Federation resolveReference left unguarded. Python subgraphs
(data-ana, ai) deferred to follow-up infrastructure work.
This commit is contained in:
SpecialX
2026-07-17 13:26:58 +08:00
parent 315b954998
commit 1b5781bf42
17 changed files with 117 additions and 30 deletions

View File

@@ -6,6 +6,10 @@
*/ */
import { Resolver, Query, ObjectType, Field, ID } from "@nestjs/graphql"; import { Resolver, Query, ObjectType, Field, ID } from "@nestjs/graphql";
import { ConfigService } from "../../config-config/config.service.js"; import { ConfigService } from "../../config-config/config.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
@ObjectType() @ObjectType()
export class LayoutTemplateGql { export class LayoutTemplateGql {
@@ -15,7 +19,7 @@ export class LayoutTemplateGql {
@Field() @Field()
displayName!: string; displayName!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
description: string | null = null; description: string | null = null;
@Field() @Field()
@@ -27,6 +31,7 @@ export class LayoutTemplateResolver {
constructor(private readonly service: ConfigService) {} constructor(private readonly service: ConfigService) {}
@Query(() => [LayoutTemplateGql]) @Query(() => [LayoutTemplateGql])
@RequirePermission(Permissions.CONFIG_USER)
async layoutTemplates(): Promise<LayoutTemplateGql[]> { async layoutTemplates(): Promise<LayoutTemplateGql[]> {
const templates = await this.service.listLayoutTemplates(); const templates = await this.service.listLayoutTemplates();
return templates.map((t) => ({ return templates.map((t) => ({

View File

@@ -21,6 +21,10 @@ import {
Int, Int,
} from "@nestjs/graphql"; } from "@nestjs/graphql";
import { ConfigService } from "../../config-config/config.service.js"; import { ConfigService } from "../../config-config/config.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
@ObjectType() @ObjectType()
export class PluginConfigLayoutGql { export class PluginConfigLayoutGql {
@@ -123,6 +127,7 @@ export class PluginConfigResolver {
* (来自 x-user-role 头),返回与 gRPC GetPluginConfig 等价的结果。 * (来自 x-user-role 头),返回与 gRPC GetPluginConfig 等价的结果。
*/ */
@Query(() => PluginConfigResponseGql) @Query(() => PluginConfigResponseGql)
@RequirePermission(Permissions.CONFIG_USER)
async pluginConfig( async pluginConfig(
@Args("userId", { type: () => ID }) userId: string, @Args("userId", { type: () => ID }) userId: string,
@Args("role", { @Args("role", {

View File

@@ -21,6 +21,10 @@ import {
type PluginRegistryEntity, type PluginRegistryEntity,
} from "../dataloader.service.js"; } from "../dataloader.service.js";
import { ConfigService } from "../../config-config/config.service.js"; import { ConfigService } from "../../config-config/config.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
@ObjectType() @ObjectType()
@Directive(`@key(fields: "pluginId")`) @Directive(`@key(fields: "pluginId")`)
@@ -62,6 +66,7 @@ export class PluginResolver {
} }
@Query(() => PluginRegistry, { nullable: true }) @Query(() => PluginRegistry, { nullable: true })
@RequirePermission(Permissions.CONFIG_USER)
async plugin( async plugin(
@Args("pluginId", { type: () => ID }) pluginId: string, @Args("pluginId", { type: () => ID }) pluginId: string,
): Promise<PluginRegistryEntity | null> { ): Promise<PluginRegistryEntity | null> {
@@ -69,6 +74,7 @@ export class PluginResolver {
} }
@Query(() => [PluginRegistry]) @Query(() => [PluginRegistry])
@RequirePermission(Permissions.CONFIG_USER)
async plugins(): Promise<PluginRegistryEntity[]> { async plugins(): Promise<PluginRegistryEntity[]> {
const list = await this.service.listPlugins({ isActive: true }); const list = await this.service.listPlugins({ isActive: true });
return list.map((p) => ({ return list.map((p) => ({

View File

@@ -20,6 +20,10 @@ import {
DataLoaderService, DataLoaderService,
type UserLayoutOverrideEntity, type UserLayoutOverrideEntity,
} from "../dataloader.service.js"; } from "../dataloader.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
@ObjectType() @ObjectType()
@Directive(`@key(fields: "userId")`) @Directive(`@key(fields: "userId")`)
@@ -46,6 +50,7 @@ export class UserLayoutResolver {
} }
@Query(() => UserLayoutOverrideGql, { nullable: true }) @Query(() => UserLayoutOverrideGql, { nullable: true })
@RequirePermission(Permissions.CONFIG_USER)
async userLayoutOverride( async userLayoutOverride(
@Args("userId", { type: () => ID }) userId: string, @Args("userId", { type: () => ID }) userId: string,
): Promise<UserLayoutOverrideEntity | null> { ): Promise<UserLayoutOverrideEntity | null> {

View File

@@ -19,6 +19,10 @@ import {
DataLoaderService, DataLoaderService,
type ChapterEntity, type ChapterEntity,
} from "../dataloader.service.js"; } from "../dataloader.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
/** /**
* Chapter ObjectTypeFederation @key * Chapter ObjectTypeFederation @key
@@ -39,7 +43,7 @@ export class Chapter {
@Field() @Field()
order!: number; order!: number;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
parentId: string | null = null; parentId: string | null = null;
@Field() @Field()
@@ -72,6 +76,7 @@ export class ChapterResolver {
* 通过 Apollo Router 访问,直连被 RouterAuthGuard 拒绝ADR-036 * 通过 Apollo Router 访问,直连被 RouterAuthGuard 拒绝ADR-036
*/ */
@Query(() => Chapter, { nullable: true }) @Query(() => Chapter, { nullable: true })
@RequirePermission(Permissions.CONTENT_CHAPTER_READ)
async chapter( async chapter(
@Args("id", { type: () => ID }) id: string, @Args("id", { type: () => ID }) id: string,
): Promise<ChapterEntity | null> { ): Promise<ChapterEntity | null> {

View File

@@ -19,6 +19,10 @@ import {
DataLoaderService, DataLoaderService,
type KnowledgePointEntity, type KnowledgePointEntity,
} from "../dataloader.service.js"; } from "../dataloader.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
/** /**
* KnowledgePoint ObjectTypeFederation @key * KnowledgePoint ObjectTypeFederation @key
@@ -36,7 +40,7 @@ export class KnowledgePoint {
@Field() @Field()
title!: string; title!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
description: string | null = null; description: string | null = null;
@Field() @Field()
@@ -71,6 +75,7 @@ export class KnowledgePointResolver {
* 通过 Apollo Router 访问,直连被 RouterAuthGuard 拒绝ADR-036 * 通过 Apollo Router 访问,直连被 RouterAuthGuard 拒绝ADR-036
*/ */
@Query(() => KnowledgePoint, { nullable: true }) @Query(() => KnowledgePoint, { nullable: true })
@RequirePermission(Permissions.CONTENT_KNOWLEDGE_POINT_READ)
async knowledgePoint( async knowledgePoint(
@Args("id", { type: () => ID }) id: string, @Args("id", { type: () => ID }) id: string,
): Promise<KnowledgePointEntity | null> { ): Promise<KnowledgePointEntity | null> {

View File

@@ -19,6 +19,10 @@ import {
DataLoaderService, DataLoaderService,
type QuestionEntity, type QuestionEntity,
} from "../dataloader.service.js"; } from "../dataloader.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
/** /**
* Question ObjectTypeFederation @key * Question ObjectTypeFederation @key
@@ -42,7 +46,7 @@ export class Question {
@Field() @Field()
answer!: string; answer!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
explanation: string | null = null; explanation: string | null = null;
@Field() @Field()
@@ -84,6 +88,7 @@ export class QuestionResolver {
* 通过 Apollo Router 访问,直连被 RouterAuthGuard 拒绝ADR-036 * 通过 Apollo Router 访问,直连被 RouterAuthGuard 拒绝ADR-036
*/ */
@Query(() => Question, { nullable: true }) @Query(() => Question, { nullable: true })
@RequirePermission(Permissions.CONTENT_QUESTION_READ)
async question( async question(
@Args("id", { type: () => ID }) id: string, @Args("id", { type: () => ID }) id: string,
): Promise<QuestionEntity | null> { ): Promise<QuestionEntity | null> {

View File

@@ -20,6 +20,10 @@ import {
DataLoaderService, DataLoaderService,
type TextbookEntity, type TextbookEntity,
} from "../dataloader.service.js"; } from "../dataloader.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
/** /**
* Textbook ObjectTypeFederation @key * Textbook ObjectTypeFederation @key
@@ -46,7 +50,7 @@ export class Textbook {
@Field() @Field()
status!: string; status!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
tenantId: string | null = null; tenantId: string | null = null;
@Field() @Field()
@@ -76,6 +80,7 @@ export class TextbookResolver {
* 通过 Apollo Router 访问,直连被 RouterAuthGuard 拒绝ADR-036 * 通过 Apollo Router 访问,直连被 RouterAuthGuard 拒绝ADR-036
*/ */
@Query(() => Textbook, { nullable: true }) @Query(() => Textbook, { nullable: true })
@RequirePermission(Permissions.CONTENT_TEXTBOOK_READ)
async textbook( async textbook(
@Args("id", { type: () => ID }) id: string, @Args("id", { type: () => ID }) id: string,
): Promise<TextbookEntity | null> { ): Promise<TextbookEntity | null> {

View File

@@ -21,6 +21,10 @@ import {
type ClassEntity, type ClassEntity,
type StudentEntity, type StudentEntity,
} from "../dataloader.service.js"; } from "../dataloader.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
/** /**
* ClassInfo ObjectTypeFederation @key * ClassInfo ObjectTypeFederation @key
@@ -38,10 +42,10 @@ export class ClassInfo {
@Field() @Field()
gradeId!: string; gradeId!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
headTeacherId: string | null = null; headTeacherId: string | null = null;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
description: string | null = null; description: string | null = null;
@Field() @Field()
@@ -78,6 +82,7 @@ export class ClassResolver {
} }
@Query(() => ClassInfo, { nullable: true }) @Query(() => ClassInfo, { nullable: true })
@RequirePermission(Permissions.CLASS_READ)
async classInfo( async classInfo(
@Args("id", { type: () => ID }) id: string, @Args("id", { type: () => ID }) id: string,
): Promise<ClassEntity | null> { ): Promise<ClassEntity | null> {

View File

@@ -26,6 +26,10 @@ import {
import type { RedisClientType } from "redis"; import type { RedisClientType } from "redis";
import { ExamsRepository } from "../../exams/exams.repository.js"; import { ExamsRepository } from "../../exams/exams.repository.js";
import { GradesRepository } from "../../grades/grades.repository.js"; import { GradesRepository } from "../../grades/grades.repository.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
/** /**
* ScopedGrade 引用类型(仅用于 DataScope 返回) * ScopedGrade 引用类型(仅用于 DataScope 返回)
@@ -40,10 +44,10 @@ class ScopedGrade {
@Field() @Field()
studentId!: string; studentId!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
examId: string | null = null; examId: string | null = null;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
homeworkId: string | null = null; homeworkId: string | null = null;
@Field() @Field()
@@ -52,7 +56,7 @@ class ScopedGrade {
@Field() @Field()
totalScore!: string; totalScore!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
feedback: string | null = null; feedback: string | null = null;
@Field() @Field()
@@ -61,7 +65,7 @@ class ScopedGrade {
@Field() @Field()
schoolId!: string; schoolId!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
idempotencyKey: string | null = null; idempotencyKey: string | null = null;
@Field() @Field()
@@ -89,7 +93,7 @@ class ScopedExam {
@Field() @Field()
title!: string; title!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
description: string | null = null; description: string | null = null;
@Field() @Field()
@@ -107,7 +111,7 @@ class ScopedExam {
@Field() @Field()
statusChangedAt!: string; statusChangedAt!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
statusChangedBy: string | null = null; statusChangedBy: string | null = null;
@Field() @Field()
@@ -116,7 +120,7 @@ class ScopedExam {
@Field() @Field()
createdBy!: string; createdBy!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
archivedAt: string | null = null; archivedAt: string | null = null;
@Field() @Field()
@@ -227,6 +231,7 @@ export class DataScopeResolver {
* Router 通过 @requires 将 studentScopeToken 传入 parent。 * Router 通过 @requires 将 studentScopeToken 传入 parent。
*/ */
@ResolveField(() => [ScopedGrade]) @ResolveField(() => [ScopedGrade])
@RequirePermission(Permissions.GRADE_READ)
async visibleGrades( async visibleGrades(
@Parent() parent: { userId: string; studentScopeToken: string }, @Parent() parent: { userId: string; studentScopeToken: string },
): Promise<ScopedGrade[]> { ): Promise<ScopedGrade[]> {
@@ -250,6 +255,7 @@ export class DataScopeResolver {
* visibleExams通过 classScopeToken 解析可见班级 ID查询考试 * visibleExams通过 classScopeToken 解析可见班级 ID查询考试
*/ */
@ResolveField(() => [ScopedExam]) @ResolveField(() => [ScopedExam])
@RequirePermission(Permissions.EXAM_READ)
async visibleExams( async visibleExams(
@Parent() parent: { userId: string; classScopeToken: string }, @Parent() parent: { userId: string; classScopeToken: string },
): Promise<ScopedExam[]> { ): Promise<ScopedExam[]> {

View File

@@ -17,6 +17,10 @@ import {
Directive, Directive,
} from "@nestjs/graphql"; } from "@nestjs/graphql";
import { DataLoaderService, type ExamEntity } from "../dataloader.service.js"; import { DataLoaderService, type ExamEntity } from "../dataloader.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
/** /**
* Exam ObjectTypeFederation @key * Exam ObjectTypeFederation @key
@@ -37,7 +41,7 @@ export class Exam {
@Field() @Field()
title!: string; title!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
description: string | null = null; description: string | null = null;
@Field() @Field()
@@ -55,7 +59,7 @@ export class Exam {
@Field() @Field()
statusChangedAt!: string; statusChangedAt!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
statusChangedBy: string | null = null; statusChangedBy: string | null = null;
@Field() @Field()
@@ -64,7 +68,7 @@ export class Exam {
@Field() @Field()
createdBy!: string; createdBy!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
archivedAt: string | null = null; archivedAt: string | null = null;
@Field() @Field()
@@ -94,6 +98,7 @@ export class ExamResolver {
* 通过 Apollo Router 访问,直连被 RouterAuthGuard 拒绝ADR-036 * 通过 Apollo Router 访问,直连被 RouterAuthGuard 拒绝ADR-036
*/ */
@Query(() => Exam, { nullable: true }) @Query(() => Exam, { nullable: true })
@RequirePermission(Permissions.EXAM_READ)
async exam( async exam(
@Args("id", { type: () => ID }) id: string, @Args("id", { type: () => ID }) id: string,
): Promise<ExamEntity | null> { ): Promise<ExamEntity | null> {

View File

@@ -16,6 +16,10 @@ import {
Directive, Directive,
} from "@nestjs/graphql"; } from "@nestjs/graphql";
import { DataLoaderService, type GradeEntity } from "../dataloader.service.js"; import { DataLoaderService, type GradeEntity } from "../dataloader.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
/** /**
* Grade ObjectTypeFederation @key * Grade ObjectTypeFederation @key
@@ -30,10 +34,10 @@ export class Grade {
@Field() @Field()
studentId!: string; studentId!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
examId: string | null = null; examId: string | null = null;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
homeworkId: string | null = null; homeworkId: string | null = null;
@Field() @Field()
@@ -42,7 +46,7 @@ export class Grade {
@Field() @Field()
totalScore!: string; totalScore!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
feedback: string | null = null; feedback: string | null = null;
@Field() @Field()
@@ -51,7 +55,7 @@ export class Grade {
@Field() @Field()
schoolId!: string; schoolId!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
idempotencyKey: string | null = null; idempotencyKey: string | null = null;
@Field() @Field()
@@ -71,6 +75,7 @@ export class GradeResolver {
} }
@Query(() => Grade, { nullable: true }) @Query(() => Grade, { nullable: true })
@RequirePermission(Permissions.GRADE_READ)
async grade( async grade(
@Args("id", { type: () => ID }) id: string, @Args("id", { type: () => ID }) id: string,
): Promise<GradeEntity | null> { ): Promise<GradeEntity | null> {

View File

@@ -19,6 +19,10 @@ import {
DataLoaderService, DataLoaderService,
type HomeworkEntity, type HomeworkEntity,
} from "../dataloader.service.js"; } from "../dataloader.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
/** /**
* Homework ObjectTypeFederation @key * Homework ObjectTypeFederation @key
@@ -39,7 +43,7 @@ export class Homework {
@Field() @Field()
title!: string; title!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
description: string | null = null; description: string | null = null;
@Field() @Field()
@@ -74,6 +78,7 @@ export class HomeworkResolver {
} }
@Query(() => Homework, { nullable: true }) @Query(() => Homework, { nullable: true })
@RequirePermission(Permissions.HOMEWORK_READ)
async homework( async homework(
@Args("id", { type: () => ID }) id: string, @Args("id", { type: () => ID }) id: string,
): Promise<HomeworkEntity | null> { ): Promise<HomeworkEntity | null> {

View File

@@ -16,6 +16,10 @@ import {
Directive, Directive,
} from "@nestjs/graphql"; } from "@nestjs/graphql";
import { DataLoaderService, type RoleEntity } from "../dataloader.service.js"; import { DataLoaderService, type RoleEntity } from "../dataloader.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
@ObjectType() @ObjectType()
@Directive(`@key(fields: "roleId")`) @Directive(`@key(fields: "roleId")`)
@@ -26,7 +30,7 @@ export class Role {
@Field() @Field()
name!: string; name!: string;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
description: string | null = null; description: string | null = null;
@Field() @Field()
@@ -46,6 +50,7 @@ export class RoleResolver {
} }
@Query(() => Role, { nullable: true }) @Query(() => Role, { nullable: true })
@RequirePermission(Permissions.IAM_USER_READ)
async role( async role(
@Args("roleId", { type: () => ID }) roleId: string, @Args("roleId", { type: () => ID }) roleId: string,
): Promise<RoleEntity | null> { ): Promise<RoleEntity | null> {

View File

@@ -17,6 +17,10 @@ import {
Directive, Directive,
} from "@nestjs/graphql"; } from "@nestjs/graphql";
import { DataLoaderService, type UserEntity } from "../dataloader.service.js"; import { DataLoaderService, type UserEntity } from "../dataloader.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
/** /**
* User ObjectTypeFederation @key * User ObjectTypeFederation @key
@@ -61,6 +65,7 @@ export class UserResolver {
* 通过 Apollo Router 访问,直连被 RouterAuthGuard 拒绝ADR-036 * 通过 Apollo Router 访问,直连被 RouterAuthGuard 拒绝ADR-036
*/ */
@Query(() => User, { nullable: true }) @Query(() => User, { nullable: true })
@RequirePermission(Permissions.IAM_USER_READ)
async user( async user(
@Args("userId", { type: () => ID }) userId: string, @Args("userId", { type: () => ID }) userId: string,
): Promise<UserEntity | null> { ): Promise<UserEntity | null> {

View File

@@ -21,6 +21,10 @@ import {
type NotificationEntity, type NotificationEntity,
} from "../dataloader.service.js"; } from "../dataloader.service.js";
import { listByUser } from "../../notifications/notifications.repository.js"; import { listByUser } from "../../notifications/notifications.repository.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
/** /**
* Notification ObjectTypeFederation @key * Notification ObjectTypeFederation @key
@@ -59,25 +63,25 @@ export class Notification {
@Field() @Field()
updatedAt!: Date; updatedAt!: Date;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
relatedEntityType: string | null = null; relatedEntityType: string | null = null;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
relatedEntityId: string | null = null; relatedEntityId: string | null = null;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
groupId: string | null = null; groupId: string | null = null;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
senderId: string | null = null; senderId: string | null = null;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
templateId: string | null = null; templateId: string | null = null;
@Field({ nullable: true }) @Field(() => String, { nullable: true })
eventId: string | null = null; eventId: string | null = null;
@Field({ nullable: true }) @Field(() => Date, { nullable: true })
readAt: Date | null = null; readAt: Date | null = null;
} }
@@ -103,6 +107,7 @@ export class NotificationResolver {
* 通过 Apollo Router 访问,直连被 RouterAuthGuard 拒绝ADR-036 * 通过 Apollo Router 访问,直连被 RouterAuthGuard 拒绝ADR-036
*/ */
@Query(() => [Notification]) @Query(() => [Notification])
@RequirePermission(Permissions.MSG_NOTIFICATION_READ)
async notifications( async notifications(
@Args("userId", { type: () => ID }) userId: string, @Args("userId", { type: () => ID }) userId: string,
): Promise<NotificationEntity[]> { ): Promise<NotificationEntity[]> {

View File

@@ -19,6 +19,10 @@ import {
DataLoaderService, DataLoaderService,
type TemplateEntity, type TemplateEntity,
} from "../dataloader.service.js"; } from "../dataloader.service.js";
import {
Permissions,
RequirePermission,
} from "../../middleware/permission.guard.js";
/** /**
* NotificationTemplate ObjectTypeFederation @key * NotificationTemplate ObjectTypeFederation @key
@@ -81,6 +85,7 @@ export class TemplateResolver {
* 通过 Apollo Router 访问,直连被 RouterAuthGuard 拒绝ADR-036 * 通过 Apollo Router 访问,直连被 RouterAuthGuard 拒绝ADR-036
*/ */
@Query(() => NotificationTemplate, { nullable: true }) @Query(() => NotificationTemplate, { nullable: true })
@RequirePermission(Permissions.MSG_NOTIFICATION_MANAGE)
async template( async template(
@Args("id", { type: () => ID }) id: string, @Args("id", { type: () => ID }) id: string,
): Promise<TemplateEntity | null> { ): Promise<TemplateEntity | null> {