import { Injectable, CanActivate, ExecutionContext, SetMetadata, } from "@nestjs/common"; import { Reflector } from "@nestjs/core"; import { PermissionDeniedError } from "../shared/errors/application-error.js"; import type { AuthenticatedRequest } from "./auth.middleware.js"; export const Permissions = { CONTENT_TEXTBOOK_CREATE: "CONTENT_TEXTBOOK_CREATE" as const, CONTENT_TEXTBOOK_READ: "CONTENT_TEXTBOOK_READ" as const, CONTENT_TEXTBOOK_UPDATE: "CONTENT_TEXTBOOK_UPDATE" as const, CONTENT_TEXTBOOK_DELETE: "CONTENT_TEXTBOOK_DELETE" as const, CONTENT_CHAPTER_CREATE: "CONTENT_CHAPTER_CREATE" as const, CONTENT_CHAPTER_READ: "CONTENT_CHAPTER_READ" as const, CONTENT_CHAPTER_UPDATE: "CONTENT_CHAPTER_UPDATE" as const, CONTENT_CHAPTER_DELETE: "CONTENT_CHAPTER_DELETE" as const, CONTENT_QUESTION_CREATE: "CONTENT_QUESTION_CREATE" as const, CONTENT_QUESTION_READ: "CONTENT_QUESTION_READ" as const, CONTENT_QUESTION_UPDATE: "CONTENT_QUESTION_UPDATE" as const, CONTENT_QUESTION_DELETE: "CONTENT_QUESTION_DELETE" as const, CONTENT_KNOWLEDGE_POINT_CREATE: "CONTENT_KNOWLEDGE_POINT_CREATE" as const, CONTENT_KNOWLEDGE_POINT_READ: "CONTENT_KNOWLEDGE_POINT_READ" as const, CONTENT_KNOWLEDGE_POINT_UPDATE: "CONTENT_KNOWLEDGE_POINT_UPDATE" as const, CONTENT_KNOWLEDGE_POINT_DELETE: "CONTENT_KNOWLEDGE_POINT_DELETE" as const, // v2 新增:选修课 / 备课计划 / 课程计划 CONTENT_ELECTIVE_CREATE: "CONTENT_ELECTIVE_CREATE" as const, CONTENT_ELECTIVE_READ: "CONTENT_ELECTIVE_READ" as const, CONTENT_ELECTIVE_SELECT: "CONTENT_ELECTIVE_SELECT" as const, CONTENT_LESSON_PLAN_CREATE: "CONTENT_LESSON_PLAN_CREATE" as const, CONTENT_LESSON_PLAN_READ: "CONTENT_LESSON_PLAN_READ" as const, CONTENT_LESSON_PLAN_UPDATE: "CONTENT_LESSON_PLAN_UPDATE" as const, CONTENT_LESSON_PLAN_DELETE: "CONTENT_LESSON_PLAN_DELETE" as const, CONTENT_COURSE_PLAN_CREATE: "CONTENT_COURSE_PLAN_CREATE" as const, CONTENT_COURSE_PLAN_READ: "CONTENT_COURSE_PLAN_READ" as const, CONTENT_COURSE_PLAN_UPDATE: "CONTENT_COURSE_PLAN_UPDATE" as const, CONTENT_COURSE_PLAN_DELETE: "CONTENT_COURSE_PLAN_DELETE" as const, } as const; export type Permission = (typeof Permissions)[keyof typeof Permissions]; export const PERMISSIONS_KEY = "permissions"; export const RequirePermission = (...permissions: Permission[]) => SetMetadata(PERMISSIONS_KEY, permissions); const ROLE_PERMISSIONS: Record = { admin: [ Permissions.CONTENT_TEXTBOOK_CREATE, Permissions.CONTENT_TEXTBOOK_READ, Permissions.CONTENT_TEXTBOOK_UPDATE, Permissions.CONTENT_TEXTBOOK_DELETE, Permissions.CONTENT_CHAPTER_CREATE, Permissions.CONTENT_CHAPTER_READ, Permissions.CONTENT_CHAPTER_UPDATE, Permissions.CONTENT_CHAPTER_DELETE, Permissions.CONTENT_QUESTION_CREATE, Permissions.CONTENT_QUESTION_READ, Permissions.CONTENT_QUESTION_UPDATE, Permissions.CONTENT_QUESTION_DELETE, Permissions.CONTENT_KNOWLEDGE_POINT_CREATE, Permissions.CONTENT_KNOWLEDGE_POINT_READ, Permissions.CONTENT_KNOWLEDGE_POINT_UPDATE, Permissions.CONTENT_KNOWLEDGE_POINT_DELETE, Permissions.CONTENT_ELECTIVE_CREATE, Permissions.CONTENT_ELECTIVE_READ, Permissions.CONTENT_ELECTIVE_SELECT, Permissions.CONTENT_LESSON_PLAN_CREATE, Permissions.CONTENT_LESSON_PLAN_READ, Permissions.CONTENT_LESSON_PLAN_UPDATE, Permissions.CONTENT_LESSON_PLAN_DELETE, Permissions.CONTENT_COURSE_PLAN_CREATE, Permissions.CONTENT_COURSE_PLAN_READ, Permissions.CONTENT_COURSE_PLAN_UPDATE, Permissions.CONTENT_COURSE_PLAN_DELETE, ], teacher: [ Permissions.CONTENT_TEXTBOOK_READ, Permissions.CONTENT_CHAPTER_CREATE, Permissions.CONTENT_CHAPTER_READ, Permissions.CONTENT_CHAPTER_UPDATE, Permissions.CONTENT_QUESTION_CREATE, Permissions.CONTENT_QUESTION_READ, Permissions.CONTENT_QUESTION_UPDATE, Permissions.CONTENT_KNOWLEDGE_POINT_CREATE, Permissions.CONTENT_KNOWLEDGE_POINT_READ, Permissions.CONTENT_KNOWLEDGE_POINT_UPDATE, Permissions.CONTENT_ELECTIVE_CREATE, Permissions.CONTENT_ELECTIVE_READ, Permissions.CONTENT_LESSON_PLAN_CREATE, Permissions.CONTENT_LESSON_PLAN_READ, Permissions.CONTENT_LESSON_PLAN_UPDATE, Permissions.CONTENT_COURSE_PLAN_READ, ], student: [ Permissions.CONTENT_TEXTBOOK_READ, Permissions.CONTENT_CHAPTER_READ, Permissions.CONTENT_QUESTION_READ, Permissions.CONTENT_KNOWLEDGE_POINT_READ, Permissions.CONTENT_ELECTIVE_READ, Permissions.CONTENT_ELECTIVE_SELECT, Permissions.CONTENT_LESSON_PLAN_READ, Permissions.CONTENT_COURSE_PLAN_READ, ], parent: [ Permissions.CONTENT_TEXTBOOK_READ, Permissions.CONTENT_CHAPTER_READ, Permissions.CONTENT_KNOWLEDGE_POINT_READ, Permissions.CONTENT_LESSON_PLAN_READ, Permissions.CONTENT_COURSE_PLAN_READ, Permissions.CONTENT_ELECTIVE_READ, ], }; @Injectable() export class PermissionGuard implements CanActivate { constructor(private readonly reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { if (process.env.DEV_MODE === "true") { return true; } const requiredPermissions = this.reflector.getAllAndOverride( PERMISSIONS_KEY, [context.getHandler(), context.getClass()], ); if (!requiredPermissions || requiredPermissions.length === 0) { return true; } const request = context.switchToHttp().getRequest(); const roles = request.userRoles ?? []; for (const role of roles) { const perms = ROLE_PERMISSIONS[role]; if (perms && requiredPermissions.some((p) => perms.includes(p))) { return true; } } throw new PermissionDeniedError(requiredPermissions.join(", ")); } }