Files
Edu/apps/portal-shell/public/pq-manifest.json
SpecialX b30d43f983 feat(portal-shell): enable Apollo Client APQ + PQ manifest generator
Task 12-13 of portal-shell data abstraction plan (M3 security).

APQ (Automatic Persisted Queries):
- Add createPersistedQueryLink with sha256 to apollo-client.ts
- Production: client sends only query hash, not plaintext query
- Dev: NEXT_PUBLIC_APOLLO_APQ=false to disable for debugging
- Prevents attackers from crafting arbitrary queries via DevTools

PQ Manifest generator:
- New scripts/generate-pq-manifest.ts iterates operations barrel
- Outputs public/pq-manifest.json (sha256 -> query text whitelist)
- prebuild hook: codegen + generate-pq-manifest before next build
- 51 queries currently registered

- crypto-hash dependency added
- typecheck + lint (0 errors) + test (85/85) verified
2026-07-17 13:31:11 +08:00

53 lines
11 KiB
JSON

{
"cc22a52e830be26094f7e628b84ae378b2956adb229e9a52249a660d14b7571a": "query GetAnnouncements($limit: Int) {\n announcements(limit: $limit) {\n id\n title\n body\n author\n publishedAt\n }\n}",
"993d23648ccd8b44bc39189067720276325235cb5d7d26c78d78986487c8bbc9": "query GetAttendance($classId: ID!, $termId: ID!) {\n attendance(classId: $classId, termId: $termId) {\n present\n absent\n late\n total\n }\n}",
"87bd02d956d7c16182aa0fc4817ea568b6e6ef922fd7891d0eece8d5db2e0caa": "query GetExams($classId: ID!, $limit: Int) {\n exams(classId: $classId, limit: $limit) {\n id\n name\n examDate\n subject\n maxScore\n }\n}",
"4fffc072f251c9b9cff1306d3f36f89af726c49aa5d898b2701389a864f4b0ca": "query GetGrades($classId: ID!) {\n grades(classId: $classId) {\n studentId\n score\n }\n}",
"6ef5a84c342249898afc04be737102bd48be909244f88e7b36acff046e3fda66": "query GetHomeworks($classId: ID!, $limit: Int) {\n homeworks(classId: $classId, limit: $limit) {\n id\n title\n dueDate\n status\n }\n}",
"77a4840fce570fdd776eadee84af6c9196491661287a5f5c1ab710e11a6bed19": "query GetNotificationsList($limit: Int, $offset: Int) {\n notifications(limit: $limit, offset: $offset) {\n items {\n id\n title\n body\n createdAt\n type\n }\n total\n }\n}",
"bb5d9a0a34c299962fe296cfd7eea62ba501cb77c425403d045bbb90efb200a2": "query GetSchedule($classId: ID!, $dayOfWeek: Int) {\n schedule(classId: $classId, dayOfWeek: $dayOfWeek) {\n id\n subject\n startTime\n endTime\n teacherName\n }\n}",
"6e5b7232d2aec0c106ead6b57a1aff4f1cf5413459b5dca63220ba165b25e37d": "query GetMyChildren {\n myChildren {\n id\n name\n grade\n className\n }\n}",
"4bceb2c86a41b705c0e3df5af592716e4b93706a2f1501f91657bbd255e65624": "query GetMyClasses {\n myClasses {\n id\n name\n }\n}",
"683b1379ab15ad07c07f1ab64906a904c31f639f2e1f302af263529048d1348d": "query GetTerms {\n terms {\n id\n name\n startDate\n endDate\n isActive\n }\n}",
"39bdf6630ce77deed9582b72645ba14cab60874c6f2da28baee676e62e2025bc": "query GetCurrentUser {\n me {\n id\n name\n email\n role\n }\n}",
"006af776ff6266b70ef5733f834ef3e33a1b88339a7b7319a76c56bb71a8e89e": "query GetNotifications($limit: Int) {\n notifications(limit: $limit) {\n id\n title\n }\n}",
"5afce24b57e8e3db709eab2ca43d19e8fbb893d398a735ba004b0ed07649ad14": "query Search($keyword: String!, $limit: Int) {\n search(keyword: $keyword, limit: $limit) {\n id\n type\n title\n subtitle\n }\n}",
"a4c3aac21ef49c7c29303f8ff84da0966f896dbaed20fa84936129d864352930": "query GetLessonPlans($classId: ID!, $unitId: ID) {\n lessonPlans(classId: $classId, unitId: $unitId) {\n id\n title\n objectives\n content\n resources\n }\n}",
"10f2d92cf392929afbb5c5bc001d37bd9c51fb37eff0383b11edd23f03a4d007": "query GetQuestions($bankId: ID!, $type: String, $limit: Int) {\n questions(bankId: $bankId, type: $type, limit: $limit) {\n id\n type\n difficulty\n content\n options\n answer\n tags\n }\n}",
"8d4c0ebf844b1790b356a0b6d70899741f7271707d214fc79da0e797764f3b48": "query GetSchedulingRules($classId: ID!) {\n schedulingRules(classId: $classId) {\n id\n dayOfWeek\n periods\n subject\n teacherId\n room\n }\n}",
"198a9e4bc14a6f2acfb2f0ed82e993ef0ee0bd05fe84c944397a356445985785": "query GetTextbooks($subjectId: ID, $grade: String) {\n textbooks(subjectId: $subjectId, grade: $grade) {\n id\n title\n author\n publisher\n isbn\n chapters {\n id\n title\n }\n }\n}",
"34a207efda8909c8906b2bd1134c1c660b515d1787c7f1fcffc1b7f8a0471dc5": "mutation SaveLessonPlan($input: LessonPlanInput!) {\n saveLessonPlan(input: $input) {\n id\n }\n}",
"751e8daa3eb334f5080bd8f88c00d1df4bfe4fb1d7093320d4be675ef95f62e5": "mutation UpdateSchedulingRule($id: ID!, $input: SchedulingRuleInput!) {\n updateSchedulingRule(id: $id, input: $input) {\n id\n }\n}",
"3600a257d1f00bc7590545b7ac090e60498a90b633c50336476c13feb20f3839": "mutation DropCourse($courseId: ID!) {\n dropCourse(courseId: $courseId)\n}",
"eb9bd2e6ce678322ffa50cdc7480385b33eb5b86d0ebebb0b08650af9d067704": "mutation EnrollCourse($courseId: ID!) {\n enrollCourse(courseId: $courseId)\n}",
"c52d26c0ef308f029b5efd53598b9d8ecc483d293297167abeccab29fd345aff": "query AiTutorSessions($limit: Int) {\n aiTutorSessions(limit: $limit) {\n id\n title\n lastMessage\n updatedAt\n }\n}",
"0a2fdb7cfac1df1fd794a6aa03b2e81741bfc1a63ababe860a85cece89e1c825": "query ElectiveCourses($termId: ID!) {\n electiveCourses(termId: $termId) {\n id\n name\n teacher\n capacity\n enrolled\n schedule\n credits\n category\n }\n}",
"ee35ac1fb866bb2919be69e3bc52b276bda5be85543d2d5238e482a1db63681f": "query MyErrorBook($subjectId: ID, $limit: Int) {\n myErrorBook(subjectId: $subjectId, limit: $limit) {\n id\n question\n myAnswer\n correctAnswer\n errorCount\n lastErrorAt\n subject\n }\n}",
"736e74c878d89645df6867a84a64fa533a42c996d3fa946ff108afc3aa56bdfe": "query MyLearningPath($subjectId: ID!) {\n myLearningPath(subjectId: $subjectId) {\n nodes {\n id\n title\n type\n status\n dependencies\n }\n progress\n }\n}",
"689bf7df7102fd09515c3fafdb1a3e8b2c38ed3c19b81d20f266fbe32a492325": "mutation MarkErrorMastered($id: ID!) {\n markErrorMastered(id: $id)\n}",
"7c3e2df6ae1ea41af2b6c994f073ded78541e3e42de8e6cc68f186bcd3c5998b": "mutation SendAiTutorMessage($sessionId: ID, $message: String!) {\n sendAiTutorMessage(sessionId: $sessionId, message: $message) {\n sessionId\n reply\n }\n}",
"3d43fd71c95d9b654c786a98a0e531d18b81ae113655de4da52343562eab9a11": "mutation ApproveLeave($id: ID!) {\n approveLeave(id: $id) {\n id\n status\n }\n}",
"61a7f7edcaaebdd90f27f61d2b0d7e0ffa6593accbc51b1411bee4faac1da55d": "query GetLeaveRequests($childId: ID, $status: String) {\n leaveRequests(childId: $childId, status: $status) {\n id\n childName\n type\n startDate\n endDate\n reason\n status\n createdAt\n }\n}",
"9a6c2fbe99c9a3ce35b6b25ad48f8b898cd56b7b6b216548663d66e7f7aa9e0b": "query GetMyChildrenOverview {\n myChildren {\n id\n name\n grade\n className\n avatar\n recentGrades {\n subject\n score\n }\n attendance {\n present\n total\n }\n homeworkCompletion {\n completed\n total\n }\n }\n}",
"40615d4701dbadf4dcd22df3a0fde0a18ba52ab1cc096d3f6c6b0b4fb90247b8": "mutation RejectLeave($id: ID!, $reason: String) {\n rejectLeave(id: $id, reason: $reason) {\n id\n status\n }\n}",
"1d02d46cf5602608fa24a193e036982729c98034a1541267f8cea7eeba8e777a": "mutation CreateInvitationCode($input: CreateInvitationCodeInput!) {\n createInvitationCode(input: $input) {\n id\n code\n role\n maxUses\n expiresAt\n }\n}",
"b6eab2d2ddb72100fef604261587be516707b45c86d9ba6e6f4c6ec3707ed17a": "query GetAuditLogs($filter: AuditLogFilter, $limit: Int, $offset: Int) {\n auditLogs(filter: $filter, limit: $limit, offset: $offset) {\n items {\n id\n userId\n userName\n action\n resource\n resourceId\n ip\n timestamp\n details\n }\n total\n }\n}",
"9ec7b17074866cd384bc5c0d5f3b6d90ea1cbc3220c5533eff98be7fc8bd5506": "query GetInvitationCodes($status: String) {\n invitationCodes(status: $status) {\n id\n code\n role\n status\n usedCount\n maxUses\n expiresAt\n createdAt\n createdBy\n }\n}",
"462711c89347b1a40af04f60ab1dd3611c8361163740cf202454d5ec90c0bb71": "query GetLayoutTemplates {\n layoutTemplates {\n layoutId\n displayName\n description\n availableSlots\n }\n}",
"00c61040a7495895b84bf3a71e1368892e0757783a7977e101f7c60675538830": "query GetPermissions {\n permissions {\n id\n name\n resource\n action\n description\n }\n}",
"7d1683c3ae0b368ca79aa9f07cd34c37b6e7dc7b38d48741c19beb5c0e412136": "query GetPluginRegistry {\n pluginRegistry {\n pluginId\n category\n version\n displayName\n description\n requiredRoles\n isBuiltin\n isActive\n defaultSlot\n defaultSize\n defaultProps\n propsSchema\n }\n}",
"013b882847e6aeceebeef7b499e2fdb39f327d6fbd8d9b793f2ec17209d01046": "query GetRoles {\n roles {\n id\n name\n permissions {\n id\n name\n resource\n action\n }\n }\n}",
"8c92bccc65a523c79bf18c567f6396be0efdd6b0cfda4567702faa57798fa170": "query GetRoleLayoutDefault($role: String) {\n roleLayoutDefault(role: $role) {\n role\n layoutId\n slotOverrides\n }\n}",
"4c44f675ea0fe1ed6303dba87ba27c6feef0393649b0909a3436c38ae0edc331": "query GetRolePluginMapping($role: String) {\n rolePluginMapping(role: $role) {\n role\n pluginId\n slot\n sortOrder\n isEnabled\n widgetProps\n }\n}",
"7d372896206547c1fc4edcc2af151543922dcd3d403370c81d59ad784a352572": "query GetSchool {\n school {\n id\n name\n address\n phone\n email\n currentAcademicYear\n currentTerm\n semesterStart\n semesterEnd\n }\n}",
"86265727fbd9321020726a78323688432e8e1d3d05f6a01678602e2d96f581d0": "query GetUsers($role: String, $limit: Int, $offset: Int) {\n users(role: $role, limit: $limit, offset: $offset) {\n items {\n id\n name\n email\n role\n status\n createdAt\n }\n total\n }\n}",
"44c439b9e2b79a7be455bd2da2e71b76290f51958c22a44ff830a6ca330c06c2": "mutation ResetUserLayoutOverride($userId: ID!) {\n resetUserLayoutOverride(userId: $userId) {\n userId\n }\n}",
"42eb5f67bd419bcfb86717b4ce857ff9ce07e87ae7d1c6ceffea84529c4fa6f6": "mutation RevokeInvitationCode($id: ID!) {\n revokeInvitationCode(id: $id) {\n id\n status\n }\n}",
"bc0493cf664efd0e3301ff91f601da9e5ffca5b070d309c56f2b3798b9b02d07": "mutation UpdatePluginRegistry($pluginId: ID!, $input: PluginRegistryInput!) {\n updatePluginRegistry(pluginId: $pluginId, input: $input) {\n pluginId\n isActive\n defaultProps\n }\n}",
"2677bf6b6c5e064dd35062044d93948b30ddc3855b26444c463dafa911e5ecf8": "mutation UpdateRoleLayoutDefault($role: String!, $layoutId: String!) {\n updateRoleLayoutDefault(role: $role, layoutId: $layoutId) {\n role\n layoutId\n }\n}",
"a34209d2f6cb3a0bd30111c45d62cb0aba888cc000408062ee5d3a2eace0f2a4": "mutation UpdateRolePermissions($roleId: ID!, $permissionIds: [ID]!) {\n updateRolePermissions(roleId: $roleId, permissionIds: $permissionIds) {\n id\n }\n}",
"f52005687a6d34d6606e60be4f63fe773c846cb3018ee75990e6d0f29d3622e6": "mutation UpdateRoleMapping($role: String!, $mappings: [RolePluginMappingInput!]!) {\n updateRolePluginMapping(role: $role, mappings: $mappings) {\n role\n pluginId\n isEnabled\n }\n}",
"aeba6eea7c2723e5f2f522ec688f47bd269fd725f7131e849ab80efa384b4369": "mutation UpdateSchool($input: SchoolInput!) {\n updateSchool(input: $input) {\n id\n name\n address\n phone\n email\n currentAcademicYear\n currentTerm\n semesterStart\n semesterEnd\n updatedAt\n }\n}",
"257075c859c0c4dbf9f253e2440a98ea778b2a66e4934055dd64263cd5a99bad": "mutation UpdateUserRole($id: ID!, $role: String!) {\n updateUserRole(id: $id, role: $role) {\n id\n role\n }\n}",
"543d328988a535e73794a1c45a53f2f19d6494f4b0be675f90917d5d5578bc46": "mutation UpdateUserStatus($id: ID!, $status: String!) {\n updateUserStatus(id: $id, status: $status) {\n id\n status\n }\n}"
}