From 54f6daea62062c1d95ef5043c033957fd2d7a645 Mon Sep 17 00:00:00 2001 From: lesingle Date: Thu, 26 Feb 2026 15:22:26 +0800 Subject: [PATCH] Initialize the Kindergarten platform source code. --- reading-platform-backend/.DS_Store | Bin 0 -> 6148 bytes reading-platform-backend/.env | 6 + reading-platform-backend/.env.development | 6 + reading-platform-backend/dist/.DS_Store | Bin 0 -> 6148 bytes .../dist/prisma/seed.d.ts | 1 + reading-platform-backend/dist/prisma/seed.js | 420 ++ .../dist/prisma/seed.js.map | 1 + .../dist/src/app.module.d.ts | 2 + .../dist/src/app.module.js | 64 + .../dist/src/app.module.js.map | 1 + .../common/filters/http-exception.filter.d.ts | 5 + .../common/filters/http-exception.filter.js | 70 + .../filters/http-exception.filter.js.map | 1 + .../dist/src/database/prisma.module.d.ts | 2 + .../dist/src/database/prisma.module.js | 22 + .../dist/src/database/prisma.module.js.map | 1 + .../dist/src/database/prisma.service.d.ts | 7 + .../dist/src/database/prisma.service.js | 45 + .../dist/src/database/prisma.service.js.map | 1 + reading-platform-backend/dist/src/main.d.ts | 1 + reading-platform-backend/dist/src/main.js | 51 + reading-platform-backend/dist/src/main.js.map | 1 + .../admin/admin-settings.controller.d.ts | 54 + .../admin/admin-settings.controller.js | 137 + .../admin/admin-settings.controller.js.map | 1 + .../modules/admin/admin-settings.service.d.ts | 44 + .../modules/admin/admin-settings.service.js | 105 + .../admin/admin-settings.service.js.map | 1 + .../modules/admin/admin-stats.controller.d.ts | 41 + .../modules/admin/admin-stats.controller.js | 84 + .../admin/admin-stats.controller.js.map | 1 + .../modules/admin/admin-stats.service.d.ts | 42 + .../src/modules/admin/admin-stats.service.js | 212 + .../modules/admin/admin-stats.service.js.map | 1 + .../dist/src/modules/admin/admin.module.d.ts | 2 + .../dist/src/modules/admin/admin.module.js | 27 + .../src/modules/admin/admin.module.js.map | 1 + .../src/modules/auth/auth.controller.d.ts | 60 + .../dist/src/modules/auth/auth.controller.js | 61 + .../src/modules/auth/auth.controller.js.map | 1 + .../dist/src/modules/auth/auth.module.d.ts | 2 + .../dist/src/modules/auth/auth.module.js | 42 + .../dist/src/modules/auth/auth.module.js.map | 1 + .../dist/src/modules/auth/auth.service.d.ts | 84 + .../dist/src/modules/auth/auth.service.js | 287 ++ .../dist/src/modules/auth/auth.service.js.map | 1 + .../dist/src/modules/auth/dto/login.dto.d.ts | 5 + .../dist/src/modules/auth/dto/login.dto.js | 33 + .../src/modules/auth/dto/login.dto.js.map | 1 + .../modules/auth/strategies/jwt.strategy.d.ts | 18 + .../modules/auth/strategies/jwt.strategy.js | 42 + .../auth/strategies/jwt.strategy.js.map | 1 + .../src/modules/common/common.module.d.ts | 2 + .../dist/src/modules/common/common.module.js | 26 + .../src/modules/common/common.module.js.map | 1 + .../decorators/log-operation.decorator.d.ts | 7 + .../decorators/log-operation.decorator.js | 10 + .../decorators/log-operation.decorator.js.map | 1 + .../common/decorators/roles.decorator.d.ts | 2 + .../common/decorators/roles.decorator.js | 8 + .../common/decorators/roles.decorator.js.map | 1 + .../modules/common/guards/jwt-auth.guard.d.ts | 9 + .../modules/common/guards/jwt-auth.guard.js | 37 + .../common/guards/jwt-auth.guard.js.map | 1 + .../modules/common/guards/roles.guard.d.ts | 7 + .../src/modules/common/guards/roles.guard.js | 37 + .../modules/common/guards/roles.guard.js.map | 1 + .../common/interceptors/log.interceptor.d.ts | 13 + .../common/interceptors/log.interceptor.js | 115 + .../interceptors/log.interceptor.js.map | 1 + .../common/operation-log.controller.d.ts | 101 + .../common/operation-log.controller.js | 108 + .../common/operation-log.controller.js.map | 1 + .../modules/common/operation-log.service.d.ts | 62 + .../modules/common/operation-log.service.js | 134 + .../common/operation-log.service.js.map | 1 + .../course/course-validation.service.d.ts | 42 + .../course/course-validation.service.js | 190 + .../course/course-validation.service.js.map | 1 + .../src/modules/course/course.controller.d.ts | 625 +++ .../src/modules/course/course.controller.js | 223 + .../modules/course/course.controller.js.map | 1 + .../src/modules/course/course.module.d.ts | 2 + .../dist/src/modules/course/course.module.js | 26 + .../src/modules/course/course.module.js.map | 1 + .../src/modules/course/course.service.d.ts | 627 +++ .../dist/src/modules/course/course.service.js | 750 ++++ .../src/modules/course/course.service.js.map | 1 + .../src/modules/export/export.controller.d.ts | 10 + .../src/modules/export/export.controller.js | 90 + .../modules/export/export.controller.js.map | 1 + .../src/modules/export/export.module.d.ts | 2 + .../dist/src/modules/export/export.module.js | 23 + .../src/modules/export/export.module.js.map | 1 + .../src/modules/export/export.service.d.ts | 10 + .../dist/src/modules/export/export.service.js | 260 ++ .../src/modules/export/export.service.js.map | 1 + .../file-upload/file-upload.controller.d.ts | 23 + .../file-upload/file-upload.controller.js | 89 + .../file-upload/file-upload.controller.js.map | 1 + .../file-upload/file-upload.module.d.ts | 2 + .../modules/file-upload/file-upload.module.js | 23 + .../file-upload/file-upload.module.js.map | 1 + .../file-upload/file-upload.service.d.ts | 18 + .../file-upload/file-upload.service.js | 161 + .../file-upload/file-upload.service.js.map | 1 + .../modules/growth/dto/create-growth.dto.d.ts | 27 + .../modules/growth/dto/create-growth.dto.js | 114 + .../growth/dto/create-growth.dto.js.map | 1 + .../src/modules/growth/growth.controller.d.ts | 277 ++ .../src/modules/growth/growth.controller.js | 194 + .../modules/growth/growth.controller.js.map | 1 + .../src/modules/growth/growth.module.d.ts | 2 + .../dist/src/modules/growth/growth.module.js | 23 + .../src/modules/growth/growth.module.js.map | 1 + .../src/modules/growth/growth.service.d.ts | 252 ++ .../dist/src/modules/growth/growth.service.js | 544 +++ .../src/modules/growth/growth.service.js.map | 1 + .../modules/lesson/dto/create-lesson.dto.d.ts | 5 + .../modules/lesson/dto/create-lesson.dto.js | 30 + .../lesson/dto/create-lesson.dto.js.map | 1 + .../modules/lesson/dto/finish-lesson.dto.d.ts | 6 + .../modules/lesson/dto/finish-lesson.dto.js | 37 + .../lesson/dto/finish-lesson.dto.js.map | 1 + .../src/modules/lesson/lesson.controller.d.ts | 485 ++ .../src/modules/lesson/lesson.controller.js | 230 + .../modules/lesson/lesson.controller.js.map | 1 + .../src/modules/lesson/lesson.module.d.ts | 2 + .../dist/src/modules/lesson/lesson.module.js | 23 + .../src/modules/lesson/lesson.module.js.map | 1 + .../src/modules/lesson/lesson.service.d.ts | 453 ++ .../dist/src/modules/lesson/lesson.service.js | 762 ++++ .../src/modules/lesson/lesson.service.js.map | 1 + .../notification/notification.controller.d.ts | 121 + .../notification/notification.controller.js | 183 + .../notification.controller.js.map | 1 + .../notification/notification.module.d.ts | 2 + .../notification/notification.module.js | 30 + .../notification/notification.module.js.map | 1 + .../notification/notification.service.d.ts | 75 + .../notification/notification.service.js | 131 + .../notification/notification.service.js.map | 1 + .../schedule-notification.service.d.ts | 32 + .../schedule-notification.service.js | 288 ++ .../schedule-notification.service.js.map | 1 + .../src/modules/parent/parent.controller.d.ts | 136 + .../src/modules/parent/parent.controller.js | 103 + .../modules/parent/parent.controller.js.map | 1 + .../src/modules/parent/parent.module.d.ts | 2 + .../dist/src/modules/parent/parent.module.js | 23 + .../src/modules/parent/parent.module.js.map | 1 + .../src/modules/parent/parent.service.d.ts | 135 + .../dist/src/modules/parent/parent.service.js | 271 ++ .../src/modules/parent/parent.service.js.map | 1 + .../resource/dto/create-resource.dto.d.ts | 53 + .../resource/dto/create-resource.dto.js | 191 + .../resource/dto/create-resource.dto.js.map | 1 + .../modules/resource/resource.controller.d.ts | 157 + .../modules/resource/resource.controller.js | 156 + .../resource/resource.controller.js.map | 1 + .../src/modules/resource/resource.module.d.ts | 2 + .../src/modules/resource/resource.module.js | 23 + .../modules/resource/resource.module.js.map | 1 + .../modules/resource/resource.service.d.ts | 157 + .../src/modules/resource/resource.service.js | 311 ++ .../modules/resource/resource.service.js.map | 1 + .../modules/school/dto/class-teacher.dto.d.ts | 14 + .../modules/school/dto/class-teacher.dto.js | 57 + .../school/dto/class-teacher.dto.js.map | 1 + .../modules/school/dto/create-class.dto.d.ts | 10 + .../modules/school/dto/create-class.dto.js | 52 + .../school/dto/create-class.dto.js.map | 1 + .../school/dto/create-student.dto.d.ts | 16 + .../modules/school/dto/create-student.dto.js | 83 + .../school/dto/create-student.dto.js.map | 1 + .../school/dto/create-teacher.dto.d.ts | 14 + .../modules/school/dto/create-teacher.dto.js | 76 + .../school/dto/create-teacher.dto.js.map | 1 + .../school/dto/import-students.dto.d.ts | 3 + .../modules/school/dto/import-students.dto.js | 22 + .../school/dto/import-students.dto.js.map | 1 + .../src/modules/school/dto/schedule.dto.d.ts | 49 + .../src/modules/school/dto/schedule.dto.js | 222 + .../modules/school/dto/schedule.dto.js.map | 1 + .../src/modules/school/export.controller.d.ts | 12 + .../src/modules/school/export.controller.js | 101 + .../modules/school/export.controller.js.map | 1 + .../src/modules/school/export.service.d.ts | 12 + .../dist/src/modules/school/export.service.js | 263 ++ .../src/modules/school/export.service.js.map | 1 + .../modules/school/package.controller.d.ts | 34 + .../src/modules/school/package.controller.js | 120 + .../modules/school/package.controller.js.map | 1 + .../src/modules/school/school.controller.d.ts | 858 ++++ .../src/modules/school/school.controller.js | 600 +++ .../modules/school/school.controller.js.map | 1 + .../src/modules/school/school.module.d.ts | 2 + .../dist/src/modules/school/school.module.js | 30 + .../src/modules/school/school.module.js.map | 1 + .../src/modules/school/school.service.d.ts | 899 ++++ .../dist/src/modules/school/school.service.js | 1952 ++++++++ .../src/modules/school/school.service.js.map | 1 + .../modules/school/settings.controller.d.ts | 36 + .../src/modules/school/settings.controller.js | 54 + .../modules/school/settings.controller.js.map | 1 + .../src/modules/school/settings.service.d.ts | 36 + .../src/modules/school/settings.service.js | 92 + .../modules/school/settings.service.js.map | 1 + .../src/modules/school/stats.controller.d.ts | 59 + .../src/modules/school/stats.controller.js | 136 + .../modules/school/stats.controller.js.map | 1 + .../src/modules/school/stats.service.d.ts | 69 + .../dist/src/modules/school/stats.service.js | 400 ++ .../src/modules/school/stats.service.js.map | 1 + .../src/modules/task/dto/create-task.dto.d.ts | 71 + .../src/modules/task/dto/create-task.dto.js | 243 + .../modules/task/dto/create-task.dto.js.map | 1 + .../src/modules/task/task.controller.d.ts | 820 ++++ .../dist/src/modules/task/task.controller.js | 462 ++ .../src/modules/task/task.controller.js.map | 1 + .../dist/src/modules/task/task.module.d.ts | 2 + .../dist/src/modules/task/task.module.js | 25 + .../dist/src/modules/task/task.module.js.map | 1 + .../dist/src/modules/task/task.service.d.ts | 479 ++ .../dist/src/modules/task/task.service.js | 786 ++++ .../dist/src/modules/task/task.service.js.map | 1 + .../teacher-course.controller.d.ts | 400 ++ .../teacher-course.controller.js | 227 + .../teacher-course.controller.js.map | 1 + .../teacher-course/teacher-course.module.d.ts | 2 + .../teacher-course/teacher-course.module.js | 23 + .../teacher-course.module.js.map | 1 + .../teacher-course.service.d.ts | 414 ++ .../teacher-course/teacher-course.service.js | 960 ++++ .../teacher-course.service.js.map | 1 + .../src/modules/tenant/dto/tenant.dto.d.ts | 40 + .../dist/src/modules/tenant/dto/tenant.dto.js | 206 + .../src/modules/tenant/dto/tenant.dto.js.map | 1 + .../src/modules/tenant/tenant.controller.d.ts | 138 + .../src/modules/tenant/tenant.controller.js | 126 + .../modules/tenant/tenant.controller.js.map | 1 + .../src/modules/tenant/tenant.module.d.ts | 2 + .../dist/src/modules/tenant/tenant.module.js | 25 + .../src/modules/tenant/tenant.module.js.map | 1 + .../src/modules/tenant/tenant.service.d.ts | 154 + .../dist/src/modules/tenant/tenant.service.js | 392 ++ .../src/modules/tenant/tenant.service.js.map | 1 + .../dist/tsconfig.tsbuildinfo | 1 + reading-platform-backend/nest-cli.json | 10 + reading-platform-backend/package.json | 66 + reading-platform-backend/prisma/dev.db | 0 .../20260210055321_init/migration.sql | 262 ++ .../20260210092744_init/migration.sql | 323 ++ .../migration.sql | 27 + .../prisma/migrations/migration_lock.toml | 3 + reading-platform-backend/prisma/schema.prisma | 840 ++++ reading-platform-backend/prisma/seed.ts | 431 ++ .../scripts/create-test-parent.ts | 99 + reading-platform-backend/src/.DS_Store | Bin 0 -> 6148 bytes reading-platform-backend/src/app.module.js | 92 + reading-platform-backend/src/app.module.ts | 58 + .../common/filters/http-exception.filter.ts | 44 + .../src/database/prisma.module.js | 67 + .../src/database/prisma.module.ts | 9 + .../src/database/prisma.service.js | 203 + .../src/database/prisma.service.ts | 38 + reading-platform-backend/src/main.js | 84 + reading-platform-backend/src/main.ts | 74 + .../admin/admin-settings.controller.ts | 67 + .../modules/admin/admin-settings.service.ts | 107 + .../modules/admin/admin-stats.controller.ts | 40 + .../src/modules/admin/admin-stats.service.ts | 236 + .../src/modules/admin/admin.module.ts | 14 + .../src/modules/auth/auth.controller.js | 133 + .../src/modules/auth/auth.controller.ts | 27 + .../src/modules/auth/auth.module.js | 128 + .../src/modules/auth/auth.module.ts | 29 + .../src/modules/auth/auth.service.js | 288 ++ .../src/modules/auth/auth.service.ts | 298 ++ .../src/modules/auth/dto/login.dto.js | 71 + .../src/modules/auth/dto/login.dto.ts | 16 + .../modules/auth/strategies/jwt.strategy.js | 140 + .../modules/auth/strategies/jwt.strategy.ts | 33 + .../src/modules/common/common.module.ts | 13 + .../decorators/log-operation.decorator.ts | 17 + .../common/decorators/roles.decorator.ts | 4 + .../modules/common/guards/jwt-auth.guard.ts | 24 + .../src/modules/common/guards/roles.guard.ts | 25 + .../common/interceptors/log.interceptor.ts | 135 + .../common/operation-log.controller.ts | 56 + .../modules/common/operation-log.service.ts | 171 + .../course/course-validation.service.ts | 270 ++ .../src/modules/course/course.controller.ts | 161 + .../src/modules/course/course.module.ts | 13 + .../src/modules/course/course.service.ts | 931 ++++ .../src/modules/export/export.controller.ts | 88 + .../src/modules/export/export.module.ts | 10 + .../src/modules/export/export.service.ts | 266 ++ .../file-upload/file-upload.controller.ts | 92 + .../modules/file-upload/file-upload.module.ts | 10 + .../file-upload/file-upload.service.ts | 194 + .../modules/growth/dto/create-growth.dto.ts | 81 + .../src/modules/growth/growth.controller.ts | 117 + .../src/modules/growth/growth.module.ts | 10 + .../src/modules/growth/growth.service.ts | 637 +++ .../modules/lesson/dto/create-lesson.dto.ts | 13 + .../modules/lesson/dto/finish-lesson.dto.ts | 19 + .../src/modules/lesson/lesson.controller.ts | 133 + .../src/modules/lesson/lesson.module.ts | 10 + .../src/modules/lesson/lesson.service.ts | 905 ++++ .../notification/notification.controller.ts | 151 + .../notification/notification.module.ts | 21 + .../notification/notification.service.ts | 169 + .../schedule-notification.service.ts | 333 ++ .../src/modules/parent/parent.controller.ts | 71 + .../src/modules/parent/parent.module.ts | 10 + .../src/modules/parent/parent.service.ts | 309 ++ .../resource/dto/create-resource.dto.ts | 144 + .../modules/resource/resource.controller.ts | 90 + .../src/modules/resource/resource.module.ts | 10 + .../src/modules/resource/resource.service.ts | 357 ++ .../modules/school/dto/class-teacher.dto.ts | 40 + .../modules/school/dto/create-class.dto.ts | 31 + .../modules/school/dto/create-student.dto.ts | 56 + .../modules/school/dto/create-teacher.dto.ts | 51 + .../modules/school/dto/import-students.dto.ts | 7 + .../src/modules/school/dto/schedule.dto.ts | 166 + .../src/modules/school/export.controller.ts | 109 + .../src/modules/school/export.service.ts | 276 ++ .../src/modules/school/package.controller.ts | 100 + .../src/modules/school/school.controller.ts | 375 ++ .../src/modules/school/school.module.ts | 17 + .../src/modules/school/school.service.ts | 2423 ++++++++++ .../src/modules/school/settings.controller.ts | 39 + .../src/modules/school/settings.service.ts | 93 + .../src/modules/school/stats.controller.ts | 79 + .../src/modules/school/stats.service.ts | 482 ++ .../src/modules/task/dto/create-task.dto.ts | 187 + .../src/modules/task/task.controller.ts | 256 ++ .../src/modules/task/task.module.ts | 12 + .../src/modules/task/task.service.ts | 930 ++++ .../teacher-course.controller.ts | 128 + .../teacher-course/teacher-course.module.ts | 10 + .../teacher-course/teacher-course.service.ts | 1140 +++++ .../src/modules/tenant/dto/tenant.dto.ts | 158 + .../src/modules/tenant/tenant.controller.js | 96 + .../src/modules/tenant/tenant.controller.ts | 74 + .../src/modules/tenant/tenant.module.js | 71 + .../src/modules/tenant/tenant.module.ts | 12 + .../src/modules/tenant/tenant.service.js | 192 + .../src/modules/tenant/tenant.service.ts | 422 ++ reading-platform-backend/start-backend.sh | 18 + reading-platform-backend/tsconfig.build.json | 10 + reading-platform-backend/tsconfig.json | 27 + reading-platform-frontend/.DS_Store | Bin 0 -> 6148 bytes reading-platform-frontend/.env.development | 3 + reading-platform-frontend/dev.db | 0 reading-platform-frontend/index.html | 13 + reading-platform-frontend/package-lock.json | 3912 +++++++++++++++++ reading-platform-frontend/package.json | 42 + reading-platform-frontend/public/logo.png | Bin 0 -> 135769 bytes reading-platform-frontend/src/.DS_Store | Bin 0 -> 6148 bytes reading-platform-frontend/src/App.vue | 27 + reading-platform-frontend/src/api/admin.ts | 215 + reading-platform-frontend/src/api/auth.ts | 51 + reading-platform-frontend/src/api/course.ts | 153 + reading-platform-frontend/src/api/file.ts | 127 + reading-platform-frontend/src/api/growth.ts | 130 + reading-platform-frontend/src/api/index.ts | 94 + reading-platform-frontend/src/api/parent.ts | 152 + reading-platform-frontend/src/api/resource.ts | 135 + reading-platform-frontend/src/api/school.ts | 958 ++++ reading-platform-frontend/src/api/task.ts | 175 + reading-platform-frontend/src/api/teacher.ts | 660 +++ .../src/auto-imports.d.ts | 90 + reading-platform-frontend/src/components.d.ts | 86 + .../src/components/FilePreviewModal.vue | 379 ++ .../src/components/NotificationBell.vue | 260 ++ reading-platform-frontend/src/main.ts | 15 + reading-platform-frontend/src/router/index.ts | 399 ++ reading-platform-frontend/src/stores/user.ts | 92 + .../src/utils/tagMaps.ts | 389 ++ .../src/views/NotFoundView.vue | 41 + .../src/views/admin/DashboardView.vue | 697 +++ .../src/views/admin/LayoutView.vue | 342 ++ .../src/views/admin/SettingsView.vue | 303 ++ .../views/admin/courses/CourseDetailView.vue | 956 ++++ .../views/admin/courses/CourseEditView.vue | 2654 +++++++++++ .../views/admin/courses/CourseListView.vue | 538 +++ .../views/admin/courses/CourseReviewView.vue | 387 ++ .../views/admin/courses/CourseStatsView.vue | 318 ++ .../admin/resources/ResourceListView.vue | 734 ++++ .../views/admin/tenants/TenantListView.vue | 707 +++ .../src/views/auth/LoginView.vue | 428 ++ .../src/views/parent/DashboardView.vue | 592 +++ .../src/views/parent/LayoutView.vue | 640 +++ .../parent/children/ChildProfileView.vue | 387 ++ .../views/parent/children/ChildrenView.vue | 407 ++ .../views/parent/growth/GrowthRecordView.vue | 330 ++ .../parent/lessons/LessonHistoryView.vue | 353 ++ .../src/views/parent/tasks/TaskListView.vue | 362 ++ .../src/views/school/DashboardView.vue | 1177 +++++ .../src/views/school/LayoutView.vue | 462 ++ .../src/views/school/PackageView.vue | 772 ++++ .../src/views/school/ReportView.vue | 1127 +++++ .../views/school/classes/ClassListView.vue | 1352 ++++++ .../views/school/courses/CourseDetailView.vue | 1204 +++++ .../views/school/courses/CourseListView.vue | 896 ++++ .../views/school/feedback/FeedbackView.vue | 940 ++++ .../views/school/growth/GrowthRecordView.vue | 812 ++++ .../views/school/parents/ParentListView.vue | 1248 ++++++ .../views/school/schedule/CalendarView.vue | 417 ++ .../views/school/schedule/ScheduleView.vue | 964 ++++ .../views/school/schedule/TimetableView.vue | 739 ++++ .../school/settings/OperationLogView.vue | 328 ++ .../views/school/settings/SettingsView.vue | 329 ++ .../views/school/students/StudentListView.vue | 1473 +++++++ .../src/views/school/tasks/TaskListView.vue | 743 ++++ .../views/school/tasks/TaskTemplateView.vue | 474 ++ .../views/school/teachers/TeacherListView.vue | 841 ++++ .../src/views/teacher/DashboardView.vue | 1331 ++++++ .../src/views/teacher/LayoutView.vue | 341 ++ .../views/teacher/classes/ClassListView.vue | 839 ++++ .../teacher/classes/ClassStudentsView.vue | 473 ++ .../teacher/courses/CourseDetailView.vue | 1219 +++++ .../views/teacher/courses/CourseListView.vue | 688 +++ .../views/teacher/courses/PrepareModeView.vue | 1426 ++++++ .../views/teacher/feedback/FeedbackView.vue | 850 ++++ .../views/teacher/growth/GrowthRecordView.vue | 886 ++++ .../views/teacher/lessons/BroadcastView.vue | 234 + .../views/teacher/lessons/LessonListView.vue | 615 +++ .../teacher/lessons/LessonRecordsView.vue | 622 +++ .../src/views/teacher/lessons/LessonView.vue | 1462 ++++++ .../teacher/lessons/components/KidsMode.vue | 1065 +++++ .../components/viewers/AudioPlayer.vue | 559 +++ .../components/viewers/EbookViewer.vue | 684 +++ .../components/viewers/SlidesViewer.vue | 624 +++ .../components/viewers/VideoPlayer.vue | 811 ++++ .../views/teacher/schedule/ScheduleView.vue | 684 +++ .../src/views/teacher/tasks/TaskListView.vue | 1023 +++++ reading-platform-frontend/src/vite-env.d.ts | 11 + reading-platform-frontend/start-frontend.sh | 18 + reading-platform-frontend/tsconfig.json | 31 + reading-platform-frontend/tsconfig.node.json | 10 + reading-platform-frontend/vite.config.ts | 72 + 445 files changed, 95591 insertions(+) create mode 100644 reading-platform-backend/.DS_Store create mode 100644 reading-platform-backend/.env create mode 100644 reading-platform-backend/.env.development create mode 100644 reading-platform-backend/dist/.DS_Store create mode 100644 reading-platform-backend/dist/prisma/seed.d.ts create mode 100644 reading-platform-backend/dist/prisma/seed.js create mode 100644 reading-platform-backend/dist/prisma/seed.js.map create mode 100644 reading-platform-backend/dist/src/app.module.d.ts create mode 100644 reading-platform-backend/dist/src/app.module.js create mode 100644 reading-platform-backend/dist/src/app.module.js.map create mode 100644 reading-platform-backend/dist/src/common/filters/http-exception.filter.d.ts create mode 100644 reading-platform-backend/dist/src/common/filters/http-exception.filter.js create mode 100644 reading-platform-backend/dist/src/common/filters/http-exception.filter.js.map create mode 100644 reading-platform-backend/dist/src/database/prisma.module.d.ts create mode 100644 reading-platform-backend/dist/src/database/prisma.module.js create mode 100644 reading-platform-backend/dist/src/database/prisma.module.js.map create mode 100644 reading-platform-backend/dist/src/database/prisma.service.d.ts create mode 100644 reading-platform-backend/dist/src/database/prisma.service.js create mode 100644 reading-platform-backend/dist/src/database/prisma.service.js.map create mode 100644 reading-platform-backend/dist/src/main.d.ts create mode 100644 reading-platform-backend/dist/src/main.js create mode 100644 reading-platform-backend/dist/src/main.js.map create mode 100644 reading-platform-backend/dist/src/modules/admin/admin-settings.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/admin/admin-settings.controller.js create mode 100644 reading-platform-backend/dist/src/modules/admin/admin-settings.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/admin/admin-settings.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/admin/admin-settings.service.js create mode 100644 reading-platform-backend/dist/src/modules/admin/admin-settings.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/admin/admin-stats.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/admin/admin-stats.controller.js create mode 100644 reading-platform-backend/dist/src/modules/admin/admin-stats.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/admin/admin-stats.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/admin/admin-stats.service.js create mode 100644 reading-platform-backend/dist/src/modules/admin/admin-stats.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/admin/admin.module.d.ts create mode 100644 reading-platform-backend/dist/src/modules/admin/admin.module.js create mode 100644 reading-platform-backend/dist/src/modules/admin/admin.module.js.map create mode 100644 reading-platform-backend/dist/src/modules/auth/auth.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/auth/auth.controller.js create mode 100644 reading-platform-backend/dist/src/modules/auth/auth.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/auth/auth.module.d.ts create mode 100644 reading-platform-backend/dist/src/modules/auth/auth.module.js create mode 100644 reading-platform-backend/dist/src/modules/auth/auth.module.js.map create mode 100644 reading-platform-backend/dist/src/modules/auth/auth.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/auth/auth.service.js create mode 100644 reading-platform-backend/dist/src/modules/auth/auth.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/auth/dto/login.dto.d.ts create mode 100644 reading-platform-backend/dist/src/modules/auth/dto/login.dto.js create mode 100644 reading-platform-backend/dist/src/modules/auth/dto/login.dto.js.map create mode 100644 reading-platform-backend/dist/src/modules/auth/strategies/jwt.strategy.d.ts create mode 100644 reading-platform-backend/dist/src/modules/auth/strategies/jwt.strategy.js create mode 100644 reading-platform-backend/dist/src/modules/auth/strategies/jwt.strategy.js.map create mode 100644 reading-platform-backend/dist/src/modules/common/common.module.d.ts create mode 100644 reading-platform-backend/dist/src/modules/common/common.module.js create mode 100644 reading-platform-backend/dist/src/modules/common/common.module.js.map create mode 100644 reading-platform-backend/dist/src/modules/common/decorators/log-operation.decorator.d.ts create mode 100644 reading-platform-backend/dist/src/modules/common/decorators/log-operation.decorator.js create mode 100644 reading-platform-backend/dist/src/modules/common/decorators/log-operation.decorator.js.map create mode 100644 reading-platform-backend/dist/src/modules/common/decorators/roles.decorator.d.ts create mode 100644 reading-platform-backend/dist/src/modules/common/decorators/roles.decorator.js create mode 100644 reading-platform-backend/dist/src/modules/common/decorators/roles.decorator.js.map create mode 100644 reading-platform-backend/dist/src/modules/common/guards/jwt-auth.guard.d.ts create mode 100644 reading-platform-backend/dist/src/modules/common/guards/jwt-auth.guard.js create mode 100644 reading-platform-backend/dist/src/modules/common/guards/jwt-auth.guard.js.map create mode 100644 reading-platform-backend/dist/src/modules/common/guards/roles.guard.d.ts create mode 100644 reading-platform-backend/dist/src/modules/common/guards/roles.guard.js create mode 100644 reading-platform-backend/dist/src/modules/common/guards/roles.guard.js.map create mode 100644 reading-platform-backend/dist/src/modules/common/interceptors/log.interceptor.d.ts create mode 100644 reading-platform-backend/dist/src/modules/common/interceptors/log.interceptor.js create mode 100644 reading-platform-backend/dist/src/modules/common/interceptors/log.interceptor.js.map create mode 100644 reading-platform-backend/dist/src/modules/common/operation-log.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/common/operation-log.controller.js create mode 100644 reading-platform-backend/dist/src/modules/common/operation-log.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/common/operation-log.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/common/operation-log.service.js create mode 100644 reading-platform-backend/dist/src/modules/common/operation-log.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/course/course-validation.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/course/course-validation.service.js create mode 100644 reading-platform-backend/dist/src/modules/course/course-validation.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/course/course.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/course/course.controller.js create mode 100644 reading-platform-backend/dist/src/modules/course/course.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/course/course.module.d.ts create mode 100644 reading-platform-backend/dist/src/modules/course/course.module.js create mode 100644 reading-platform-backend/dist/src/modules/course/course.module.js.map create mode 100644 reading-platform-backend/dist/src/modules/course/course.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/course/course.service.js create mode 100644 reading-platform-backend/dist/src/modules/course/course.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/export/export.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/export/export.controller.js create mode 100644 reading-platform-backend/dist/src/modules/export/export.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/export/export.module.d.ts create mode 100644 reading-platform-backend/dist/src/modules/export/export.module.js create mode 100644 reading-platform-backend/dist/src/modules/export/export.module.js.map create mode 100644 reading-platform-backend/dist/src/modules/export/export.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/export/export.service.js create mode 100644 reading-platform-backend/dist/src/modules/export/export.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/file-upload/file-upload.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/file-upload/file-upload.controller.js create mode 100644 reading-platform-backend/dist/src/modules/file-upload/file-upload.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/file-upload/file-upload.module.d.ts create mode 100644 reading-platform-backend/dist/src/modules/file-upload/file-upload.module.js create mode 100644 reading-platform-backend/dist/src/modules/file-upload/file-upload.module.js.map create mode 100644 reading-platform-backend/dist/src/modules/file-upload/file-upload.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/file-upload/file-upload.service.js create mode 100644 reading-platform-backend/dist/src/modules/file-upload/file-upload.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/growth/dto/create-growth.dto.d.ts create mode 100644 reading-platform-backend/dist/src/modules/growth/dto/create-growth.dto.js create mode 100644 reading-platform-backend/dist/src/modules/growth/dto/create-growth.dto.js.map create mode 100644 reading-platform-backend/dist/src/modules/growth/growth.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/growth/growth.controller.js create mode 100644 reading-platform-backend/dist/src/modules/growth/growth.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/growth/growth.module.d.ts create mode 100644 reading-platform-backend/dist/src/modules/growth/growth.module.js create mode 100644 reading-platform-backend/dist/src/modules/growth/growth.module.js.map create mode 100644 reading-platform-backend/dist/src/modules/growth/growth.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/growth/growth.service.js create mode 100644 reading-platform-backend/dist/src/modules/growth/growth.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/lesson/dto/create-lesson.dto.d.ts create mode 100644 reading-platform-backend/dist/src/modules/lesson/dto/create-lesson.dto.js create mode 100644 reading-platform-backend/dist/src/modules/lesson/dto/create-lesson.dto.js.map create mode 100644 reading-platform-backend/dist/src/modules/lesson/dto/finish-lesson.dto.d.ts create mode 100644 reading-platform-backend/dist/src/modules/lesson/dto/finish-lesson.dto.js create mode 100644 reading-platform-backend/dist/src/modules/lesson/dto/finish-lesson.dto.js.map create mode 100644 reading-platform-backend/dist/src/modules/lesson/lesson.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/lesson/lesson.controller.js create mode 100644 reading-platform-backend/dist/src/modules/lesson/lesson.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/lesson/lesson.module.d.ts create mode 100644 reading-platform-backend/dist/src/modules/lesson/lesson.module.js create mode 100644 reading-platform-backend/dist/src/modules/lesson/lesson.module.js.map create mode 100644 reading-platform-backend/dist/src/modules/lesson/lesson.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/lesson/lesson.service.js create mode 100644 reading-platform-backend/dist/src/modules/lesson/lesson.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/notification/notification.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/notification/notification.controller.js create mode 100644 reading-platform-backend/dist/src/modules/notification/notification.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/notification/notification.module.d.ts create mode 100644 reading-platform-backend/dist/src/modules/notification/notification.module.js create mode 100644 reading-platform-backend/dist/src/modules/notification/notification.module.js.map create mode 100644 reading-platform-backend/dist/src/modules/notification/notification.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/notification/notification.service.js create mode 100644 reading-platform-backend/dist/src/modules/notification/notification.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/notification/schedule-notification.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/notification/schedule-notification.service.js create mode 100644 reading-platform-backend/dist/src/modules/notification/schedule-notification.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/parent/parent.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/parent/parent.controller.js create mode 100644 reading-platform-backend/dist/src/modules/parent/parent.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/parent/parent.module.d.ts create mode 100644 reading-platform-backend/dist/src/modules/parent/parent.module.js create mode 100644 reading-platform-backend/dist/src/modules/parent/parent.module.js.map create mode 100644 reading-platform-backend/dist/src/modules/parent/parent.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/parent/parent.service.js create mode 100644 reading-platform-backend/dist/src/modules/parent/parent.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/resource/dto/create-resource.dto.d.ts create mode 100644 reading-platform-backend/dist/src/modules/resource/dto/create-resource.dto.js create mode 100644 reading-platform-backend/dist/src/modules/resource/dto/create-resource.dto.js.map create mode 100644 reading-platform-backend/dist/src/modules/resource/resource.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/resource/resource.controller.js create mode 100644 reading-platform-backend/dist/src/modules/resource/resource.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/resource/resource.module.d.ts create mode 100644 reading-platform-backend/dist/src/modules/resource/resource.module.js create mode 100644 reading-platform-backend/dist/src/modules/resource/resource.module.js.map create mode 100644 reading-platform-backend/dist/src/modules/resource/resource.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/resource/resource.service.js create mode 100644 reading-platform-backend/dist/src/modules/resource/resource.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/school/dto/class-teacher.dto.d.ts create mode 100644 reading-platform-backend/dist/src/modules/school/dto/class-teacher.dto.js create mode 100644 reading-platform-backend/dist/src/modules/school/dto/class-teacher.dto.js.map create mode 100644 reading-platform-backend/dist/src/modules/school/dto/create-class.dto.d.ts create mode 100644 reading-platform-backend/dist/src/modules/school/dto/create-class.dto.js create mode 100644 reading-platform-backend/dist/src/modules/school/dto/create-class.dto.js.map create mode 100644 reading-platform-backend/dist/src/modules/school/dto/create-student.dto.d.ts create mode 100644 reading-platform-backend/dist/src/modules/school/dto/create-student.dto.js create mode 100644 reading-platform-backend/dist/src/modules/school/dto/create-student.dto.js.map create mode 100644 reading-platform-backend/dist/src/modules/school/dto/create-teacher.dto.d.ts create mode 100644 reading-platform-backend/dist/src/modules/school/dto/create-teacher.dto.js create mode 100644 reading-platform-backend/dist/src/modules/school/dto/create-teacher.dto.js.map create mode 100644 reading-platform-backend/dist/src/modules/school/dto/import-students.dto.d.ts create mode 100644 reading-platform-backend/dist/src/modules/school/dto/import-students.dto.js create mode 100644 reading-platform-backend/dist/src/modules/school/dto/import-students.dto.js.map create mode 100644 reading-platform-backend/dist/src/modules/school/dto/schedule.dto.d.ts create mode 100644 reading-platform-backend/dist/src/modules/school/dto/schedule.dto.js create mode 100644 reading-platform-backend/dist/src/modules/school/dto/schedule.dto.js.map create mode 100644 reading-platform-backend/dist/src/modules/school/export.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/school/export.controller.js create mode 100644 reading-platform-backend/dist/src/modules/school/export.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/school/export.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/school/export.service.js create mode 100644 reading-platform-backend/dist/src/modules/school/export.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/school/package.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/school/package.controller.js create mode 100644 reading-platform-backend/dist/src/modules/school/package.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/school/school.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/school/school.controller.js create mode 100644 reading-platform-backend/dist/src/modules/school/school.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/school/school.module.d.ts create mode 100644 reading-platform-backend/dist/src/modules/school/school.module.js create mode 100644 reading-platform-backend/dist/src/modules/school/school.module.js.map create mode 100644 reading-platform-backend/dist/src/modules/school/school.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/school/school.service.js create mode 100644 reading-platform-backend/dist/src/modules/school/school.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/school/settings.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/school/settings.controller.js create mode 100644 reading-platform-backend/dist/src/modules/school/settings.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/school/settings.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/school/settings.service.js create mode 100644 reading-platform-backend/dist/src/modules/school/settings.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/school/stats.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/school/stats.controller.js create mode 100644 reading-platform-backend/dist/src/modules/school/stats.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/school/stats.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/school/stats.service.js create mode 100644 reading-platform-backend/dist/src/modules/school/stats.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/task/dto/create-task.dto.d.ts create mode 100644 reading-platform-backend/dist/src/modules/task/dto/create-task.dto.js create mode 100644 reading-platform-backend/dist/src/modules/task/dto/create-task.dto.js.map create mode 100644 reading-platform-backend/dist/src/modules/task/task.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/task/task.controller.js create mode 100644 reading-platform-backend/dist/src/modules/task/task.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/task/task.module.d.ts create mode 100644 reading-platform-backend/dist/src/modules/task/task.module.js create mode 100644 reading-platform-backend/dist/src/modules/task/task.module.js.map create mode 100644 reading-platform-backend/dist/src/modules/task/task.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/task/task.service.js create mode 100644 reading-platform-backend/dist/src/modules/task/task.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/teacher-course/teacher-course.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/teacher-course/teacher-course.controller.js create mode 100644 reading-platform-backend/dist/src/modules/teacher-course/teacher-course.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/teacher-course/teacher-course.module.d.ts create mode 100644 reading-platform-backend/dist/src/modules/teacher-course/teacher-course.module.js create mode 100644 reading-platform-backend/dist/src/modules/teacher-course/teacher-course.module.js.map create mode 100644 reading-platform-backend/dist/src/modules/teacher-course/teacher-course.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/teacher-course/teacher-course.service.js create mode 100644 reading-platform-backend/dist/src/modules/teacher-course/teacher-course.service.js.map create mode 100644 reading-platform-backend/dist/src/modules/tenant/dto/tenant.dto.d.ts create mode 100644 reading-platform-backend/dist/src/modules/tenant/dto/tenant.dto.js create mode 100644 reading-platform-backend/dist/src/modules/tenant/dto/tenant.dto.js.map create mode 100644 reading-platform-backend/dist/src/modules/tenant/tenant.controller.d.ts create mode 100644 reading-platform-backend/dist/src/modules/tenant/tenant.controller.js create mode 100644 reading-platform-backend/dist/src/modules/tenant/tenant.controller.js.map create mode 100644 reading-platform-backend/dist/src/modules/tenant/tenant.module.d.ts create mode 100644 reading-platform-backend/dist/src/modules/tenant/tenant.module.js create mode 100644 reading-platform-backend/dist/src/modules/tenant/tenant.module.js.map create mode 100644 reading-platform-backend/dist/src/modules/tenant/tenant.service.d.ts create mode 100644 reading-platform-backend/dist/src/modules/tenant/tenant.service.js create mode 100644 reading-platform-backend/dist/src/modules/tenant/tenant.service.js.map create mode 100644 reading-platform-backend/dist/tsconfig.tsbuildinfo create mode 100644 reading-platform-backend/nest-cli.json create mode 100644 reading-platform-backend/package.json create mode 100644 reading-platform-backend/prisma/dev.db create mode 100644 reading-platform-backend/prisma/migrations/20260210055321_init/migration.sql create mode 100644 reading-platform-backend/prisma/migrations/20260210092744_init/migration.sql create mode 100644 reading-platform-backend/prisma/migrations/20260210093209_make_picture_book_nullable/migration.sql create mode 100644 reading-platform-backend/prisma/migrations/migration_lock.toml create mode 100644 reading-platform-backend/prisma/schema.prisma create mode 100644 reading-platform-backend/prisma/seed.ts create mode 100644 reading-platform-backend/scripts/create-test-parent.ts create mode 100644 reading-platform-backend/src/.DS_Store create mode 100644 reading-platform-backend/src/app.module.js create mode 100644 reading-platform-backend/src/app.module.ts create mode 100644 reading-platform-backend/src/common/filters/http-exception.filter.ts create mode 100644 reading-platform-backend/src/database/prisma.module.js create mode 100644 reading-platform-backend/src/database/prisma.module.ts create mode 100644 reading-platform-backend/src/database/prisma.service.js create mode 100644 reading-platform-backend/src/database/prisma.service.ts create mode 100644 reading-platform-backend/src/main.js create mode 100644 reading-platform-backend/src/main.ts create mode 100644 reading-platform-backend/src/modules/admin/admin-settings.controller.ts create mode 100644 reading-platform-backend/src/modules/admin/admin-settings.service.ts create mode 100644 reading-platform-backend/src/modules/admin/admin-stats.controller.ts create mode 100644 reading-platform-backend/src/modules/admin/admin-stats.service.ts create mode 100644 reading-platform-backend/src/modules/admin/admin.module.ts create mode 100644 reading-platform-backend/src/modules/auth/auth.controller.js create mode 100644 reading-platform-backend/src/modules/auth/auth.controller.ts create mode 100644 reading-platform-backend/src/modules/auth/auth.module.js create mode 100644 reading-platform-backend/src/modules/auth/auth.module.ts create mode 100644 reading-platform-backend/src/modules/auth/auth.service.js create mode 100644 reading-platform-backend/src/modules/auth/auth.service.ts create mode 100644 reading-platform-backend/src/modules/auth/dto/login.dto.js create mode 100644 reading-platform-backend/src/modules/auth/dto/login.dto.ts create mode 100644 reading-platform-backend/src/modules/auth/strategies/jwt.strategy.js create mode 100644 reading-platform-backend/src/modules/auth/strategies/jwt.strategy.ts create mode 100644 reading-platform-backend/src/modules/common/common.module.ts create mode 100644 reading-platform-backend/src/modules/common/decorators/log-operation.decorator.ts create mode 100644 reading-platform-backend/src/modules/common/decorators/roles.decorator.ts create mode 100644 reading-platform-backend/src/modules/common/guards/jwt-auth.guard.ts create mode 100644 reading-platform-backend/src/modules/common/guards/roles.guard.ts create mode 100644 reading-platform-backend/src/modules/common/interceptors/log.interceptor.ts create mode 100644 reading-platform-backend/src/modules/common/operation-log.controller.ts create mode 100644 reading-platform-backend/src/modules/common/operation-log.service.ts create mode 100644 reading-platform-backend/src/modules/course/course-validation.service.ts create mode 100644 reading-platform-backend/src/modules/course/course.controller.ts create mode 100644 reading-platform-backend/src/modules/course/course.module.ts create mode 100644 reading-platform-backend/src/modules/course/course.service.ts create mode 100644 reading-platform-backend/src/modules/export/export.controller.ts create mode 100644 reading-platform-backend/src/modules/export/export.module.ts create mode 100644 reading-platform-backend/src/modules/export/export.service.ts create mode 100644 reading-platform-backend/src/modules/file-upload/file-upload.controller.ts create mode 100644 reading-platform-backend/src/modules/file-upload/file-upload.module.ts create mode 100644 reading-platform-backend/src/modules/file-upload/file-upload.service.ts create mode 100644 reading-platform-backend/src/modules/growth/dto/create-growth.dto.ts create mode 100644 reading-platform-backend/src/modules/growth/growth.controller.ts create mode 100644 reading-platform-backend/src/modules/growth/growth.module.ts create mode 100644 reading-platform-backend/src/modules/growth/growth.service.ts create mode 100644 reading-platform-backend/src/modules/lesson/dto/create-lesson.dto.ts create mode 100644 reading-platform-backend/src/modules/lesson/dto/finish-lesson.dto.ts create mode 100644 reading-platform-backend/src/modules/lesson/lesson.controller.ts create mode 100644 reading-platform-backend/src/modules/lesson/lesson.module.ts create mode 100644 reading-platform-backend/src/modules/lesson/lesson.service.ts create mode 100644 reading-platform-backend/src/modules/notification/notification.controller.ts create mode 100644 reading-platform-backend/src/modules/notification/notification.module.ts create mode 100644 reading-platform-backend/src/modules/notification/notification.service.ts create mode 100644 reading-platform-backend/src/modules/notification/schedule-notification.service.ts create mode 100644 reading-platform-backend/src/modules/parent/parent.controller.ts create mode 100644 reading-platform-backend/src/modules/parent/parent.module.ts create mode 100644 reading-platform-backend/src/modules/parent/parent.service.ts create mode 100644 reading-platform-backend/src/modules/resource/dto/create-resource.dto.ts create mode 100644 reading-platform-backend/src/modules/resource/resource.controller.ts create mode 100644 reading-platform-backend/src/modules/resource/resource.module.ts create mode 100644 reading-platform-backend/src/modules/resource/resource.service.ts create mode 100644 reading-platform-backend/src/modules/school/dto/class-teacher.dto.ts create mode 100644 reading-platform-backend/src/modules/school/dto/create-class.dto.ts create mode 100644 reading-platform-backend/src/modules/school/dto/create-student.dto.ts create mode 100644 reading-platform-backend/src/modules/school/dto/create-teacher.dto.ts create mode 100644 reading-platform-backend/src/modules/school/dto/import-students.dto.ts create mode 100644 reading-platform-backend/src/modules/school/dto/schedule.dto.ts create mode 100644 reading-platform-backend/src/modules/school/export.controller.ts create mode 100644 reading-platform-backend/src/modules/school/export.service.ts create mode 100644 reading-platform-backend/src/modules/school/package.controller.ts create mode 100644 reading-platform-backend/src/modules/school/school.controller.ts create mode 100644 reading-platform-backend/src/modules/school/school.module.ts create mode 100644 reading-platform-backend/src/modules/school/school.service.ts create mode 100644 reading-platform-backend/src/modules/school/settings.controller.ts create mode 100644 reading-platform-backend/src/modules/school/settings.service.ts create mode 100644 reading-platform-backend/src/modules/school/stats.controller.ts create mode 100644 reading-platform-backend/src/modules/school/stats.service.ts create mode 100644 reading-platform-backend/src/modules/task/dto/create-task.dto.ts create mode 100644 reading-platform-backend/src/modules/task/task.controller.ts create mode 100644 reading-platform-backend/src/modules/task/task.module.ts create mode 100644 reading-platform-backend/src/modules/task/task.service.ts create mode 100644 reading-platform-backend/src/modules/teacher-course/teacher-course.controller.ts create mode 100644 reading-platform-backend/src/modules/teacher-course/teacher-course.module.ts create mode 100644 reading-platform-backend/src/modules/teacher-course/teacher-course.service.ts create mode 100644 reading-platform-backend/src/modules/tenant/dto/tenant.dto.ts create mode 100644 reading-platform-backend/src/modules/tenant/tenant.controller.js create mode 100644 reading-platform-backend/src/modules/tenant/tenant.controller.ts create mode 100644 reading-platform-backend/src/modules/tenant/tenant.module.js create mode 100644 reading-platform-backend/src/modules/tenant/tenant.module.ts create mode 100644 reading-platform-backend/src/modules/tenant/tenant.service.js create mode 100644 reading-platform-backend/src/modules/tenant/tenant.service.ts create mode 100644 reading-platform-backend/start-backend.sh create mode 100644 reading-platform-backend/tsconfig.build.json create mode 100644 reading-platform-backend/tsconfig.json create mode 100644 reading-platform-frontend/.DS_Store create mode 100644 reading-platform-frontend/.env.development create mode 100644 reading-platform-frontend/dev.db create mode 100644 reading-platform-frontend/index.html create mode 100644 reading-platform-frontend/package-lock.json create mode 100644 reading-platform-frontend/package.json create mode 100644 reading-platform-frontend/public/logo.png create mode 100644 reading-platform-frontend/src/.DS_Store create mode 100644 reading-platform-frontend/src/App.vue create mode 100644 reading-platform-frontend/src/api/admin.ts create mode 100644 reading-platform-frontend/src/api/auth.ts create mode 100644 reading-platform-frontend/src/api/course.ts create mode 100644 reading-platform-frontend/src/api/file.ts create mode 100644 reading-platform-frontend/src/api/growth.ts create mode 100644 reading-platform-frontend/src/api/index.ts create mode 100644 reading-platform-frontend/src/api/parent.ts create mode 100644 reading-platform-frontend/src/api/resource.ts create mode 100644 reading-platform-frontend/src/api/school.ts create mode 100644 reading-platform-frontend/src/api/task.ts create mode 100644 reading-platform-frontend/src/api/teacher.ts create mode 100644 reading-platform-frontend/src/auto-imports.d.ts create mode 100644 reading-platform-frontend/src/components.d.ts create mode 100644 reading-platform-frontend/src/components/FilePreviewModal.vue create mode 100644 reading-platform-frontend/src/components/NotificationBell.vue create mode 100644 reading-platform-frontend/src/main.ts create mode 100644 reading-platform-frontend/src/router/index.ts create mode 100644 reading-platform-frontend/src/stores/user.ts create mode 100644 reading-platform-frontend/src/utils/tagMaps.ts create mode 100644 reading-platform-frontend/src/views/NotFoundView.vue create mode 100644 reading-platform-frontend/src/views/admin/DashboardView.vue create mode 100644 reading-platform-frontend/src/views/admin/LayoutView.vue create mode 100644 reading-platform-frontend/src/views/admin/SettingsView.vue create mode 100644 reading-platform-frontend/src/views/admin/courses/CourseDetailView.vue create mode 100644 reading-platform-frontend/src/views/admin/courses/CourseEditView.vue create mode 100644 reading-platform-frontend/src/views/admin/courses/CourseListView.vue create mode 100644 reading-platform-frontend/src/views/admin/courses/CourseReviewView.vue create mode 100644 reading-platform-frontend/src/views/admin/courses/CourseStatsView.vue create mode 100644 reading-platform-frontend/src/views/admin/resources/ResourceListView.vue create mode 100644 reading-platform-frontend/src/views/admin/tenants/TenantListView.vue create mode 100644 reading-platform-frontend/src/views/auth/LoginView.vue create mode 100644 reading-platform-frontend/src/views/parent/DashboardView.vue create mode 100644 reading-platform-frontend/src/views/parent/LayoutView.vue create mode 100644 reading-platform-frontend/src/views/parent/children/ChildProfileView.vue create mode 100644 reading-platform-frontend/src/views/parent/children/ChildrenView.vue create mode 100644 reading-platform-frontend/src/views/parent/growth/GrowthRecordView.vue create mode 100644 reading-platform-frontend/src/views/parent/lessons/LessonHistoryView.vue create mode 100644 reading-platform-frontend/src/views/parent/tasks/TaskListView.vue create mode 100644 reading-platform-frontend/src/views/school/DashboardView.vue create mode 100644 reading-platform-frontend/src/views/school/LayoutView.vue create mode 100644 reading-platform-frontend/src/views/school/PackageView.vue create mode 100644 reading-platform-frontend/src/views/school/ReportView.vue create mode 100644 reading-platform-frontend/src/views/school/classes/ClassListView.vue create mode 100644 reading-platform-frontend/src/views/school/courses/CourseDetailView.vue create mode 100644 reading-platform-frontend/src/views/school/courses/CourseListView.vue create mode 100644 reading-platform-frontend/src/views/school/feedback/FeedbackView.vue create mode 100644 reading-platform-frontend/src/views/school/growth/GrowthRecordView.vue create mode 100644 reading-platform-frontend/src/views/school/parents/ParentListView.vue create mode 100644 reading-platform-frontend/src/views/school/schedule/CalendarView.vue create mode 100644 reading-platform-frontend/src/views/school/schedule/ScheduleView.vue create mode 100644 reading-platform-frontend/src/views/school/schedule/TimetableView.vue create mode 100644 reading-platform-frontend/src/views/school/settings/OperationLogView.vue create mode 100644 reading-platform-frontend/src/views/school/settings/SettingsView.vue create mode 100644 reading-platform-frontend/src/views/school/students/StudentListView.vue create mode 100644 reading-platform-frontend/src/views/school/tasks/TaskListView.vue create mode 100644 reading-platform-frontend/src/views/school/tasks/TaskTemplateView.vue create mode 100644 reading-platform-frontend/src/views/school/teachers/TeacherListView.vue create mode 100644 reading-platform-frontend/src/views/teacher/DashboardView.vue create mode 100644 reading-platform-frontend/src/views/teacher/LayoutView.vue create mode 100644 reading-platform-frontend/src/views/teacher/classes/ClassListView.vue create mode 100644 reading-platform-frontend/src/views/teacher/classes/ClassStudentsView.vue create mode 100644 reading-platform-frontend/src/views/teacher/courses/CourseDetailView.vue create mode 100644 reading-platform-frontend/src/views/teacher/courses/CourseListView.vue create mode 100644 reading-platform-frontend/src/views/teacher/courses/PrepareModeView.vue create mode 100644 reading-platform-frontend/src/views/teacher/feedback/FeedbackView.vue create mode 100644 reading-platform-frontend/src/views/teacher/growth/GrowthRecordView.vue create mode 100644 reading-platform-frontend/src/views/teacher/lessons/BroadcastView.vue create mode 100644 reading-platform-frontend/src/views/teacher/lessons/LessonListView.vue create mode 100644 reading-platform-frontend/src/views/teacher/lessons/LessonRecordsView.vue create mode 100644 reading-platform-frontend/src/views/teacher/lessons/LessonView.vue create mode 100644 reading-platform-frontend/src/views/teacher/lessons/components/KidsMode.vue create mode 100644 reading-platform-frontend/src/views/teacher/lessons/components/viewers/AudioPlayer.vue create mode 100644 reading-platform-frontend/src/views/teacher/lessons/components/viewers/EbookViewer.vue create mode 100644 reading-platform-frontend/src/views/teacher/lessons/components/viewers/SlidesViewer.vue create mode 100644 reading-platform-frontend/src/views/teacher/lessons/components/viewers/VideoPlayer.vue create mode 100644 reading-platform-frontend/src/views/teacher/schedule/ScheduleView.vue create mode 100644 reading-platform-frontend/src/views/teacher/tasks/TaskListView.vue create mode 100644 reading-platform-frontend/src/vite-env.d.ts create mode 100644 reading-platform-frontend/start-frontend.sh create mode 100644 reading-platform-frontend/tsconfig.json create mode 100644 reading-platform-frontend/tsconfig.node.json create mode 100644 reading-platform-frontend/vite.config.ts diff --git a/reading-platform-backend/.DS_Store b/reading-platform-backend/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..08110a9d33bcc5bd04a45485edb8a9f28f210a0c GIT binary patch literal 6148 zcmeHLJugH-6urZST@(uO(Y-=RK_UtgkE~vzpz^W1f@Gh!!6q8=h@R;MbSHf$ap+e61+#oD~C&(FJ>&V*NH-%f2@1Fey=G{a!S{KD4TC};68o@s3`fYxN{;RD~&Clt>=9Cp<^B|Qlk7m5HvM{O9 zrmGrJVJaPAb9wIDyMH+mtv>W)v&oKf_{jM|jo`CRWk-?b*$4129((-z!Mo9!QyKrw z`~GIqUT3_PBXo-S*YPH*SUHPWMb4_3D?N>+Bkb|-b{=mp4$SYN7;PlDu43G z9!NN;P@^xs0$u@Efkyq<&gcJR_Wj>& z@fGSfnbCIL z1#ePx29htCpG@|HO)^Bp)5qn6XiP*Injp)dM?~FgIxuH3a$2LJ+wyKQThvw4v(P@O zWbY$tXifL@K%M=sp1#|wx+vylJ%>KKE=T9jFK?G!pXHA}&1#pYh$U!%9js~5V({pF zv$gEzzBt{SS#FD~>TTApwUzJWRr0ww1I~am;0*jv25@JKWQU60I|I&uGw{WLoDTs_ zFgJ{f>F7Y0N&w&x>LeITEg><%FgJ{fus~Qtff~wIVz7ooAIvW|jEWjgY{ds#=Fj4V zQ+BK$k~?v(=)E)G473>-=yWXi{}q0j-Xgyp;xlK!8Texi@UWN_Q+$-&t)0)4yEdTR tpoxfIlmP_e)*}EDIY*9@QSCv-@XHOOqO2nN6b|%^! literal 0 HcmV?d00001 diff --git a/reading-platform-backend/dist/prisma/seed.d.ts b/reading-platform-backend/dist/prisma/seed.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/reading-platform-backend/dist/prisma/seed.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/reading-platform-backend/dist/prisma/seed.js b/reading-platform-backend/dist/prisma/seed.js new file mode 100644 index 0000000..6b9eb13 --- /dev/null +++ b/reading-platform-backend/dist/prisma/seed.js @@ -0,0 +1,420 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const client_1 = require("@prisma/client"); +const bcrypt = __importStar(require("bcrypt")); +const prisma = new client_1.PrismaClient(); +async function main() { + console.log('开始种子数据...'); + const tenant = await prisma.tenant.upsert({ + where: { id: 1 }, + update: {}, + create: { + name: '阳光幼儿园', + address: '北京市朝阳区xxx街道', + contactPerson: '张园长', + contactPhone: '13800138000', + packageType: 'STANDARD', + teacherQuota: 20, + studentQuota: 200, + storageQuota: BigInt(5368709120), + startDate: '2024-01-01', + expireDate: '2025-12-31', + status: 'ACTIVE', + }, + }); + console.log('创建租户:', tenant.name); + const passwordHash = await bcrypt.hash('123456', 10); + const teacher = await prisma.teacher.upsert({ + where: { loginAccount: 'teacher1' }, + update: {}, + create: { + tenantId: tenant.id, + name: '李老师', + phone: '13900139000', + email: 'teacher1@example.com', + loginAccount: 'teacher1', + passwordHash: passwordHash, + status: 'ACTIVE', + }, + }); + console.log('创建教师:', teacher.name); + const class1 = await prisma.class.upsert({ + where: { id: 1 }, + update: {}, + create: { + tenantId: tenant.id, + name: '中一班', + grade: 'MIDDLE', + teacherId: teacher.id, + studentCount: 25, + }, + }); + console.log('创建班级:', class1.name); + const class2 = await prisma.class.upsert({ + where: { id: 2 }, + update: {}, + create: { + tenantId: tenant.id, + name: '大一班', + grade: 'BIG', + teacherId: teacher.id, + studentCount: 30, + }, + }); + console.log('创建班级:', class2.name); + await prisma.teacher.update({ + where: { id: teacher.id }, + data: { + classIds: JSON.stringify([class1.id, class2.id]), + }, + }); + const students = [ + { name: '小明', gender: 'MALE', classId: class1.id }, + { name: '小红', gender: 'FEMALE', classId: class1.id }, + { name: '小华', gender: 'MALE', classId: class1.id }, + { name: '小丽', gender: 'FEMALE', classId: class2.id }, + { name: '小强', gender: 'MALE', classId: class2.id }, + ]; + for (const studentData of students) { + await prisma.student.upsert({ + where: { + id: students.indexOf(studentData) + 1, + }, + update: {}, + create: { + tenantId: tenant.id, + classId: studentData.classId, + name: studentData.name, + gender: studentData.gender, + }, + }); + } + console.log('创建学生:', students.length, '名'); + const course = await prisma.course.upsert({ + where: { id: 1 }, + update: {}, + create: { + name: '好饿的毛毛虫', + description: '这是一本经典的绘本,讲述了一只毛毛虫从孵化到变成蝴蝶的故事。通过这个故事,孩子们可以学习到星期的概念、数字的认知,以及毛毛虫变蝴蝶的科学知识。', + pictureBookName: '好饿的毛毛虫', + gradeTags: JSON.stringify(['SMALL', 'MIDDLE']), + domainTags: JSON.stringify(['LANGUAGE', 'SCIENCE', 'MATH']), + duration: 30, + status: 'PUBLISHED', + version: '1.0', + coverImagePath: '/uploads/covers/caterpillar.jpg', + }, + }); + console.log('创建课程:', course.name); + const scripts = [ + { + stepIndex: 1, + stepName: '阅读导入', + stepType: 'INTRODUCTION', + duration: 5, + objective: '激发幼儿阅读兴趣,建立阅读期待', + teacherScript: '小朋友们,今天我们要认识一位新朋友——一只小小的毛毛虫。你们见过毛毛虫吗?它长什么样子呢?让我们一起来看看这只特别的毛毛虫的故事吧!', + interactionPoints: JSON.stringify([ + '展示毛毛虫图片或玩偶', + '引导幼儿分享见过的毛毛虫', + '预测故事内容', + ]), + }, + { + stepIndex: 2, + stepName: '绘本共读', + stepType: 'READING', + duration: 10, + objective: '理解故事内容,发展语言能力', + teacherScript: '(逐页讲述)从前,有一颗小小的蛋躺在叶子上...月光下,一条又小又饿的毛毛虫从蛋里爬了出来...', + interactionPoints: JSON.stringify([ + '提问预测', + '模仿毛毛虫吃东西的动作', + '一起数食物的数量', + ]), + }, + { + stepIndex: 3, + stepName: '理解讨论', + stepType: 'DISCUSSION', + duration: 5, + objective: '加深对故事的理解,发展思维能力', + teacherScript: '小朋友们,毛毛虫吃了哪些东西呢?为什么最后它肚子痛了?它最后变成了什么?', + interactionPoints: JSON.stringify([ + '回顾毛毛虫吃的食物', + '讨论健康饮食的重要性', + '讨论毛毛虫的成长变化', + ]), + }, + { + stepIndex: 4, + stepName: '互动游戏', + stepType: 'ACTIVITY', + duration: 5, + objective: '通过游戏巩固学习内容', + teacherScript: '现在我们来玩一个游戏,老师说出星期几,小朋友们来模仿毛毛虫吃了什么!', + interactionPoints: JSON.stringify([ + '星期与食物配对游戏', + '毛毛虫动作模仿', + '食物分类活动', + ]), + }, + { + stepIndex: 5, + stepName: '创意表达', + stepType: 'CREATIVE', + duration: 3, + objective: '发展创造力和表达能力', + teacherScript: '如果你是毛毛虫,你想吃什么?画一画你心目中的毛毛虫吧!', + interactionPoints: JSON.stringify([ + '自由绘画', + '分享作品', + '创意表达', + ]), + }, + { + stepIndex: 6, + stepName: '总结延伸', + stepType: 'SUMMARY', + duration: 2, + objective: '总结学习内容,激发延伸探索兴趣', + teacherScript: '今天我们认识了一只可爱的毛毛虫,它从一颗小蛋,变成毛毛虫,最后变成了漂亮的蝴蝶!回家后可以和爸爸妈妈一起找找看,还有哪些动物会变形呢?', + interactionPoints: JSON.stringify([ + '总结毛毛虫的成长过程', + '布置家庭延伸任务', + '预告下次活动', + ]), + }, + ]; + for (const script of scripts) { + await prisma.courseScript.upsert({ + where: { + courseId_stepIndex: { + courseId: course.id, + stepIndex: script.stepIndex, + }, + }, + update: {}, + create: { + courseId: course.id, + ...script, + sortOrder: script.stepIndex, + }, + }); + } + console.log('创建课程脚本:', scripts.length, '个步骤'); + const pages = [ + { pageNumber: 1, questions: '你们看到了什么?这是什么颜色的?', teacherNotes: '引导观察封面' }, + { pageNumber: 2, questions: '蛋在哪里?是谁的蛋呢?', teacherNotes: '引入故事悬念' }, + { pageNumber: 3, questions: '毛毛虫从蛋里出来了!它说了什么?', teacherNotes: '模仿毛毛虫的声音' }, + { pageNumber: 4, questions: '星期一,毛毛虫吃了什么?吃了几个?', teacherNotes: '学习星期和数字' }, + { pageNumber: 5, questions: '星期二,它又吃了什么?', teacherNotes: '继续学习星期' }, + ]; + const readingScript = await prisma.courseScript.findFirst({ + where: { courseId: course.id, stepType: 'READING' }, + }); + if (readingScript) { + for (const page of pages) { + await prisma.courseScriptPage.upsert({ + where: { + scriptId_pageNumber: { + scriptId: readingScript.id, + pageNumber: page.pageNumber, + }, + }, + update: {}, + create: { + scriptId: readingScript.id, + ...page, + }, + }); + } + console.log('创建逐页配置:', pages.length, '页'); + } + const activities = [ + { + name: '毛毛虫手偶制作', + domain: 'ART', + activityType: 'HANDICRAFT', + duration: 20, + onlineMaterials: JSON.stringify(['毛毛虫模板PDF', '制作视频']), + offlineMaterials: '彩纸、剪刀、胶水、眼睛贴纸', + activityGuide: '1. 准备材料\n2. 按照模板剪裁\n3. 粘贴组装\n4. 添加装饰', + objectives: JSON.stringify(['锻炼手部精细动作', '培养创造力', '巩固毛毛虫认知']), + sortOrder: 1, + }, + { + name: '健康饮食分类', + domain: 'SCIENCE', + activityType: 'GAME', + duration: 15, + onlineMaterials: JSON.stringify(['食物卡片PPT']), + offlineMaterials: '食物图片卡片、分类筐', + activityGuide: '1. 展示各种食物图片\n2. 讨论健康与不健康食物\n3. 进行分类游戏', + objectives: JSON.stringify(['认识健康饮食', '学习分类', '培养健康饮食习惯']), + sortOrder: 2, + }, + { + name: '蝴蝶的生命周期', + domain: 'SCIENCE', + activityType: 'EXPLORATION', + duration: 25, + onlineMaterials: JSON.stringify(['蝴蝶生长视频', '生命周期图']), + offlineMaterials: '绘本、放大镜、观察记录本', + activityGuide: '1. 观看蝴蝶生长视频\n2. 讨论四个阶段\n3. 绘制生命周期图', + objectives: JSON.stringify(['了解变态发育', '培养科学探究精神', '学习观察记录']), + sortOrder: 3, + }, + ]; + for (const activity of activities) { + await prisma.courseActivity.upsert({ + where: { id: activities.indexOf(activity) + 1 }, + update: {}, + create: { + courseId: course.id, + ...activity, + }, + }); + } + console.log('创建延伸活动:', activities.length, '个'); + const tenantCourse = await prisma.tenantCourse.upsert({ + where: { + tenantId_courseId: { + tenantId: tenant.id, + courseId: course.id, + }, + }, + update: {}, + create: { + tenantId: tenant.id, + courseId: course.id, + authorized: true, + authorizedAt: new Date(), + }, + }); + console.log('授权课程给租户:', tenant.id, '->', course.id); + const course2 = await prisma.course.upsert({ + where: { id: 2 }, + update: {}, + create: { + name: '猜猜我有多爱你', + description: '这是一本关于爱的温暖绘本,小兔子和大兔子用各种方式表达彼此的爱。通过这个故事,孩子们可以学习到表达爱的方式,感受亲情的温暖。', + pictureBookName: '猜猜我有多爱你', + gradeTags: JSON.stringify(['MIDDLE', 'BIG']), + domainTags: JSON.stringify(['LANGUAGE', 'SOCIAL']), + duration: 25, + status: 'PUBLISHED', + version: '1.0', + coverImagePath: '/uploads/covers/love.jpg', + }, + }); + console.log('创建课程:', course2.name); + const scripts2 = [ + { + stepIndex: 1, + stepName: '导入环节', + stepType: 'INTRODUCTION', + duration: 3, + objective: '引入爱的主题', + teacherScript: '小朋友们,你们爱爸爸妈妈吗?你们是怎么表达爱的呢?', + interactionPoints: JSON.stringify(['分享表达爱的方式']), + }, + { + stepIndex: 2, + stepName: '绘本共读', + stepType: 'READING', + duration: 10, + objective: '理解故事,感受爱的表达', + teacherScript: '小栗色兔子该上床睡觉了,可是他紧紧地抓住大栗色兔子的长耳朵不放...', + interactionPoints: JSON.stringify(['模仿动作', '感受爱的比较']), + }, + { + stepIndex: 3, + stepName: '情感讨论', + stepType: 'DISCUSSION', + duration: 5, + objective: '表达自己的感受', + teacherScript: '小兔子和大兔子谁的爱更多呢?你们觉得呢?', + interactionPoints: JSON.stringify(['讨论爱的深度', '分享感受']), + }, + { + stepIndex: 4, + stepName: '爱的表达', + stepType: 'ACTIVITY', + duration: 5, + objective: '学会表达爱', + teacherScript: '让我们也来学学小兔子,用手臂来量量我们有多爱爸爸妈妈!', + interactionPoints: JSON.stringify(['肢体表达', '语言表达']), + }, + ]; + for (const script of scripts2) { + await prisma.courseScript.upsert({ + where: { + courseId_stepIndex: { + courseId: course2.id, + stepIndex: script.stepIndex, + }, + }, + update: {}, + create: { + courseId: course2.id, + ...script, + sortOrder: script.stepIndex, + }, + }); + } + await prisma.tenantCourse.upsert({ + where: { + tenantId_courseId: { + tenantId: tenant.id, + courseId: course2.id, + }, + }, + update: {}, + create: { + tenantId: tenant.id, + courseId: course2.id, + authorized: true, + authorizedAt: new Date(), + }, + }); + console.log('授权课程给租户:', tenant.id, '->', course2.id); + console.log('\n种子数据创建完成!'); + console.log('===================='); + console.log('测试账号信息:'); + console.log('超管: admin / 123456'); + console.log('教师: teacher1 / 123456'); + console.log('===================='); +} +main() + .catch((e) => { + console.error(e); + process.exit(1); +}) + .finally(async () => { + await prisma.$disconnect(); +}); +//# sourceMappingURL=seed.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/prisma/seed.js.map b/reading-platform-backend/dist/prisma/seed.js.map new file mode 100644 index 0000000..cae8a4c --- /dev/null +++ b/reading-platform-backend/dist/prisma/seed.js.map @@ -0,0 +1 @@ +{"version":3,"file":"seed.js","sourceRoot":"","sources":["../../prisma/seed.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/app.module.d.ts b/reading-platform-backend/dist/src/app.module.d.ts new file mode 100644 index 0000000..09cdb35 --- /dev/null +++ b/reading-platform-backend/dist/src/app.module.d.ts @@ -0,0 +1,2 @@ +export declare class AppModule { +} diff --git a/reading-platform-backend/dist/src/app.module.js b/reading-platform-backend/dist/src/app.module.js new file mode 100644 index 0000000..77a43f4 --- /dev/null +++ b/reading-platform-backend/dist/src/app.module.js @@ -0,0 +1,64 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AppModule = void 0; +const common_1 = require("@nestjs/common"); +const config_1 = require("@nestjs/config"); +const throttler_1 = require("@nestjs/throttler"); +const prisma_module_1 = require("./database/prisma.module"); +const auth_module_1 = require("./modules/auth/auth.module"); +const course_module_1 = require("./modules/course/course.module"); +const tenant_module_1 = require("./modules/tenant/tenant.module"); +const common_module_1 = require("./modules/common/common.module"); +const file_upload_module_1 = require("./modules/file-upload/file-upload.module"); +const teacher_course_module_1 = require("./modules/teacher-course/teacher-course.module"); +const lesson_module_1 = require("./modules/lesson/lesson.module"); +const school_module_1 = require("./modules/school/school.module"); +const resource_module_1 = require("./modules/resource/resource.module"); +const growth_module_1 = require("./modules/growth/growth.module"); +const task_module_1 = require("./modules/task/task.module"); +const parent_module_1 = require("./modules/parent/parent.module"); +const notification_module_1 = require("./modules/notification/notification.module"); +const export_module_1 = require("./modules/export/export.module"); +const admin_module_1 = require("./modules/admin/admin.module"); +let AppModule = class AppModule { +}; +exports.AppModule = AppModule; +exports.AppModule = AppModule = __decorate([ + (0, common_1.Module)({ + imports: [ + config_1.ConfigModule.forRoot({ + isGlobal: true, + envFilePath: `.env.${process.env.NODE_ENV || 'development'}`, + }), + throttler_1.ThrottlerModule.forRoot([ + { + ttl: 60000, + limit: 100, + }, + ]), + prisma_module_1.PrismaModule, + auth_module_1.AuthModule, + course_module_1.CourseModule, + tenant_module_1.TenantModule, + common_module_1.CommonModule, + file_upload_module_1.FileUploadModule, + teacher_course_module_1.TeacherCourseModule, + lesson_module_1.LessonModule, + school_module_1.SchoolModule, + resource_module_1.ResourceModule, + growth_module_1.GrowthModule, + task_module_1.TaskModule, + parent_module_1.ParentModule, + notification_module_1.NotificationModule, + export_module_1.ExportModule, + admin_module_1.AdminModule, + ], + }) +], AppModule); +//# sourceMappingURL=app.module.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/app.module.js.map b/reading-platform-backend/dist/src/app.module.js.map new file mode 100644 index 0000000..8f6a9a5 --- /dev/null +++ b/reading-platform-backend/dist/src/app.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"app.module.js","sourceRoot":"","sources":["../../src/app.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2CAA6D;AAC7D,iDAAoD;AACpD,4DAAwD;AACxD,4DAAwD;AACxD,kEAA8D;AAC9D,kEAA8D;AAC9D,kEAA8D;AAC9D,iFAA4E;AAC5E,0FAAqF;AACrF,kEAA8D;AAC9D,kEAA8D;AAC9D,wEAAoE;AACpE,kEAA8D;AAC9D,4DAAwD;AACxD,kEAA8D;AAC9D,oFAAgF;AAChF,kEAA8D;AAC9D,+DAA2D;AAuCpD,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,8BAAS;oBAAT,SAAS;IArCrB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YAEP,qBAAY,CAAC,OAAO,CAAC;gBACnB,QAAQ,EAAE,IAAI;gBACd,WAAW,EAAE,QAAQ,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,aAAa,EAAE;aAC7D,CAAC;YAGF,2BAAe,CAAC,OAAO,CAAC;gBACtB;oBACE,GAAG,EAAE,KAAK;oBACV,KAAK,EAAE,GAAG;iBACX;aACF,CAAC;YAGF,4BAAY;YAGZ,wBAAU;YACV,4BAAY;YACZ,4BAAY;YACZ,4BAAY;YACZ,qCAAgB;YAChB,2CAAmB;YACnB,4BAAY;YACZ,4BAAY;YACZ,gCAAc;YACd,4BAAY;YACZ,wBAAU;YACV,4BAAY;YACZ,wCAAkB;YAClB,4BAAY;YACZ,0BAAW;SACZ;KACF,CAAC;GACW,SAAS,CAAG"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/common/filters/http-exception.filter.d.ts b/reading-platform-backend/dist/src/common/filters/http-exception.filter.d.ts new file mode 100644 index 0000000..a45bf0c --- /dev/null +++ b/reading-platform-backend/dist/src/common/filters/http-exception.filter.d.ts @@ -0,0 +1,5 @@ +import { ExceptionFilter, ArgumentsHost } from '@nestjs/common'; +export declare class HttpExceptionFilter implements ExceptionFilter { + private readonly logger; + catch(exception: unknown, host: ArgumentsHost): void; +} diff --git a/reading-platform-backend/dist/src/common/filters/http-exception.filter.js b/reading-platform-backend/dist/src/common/filters/http-exception.filter.js new file mode 100644 index 0000000..a77d744 --- /dev/null +++ b/reading-platform-backend/dist/src/common/filters/http-exception.filter.js @@ -0,0 +1,70 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var HttpExceptionFilter_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.HttpExceptionFilter = void 0; +const common_1 = require("@nestjs/common"); +const fs = __importStar(require("fs")); +const path = __importStar(require("path")); +let HttpExceptionFilter = HttpExceptionFilter_1 = class HttpExceptionFilter { + constructor() { + this.logger = new common_1.Logger(HttpExceptionFilter_1.name); + } + catch(exception, host) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + const status = exception instanceof common_1.HttpException + ? exception.getStatus() + : common_1.HttpStatus.INTERNAL_SERVER_ERROR; + const message = exception instanceof common_1.HttpException + ? exception.getResponse() + : { message: 'Internal server error', statusCode: 500 }; + const errorLog = { + timestamp: new Date().toISOString(), + path: request.url, + method: request.method, + body: request.body, + status, + exception: exception instanceof Error ? exception.message : String(exception), + stack: exception instanceof Error ? exception.stack : undefined, + }; + const logPath = path.join(process.cwd(), 'error.log'); + fs.appendFileSync(logPath, JSON.stringify(errorLog, null, 2) + '\n'); + this.logger.error('Exception caught', errorLog); + response.status(status).json(message); + } +}; +exports.HttpExceptionFilter = HttpExceptionFilter; +exports.HttpExceptionFilter = HttpExceptionFilter = HttpExceptionFilter_1 = __decorate([ + (0, common_1.Catch)() +], HttpExceptionFilter); +//# sourceMappingURL=http-exception.filter.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/common/filters/http-exception.filter.js.map b/reading-platform-backend/dist/src/common/filters/http-exception.filter.js.map new file mode 100644 index 0000000..cdf4c77 --- /dev/null +++ b/reading-platform-backend/dist/src/common/filters/http-exception.filter.js.map @@ -0,0 +1 @@ +{"version":3,"file":"http-exception.filter.js","sourceRoot":"","sources":["../../../../src/common/filters/http-exception.filter.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAA0G;AAE1G,uCAAyB;AACzB,2CAA6B;AAGtB,IAAM,mBAAmB,2BAAzB,MAAM,mBAAmB;IAAzB;QACY,WAAM,GAAG,IAAI,eAAM,CAAC,qBAAmB,CAAC,IAAI,CAAC,CAAC;IAoCjE,CAAC;IAlCC,KAAK,CAAC,SAAkB,EAAE,IAAmB;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,GAAG,CAAC,WAAW,EAAY,CAAC;QAC7C,MAAM,OAAO,GAAG,GAAG,CAAC,UAAU,EAAW,CAAC;QAE1C,MAAM,MAAM,GACV,SAAS,YAAY,sBAAa;YAChC,CAAC,CAAC,SAAS,CAAC,SAAS,EAAE;YACvB,CAAC,CAAC,mBAAU,CAAC,qBAAqB,CAAC;QAEvC,MAAM,OAAO,GACX,SAAS,YAAY,sBAAa;YAChC,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE;YACzB,CAAC,CAAC,EAAE,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC;QAE5D,MAAM,QAAQ,GAAG;YACf,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,IAAI,EAAE,OAAO,CAAC,GAAG;YACjB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,MAAM;YACN,SAAS,EAAE,SAAS,YAAY,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC;YAC7E,KAAK,EAAE,SAAS,YAAY,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;SAChE,CAAC;QAGF,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,WAAW,CAAC,CAAC;QACtD,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAGrE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;QAEhD,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC;CACF,CAAA;AArCY,kDAAmB;8BAAnB,mBAAmB;IAD/B,IAAA,cAAK,GAAE;GACK,mBAAmB,CAqC/B"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/database/prisma.module.d.ts b/reading-platform-backend/dist/src/database/prisma.module.d.ts new file mode 100644 index 0000000..1cba5ae --- /dev/null +++ b/reading-platform-backend/dist/src/database/prisma.module.d.ts @@ -0,0 +1,2 @@ +export declare class PrismaModule { +} diff --git a/reading-platform-backend/dist/src/database/prisma.module.js b/reading-platform-backend/dist/src/database/prisma.module.js new file mode 100644 index 0000000..d3729fa --- /dev/null +++ b/reading-platform-backend/dist/src/database/prisma.module.js @@ -0,0 +1,22 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PrismaModule = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("./prisma.service"); +let PrismaModule = class PrismaModule { +}; +exports.PrismaModule = PrismaModule; +exports.PrismaModule = PrismaModule = __decorate([ + (0, common_1.Global)(), + (0, common_1.Module)({ + providers: [prisma_service_1.PrismaService], + exports: [prisma_service_1.PrismaService], + }) +], PrismaModule); +//# sourceMappingURL=prisma.module.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/database/prisma.module.js.map b/reading-platform-backend/dist/src/database/prisma.module.js.map new file mode 100644 index 0000000..8cdb7d4 --- /dev/null +++ b/reading-platform-backend/dist/src/database/prisma.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"prisma.module.js","sourceRoot":"","sources":["../../../src/database/prisma.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAgD;AAChD,qDAAiD;AAO1C,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,oCAAY;uBAAZ,YAAY;IALxB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,SAAS,EAAE,CAAC,8BAAa,CAAC;QAC1B,OAAO,EAAE,CAAC,8BAAa,CAAC;KACzB,CAAC;GACW,YAAY,CAAG"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/database/prisma.service.d.ts b/reading-platform-backend/dist/src/database/prisma.service.d.ts new file mode 100644 index 0000000..3ed1f2d --- /dev/null +++ b/reading-platform-backend/dist/src/database/prisma.service.d.ts @@ -0,0 +1,7 @@ +import { OnModuleInit, OnModuleDestroy } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; +export declare class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { + onModuleInit(): Promise; + onModuleDestroy(): Promise; + cleanDatabase(): Promise; +} diff --git a/reading-platform-backend/dist/src/database/prisma.service.js b/reading-platform-backend/dist/src/database/prisma.service.js new file mode 100644 index 0000000..a0029ff --- /dev/null +++ b/reading-platform-backend/dist/src/database/prisma.service.js @@ -0,0 +1,45 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PrismaService = void 0; +const common_1 = require("@nestjs/common"); +const client_1 = require("@prisma/client"); +let PrismaService = class PrismaService extends client_1.PrismaClient { + async onModuleInit() { + await this.$connect(); + console.log('✅ Database connected successfully'); + } + async onModuleDestroy() { + await this.$disconnect(); + console.log('👋 Database disconnected'); + } + async cleanDatabase() { + if (process.env.NODE_ENV === 'production') { + throw new Error('Cannot clean database in production'); + } + await this.studentRecord.deleteMany(); + await this.lessonFeedback.deleteMany(); + await this.lesson.deleteMany(); + await this.tenantCourse.deleteMany(); + await this.courseScriptPage.deleteMany(); + await this.courseScript.deleteMany(); + await this.courseActivity.deleteMany(); + await this.courseResource.deleteMany(); + await this.course.deleteMany(); + await this.student.deleteMany(); + await this.class.deleteMany(); + await this.teacher.deleteMany(); + await this.tenant.deleteMany(); + await this.tag.deleteMany(); + } +}; +exports.PrismaService = PrismaService; +exports.PrismaService = PrismaService = __decorate([ + (0, common_1.Injectable)() +], PrismaService); +//# sourceMappingURL=prisma.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/database/prisma.service.js.map b/reading-platform-backend/dist/src/database/prisma.service.js.map new file mode 100644 index 0000000..5ee52d0 --- /dev/null +++ b/reading-platform-backend/dist/src/database/prisma.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"prisma.service.js","sourceRoot":"","sources":["../../../src/database/prisma.service.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA2E;AAC3E,2CAA8C;AAGvC,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,qBAAY;IAC7C,KAAK,CAAC,YAAY;QAChB,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;IAC1C,CAAC;IAGD,KAAK,CAAC,aAAa;QACjB,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACzD,CAAC;QAGD,MAAM,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE,CAAC;QACtC,MAAM,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE,CAAC;QACvC,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QAC/B,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,CAAC;QACrC,MAAM,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE,CAAC;QACzC,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,CAAC;QACrC,MAAM,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE,CAAC;QACvC,MAAM,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE,CAAC;QACvC,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QAC/B,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;QAChC,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QAC9B,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;QAChC,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QAC/B,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC;IAC9B,CAAC;CACF,CAAA;AAjCY,sCAAa;wBAAb,aAAa;IADzB,IAAA,mBAAU,GAAE;GACA,aAAa,CAiCzB"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/main.d.ts b/reading-platform-backend/dist/src/main.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/reading-platform-backend/dist/src/main.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/reading-platform-backend/dist/src/main.js b/reading-platform-backend/dist/src/main.js new file mode 100644 index 0000000..ba0e58a --- /dev/null +++ b/reading-platform-backend/dist/src/main.js @@ -0,0 +1,51 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const core_1 = require("@nestjs/core"); +const common_1 = require("@nestjs/common"); +const config_1 = require("@nestjs/config"); +const path_1 = require("path"); +const app_module_1 = require("./app.module"); +const http_exception_filter_1 = require("./common/filters/http-exception.filter"); +async function bootstrap() { + const app = await core_1.NestFactory.create(app_module_1.AppModule, { + logger: ['error', 'warn', 'log', 'debug', 'verbose'], + }); + app.useBodyParser('json', { limit: '1500mb' }); + app.useBodyParser('urlencoded', { limit: '1500mb', extended: true }); + const configService = app.get(config_1.ConfigService); + app.useGlobalPipes(new common_1.ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + transform: true, + transformOptions: { + enableImplicitConversion: true, + }, + })); + app.useGlobalFilters(new http_exception_filter_1.HttpExceptionFilter()); + app.enableCors({ + origin: configService.get('FRONTEND_URL') || 'http://localhost:5173', + credentials: true, + methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', + allowedHeaders: 'Content-Type, Accept, Authorization', + }); + const uploadsPath = (0, path_1.join)(__dirname, '..', '..', 'uploads'); + app.useStaticAssets(uploadsPath, { + prefix: '/uploads/', + }); + app.setGlobalPrefix('api/v1'); + const port = configService.get('PORT') || 3000; + await app.listen(port); + console.log(` + ╔═════════════════════════════════════════════════════╗ + ║ ║ + ║ 🚀 幼儿阅读教学服务平台后端启动成功 ║ + ║ ║ + ║ 📍 Local: http://localhost:${port} ║ + ║ 📍 API: http://localhost:${port}/api/v1 ║ + ║ 📍 Prisma: npx prisma studio ║ + ║ ║ + ╚═════════════════════════════════════════════════════╝ + `); +} +bootstrap(); +//# sourceMappingURL=main.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/main.js.map b/reading-platform-backend/dist/src/main.js.map new file mode 100644 index 0000000..b9c22e7 --- /dev/null +++ b/reading-platform-backend/dist/src/main.js.map @@ -0,0 +1 @@ +{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":";;AAAA,uCAA2C;AAC3C,2CAAwD;AACxD,2CAA+C;AAE/C,+BAA4B;AAC5B,6CAAyC;AAEzC,kFAA6E;AAE7E,KAAK,UAAU,SAAS;IACtB,MAAM,GAAG,GAAG,MAAM,kBAAW,CAAC,MAAM,CAAyB,sBAAS,EAAE;QACtE,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,CAAC;KACrD,CAAC,CAAC;IAIH,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC/C,GAAG,CAAC,aAAa,CAAC,YAAY,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAErE,MAAM,aAAa,GAAG,GAAG,CAAC,GAAG,CAAC,sBAAa,CAAC,CAAC;IAG7C,GAAG,CAAC,cAAc,CAChB,IAAI,uBAAc,CAAC;QACjB,SAAS,EAAE,IAAI;QACf,oBAAoB,EAAE,IAAI;QAC1B,SAAS,EAAE,IAAI;QACf,gBAAgB,EAAE;YAChB,wBAAwB,EAAE,IAAI;SAC/B;KACF,CAAC,CACH,CAAC;IAGF,GAAG,CAAC,gBAAgB,CAAC,IAAI,2CAAmB,EAAE,CAAC,CAAC;IAMhD,GAAG,CAAC,UAAU,CAAC;QACb,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,uBAAuB;QACpE,WAAW,EAAE,IAAI;QACjB,OAAO,EAAE,wCAAwC;QACjD,cAAc,EAAE,qCAAqC;KACtD,CAAC,CAAC;IAIH,MAAM,WAAW,GAAG,IAAA,WAAI,EAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAC3D,GAAG,CAAC,eAAe,CAAC,WAAW,EAAE;QAC/B,MAAM,EAAE,WAAW;KACpB,CAAC,CAAC;IAGH,GAAG,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;IAE9B,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAS,MAAM,CAAC,IAAI,IAAI,CAAC;IACvD,MAAM,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAEvB,OAAO,CAAC,GAAG,CAAC;;;;;yCAK2B,IAAI;yCACJ,IAAI;;;;GAI1C,CAAC,CAAC;AACL,CAAC;AAED,SAAS,EAAE,CAAC"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/admin/admin-settings.controller.d.ts b/reading-platform-backend/dist/src/modules/admin/admin-settings.controller.d.ts new file mode 100644 index 0000000..f4d9180 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/admin/admin-settings.controller.d.ts @@ -0,0 +1,54 @@ +import { AdminSettingsService } from './admin-settings.service'; +export declare class AdminSettingsController { + private readonly settingsService; + constructor(settingsService: AdminSettingsService); + getAllSettings(): Promise<{ + [x: string]: any; + }>; + updateSettings(data: Record): Promise<{ + [x: string]: any; + }>; + getBasicSettings(): Promise<{ + systemName: any; + systemDesc: any; + contactPhone: any; + contactEmail: any; + systemLogo: any; + }>; + updateBasicSettings(data: Record): Promise<{ + [x: string]: any; + }>; + getSecuritySettings(): Promise<{ + passwordStrength: any; + maxLoginAttempts: any; + tokenExpire: any; + forceHttps: any; + }>; + updateSecuritySettings(data: Record): Promise<{ + [x: string]: any; + }>; + getNotificationSettings(): Promise<{ + emailEnabled: any; + smtpHost: any; + smtpPort: any; + fromEmail: any; + smsEnabled: any; + }>; + updateNotificationSettings(data: Record): Promise<{ + [x: string]: any; + }>; + getStorageSettings(): Promise<{ + type: any; + maxFileSize: any; + allowedTypes: any; + }>; + updateStorageSettings(data: Record): Promise<{ + [x: string]: any; + }>; + getTenantDefaults(): Promise<{ + defaultTeacherQuota: any; + defaultStudentQuota: any; + enableAutoExpire: any; + notifyBeforeDays: any; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/admin/admin-settings.controller.js b/reading-platform-backend/dist/src/modules/admin/admin-settings.controller.js new file mode 100644 index 0000000..b68f017 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/admin/admin-settings.controller.js @@ -0,0 +1,137 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AdminSettingsController = void 0; +const common_1 = require("@nestjs/common"); +const admin_settings_service_1 = require("./admin-settings.service"); +const jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +const roles_guard_1 = require("../common/guards/roles.guard"); +const roles_decorator_1 = require("../common/decorators/roles.decorator"); +let AdminSettingsController = class AdminSettingsController { + constructor(settingsService) { + this.settingsService = settingsService; + } + async getAllSettings() { + return this.settingsService.getSettings(); + } + async updateSettings(data) { + return this.settingsService.updateSettings(data); + } + async getBasicSettings() { + return this.settingsService.getBasicSettings(); + } + async updateBasicSettings(data) { + return this.settingsService.updateSettings(data); + } + async getSecuritySettings() { + return this.settingsService.getSecuritySettings(); + } + async updateSecuritySettings(data) { + return this.settingsService.updateSettings(data); + } + async getNotificationSettings() { + return this.settingsService.getNotificationSettings(); + } + async updateNotificationSettings(data) { + return this.settingsService.updateSettings(data); + } + async getStorageSettings() { + return this.settingsService.getStorageSettings(); + } + async updateStorageSettings(data) { + return this.settingsService.updateSettings(data); + } + async getTenantDefaults() { + return this.settingsService.getTenantDefaults(); + } +}; +exports.AdminSettingsController = AdminSettingsController; +__decorate([ + (0, common_1.Get)(), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", Promise) +], AdminSettingsController.prototype, "getAllSettings", null); +__decorate([ + (0, common_1.Put)(), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], AdminSettingsController.prototype, "updateSettings", null); +__decorate([ + (0, common_1.Get)('basic'), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", Promise) +], AdminSettingsController.prototype, "getBasicSettings", null); +__decorate([ + (0, common_1.Put)('basic'), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], AdminSettingsController.prototype, "updateBasicSettings", null); +__decorate([ + (0, common_1.Get)('security'), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", Promise) +], AdminSettingsController.prototype, "getSecuritySettings", null); +__decorate([ + (0, common_1.Put)('security'), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], AdminSettingsController.prototype, "updateSecuritySettings", null); +__decorate([ + (0, common_1.Get)('notification'), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", Promise) +], AdminSettingsController.prototype, "getNotificationSettings", null); +__decorate([ + (0, common_1.Put)('notification'), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], AdminSettingsController.prototype, "updateNotificationSettings", null); +__decorate([ + (0, common_1.Get)('storage'), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", Promise) +], AdminSettingsController.prototype, "getStorageSettings", null); +__decorate([ + (0, common_1.Put)('storage'), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], AdminSettingsController.prototype, "updateStorageSettings", null); +__decorate([ + (0, common_1.Get)('tenant-defaults'), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", Promise) +], AdminSettingsController.prototype, "getTenantDefaults", null); +exports.AdminSettingsController = AdminSettingsController = __decorate([ + (0, common_1.Controller)('admin/settings'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('admin'), + __metadata("design:paramtypes", [admin_settings_service_1.AdminSettingsService]) +], AdminSettingsController); +//# sourceMappingURL=admin-settings.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/admin/admin-settings.controller.js.map b/reading-platform-backend/dist/src/modules/admin/admin-settings.controller.js.map new file mode 100644 index 0000000..b61837d --- /dev/null +++ b/reading-platform-backend/dist/src/modules/admin/admin-settings.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"admin-settings.controller.js","sourceRoot":"","sources":["../../../../src/modules/admin/admin-settings.controller.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/admin/admin-settings.service.d.ts b/reading-platform-backend/dist/src/modules/admin/admin-settings.service.d.ts new file mode 100644 index 0000000..4330f10 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/admin/admin-settings.service.d.ts @@ -0,0 +1,44 @@ +import { PrismaService } from '../../database/prisma.service'; +export declare class AdminSettingsService { + private prisma; + private settings; + constructor(prisma: PrismaService); + getSettings(): Promise<{ + [x: string]: any; + }>; + getSetting(key: string): Promise; + updateSettings(data: Record): Promise<{ + [x: string]: any; + }>; + getBasicSettings(): Promise<{ + systemName: any; + systemDesc: any; + contactPhone: any; + contactEmail: any; + systemLogo: any; + }>; + getSecuritySettings(): Promise<{ + passwordStrength: any; + maxLoginAttempts: any; + tokenExpire: any; + forceHttps: any; + }>; + getNotificationSettings(): Promise<{ + emailEnabled: any; + smtpHost: any; + smtpPort: any; + fromEmail: any; + smsEnabled: any; + }>; + getStorageSettings(): Promise<{ + type: any; + maxFileSize: any; + allowedTypes: any; + }>; + getTenantDefaults(): Promise<{ + defaultTeacherQuota: any; + defaultStudentQuota: any; + enableAutoExpire: any; + notifyBeforeDays: any; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/admin/admin-settings.service.js b/reading-platform-backend/dist/src/modules/admin/admin-settings.service.js new file mode 100644 index 0000000..829716d --- /dev/null +++ b/reading-platform-backend/dist/src/modules/admin/admin-settings.service.js @@ -0,0 +1,105 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AdminSettingsService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../../database/prisma.service"); +let AdminSettingsService = class AdminSettingsService { + constructor(prisma) { + this.prisma = prisma; + this.settings = { + systemName: '幼儿阅读教学服务平台', + systemDesc: '', + contactPhone: '', + contactEmail: '', + systemLogo: '', + passwordStrength: 'medium', + maxLoginAttempts: 5, + tokenExpire: '7d', + forceHttps: false, + emailEnabled: true, + smtpHost: '', + smtpPort: 465, + smtpUser: '', + smtpPassword: '', + fromEmail: '', + smsEnabled: false, + storageType: 'local', + maxFileSize: 100, + allowedTypes: '.jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.ppt,.pptx', + defaultTeacherQuota: 20, + defaultStudentQuota: 200, + enableAutoExpire: true, + notifyBeforeDays: 30, + }; + } + async getSettings() { + return { ...this.settings }; + } + async getSetting(key) { + return this.settings[key]; + } + async updateSettings(data) { + for (const key of Object.keys(data)) { + if (key in this.settings) { + this.settings[key] = data[key]; + } + } + return { ...this.settings }; + } + async getBasicSettings() { + return { + systemName: this.settings.systemName, + systemDesc: this.settings.systemDesc, + contactPhone: this.settings.contactPhone, + contactEmail: this.settings.contactEmail, + systemLogo: this.settings.systemLogo, + }; + } + async getSecuritySettings() { + return { + passwordStrength: this.settings.passwordStrength, + maxLoginAttempts: this.settings.maxLoginAttempts, + tokenExpire: this.settings.tokenExpire, + forceHttps: this.settings.forceHttps, + }; + } + async getNotificationSettings() { + return { + emailEnabled: this.settings.emailEnabled, + smtpHost: this.settings.smtpHost, + smtpPort: this.settings.smtpPort, + fromEmail: this.settings.fromEmail, + smsEnabled: this.settings.smsEnabled, + }; + } + async getStorageSettings() { + return { + type: this.settings.storageType, + maxFileSize: this.settings.maxFileSize, + allowedTypes: this.settings.allowedTypes, + }; + } + async getTenantDefaults() { + return { + defaultTeacherQuota: this.settings.defaultTeacherQuota, + defaultStudentQuota: this.settings.defaultStudentQuota, + enableAutoExpire: this.settings.enableAutoExpire, + notifyBeforeDays: this.settings.notifyBeforeDays, + }; + } +}; +exports.AdminSettingsService = AdminSettingsService; +exports.AdminSettingsService = AdminSettingsService = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], AdminSettingsService); +//# sourceMappingURL=admin-settings.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/admin/admin-settings.service.js.map b/reading-platform-backend/dist/src/modules/admin/admin-settings.service.js.map new file mode 100644 index 0000000..832ff55 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/admin/admin-settings.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"admin-settings.service.js","sourceRoot":"","sources":["../../../../src/modules/admin/admin-settings.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/admin/admin-stats.controller.d.ts b/reading-platform-backend/dist/src/modules/admin/admin-stats.controller.d.ts new file mode 100644 index 0000000..2b0acfe --- /dev/null +++ b/reading-platform-backend/dist/src/modules/admin/admin-stats.controller.d.ts @@ -0,0 +1,41 @@ +import { AdminStatsService } from './admin-stats.service'; +export declare class AdminStatsController { + private readonly statsService; + constructor(statsService: AdminStatsService); + getStats(): Promise<{ + tenantCount: number; + activeTenantCount: number; + courseCount: number; + publishedCourseCount: number; + studentCount: number; + teacherCount: number; + lessonCount: number; + monthlyLessons: number; + }>; + getTrendData(): Promise<{ + month: string; + tenantCount: number; + lessonCount: number; + studentCount: number; + }[]>; + getActiveTenants(limit?: string): Promise<{ + id: number; + name: string; + lessonCount: number; + teacherCount: number; + studentCount: number; + }[]>; + getPopularCourses(limit?: string): Promise<{ + id: number; + name: string; + usageCount: number; + teacherCount: number; + }[]>; + getRecentActivities(limit?: string): Promise<{ + time: string; + id: number; + type: string; + title: string; + description?: string; + }[]>; +} diff --git a/reading-platform-backend/dist/src/modules/admin/admin-stats.controller.js b/reading-platform-backend/dist/src/modules/admin/admin-stats.controller.js new file mode 100644 index 0000000..8de7ba9 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/admin/admin-stats.controller.js @@ -0,0 +1,84 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AdminStatsController = void 0; +const common_1 = require("@nestjs/common"); +const admin_stats_service_1 = require("./admin-stats.service"); +const jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +const roles_guard_1 = require("../common/guards/roles.guard"); +const roles_decorator_1 = require("../common/decorators/roles.decorator"); +let AdminStatsController = class AdminStatsController { + constructor(statsService) { + this.statsService = statsService; + } + async getStats() { + return this.statsService.getStats(); + } + async getTrendData() { + return this.statsService.getTrendData(); + } + async getActiveTenants(limit) { + const limitNum = limit ? parseInt(limit, 10) : 5; + return this.statsService.getActiveTenants(limitNum); + } + async getPopularCourses(limit) { + const limitNum = limit ? parseInt(limit, 10) : 5; + return this.statsService.getPopularCourses(limitNum); + } + async getRecentActivities(limit) { + const limitNum = limit ? parseInt(limit, 10) : 10; + return this.statsService.getRecentActivities(limitNum); + } +}; +exports.AdminStatsController = AdminStatsController; +__decorate([ + (0, common_1.Get)(), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", Promise) +], AdminStatsController.prototype, "getStats", null); +__decorate([ + (0, common_1.Get)('trend'), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", Promise) +], AdminStatsController.prototype, "getTrendData", null); +__decorate([ + (0, common_1.Get)('tenants/active'), + __param(0, (0, common_1.Query)('limit')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", Promise) +], AdminStatsController.prototype, "getActiveTenants", null); +__decorate([ + (0, common_1.Get)('courses/popular'), + __param(0, (0, common_1.Query)('limit')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", Promise) +], AdminStatsController.prototype, "getPopularCourses", null); +__decorate([ + (0, common_1.Get)('activities'), + __param(0, (0, common_1.Query)('limit')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", Promise) +], AdminStatsController.prototype, "getRecentActivities", null); +exports.AdminStatsController = AdminStatsController = __decorate([ + (0, common_1.Controller)('admin/stats'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('admin'), + __metadata("design:paramtypes", [admin_stats_service_1.AdminStatsService]) +], AdminStatsController); +//# sourceMappingURL=admin-stats.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/admin/admin-stats.controller.js.map b/reading-platform-backend/dist/src/modules/admin/admin-stats.controller.js.map new file mode 100644 index 0000000..11c5f5d --- /dev/null +++ b/reading-platform-backend/dist/src/modules/admin/admin-stats.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"admin-stats.controller.js","sourceRoot":"","sources":["../../../../src/modules/admin/admin-stats.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAmE;AACnE,+DAA0D;AAC1D,oEAA+D;AAC/D,8DAA0D;AAC1D,0EAA6D;AAKtD,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IAC/B,YAA6B,YAA+B;QAA/B,iBAAY,GAAZ,YAAY,CAAmB;IAAG,CAAC;IAG1D,AAAN,KAAK,CAAC,QAAQ;QACZ,OAAO,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC;IACtC,CAAC;IAGK,AAAN,KAAK,CAAC,YAAY;QAChB,OAAO,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE,CAAC;IAC1C,CAAC;IAGK,AAAN,KAAK,CAAC,gBAAgB,CAAiB,KAAc;QACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjD,OAAO,IAAI,CAAC,YAAY,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACtD,CAAC;IAGK,AAAN,KAAK,CAAC,iBAAiB,CAAiB,KAAc;QACpD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjD,OAAO,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IACvD,CAAC;IAGK,AAAN,KAAK,CAAC,mBAAmB,CAAiB,KAAc;QACtD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAClD,OAAO,IAAI,CAAC,YAAY,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IACzD,CAAC;CACF,CAAA;AA9BY,oDAAoB;AAIzB;IADL,IAAA,YAAG,GAAE;;;;oDAGL;AAGK;IADL,IAAA,YAAG,EAAC,OAAO,CAAC;;;;wDAGZ;AAGK;IADL,IAAA,YAAG,EAAC,gBAAgB,CAAC;IACE,WAAA,IAAA,cAAK,EAAC,OAAO,CAAC,CAAA;;;;4DAGrC;AAGK;IADL,IAAA,YAAG,EAAC,iBAAiB,CAAC;IACE,WAAA,IAAA,cAAK,EAAC,OAAO,CAAC,CAAA;;;;6DAGtC;AAGK;IADL,IAAA,YAAG,EAAC,YAAY,CAAC;IACS,WAAA,IAAA,cAAK,EAAC,OAAO,CAAC,CAAA;;;;+DAGxC;+BA7BU,oBAAoB;IAHhC,IAAA,mBAAU,EAAC,aAAa,CAAC;IACzB,IAAA,kBAAS,EAAC,6BAAY,EAAE,wBAAU,CAAC;IACnC,IAAA,uBAAK,EAAC,OAAO,CAAC;qCAE8B,uCAAiB;GADjD,oBAAoB,CA8BhC"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/admin/admin-stats.service.d.ts b/reading-platform-backend/dist/src/modules/admin/admin-stats.service.d.ts new file mode 100644 index 0000000..4b4114d --- /dev/null +++ b/reading-platform-backend/dist/src/modules/admin/admin-stats.service.d.ts @@ -0,0 +1,42 @@ +import { PrismaService } from '../../database/prisma.service'; +export declare class AdminStatsService { + private prisma; + constructor(prisma: PrismaService); + getStats(): Promise<{ + tenantCount: number; + activeTenantCount: number; + courseCount: number; + publishedCourseCount: number; + studentCount: number; + teacherCount: number; + lessonCount: number; + monthlyLessons: number; + }>; + private getThisMonthLessonCount; + getTrendData(): Promise<{ + month: string; + tenantCount: number; + lessonCount: number; + studentCount: number; + }[]>; + getActiveTenants(limit?: number): Promise<{ + id: number; + name: string; + lessonCount: number; + teacherCount: number; + studentCount: number; + }[]>; + getPopularCourses(limit?: number): Promise<{ + id: number; + name: string; + usageCount: number; + teacherCount: number; + }[]>; + getRecentActivities(limit?: number): Promise<{ + time: string; + id: number; + type: string; + title: string; + description?: string; + }[]>; +} diff --git a/reading-platform-backend/dist/src/modules/admin/admin-stats.service.js b/reading-platform-backend/dist/src/modules/admin/admin-stats.service.js new file mode 100644 index 0000000..ad8b556 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/admin/admin-stats.service.js @@ -0,0 +1,212 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AdminStatsService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../../database/prisma.service"); +let AdminStatsService = class AdminStatsService { + constructor(prisma) { + this.prisma = prisma; + } + async getStats() { + const [tenantCount, activeTenantCount, courseCount, publishedCourseCount, studentCount, teacherCount, lessonCount, monthlyLessons,] = await Promise.all([ + this.prisma.tenant.count(), + this.prisma.tenant.count({ where: { status: 'ACTIVE' } }), + this.prisma.course.count(), + this.prisma.course.count({ where: { status: 'PUBLISHED' } }), + this.prisma.student.count(), + this.prisma.teacher.count(), + this.prisma.lesson.count(), + this.getThisMonthLessonCount(), + ]); + return { + tenantCount, + activeTenantCount, + courseCount, + publishedCourseCount, + studentCount, + teacherCount, + lessonCount, + monthlyLessons, + }; + } + async getThisMonthLessonCount() { + const now = new Date(); + const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); + return this.prisma.lesson.count({ + where: { + createdAt: { + gte: firstDayOfMonth, + }, + }, + }); + } + async getTrendData() { + const months = []; + for (let i = 5; i >= 0; i--) { + const date = new Date(); + date.setMonth(date.getMonth() - i); + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const monthStr = `${year}-${String(month).padStart(2, '0')}`; + const firstDay = new Date(year, month - 1, 1); + const lastDay = new Date(year, month, 0, 23, 59, 59); + const [tenantCount, lessonCount, studentCount] = await Promise.all([ + this.prisma.tenant.count({ + where: { + createdAt: { + lte: lastDay, + }, + }, + }), + this.prisma.lesson.count({ + where: { + createdAt: { + gte: firstDay, + lte: lastDay, + }, + }, + }), + this.prisma.student.count({ + where: { + createdAt: { + lte: lastDay, + }, + }, + }), + ]); + months.push({ + month: monthStr, + tenantCount, + lessonCount, + studentCount, + }); + } + return months; + } + async getActiveTenants(limit = 5) { + const thirtyDaysAgo = new Date(); + thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); + const tenants = await this.prisma.tenant.findMany({ + select: { + id: true, + name: true, + teacherCount: true, + studentCount: true, + _count: { + select: { + lessons: { + where: { + createdAt: { + gte: thirtyDaysAgo, + }, + }, + }, + }, + }, + }, + orderBy: { + lessons: { + _count: 'desc', + }, + }, + take: limit, + }); + return tenants.map((t) => ({ + id: t.id, + name: t.name, + lessonCount: t._count.lessons, + teacherCount: t.teacherCount, + studentCount: t.studentCount, + })); + } + async getPopularCourses(limit = 5) { + const courses = await this.prisma.course.findMany({ + select: { + id: true, + name: true, + usageCount: true, + teacherCount: true, + }, + where: { + status: 'PUBLISHED', + }, + orderBy: { + usageCount: 'desc', + }, + take: limit, + }); + return courses; + } + async getRecentActivities(limit = 10) { + const activities = []; + const recentLessons = await this.prisma.lesson.findMany({ + select: { + id: true, + createdAt: true, + tenant: { + select: { + name: true, + }, + }, + course: { + select: { + name: true, + }, + }, + }, + orderBy: { + createdAt: 'desc', + }, + take: limit, + }); + for (const lesson of recentLessons) { + activities.push({ + id: lesson.id, + type: 'lesson', + title: `${lesson.tenant.name} 完成了课程《${lesson.course.name}》`, + time: lesson.createdAt, + }); + } + const recentTenants = await this.prisma.tenant.findMany({ + select: { + id: true, + name: true, + createdAt: true, + }, + orderBy: { + createdAt: 'desc', + }, + take: limit, + }); + for (const tenant of recentTenants) { + activities.push({ + id: tenant.id + 10000, + type: 'tenant', + title: `新租户注册: ${tenant.name}`, + time: tenant.createdAt, + }); + } + return activities + .sort((a, b) => b.time.getTime() - a.time.getTime()) + .slice(0, limit) + .map((a) => ({ + ...a, + time: a.time.toISOString(), + })); + } +}; +exports.AdminStatsService = AdminStatsService; +exports.AdminStatsService = AdminStatsService = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], AdminStatsService); +//# sourceMappingURL=admin-stats.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/admin/admin-stats.service.js.map b/reading-platform-backend/dist/src/modules/admin/admin-stats.service.js.map new file mode 100644 index 0000000..e55ec15 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/admin/admin-stats.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"admin-stats.service.js","sourceRoot":"","sources":["../../../../src/modules/admin/admin-stats.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/admin/admin.module.d.ts b/reading-platform-backend/dist/src/modules/admin/admin.module.d.ts new file mode 100644 index 0000000..41d0207 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/admin/admin.module.d.ts @@ -0,0 +1,2 @@ +export declare class AdminModule { +} diff --git a/reading-platform-backend/dist/src/modules/admin/admin.module.js b/reading-platform-backend/dist/src/modules/admin/admin.module.js new file mode 100644 index 0000000..8597542 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/admin/admin.module.js @@ -0,0 +1,27 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AdminModule = void 0; +const common_1 = require("@nestjs/common"); +const admin_settings_controller_1 = require("./admin-settings.controller"); +const admin_settings_service_1 = require("./admin-settings.service"); +const admin_stats_controller_1 = require("./admin-stats.controller"); +const admin_stats_service_1 = require("./admin-stats.service"); +const prisma_module_1 = require("../../database/prisma.module"); +let AdminModule = class AdminModule { +}; +exports.AdminModule = AdminModule; +exports.AdminModule = AdminModule = __decorate([ + (0, common_1.Module)({ + imports: [prisma_module_1.PrismaModule], + controllers: [admin_settings_controller_1.AdminSettingsController, admin_stats_controller_1.AdminStatsController], + providers: [admin_settings_service_1.AdminSettingsService, admin_stats_service_1.AdminStatsService], + exports: [admin_settings_service_1.AdminSettingsService, admin_stats_service_1.AdminStatsService], + }) +], AdminModule); +//# sourceMappingURL=admin.module.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/admin/admin.module.js.map b/reading-platform-backend/dist/src/modules/admin/admin.module.js.map new file mode 100644 index 0000000..4a2be4b --- /dev/null +++ b/reading-platform-backend/dist/src/modules/admin/admin.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"admin.module.js","sourceRoot":"","sources":["../../../../src/modules/admin/admin.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2EAAsE;AACtE,qEAAgE;AAChE,qEAAgE;AAChE,+DAA0D;AAC1D,gEAA4D;AAQrD,IAAM,WAAW,GAAjB,MAAM,WAAW;CAAG,CAAA;AAAd,kCAAW;sBAAX,WAAW;IANvB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,4BAAY,CAAC;QACvB,WAAW,EAAE,CAAC,mDAAuB,EAAE,6CAAoB,CAAC;QAC5D,SAAS,EAAE,CAAC,6CAAoB,EAAE,uCAAiB,CAAC;QACpD,OAAO,EAAE,CAAC,6CAAoB,EAAE,uCAAiB,CAAC;KACnD,CAAC;GACW,WAAW,CAAG"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/auth/auth.controller.d.ts b/reading-platform-backend/dist/src/modules/auth/auth.controller.d.ts new file mode 100644 index 0000000..3518ffb --- /dev/null +++ b/reading-platform-backend/dist/src/modules/auth/auth.controller.d.ts @@ -0,0 +1,60 @@ +import { AuthService } from './auth.service'; +import { LoginDto } from './dto/login.dto'; +export declare class AuthController { + private authService; + constructor(authService: AuthService); + login(loginDto: LoginDto): Promise<{ + token: string; + user: { + id: any; + name: any; + role: any; + tenantId: any; + tenantName: any; + }; + }>; + logout(): Promise<{ + message: string; + }>; + getProfile(req: any): Promise<{ + id: number; + name: string; + role: string; + tenantId?: undefined; + tenantName?: undefined; + email?: undefined; + phone?: undefined; + children?: undefined; + } | { + id: number; + name: string; + role: string; + tenantId: number; + tenantName: string; + email?: undefined; + phone?: undefined; + children?: undefined; + } | { + id: number; + name: string; + role: string; + tenantId: number; + tenantName: string; + email: string; + phone: string; + children?: undefined; + } | { + id: number; + name: string; + role: string; + tenantId: number; + tenantName: string; + email: string; + phone: string; + children: { + id: number; + name: string; + relationship: string; + }[]; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/auth/auth.controller.js b/reading-platform-backend/dist/src/modules/auth/auth.controller.js new file mode 100644 index 0000000..927058f --- /dev/null +++ b/reading-platform-backend/dist/src/modules/auth/auth.controller.js @@ -0,0 +1,61 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AuthController = void 0; +const common_1 = require("@nestjs/common"); +const auth_service_1 = require("./auth.service"); +const jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +const login_dto_1 = require("./dto/login.dto"); +let AuthController = class AuthController { + constructor(authService) { + this.authService = authService; + } + async login(loginDto) { + return this.authService.login(loginDto); + } + async logout() { + return { message: 'Logged out successfully' }; + } + async getProfile(req) { + return this.authService.getProfile(req.user.userId, req.user.role); + } +}; +exports.AuthController = AuthController; +__decorate([ + (0, common_1.Post)('login'), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [login_dto_1.LoginDto]), + __metadata("design:returntype", Promise) +], AuthController.prototype, "login", null); +__decorate([ + (0, common_1.Post)('logout'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", Promise) +], AuthController.prototype, "logout", null); +__decorate([ + (0, common_1.Get)('profile'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], AuthController.prototype, "getProfile", null); +exports.AuthController = AuthController = __decorate([ + (0, common_1.Controller)('auth'), + __metadata("design:paramtypes", [auth_service_1.AuthService]) +], AuthController); +//# sourceMappingURL=auth.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/auth/auth.controller.js.map b/reading-platform-backend/dist/src/modules/auth/auth.controller.js.map new file mode 100644 index 0000000..6c2576c --- /dev/null +++ b/reading-platform-backend/dist/src/modules/auth/auth.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"auth.controller.js","sourceRoot":"","sources":["../../../../src/modules/auth/auth.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAiF;AACjF,iDAA6C;AAC7C,oEAA+D;AAC/D,+CAA2C;AAGpC,IAAM,cAAc,GAApB,MAAM,cAAc;IACzB,YAAoB,WAAwB;QAAxB,gBAAW,GAAX,WAAW,CAAa;IAAG,CAAC;IAG1C,AAAN,KAAK,CAAC,KAAK,CAAS,QAAkB;QACpC,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC1C,CAAC;IAIK,AAAN,KAAK,CAAC,MAAM;QAEV,OAAO,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC;IAChD,CAAC;IAIK,AAAN,KAAK,CAAC,UAAU,CAAY,GAAG;QAC7B,OAAO,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrE,CAAC;CACF,CAAA;AApBY,wCAAc;AAInB;IADL,IAAA,aAAI,EAAC,OAAO,CAAC;IACD,WAAA,IAAA,aAAI,GAAE,CAAA;;qCAAW,oBAAQ;;2CAErC;AAIK;IAFL,IAAA,aAAI,EAAC,QAAQ,CAAC;IACd,IAAA,kBAAS,EAAC,6BAAY,CAAC;;;;4CAIvB;AAIK;IAFL,IAAA,YAAG,EAAC,SAAS,CAAC;IACd,IAAA,kBAAS,EAAC,6BAAY,CAAC;IACN,WAAA,IAAA,gBAAO,GAAE,CAAA;;;;gDAE1B;yBAnBU,cAAc;IAD1B,IAAA,mBAAU,EAAC,MAAM,CAAC;qCAEgB,0BAAW;GADjC,cAAc,CAoB1B"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/auth/auth.module.d.ts b/reading-platform-backend/dist/src/modules/auth/auth.module.d.ts new file mode 100644 index 0000000..3f7dba9 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/auth/auth.module.d.ts @@ -0,0 +1,2 @@ +export declare class AuthModule { +} diff --git a/reading-platform-backend/dist/src/modules/auth/auth.module.js b/reading-platform-backend/dist/src/modules/auth/auth.module.js new file mode 100644 index 0000000..1f3bdae --- /dev/null +++ b/reading-platform-backend/dist/src/modules/auth/auth.module.js @@ -0,0 +1,42 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AuthModule = void 0; +const common_1 = require("@nestjs/common"); +const jwt_1 = require("@nestjs/jwt"); +const passport_1 = require("@nestjs/passport"); +const config_1 = require("@nestjs/config"); +const auth_service_1 = require("./auth.service"); +const auth_controller_1 = require("./auth.controller"); +const jwt_strategy_1 = require("./strategies/jwt.strategy"); +const prisma_module_1 = require("../../database/prisma.module"); +let AuthModule = class AuthModule { +}; +exports.AuthModule = AuthModule; +exports.AuthModule = AuthModule = __decorate([ + (0, common_1.Module)({ + imports: [ + passport_1.PassportModule, + jwt_1.JwtModule.registerAsync({ + imports: [config_1.ConfigModule], + useFactory: async (configService) => ({ + secret: configService.get('JWT_SECRET') || 'your-secret-key', + signOptions: { + expiresIn: configService.get('JWT_EXPIRES_IN') || '7d', + }, + }), + inject: [config_1.ConfigService], + }), + prisma_module_1.PrismaModule, + ], + controllers: [auth_controller_1.AuthController], + providers: [auth_service_1.AuthService, jwt_strategy_1.JwtStrategy], + exports: [auth_service_1.AuthService], + }) +], AuthModule); +//# sourceMappingURL=auth.module.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/auth/auth.module.js.map b/reading-platform-backend/dist/src/modules/auth/auth.module.js.map new file mode 100644 index 0000000..b2f1d18 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/auth/auth.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"auth.module.js","sourceRoot":"","sources":["../../../../src/modules/auth/auth.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,qCAAwC;AACxC,+CAAkD;AAClD,2CAA6D;AAC7D,iDAA6C;AAC7C,uDAAmD;AACnD,4DAAwD;AACxD,gEAA4D;AAqBrD,IAAM,UAAU,GAAhB,MAAM,UAAU;CAAG,CAAA;AAAb,gCAAU;qBAAV,UAAU;IAnBtB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,yBAAc;YACd,eAAS,CAAC,aAAa,CAAC;gBACtB,OAAO,EAAE,CAAC,qBAAY,CAAC;gBACvB,UAAU,EAAE,KAAK,EAAE,aAA4B,EAAE,EAAE,CAAC,CAAC;oBACnD,MAAM,EAAE,aAAa,CAAC,GAAG,CAAS,YAAY,CAAC,IAAI,iBAAiB;oBACpE,WAAW,EAAE;wBACX,SAAS,EAAE,aAAa,CAAC,GAAG,CAAS,gBAAgB,CAAC,IAAI,IAAI;qBAC/D;iBACF,CAAC;gBACF,MAAM,EAAE,CAAC,sBAAa,CAAC;aACxB,CAAC;YACF,4BAAY;SACb;QACD,WAAW,EAAE,CAAC,gCAAc,CAAC;QAC7B,SAAS,EAAE,CAAC,0BAAW,EAAE,0BAAW,CAAC;QACrC,OAAO,EAAE,CAAC,0BAAW,CAAC;KACvB,CAAC;GACW,UAAU,CAAG"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/auth/auth.service.d.ts b/reading-platform-backend/dist/src/modules/auth/auth.service.d.ts new file mode 100644 index 0000000..f203ca3 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/auth/auth.service.d.ts @@ -0,0 +1,84 @@ +import { JwtService } from '@nestjs/jwt'; +import { PrismaService } from '../../database/prisma.service'; +export interface LoginDto { + account: string; + password: string; + role: string; +} +export interface JwtPayload { + sub: number; + role: string; + tenantId?: number; +} +export declare class AuthService { + private prisma; + private jwtService; + constructor(prisma: PrismaService, jwtService: JwtService); + validateUser(account: string, password: string): Promise<{ + id: number; + tenantId: number; + name: string; + phone: string; + email: string | null; + loginAccount: string; + passwordHash: string; + classIds: string | null; + status: string; + lessonCount: number; + feedbackCount: number; + createdAt: Date; + updatedAt: Date; + lastLoginAt: Date | null; + }>; + login(dto: LoginDto): Promise<{ + token: string; + user: { + id: any; + name: any; + role: any; + tenantId: any; + tenantName: any; + }; + }>; + getProfile(userId: number, role: string): Promise<{ + id: number; + name: string; + role: string; + tenantId?: undefined; + tenantName?: undefined; + email?: undefined; + phone?: undefined; + children?: undefined; + } | { + id: number; + name: string; + role: string; + tenantId: number; + tenantName: string; + email?: undefined; + phone?: undefined; + children?: undefined; + } | { + id: number; + name: string; + role: string; + tenantId: number; + tenantName: string; + email: string; + phone: string; + children?: undefined; + } | { + id: number; + name: string; + role: string; + tenantId: number; + tenantName: string; + email: string; + phone: string; + children: { + id: number; + name: string; + relationship: string; + }[]; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/auth/auth.service.js b/reading-platform-backend/dist/src/modules/auth/auth.service.js new file mode 100644 index 0000000..9209269 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/auth/auth.service.js @@ -0,0 +1,287 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AuthService = void 0; +const common_1 = require("@nestjs/common"); +const jwt_1 = require("@nestjs/jwt"); +const prisma_service_1 = require("../../database/prisma.service"); +const bcrypt = __importStar(require("bcrypt")); +let AuthService = class AuthService { + constructor(prisma, jwtService) { + this.prisma = prisma; + this.jwtService = jwtService; + } + async validateUser(account, password) { + const user = await this.prisma.teacher.findUnique({ + where: { loginAccount: account }, + }); + if (!user) { + throw new common_1.UnauthorizedException('账号或密码错误'); + } + const isPasswordValid = await bcrypt.compare(password, user.passwordHash); + if (!isPasswordValid) { + throw new common_1.UnauthorizedException('账号或密码错误'); + } + if (user.status !== 'ACTIVE') { + throw new common_1.UnauthorizedException('账号已被停用'); + } + return user; + } + async login(dto) { + let user; + if (dto.role === 'admin') { + if (dto.account === 'admin' && dto.password === 'admin123') { + user = { + id: 1, + name: '超级管理员', + role: 'admin', + }; + } + else { + throw new common_1.UnauthorizedException('账号或密码错误'); + } + } + else if (dto.role === 'school') { + const tenant = await this.prisma.tenant.findUnique({ + where: { loginAccount: dto.account }, + }); + if (!tenant) { + throw new common_1.UnauthorizedException('账号或密码错误'); + } + if (!tenant.passwordHash) { + throw new common_1.UnauthorizedException('账号未设置密码'); + } + const isPasswordValid = await bcrypt.compare(dto.password, tenant.passwordHash); + if (!isPasswordValid) { + throw new common_1.UnauthorizedException('账号或密码错误'); + } + if (tenant.status !== 'ACTIVE') { + throw new common_1.UnauthorizedException('账号已被停用'); + } + user = { + id: tenant.id, + name: tenant.name, + role: 'school', + tenantId: tenant.id, + tenantName: tenant.name, + }; + } + else if (dto.role === 'teacher') { + const teacher = await this.prisma.teacher.findUnique({ + where: { loginAccount: dto.account }, + include: { + tenant: { + select: { + id: true, + name: true, + }, + }, + }, + }); + if (!teacher) { + throw new common_1.UnauthorizedException('账号或密码错误'); + } + const isPasswordValid = await bcrypt.compare(dto.password, teacher.passwordHash); + if (!isPasswordValid) { + throw new common_1.UnauthorizedException('账号或密码错误'); + } + if (teacher.status !== 'ACTIVE') { + throw new common_1.UnauthorizedException('账号已被停用'); + } + user = { + id: teacher.id, + name: teacher.name, + role: 'teacher', + tenantId: teacher.tenantId, + tenantName: teacher.tenant?.name, + }; + } + else if (dto.role === 'parent') { + const parent = await this.prisma.parent.findUnique({ + where: { loginAccount: dto.account }, + include: { + tenant: { + select: { + id: true, + name: true, + }, + }, + }, + }); + if (!parent) { + throw new common_1.UnauthorizedException('账号或密码错误'); + } + const isPasswordValid = await bcrypt.compare(dto.password, parent.passwordHash); + if (!isPasswordValid) { + throw new common_1.UnauthorizedException('账号或密码错误'); + } + if (parent.status !== 'ACTIVE') { + throw new common_1.UnauthorizedException('账号已被停用'); + } + user = { + id: parent.id, + name: parent.name, + role: 'parent', + tenantId: parent.tenantId, + tenantName: parent.tenant?.name, + }; + await this.prisma.parent.update({ + where: { id: parent.id }, + data: { lastLoginAt: new Date() }, + }); + } + else { + throw new common_1.UnauthorizedException('无效的角色'); + } + const payload = { + sub: user.id, + role: user.role, + tenantId: user.tenantId, + }; + const token = this.jwtService.sign(payload); + if (dto.role === 'teacher') { + await this.prisma.teacher.update({ + where: { id: user.id }, + data: { lastLoginAt: new Date() }, + }); + } + return { + token, + user: { + id: user.id, + name: user.name, + role: user.role, + tenantId: user.tenantId, + tenantName: user.tenantName, + }, + }; + } + async getProfile(userId, role) { + if (role === 'admin') { + return { + id: 1, + name: '超级管理员', + role: 'admin', + }; + } + else if (role === 'school') { + const tenant = await this.prisma.tenant.findUnique({ + where: { id: userId }, + }); + if (!tenant) { + throw new common_1.UnauthorizedException('用户不存在'); + } + return { + id: tenant.id, + name: tenant.name, + role: 'school', + tenantId: tenant.id, + tenantName: tenant.name, + }; + } + else if (role === 'teacher') { + const teacher = await this.prisma.teacher.findUnique({ + where: { id: userId }, + include: { + tenant: { + select: { + id: true, + name: true, + }, + }, + }, + }); + if (!teacher) { + throw new common_1.UnauthorizedException('用户不存在'); + } + return { + id: teacher.id, + name: teacher.name, + role: 'teacher', + tenantId: teacher.tenantId, + tenantName: teacher.tenant?.name, + email: teacher.email, + phone: teacher.phone, + }; + } + else if (role === 'parent') { + const parent = await this.prisma.parent.findUnique({ + where: { id: userId }, + include: { + tenant: { + select: { + id: true, + name: true, + }, + }, + children: { + include: { + student: { + select: { + id: true, + name: true, + }, + }, + }, + }, + }, + }); + if (!parent) { + throw new common_1.UnauthorizedException('用户不存在'); + } + return { + id: parent.id, + name: parent.name, + role: 'parent', + tenantId: parent.tenantId, + tenantName: parent.tenant?.name, + email: parent.email, + phone: parent.phone, + children: parent.children.map((c) => ({ + id: c.student.id, + name: c.student.name, + relationship: c.relationship, + })), + }; + } + throw new common_1.UnauthorizedException('无效的角色'); + } +}; +exports.AuthService = AuthService; +exports.AuthService = AuthService = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService, + jwt_1.JwtService]) +], AuthService); +//# sourceMappingURL=auth.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/auth/auth.service.js.map b/reading-platform-backend/dist/src/modules/auth/auth.service.js.map new file mode 100644 index 0000000..0986acc --- /dev/null +++ b/reading-platform-backend/dist/src/modules/auth/auth.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"auth.service.js","sourceRoot":"","sources":["../../../../src/modules/auth/auth.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/auth/dto/login.dto.d.ts b/reading-platform-backend/dist/src/modules/auth/dto/login.dto.d.ts new file mode 100644 index 0000000..705328a --- /dev/null +++ b/reading-platform-backend/dist/src/modules/auth/dto/login.dto.d.ts @@ -0,0 +1,5 @@ +export declare class LoginDto { + account: string; + password: string; + role: string; +} diff --git a/reading-platform-backend/dist/src/modules/auth/dto/login.dto.js b/reading-platform-backend/dist/src/modules/auth/dto/login.dto.js new file mode 100644 index 0000000..84eace0 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/auth/dto/login.dto.js @@ -0,0 +1,33 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.LoginDto = void 0; +const class_validator_1 = require("class-validator"); +class LoginDto { +} +exports.LoginDto = LoginDto; +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)(), + __metadata("design:type", String) +], LoginDto.prototype, "account", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)(), + __metadata("design:type", String) +], LoginDto.prototype, "password", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsIn)(['admin', 'school', 'teacher', 'parent']), + (0, class_validator_1.IsNotEmpty)(), + __metadata("design:type", String) +], LoginDto.prototype, "role", void 0); +//# sourceMappingURL=login.dto.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/auth/dto/login.dto.js.map b/reading-platform-backend/dist/src/modules/auth/dto/login.dto.js.map new file mode 100644 index 0000000..232b806 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/auth/dto/login.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"login.dto.js","sourceRoot":"","sources":["../../../../../src/modules/auth/dto/login.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAA6D;AAE7D,MAAa,QAAQ;CAapB;AAbD,4BAaC;AAVC;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;yCACG;AAIhB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;0CACI;AAKjB;IAHC,IAAA,0BAAQ,GAAE;IACV,IAAA,sBAAI,EAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IAC9C,IAAA,4BAAU,GAAE;;sCACA"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/auth/strategies/jwt.strategy.d.ts b/reading-platform-backend/dist/src/modules/auth/strategies/jwt.strategy.d.ts new file mode 100644 index 0000000..d6259c0 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/auth/strategies/jwt.strategy.d.ts @@ -0,0 +1,18 @@ +import { Strategy } from 'passport-jwt'; +import { ConfigService } from '@nestjs/config'; +export interface JwtPayload { + sub: number; + role: string; + tenantId?: number; +} +declare const JwtStrategy_base: new (...args: any[]) => Strategy; +export declare class JwtStrategy extends JwtStrategy_base { + private configService; + constructor(configService: ConfigService); + validate(payload: JwtPayload): Promise<{ + userId: number; + role: string; + tenantId: number; + }>; +} +export {}; diff --git a/reading-platform-backend/dist/src/modules/auth/strategies/jwt.strategy.js b/reading-platform-backend/dist/src/modules/auth/strategies/jwt.strategy.js new file mode 100644 index 0000000..b32a1b4 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/auth/strategies/jwt.strategy.js @@ -0,0 +1,42 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.JwtStrategy = void 0; +const common_1 = require("@nestjs/common"); +const passport_1 = require("@nestjs/passport"); +const passport_jwt_1 = require("passport-jwt"); +const config_1 = require("@nestjs/config"); +let JwtStrategy = class JwtStrategy extends (0, passport_1.PassportStrategy)(passport_jwt_1.Strategy) { + constructor(configService) { + super({ + jwtFromRequest: passport_jwt_1.ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: configService.get('JWT_SECRET') || 'your-secret-key', + }); + this.configService = configService; + } + async validate(payload) { + if (!payload.sub || !payload.role) { + throw new common_1.UnauthorizedException(); + } + return { + userId: payload.sub, + role: payload.role, + tenantId: payload.tenantId, + }; + } +}; +exports.JwtStrategy = JwtStrategy; +exports.JwtStrategy = JwtStrategy = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [config_1.ConfigService]) +], JwtStrategy); +//# sourceMappingURL=jwt.strategy.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/auth/strategies/jwt.strategy.js.map b/reading-platform-backend/dist/src/modules/auth/strategies/jwt.strategy.js.map new file mode 100644 index 0000000..b3c2301 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/auth/strategies/jwt.strategy.js.map @@ -0,0 +1 @@ +{"version":3,"file":"jwt.strategy.js","sourceRoot":"","sources":["../../../../../src/modules/auth/strategies/jwt.strategy.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAmE;AACnE,+CAAoD;AACpD,+CAAoD;AACpD,2CAA+C;AASxC,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,IAAA,2BAAgB,EAAC,uBAAQ,CAAC;IACzD,YAAoB,aAA4B;QAC9C,KAAK,CAAC;YACJ,cAAc,EAAE,yBAAU,CAAC,2BAA2B,EAAE;YACxD,gBAAgB,EAAE,KAAK;YACvB,WAAW,EAAE,aAAa,CAAC,GAAG,CAAS,YAAY,CAAC,IAAI,iBAAiB;SAC1E,CAAC,CAAC;QALe,kBAAa,GAAb,aAAa,CAAe;IAMhD,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,OAAmB;QAChC,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAClC,MAAM,IAAI,8BAAqB,EAAE,CAAC;QACpC,CAAC;QAED,OAAO;YACL,MAAM,EAAE,OAAO,CAAC,GAAG;YACnB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;SAC3B,CAAC;IACJ,CAAC;CACF,CAAA;AApBY,kCAAW;sBAAX,WAAW;IADvB,IAAA,mBAAU,GAAE;qCAEwB,sBAAa;GADrC,WAAW,CAoBvB"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/common/common.module.d.ts b/reading-platform-backend/dist/src/modules/common/common.module.d.ts new file mode 100644 index 0000000..463c737 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/common.module.d.ts @@ -0,0 +1,2 @@ +export declare class CommonModule { +} diff --git a/reading-platform-backend/dist/src/modules/common/common.module.js b/reading-platform-backend/dist/src/modules/common/common.module.js new file mode 100644 index 0000000..1501d39 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/common.module.js @@ -0,0 +1,26 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CommonModule = void 0; +const common_1 = require("@nestjs/common"); +const jwt_auth_guard_1 = require("./guards/jwt-auth.guard"); +const roles_guard_1 = require("./guards/roles.guard"); +const log_interceptor_1 = require("./interceptors/log.interceptor"); +const operation_log_service_1 = require("./operation-log.service"); +const operation_log_controller_1 = require("./operation-log.controller"); +let CommonModule = class CommonModule { +}; +exports.CommonModule = CommonModule; +exports.CommonModule = CommonModule = __decorate([ + (0, common_1.Module)({ + controllers: [operation_log_controller_1.SchoolOperationLogController, operation_log_controller_1.AdminOperationLogController], + providers: [jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard, log_interceptor_1.LogInterceptor, operation_log_service_1.OperationLogService], + exports: [jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard, log_interceptor_1.LogInterceptor, operation_log_service_1.OperationLogService], + }) +], CommonModule); +//# sourceMappingURL=common.module.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/common/common.module.js.map b/reading-platform-backend/dist/src/modules/common/common.module.js.map new file mode 100644 index 0000000..a25f747 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/common.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"common.module.js","sourceRoot":"","sources":["../../../../src/modules/common/common.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,4DAAuD;AACvD,sDAAkD;AAClD,oEAAgE;AAChE,mEAA8D;AAC9D,yEAAuG;AAOhG,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,oCAAY;uBAAZ,YAAY;IALxB,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,uDAA4B,EAAE,sDAA2B,CAAC;QACxE,SAAS,EAAE,CAAC,6BAAY,EAAE,wBAAU,EAAE,gCAAc,EAAE,2CAAmB,CAAC;QAC1E,OAAO,EAAE,CAAC,6BAAY,EAAE,wBAAU,EAAE,gCAAc,EAAE,2CAAmB,CAAC;KACzE,CAAC;GACW,YAAY,CAAG"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/common/decorators/log-operation.decorator.d.ts b/reading-platform-backend/dist/src/modules/common/decorators/log-operation.decorator.d.ts new file mode 100644 index 0000000..5ab111e --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/decorators/log-operation.decorator.d.ts @@ -0,0 +1,7 @@ +export declare const LOG_OPERATION_KEY = "log_operation"; +export interface LogOperationOptions { + action: string; + module: string; + description: string; +} +export declare const LogOperation: (options: LogOperationOptions) => import("@nestjs/common").CustomDecorator; diff --git a/reading-platform-backend/dist/src/modules/common/decorators/log-operation.decorator.js b/reading-platform-backend/dist/src/modules/common/decorators/log-operation.decorator.js new file mode 100644 index 0000000..968d232 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/decorators/log-operation.decorator.js @@ -0,0 +1,10 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.LogOperation = exports.LOG_OPERATION_KEY = void 0; +const common_1 = require("@nestjs/common"); +exports.LOG_OPERATION_KEY = 'log_operation'; +const LogOperation = (options) => { + return (0, common_1.SetMetadata)(exports.LOG_OPERATION_KEY, options); +}; +exports.LogOperation = LogOperation; +//# sourceMappingURL=log-operation.decorator.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/common/decorators/log-operation.decorator.js.map b/reading-platform-backend/dist/src/modules/common/decorators/log-operation.decorator.js.map new file mode 100644 index 0000000..2ff7935 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/decorators/log-operation.decorator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"log-operation.decorator.js","sourceRoot":"","sources":["../../../../../src/modules/common/decorators/log-operation.decorator.ts"],"names":[],"mappings":";;;AAAA,2CAA6C;AAEhC,QAAA,iBAAiB,GAAG,eAAe,CAAC;AAY1C,MAAM,YAAY,GAAG,CAAC,OAA4B,EAAE,EAAE;IAC3D,OAAO,IAAA,oBAAW,EAAC,yBAAiB,EAAE,OAAO,CAAC,CAAC;AACjD,CAAC,CAAC;AAFW,QAAA,YAAY,gBAEvB"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/common/decorators/roles.decorator.d.ts b/reading-platform-backend/dist/src/modules/common/decorators/roles.decorator.d.ts new file mode 100644 index 0000000..bd39810 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/decorators/roles.decorator.d.ts @@ -0,0 +1,2 @@ +export declare const ROLES_KEY = "roles"; +export declare const Roles: (...roles: string[]) => import("@nestjs/common").CustomDecorator; diff --git a/reading-platform-backend/dist/src/modules/common/decorators/roles.decorator.js b/reading-platform-backend/dist/src/modules/common/decorators/roles.decorator.js new file mode 100644 index 0000000..126533b --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/decorators/roles.decorator.js @@ -0,0 +1,8 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Roles = exports.ROLES_KEY = void 0; +const common_1 = require("@nestjs/common"); +exports.ROLES_KEY = 'roles'; +const Roles = (...roles) => (0, common_1.SetMetadata)(exports.ROLES_KEY, roles); +exports.Roles = Roles; +//# sourceMappingURL=roles.decorator.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/common/decorators/roles.decorator.js.map b/reading-platform-backend/dist/src/modules/common/decorators/roles.decorator.js.map new file mode 100644 index 0000000..2f74a2e --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/decorators/roles.decorator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"roles.decorator.js","sourceRoot":"","sources":["../../../../../src/modules/common/decorators/roles.decorator.ts"],"names":[],"mappings":";;;AAAA,2CAA6C;AAEhC,QAAA,SAAS,GAAG,OAAO,CAAC;AAC1B,MAAM,KAAK,GAAG,CAAC,GAAG,KAAe,EAAE,EAAE,CAAC,IAAA,oBAAW,EAAC,iBAAS,EAAE,KAAK,CAAC,CAAC;AAA9D,QAAA,KAAK,SAAyD"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/common/guards/jwt-auth.guard.d.ts b/reading-platform-backend/dist/src/modules/common/guards/jwt-auth.guard.d.ts new file mode 100644 index 0000000..f7044e7 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/guards/jwt-auth.guard.d.ts @@ -0,0 +1,9 @@ +import { ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +declare const JwtAuthGuard_base: import("@nestjs/passport").Type; +export declare class JwtAuthGuard extends JwtAuthGuard_base { + private reflector; + constructor(reflector: Reflector); + canActivate(context: ExecutionContext): boolean | Promise | import("rxjs").Observable; +} +export {}; diff --git a/reading-platform-backend/dist/src/modules/common/guards/jwt-auth.guard.js b/reading-platform-backend/dist/src/modules/common/guards/jwt-auth.guard.js new file mode 100644 index 0000000..703d1bf --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/guards/jwt-auth.guard.js @@ -0,0 +1,37 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.JwtAuthGuard = void 0; +const common_1 = require("@nestjs/common"); +const core_1 = require("@nestjs/core"); +const passport_1 = require("@nestjs/passport"); +let JwtAuthGuard = class JwtAuthGuard extends (0, passport_1.AuthGuard)('jwt') { + constructor(reflector) { + super(); + this.reflector = reflector; + } + canActivate(context) { + const requireAuth = this.reflector.getAllAndOverride('requireAuth', [ + context.getHandler(), + context.getClass(), + ]); + if (requireAuth === false) { + return true; + } + return super.canActivate(context); + } +}; +exports.JwtAuthGuard = JwtAuthGuard; +exports.JwtAuthGuard = JwtAuthGuard = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [core_1.Reflector]) +], JwtAuthGuard); +//# sourceMappingURL=jwt-auth.guard.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/common/guards/jwt-auth.guard.js.map b/reading-platform-backend/dist/src/modules/common/guards/jwt-auth.guard.js.map new file mode 100644 index 0000000..fa26252 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/guards/jwt-auth.guard.js.map @@ -0,0 +1 @@ +{"version":3,"file":"jwt-auth.guard.js","sourceRoot":"","sources":["../../../../../src/modules/common/guards/jwt-auth.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA8D;AAC9D,uCAAyC;AACzC,+CAA6C;AAGtC,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,IAAA,oBAAS,EAAC,KAAK,CAAC;IAChD,YAAoB,SAAoB;QACtC,KAAK,EAAE,CAAC;QADU,cAAS,GAAT,SAAS,CAAW;IAExC,CAAC;IAED,WAAW,CAAC,OAAyB;QAEnC,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAU,aAAa,EAAE;YAC3E,OAAO,CAAC,UAAU,EAAE;YACpB,OAAO,CAAC,QAAQ,EAAE;SACnB,CAAC,CAAC;QAEH,IAAI,WAAW,KAAK,KAAK,EAAE,CAAC;YAC1B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;CACF,CAAA;AAlBY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,mBAAU,GAAE;qCAEoB,gBAAS;GAD7B,YAAY,CAkBxB"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/common/guards/roles.guard.d.ts b/reading-platform-backend/dist/src/modules/common/guards/roles.guard.d.ts new file mode 100644 index 0000000..3e9645a --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/guards/roles.guard.d.ts @@ -0,0 +1,7 @@ +import { CanActivate, ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +export declare class RolesGuard implements CanActivate { + private reflector; + constructor(reflector: Reflector); + canActivate(context: ExecutionContext): boolean; +} diff --git a/reading-platform-backend/dist/src/modules/common/guards/roles.guard.js b/reading-platform-backend/dist/src/modules/common/guards/roles.guard.js new file mode 100644 index 0000000..6cab0e8 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/guards/roles.guard.js @@ -0,0 +1,37 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RolesGuard = void 0; +const common_1 = require("@nestjs/common"); +const core_1 = require("@nestjs/core"); +const roles_decorator_1 = require("../decorators/roles.decorator"); +let RolesGuard = class RolesGuard { + constructor(reflector) { + this.reflector = reflector; + } + canActivate(context) { + const requiredRoles = this.reflector.getAllAndOverride(roles_decorator_1.ROLES_KEY, [ + context.getHandler(), + context.getClass(), + ]); + if (!requiredRoles) { + return true; + } + const { user } = context.switchToHttp().getRequest(); + return requiredRoles.some((role) => user?.role === role); + } +}; +exports.RolesGuard = RolesGuard; +exports.RolesGuard = RolesGuard = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [core_1.Reflector]) +], RolesGuard); +//# sourceMappingURL=roles.guard.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/common/guards/roles.guard.js.map b/reading-platform-backend/dist/src/modules/common/guards/roles.guard.js.map new file mode 100644 index 0000000..318706e --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/guards/roles.guard.js.map @@ -0,0 +1 @@ +{"version":3,"file":"roles.guard.js","sourceRoot":"","sources":["../../../../../src/modules/common/guards/roles.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA2E;AAC3E,uCAAyC;AACzC,mEAA0D;AAGnD,IAAM,UAAU,GAAhB,MAAM,UAAU;IACrB,YAAoB,SAAoB;QAApB,cAAS,GAAT,SAAS,CAAW;IAAG,CAAC;IAE5C,WAAW,CAAC,OAAyB;QACnC,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAW,2BAAS,EAAE;YAC1E,OAAO,CAAC,UAAU,EAAE;YACpB,OAAO,CAAC,QAAQ,EAAE;SACnB,CAAC,CAAC;QAGH,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QAGrD,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC;IAC3D,CAAC;CACF,CAAA;AAnBY,gCAAU;qBAAV,UAAU;IADtB,IAAA,mBAAU,GAAE;qCAEoB,gBAAS;GAD7B,UAAU,CAmBtB"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/common/interceptors/log.interceptor.d.ts b/reading-platform-backend/dist/src/modules/common/interceptors/log.interceptor.d.ts new file mode 100644 index 0000000..376f85c --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/interceptors/log.interceptor.d.ts @@ -0,0 +1,13 @@ +import { NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { Observable } from 'rxjs'; +import { PrismaService } from '../../../database/prisma.service'; +export declare class LogInterceptor implements NestInterceptor { + private reflector; + private prisma; + private readonly logger; + constructor(reflector: Reflector, prisma: PrismaService); + intercept(context: ExecutionContext, next: CallHandler): Observable; + private saveLog; + private getIpAddress; +} diff --git a/reading-platform-backend/dist/src/modules/common/interceptors/log.interceptor.js b/reading-platform-backend/dist/src/modules/common/interceptors/log.interceptor.js new file mode 100644 index 0000000..68cbef5 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/interceptors/log.interceptor.js @@ -0,0 +1,115 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var LogInterceptor_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.LogInterceptor = void 0; +const common_1 = require("@nestjs/common"); +const core_1 = require("@nestjs/core"); +const rxjs_1 = require("rxjs"); +const prisma_service_1 = require("../../../database/prisma.service"); +const log_operation_decorator_1 = require("../decorators/log-operation.decorator"); +let LogInterceptor = LogInterceptor_1 = class LogInterceptor { + constructor(reflector, prisma) { + this.reflector = reflector; + this.prisma = prisma; + this.logger = new common_1.Logger(LogInterceptor_1.name); + } + intercept(context, next) { + const logOptions = this.reflector.getAllAndOverride(log_operation_decorator_1.LOG_OPERATION_KEY, [context.getHandler(), context.getClass()]); + if (!logOptions) { + return next.handle(); + } + const request = context.switchToHttp().getRequest(); + const user = request.user; + const startTime = Date.now(); + const body = request.body; + const params = request.params; + return next.handle().pipe((0, rxjs_1.tap)({ + next: (response) => { + this.saveLog({ + user, + logOptions, + body, + params, + response, + ipAddress: this.getIpAddress(request), + userAgent: request.headers['user-agent'], + duration: Date.now() - startTime, + }); + }, + error: (error) => { + this.saveLog({ + user, + logOptions, + body, + params, + response: { error: error.message }, + ipAddress: this.getIpAddress(request), + userAgent: request.headers['user-agent'], + duration: Date.now() - startTime, + isError: true, + }); + }, + })); + } + async saveLog(data) { + try { + const { user, logOptions, body, params, response, ipAddress, userAgent } = data; + let targetId; + if (params?.id) { + targetId = parseInt(params.id, 10); + } + else if (response?.id) { + targetId = response.id; + } + else if (body?.id) { + targetId = body.id; + } + let description = logOptions.description; + if (data.isError) { + description = `[失败] ${description}`; + } + await this.prisma.operationLog.create({ + data: { + tenantId: user?.tenantId || null, + userId: user?.userId || 0, + userType: user?.role || 'UNKNOWN', + action: logOptions.action, + module: logOptions.module, + description, + targetId, + oldValue: body ? JSON.stringify(body) : null, + newValue: response ? JSON.stringify(response) : null, + ipAddress, + userAgent, + }, + }); + this.logger.debug(`Operation logged: ${logOptions.module} - ${logOptions.action} (${data.duration}ms)`); + } + catch (error) { + this.logger.error('Failed to save operation log:', error); + } + } + getIpAddress(request) { + return (request.headers['x-forwarded-for']?.split(',')[0]?.trim() || + request.headers['x-real-ip'] || + request.connection?.remoteAddress || + request.socket?.remoteAddress || + 'unknown'); + } +}; +exports.LogInterceptor = LogInterceptor; +exports.LogInterceptor = LogInterceptor = LogInterceptor_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [core_1.Reflector, + prisma_service_1.PrismaService]) +], LogInterceptor); +//# sourceMappingURL=log.interceptor.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/common/interceptors/log.interceptor.js.map b/reading-platform-backend/dist/src/modules/common/interceptors/log.interceptor.js.map new file mode 100644 index 0000000..9dece1a --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/interceptors/log.interceptor.js.map @@ -0,0 +1 @@ +{"version":3,"file":"log.interceptor.js","sourceRoot":"","sources":["../../../../../src/modules/common/interceptors/log.interceptor.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/common/operation-log.controller.d.ts b/reading-platform-backend/dist/src/modules/common/operation-log.controller.d.ts new file mode 100644 index 0000000..bdfa410 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/operation-log.controller.d.ts @@ -0,0 +1,101 @@ +import { OperationLogService } from './operation-log.service'; +export declare class SchoolOperationLogController { + private readonly logService; + constructor(logService: OperationLogService); + getLogs(req: any, query: any): Promise<{ + items: { + id: number; + tenantId: number | null; + description: string; + createdAt: Date; + ipAddress: string | null; + userAgent: string | null; + userId: number; + userType: string; + action: string; + module: string; + targetId: number | null; + oldValue: string | null; + newValue: string | null; + }[]; + total: number; + page: number; + pageSize: number; + }>; + getStats(req: any, query: any): Promise<{ + modules: { + name: string; + count: number; + }[]; + actions: { + name: string; + count: number; + }[]; + total: number; + }>; + getLogById(req: any, id: string): Promise<{ + oldValue: any; + newValue: any; + id: number; + tenantId: number | null; + description: string; + createdAt: Date; + ipAddress: string | null; + userAgent: string | null; + userId: number; + userType: string; + action: string; + module: string; + targetId: number | null; + }>; +} +export declare class AdminOperationLogController { + private readonly logService; + constructor(logService: OperationLogService); + getLogs(query: any): Promise<{ + items: { + id: number; + tenantId: number | null; + description: string; + createdAt: Date; + ipAddress: string | null; + userAgent: string | null; + userId: number; + userType: string; + action: string; + module: string; + targetId: number | null; + oldValue: string | null; + newValue: string | null; + }[]; + total: number; + page: number; + pageSize: number; + }>; + getStats(query: any): Promise<{ + modules: { + name: string; + count: number; + }[]; + actions: { + name: string; + count: number; + }[]; + total: number; + }>; + getLogById(id: string): Promise<{ + oldValue: any; + newValue: any; + id: number; + tenantId: number | null; + description: string; + createdAt: Date; + ipAddress: string | null; + userAgent: string | null; + userId: number; + userType: string; + action: string; + module: string; + targetId: number | null; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/common/operation-log.controller.js b/reading-platform-backend/dist/src/modules/common/operation-log.controller.js new file mode 100644 index 0000000..e5a8e18 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/operation-log.controller.js @@ -0,0 +1,108 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AdminOperationLogController = exports.SchoolOperationLogController = void 0; +const common_1 = require("@nestjs/common"); +const operation_log_service_1 = require("./operation-log.service"); +const jwt_auth_guard_1 = require("./guards/jwt-auth.guard"); +const roles_guard_1 = require("./guards/roles.guard"); +const roles_decorator_1 = require("./decorators/roles.decorator"); +let SchoolOperationLogController = class SchoolOperationLogController { + constructor(logService) { + this.logService = logService; + } + getLogs(req, query) { + return this.logService.getLogs(req.user.tenantId, query); + } + getStats(req, query) { + return this.logService.getModuleStats(req.user.tenantId, query.startDate, query.endDate); + } + getLogById(req, id) { + return this.logService.getLogById(req.user.tenantId, +id); + } +}; +exports.SchoolOperationLogController = SchoolOperationLogController; +__decorate([ + (0, common_1.Get)('operation-logs'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], SchoolOperationLogController.prototype, "getLogs", null); +__decorate([ + (0, common_1.Get)('operation-logs/stats'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], SchoolOperationLogController.prototype, "getStats", null); +__decorate([ + (0, common_1.Get)('operation-logs/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolOperationLogController.prototype, "getLogById", null); +exports.SchoolOperationLogController = SchoolOperationLogController = __decorate([ + (0, common_1.Controller)('school'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('school'), + __metadata("design:paramtypes", [operation_log_service_1.OperationLogService]) +], SchoolOperationLogController); +let AdminOperationLogController = class AdminOperationLogController { + constructor(logService) { + this.logService = logService; + } + getLogs(query) { + return this.logService.getLogs(null, query); + } + getStats(query) { + return this.logService.getModuleStats(null, query.startDate, query.endDate); + } + getLogById(id) { + return this.logService.getLogById(null, +id); + } +}; +exports.AdminOperationLogController = AdminOperationLogController; +__decorate([ + (0, common_1.Get)('operation-logs'), + __param(0, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], AdminOperationLogController.prototype, "getLogs", null); +__decorate([ + (0, common_1.Get)('operation-logs/stats'), + __param(0, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], AdminOperationLogController.prototype, "getStats", null); +__decorate([ + (0, common_1.Get)('operation-logs/:id'), + __param(0, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", void 0) +], AdminOperationLogController.prototype, "getLogById", null); +exports.AdminOperationLogController = AdminOperationLogController = __decorate([ + (0, common_1.Controller)('admin'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('admin'), + __metadata("design:paramtypes", [operation_log_service_1.OperationLogService]) +], AdminOperationLogController); +//# sourceMappingURL=operation-log.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/common/operation-log.controller.js.map b/reading-platform-backend/dist/src/modules/common/operation-log.controller.js.map new file mode 100644 index 0000000..57bd5ac --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/operation-log.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"operation-log.controller.js","sourceRoot":"","sources":["../../../../src/modules/common/operation-log.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAOwB;AACxB,mEAA8D;AAC9D,4DAAuD;AACvD,sDAAkD;AAClD,kEAAqD;AAK9C,IAAM,4BAA4B,GAAlC,MAAM,4BAA4B;IACvC,YAA6B,UAA+B;QAA/B,eAAU,GAAV,UAAU,CAAqB;IAAG,CAAC;IAGhE,OAAO,CAAY,GAAQ,EAAW,KAAU;QAC9C,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC3D,CAAC;IAGD,QAAQ,CAAY,GAAQ,EAAW,KAAU;QAC/C,OAAO,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3F,CAAC;IAGD,UAAU,CAAY,GAAQ,EAAe,EAAU;QACrD,OAAO,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5D,CAAC;CACF,CAAA;AAjBY,oEAA4B;AAIvC;IADC,IAAA,YAAG,EAAC,gBAAgB,CAAC;IACb,WAAA,IAAA,gBAAO,GAAE,CAAA;IAAY,WAAA,IAAA,cAAK,GAAE,CAAA;;;;2DAEpC;AAGD;IADC,IAAA,YAAG,EAAC,sBAAsB,CAAC;IAClB,WAAA,IAAA,gBAAO,GAAE,CAAA;IAAY,WAAA,IAAA,cAAK,GAAE,CAAA;;;;4DAErC;AAGD;IADC,IAAA,YAAG,EAAC,oBAAoB,CAAC;IACd,WAAA,IAAA,gBAAO,GAAE,CAAA;IAAY,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;8DAE3C;uCAhBU,4BAA4B;IAHxC,IAAA,mBAAU,EAAC,QAAQ,CAAC;IACpB,IAAA,kBAAS,EAAC,6BAAY,EAAE,wBAAU,CAAC;IACnC,IAAA,uBAAK,EAAC,QAAQ,CAAC;qCAE2B,2CAAmB;GADjD,4BAA4B,CAiBxC;AAKM,IAAM,2BAA2B,GAAjC,MAAM,2BAA2B;IACtC,YAA6B,UAA+B;QAA/B,eAAU,GAAV,UAAU,CAAqB;IAAG,CAAC;IAGhE,OAAO,CAAU,KAAU;QACzB,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC9C,CAAC;IAGD,QAAQ,CAAU,KAAU;QAC1B,OAAO,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9E,CAAC;IAGD,UAAU,CAAc,EAAU;QAChC,OAAO,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/C,CAAC;CACF,CAAA;AAjBY,kEAA2B;AAItC;IADC,IAAA,YAAG,EAAC,gBAAgB,CAAC;IACb,WAAA,IAAA,cAAK,GAAE,CAAA;;;;0DAEf;AAGD;IADC,IAAA,YAAG,EAAC,sBAAsB,CAAC;IAClB,WAAA,IAAA,cAAK,GAAE,CAAA;;;;2DAEhB;AAGD;IADC,IAAA,YAAG,EAAC,oBAAoB,CAAC;IACd,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;6DAEtB;sCAhBU,2BAA2B;IAHvC,IAAA,mBAAU,EAAC,OAAO,CAAC;IACnB,IAAA,kBAAS,EAAC,6BAAY,EAAE,wBAAU,CAAC;IACnC,IAAA,uBAAK,EAAC,OAAO,CAAC;qCAE4B,2CAAmB;GADjD,2BAA2B,CAiBvC"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/common/operation-log.service.d.ts b/reading-platform-backend/dist/src/modules/common/operation-log.service.d.ts new file mode 100644 index 0000000..c6ff3f2 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/operation-log.service.d.ts @@ -0,0 +1,62 @@ +import { PrismaService } from '../../database/prisma.service'; +export declare class OperationLogService { + private prisma; + private readonly logger; + constructor(prisma: PrismaService); + getLogs(tenantId: number | null, query: { + page?: number; + pageSize?: number; + userId?: number; + userType?: string; + action?: string; + module?: string; + startDate?: string; + endDate?: string; + }): Promise<{ + items: { + id: number; + tenantId: number | null; + description: string; + createdAt: Date; + ipAddress: string | null; + userAgent: string | null; + userId: number; + userType: string; + action: string; + module: string; + targetId: number | null; + oldValue: string | null; + newValue: string | null; + }[]; + total: number; + page: number; + pageSize: number; + }>; + getLogById(tenantId: number | null, id: number): Promise<{ + oldValue: any; + newValue: any; + id: number; + tenantId: number | null; + description: string; + createdAt: Date; + ipAddress: string | null; + userAgent: string | null; + userId: number; + userType: string; + action: string; + module: string; + targetId: number | null; + }>; + getModuleStats(tenantId: number | null, startDate?: string, endDate?: string): Promise<{ + modules: { + name: string; + count: number; + }[]; + actions: { + name: string; + count: number; + }[]; + total: number; + }>; + private safeParseJson; +} diff --git a/reading-platform-backend/dist/src/modules/common/operation-log.service.js b/reading-platform-backend/dist/src/modules/common/operation-log.service.js new file mode 100644 index 0000000..fdddfe6 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/operation-log.service.js @@ -0,0 +1,134 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var OperationLogService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.OperationLogService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../../database/prisma.service"); +let OperationLogService = OperationLogService_1 = class OperationLogService { + constructor(prisma) { + this.prisma = prisma; + this.logger = new common_1.Logger(OperationLogService_1.name); + } + async getLogs(tenantId, query) { + const { page = 1, pageSize = 20, userId, userType, action, module, startDate, endDate, } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = {}; + if (tenantId !== null) { + where.tenantId = tenantId; + } + if (userId) { + where.userId = userId; + } + if (userType) { + where.userType = userType; + } + if (action) { + where.action = action; + } + if (module) { + where.module = module; + } + if (startDate || endDate) { + where.createdAt = {}; + if (startDate) { + where.createdAt.gte = new Date(startDate); + } + if (endDate) { + where.createdAt.lte = new Date(endDate); + } + } + const [items, total] = await Promise.all([ + this.prisma.operationLog.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + }), + this.prisma.operationLog.count({ where }), + ]); + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + async getLogById(tenantId, id) { + const where = { id }; + if (tenantId !== null) { + where.tenantId = tenantId; + } + const log = await this.prisma.operationLog.findFirst({ + where, + }); + if (!log) { + return null; + } + return { + ...log, + oldValue: log.oldValue ? this.safeParseJson(log.oldValue) : null, + newValue: log.newValue ? this.safeParseJson(log.newValue) : null, + }; + } + async getModuleStats(tenantId, startDate, endDate) { + const where = {}; + if (tenantId !== null) { + where.tenantId = tenantId; + } + if (startDate || endDate) { + where.createdAt = {}; + if (startDate) { + where.createdAt.gte = new Date(startDate); + } + if (endDate) { + where.createdAt.lte = new Date(endDate); + } + } + const logs = await this.prisma.operationLog.findMany({ + where, + select: { + module: true, + action: true, + }, + }); + const moduleStats = new Map(); + const actionStats = new Map(); + logs.forEach((log) => { + moduleStats.set(log.module, (moduleStats.get(log.module) || 0) + 1); + actionStats.set(log.action, (actionStats.get(log.action) || 0) + 1); + }); + return { + modules: Array.from(moduleStats.entries()) + .map(([name, count]) => ({ name, count })) + .sort((a, b) => b.count - a.count), + actions: Array.from(actionStats.entries()) + .map(([name, count]) => ({ name, count })) + .sort((a, b) => b.count - a.count), + total: logs.length, + }; + } + safeParseJson(str) { + try { + return JSON.parse(str); + } + catch { + return str; + } + } +}; +exports.OperationLogService = OperationLogService; +exports.OperationLogService = OperationLogService = OperationLogService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], OperationLogService); +//# sourceMappingURL=operation-log.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/common/operation-log.service.js.map b/reading-platform-backend/dist/src/modules/common/operation-log.service.js.map new file mode 100644 index 0000000..76d85fa --- /dev/null +++ b/reading-platform-backend/dist/src/modules/common/operation-log.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"operation-log.service.js","sourceRoot":"","sources":["../../../../src/modules/common/operation-log.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAAoD;AACpD,kEAA8D;AAGvD,IAAM,mBAAmB,2BAAzB,MAAM,mBAAmB;IAG9B,YAAoB,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;QAFxB,WAAM,GAAG,IAAI,eAAM,CAAC,qBAAmB,CAAC,IAAI,CAAC,CAAC;IAEnB,CAAC;IAK7C,KAAK,CAAC,OAAO,CAAC,QAAuB,EAAE,KAStC;QACC,MAAM,EACJ,IAAI,GAAG,CAAC,EACR,QAAQ,GAAG,EAAE,EACb,MAAM,EACN,QAAQ,EACR,MAAM,EACN,MAAM,EACN,SAAS,EACT,OAAO,GACR,GAAG,KAAK,CAAC;QAEV,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC;QACnC,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC;QAEvB,MAAM,KAAK,GAAQ,EAAE,CAAC;QAEtB,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtB,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAC5B,CAAC;QAED,IAAI,MAAM,EAAE,CAAC;YACX,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;QACxB,CAAC;QAED,IAAI,QAAQ,EAAE,CAAC;YACb,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAC5B,CAAC;QAED,IAAI,MAAM,EAAE,CAAC;YACX,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;QACxB,CAAC;QAED,IAAI,MAAM,EAAE,CAAC;YACX,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;QACxB,CAAC;QAED,IAAI,SAAS,IAAI,OAAO,EAAE,CAAC;YACzB,KAAK,CAAC,SAAS,GAAG,EAAE,CAAC;YACrB,IAAI,SAAS,EAAE,CAAC;gBACd,KAAK,CAAC,SAAS,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;YAC5C,CAAC;YACD,IAAI,OAAO,EAAE,CAAC;gBACZ,KAAK,CAAC,SAAS,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC;QAED,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACvC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC;gBAChC,KAAK;gBACL,IAAI;gBACJ,IAAI;gBACJ,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;aAC/B,CAAC;YACF,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC;SAC1C,CAAC,CAAC;QAEH,OAAO;YACL,KAAK;YACL,KAAK;YACL,IAAI,EAAE,CAAC,IAAI;YACX,QAAQ,EAAE,CAAC,QAAQ;SACpB,CAAC;IACJ,CAAC;IAKD,KAAK,CAAC,UAAU,CAAC,QAAuB,EAAE,EAAU;QAClD,MAAM,KAAK,GAAQ,EAAE,EAAE,EAAE,CAAC;QAE1B,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtB,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAC5B,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC;YACnD,KAAK;SACN,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,IAAI,CAAC;QACd,CAAC;QAGD,OAAO;YACL,GAAG,GAAG;YACN,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;YAChE,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;SACjE,CAAC;IACJ,CAAC;IAKD,KAAK,CAAC,cAAc,CAAC,QAAuB,EAAE,SAAkB,EAAE,OAAgB;QAChF,MAAM,KAAK,GAAQ,EAAE,CAAC;QAEtB,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtB,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAC5B,CAAC;QAED,IAAI,SAAS,IAAI,OAAO,EAAE,CAAC;YACzB,KAAK,CAAC,SAAS,GAAG,EAAE,CAAC;YACrB,IAAI,SAAS,EAAE,CAAC;gBACd,KAAK,CAAC,SAAS,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;YAC5C,CAAC;YACD,IAAI,OAAO,EAAE,CAAC;gBACZ,KAAK,CAAC,SAAS,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC;YACnD,KAAK;YACL,MAAM,EAAE;gBACN,MAAM,EAAE,IAAI;gBACZ,MAAM,EAAE,IAAI;aACb;SACF,CAAC,CAAC;QAGH,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC9C,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;QAE9C,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;YACnB,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACpE,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACtE,CAAC,CAAC,CAAC;QAEH,OAAO;YACL,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;iBACvC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;iBACzC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;YACpC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;iBACvC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;iBACzC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;YACpC,KAAK,EAAE,IAAI,CAAC,MAAM;SACnB,CAAC;IACJ,CAAC;IAKO,aAAa,CAAC,GAAW;QAC/B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,GAAG,CAAC;QACb,CAAC;IACH,CAAC;CACF,CAAA;AAtKY,kDAAmB;8BAAnB,mBAAmB;IAD/B,IAAA,mBAAU,GAAE;qCAIiB,8BAAa;GAH9B,mBAAmB,CAsK/B"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/course/course-validation.service.d.ts b/reading-platform-backend/dist/src/modules/course/course-validation.service.d.ts new file mode 100644 index 0000000..9ab435d --- /dev/null +++ b/reading-platform-backend/dist/src/modules/course/course-validation.service.d.ts @@ -0,0 +1,42 @@ +export interface ValidationResult { + valid: boolean; + errors: ValidationError[]; + warnings: ValidationWarning[]; +} +export interface ValidationError { + field: string; + message: string; + code: string; +} +export interface ValidationWarning { + field: string; + message: string; + code: string; +} +export interface CourseValidationData { + name?: string; + description?: string; + coverImagePath?: string; + gradeTags?: string; + domainTags?: string; + duration?: number; + ebookPaths?: string; + audioPaths?: string; + videoPaths?: string; + otherResources?: string; + scripts?: any[]; + lessonPlanData?: string; +} +export declare class CourseValidationService { + private readonly logger; + validateForSubmit(course: CourseValidationData): Promise; + private validateBasicInfo; + private validateCover; + private validateGradeTags; + private validateDuration; + private validateResources; + private validateScripts; + private hasValidJsonArray; + canSubmit(course: CourseValidationData): Promise; + getValidationSummary(result: ValidationResult): string; +} diff --git a/reading-platform-backend/dist/src/modules/course/course-validation.service.js b/reading-platform-backend/dist/src/modules/course/course-validation.service.js new file mode 100644 index 0000000..faed448 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/course/course-validation.service.js @@ -0,0 +1,190 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var CourseValidationService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CourseValidationService = void 0; +const common_1 = require("@nestjs/common"); +let CourseValidationService = CourseValidationService_1 = class CourseValidationService { + constructor() { + this.logger = new common_1.Logger(CourseValidationService_1.name); + } + async validateForSubmit(course) { + const errors = []; + const warnings = []; + this.validateBasicInfo(course, errors); + this.validateCover(course, errors); + this.validateGradeTags(course, errors); + this.validateDuration(course, errors); + this.validateResources(course, warnings); + this.validateScripts(course, errors); + const result = { + valid: errors.length === 0, + errors, + warnings, + }; + this.logger.log(`Validation result: valid=${result.valid}, errors=${errors.length}, warnings=${warnings.length}`); + return result; + } + validateBasicInfo(course, errors) { + if (!course.name || course.name.trim().length === 0) { + errors.push({ + field: 'name', + message: '请输入课程名称', + code: 'NAME_REQUIRED', + }); + } + else if (course.name.length < 2) { + errors.push({ + field: 'name', + message: '课程名称至少需要2个字符', + code: 'NAME_TOO_SHORT', + }); + } + else if (course.name.length > 50) { + errors.push({ + field: 'name', + message: '课程名称不能超过50个字符', + code: 'NAME_TOO_LONG', + }); + } + } + validateCover(course, errors) { + if (!course.coverImagePath) { + errors.push({ + field: 'coverImagePath', + message: '请上传课程封面', + code: 'COVER_REQUIRED', + }); + } + } + validateGradeTags(course, errors) { + if (!course.gradeTags) { + errors.push({ + field: 'gradeTags', + message: '请选择适用年级', + code: 'GRADE_REQUIRED', + }); + return; + } + try { + const grades = JSON.parse(course.gradeTags); + if (!Array.isArray(grades) || grades.length === 0) { + errors.push({ + field: 'gradeTags', + message: '请至少选择一个适用年级', + code: 'GRADE_EMPTY', + }); + } + } + catch { + errors.push({ + field: 'gradeTags', + message: '年级标签格式错误', + code: 'GRADE_FORMAT_ERROR', + }); + } + } + validateDuration(course, errors) { + if (course.duration === undefined || course.duration === null) { + errors.push({ + field: 'duration', + message: '请设置课程时长', + code: 'DURATION_REQUIRED', + }); + return; + } + if (course.duration < 5) { + errors.push({ + field: 'duration', + message: '课程时长不能少于5分钟', + code: 'DURATION_TOO_SHORT', + }); + } + else if (course.duration > 60) { + errors.push({ + field: 'duration', + message: '课程时长不能超过60分钟', + code: 'DURATION_TOO_LONG', + }); + } + } + validateResources(course, warnings) { + const hasEbook = this.hasValidJsonArray(course.ebookPaths); + const hasAudio = this.hasValidJsonArray(course.audioPaths); + const hasVideo = this.hasValidJsonArray(course.videoPaths); + const hasOther = this.hasValidJsonArray(course.otherResources); + if (!hasEbook && !hasAudio && !hasVideo && !hasOther) { + warnings.push({ + field: 'resources', + message: '建议上传至少1个数字资源(电子绘本、音频或视频)', + code: 'RESOURCES_SUGGESTED', + }); + } + } + validateScripts(course, errors) { + if (course.lessonPlanData) { + try { + const lessonPlan = JSON.parse(course.lessonPlanData); + if (!lessonPlan.phases || !Array.isArray(lessonPlan.phases) || lessonPlan.phases.length === 0) { + errors.push({ + field: 'lessonPlanData', + message: '请至少配置一个教学环节', + code: 'SCRIPTS_REQUIRED', + }); + } + return; + } + catch { + errors.push({ + field: 'lessonPlanData', + message: '教学计划数据格式错误', + code: 'LESSON_PLAN_FORMAT_ERROR', + }); + return; + } + } + if (course.scripts !== undefined) { + if (!Array.isArray(course.scripts) || course.scripts.length === 0) { + errors.push({ + field: 'scripts', + message: '请至少配置一个教学环节', + code: 'SCRIPTS_REQUIRED', + }); + } + } + } + hasValidJsonArray(jsonStr) { + if (!jsonStr) + return false; + try { + const arr = JSON.parse(jsonStr); + return Array.isArray(arr) && arr.length > 0; + } + catch { + return false; + } + } + async canSubmit(course) { + const result = await this.validateForSubmit(course); + return result.valid; + } + getValidationSummary(result) { + if (result.valid && result.warnings.length === 0) { + return '课程内容完整,可以提交审核'; + } + if (result.valid && result.warnings.length > 0) { + return `课程可以提交,但有 ${result.warnings.length} 条建议`; + } + return `课程有 ${result.errors.length} 个问题需要修复`; + } +}; +exports.CourseValidationService = CourseValidationService; +exports.CourseValidationService = CourseValidationService = CourseValidationService_1 = __decorate([ + (0, common_1.Injectable)() +], CourseValidationService); +//# sourceMappingURL=course-validation.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/course/course-validation.service.js.map b/reading-platform-backend/dist/src/modules/course/course-validation.service.js.map new file mode 100644 index 0000000..7fc017b --- /dev/null +++ b/reading-platform-backend/dist/src/modules/course/course-validation.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"course-validation.service.js","sourceRoot":"","sources":["../../../../src/modules/course/course-validation.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/course/course.controller.d.ts b/reading-platform-backend/dist/src/modules/course/course.controller.d.ts new file mode 100644 index 0000000..60502e7 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/course/course.controller.d.ts @@ -0,0 +1,625 @@ +import { CourseService } from './course.service'; +export declare class CourseController { + private readonly courseService; + constructor(courseService: CourseService); + findAll(query: any): Promise<{ + items: { + id: number; + status: string; + createdAt: Date; + name: string; + pictureBookName: string; + gradeTags: string; + version: string; + submittedAt: Date; + reviewedAt: Date; + reviewComment: string; + usageCount: number; + teacherCount: number; + avgRating: number; + }[]; + total: number; + page: number; + pageSize: number; + }>; + getReviewList(query: any): Promise<{ + items: { + id: number; + status: string; + name: string; + coverImagePath: string; + gradeTags: string; + submittedAt: Date; + submittedBy: number; + reviewedAt: Date; + reviewedBy: number; + reviewComment: string; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findOne(id: string): Promise<{ + resources: { + id: number; + createdAt: Date; + sortOrder: number; + courseId: number; + resourceType: string; + resourceName: string; + fileUrl: string; + fileSize: number | null; + mimeType: string | null; + metadata: string | null; + }[]; + scripts: ({ + pages: { + id: number; + createdAt: Date; + updatedAt: Date; + resourceIds: string | null; + pageNumber: number; + scriptId: number; + questions: string | null; + interactionComponent: string | null; + teacherNotes: string | null; + }[]; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + duration: number; + sortOrder: number; + courseId: number; + stepIndex: number; + stepName: string; + stepType: string; + objective: string | null; + teacherScript: string | null; + interactionPoints: string | null; + resourceIds: string | null; + })[]; + activities: { + id: number; + createdAt: Date; + name: string; + duration: number | null; + sortOrder: number; + courseId: number; + domain: string | null; + domainTagId: number | null; + activityType: string; + onlineMaterials: string | null; + offlineMaterials: string | null; + activityGuide: string | null; + objectives: string | null; + }[]; + } & { + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + getStats(id: string): Promise<{ + courseName: string; + totalLessons: number; + totalTeachers: number; + totalStudents: number; + avgRating: number; + lessonTrend: any[]; + feedbackDistribution: { + designQuality: number; + participation: number; + goalAchievement: number; + totalFeedbacks: number; + }; + recentLessons: any[]; + studentPerformance: { + avgFocus: number; + avgParticipation: number; + avgInterest: number; + avgUnderstanding: number; + }; + }>; + validate(id: string): Promise; + getVersionHistory(id: string): Promise<{ + id: number; + version: string; + changeLog: string; + publishedAt: Date; + publishedBy: number; + }[]>; + create(createCourseDto: any, req: any): Promise<{ + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + update(id: string, updateCourseDto: any): Promise<{ + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + remove(id: string): Promise<{ + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + submit(id: string, body: { + copyrightConfirmed: boolean; + }, req: any): Promise<{ + validationSummary: string; + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + withdraw(id: string, req: any): Promise<{ + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + approve(id: string, body: { + checklist?: any; + comment?: string; + }, req: any): Promise<{ + authorizedTenantCount: number; + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + reject(id: string, body: { + checklist?: any; + comment: string; + }, req: any): Promise<{ + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + directPublish(id: string, body: { + skipValidation?: boolean; + }, req: any): Promise<{ + authorizedTenantCount: number; + validationSkipped: boolean; + validationWarnings: import("./course-validation.service").ValidationWarning[]; + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + publish(id: string): Promise<{ + authorizedTenantCount: number; + validationSkipped: boolean; + validationWarnings: import("./course-validation.service").ValidationWarning[]; + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + unpublish(id: string): Promise<{ + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + republish(id: string): Promise<{ + authorizedTenantCount: number; + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/course/course.controller.js b/reading-platform-backend/dist/src/modules/course/course.controller.js new file mode 100644 index 0000000..c8eeb3e --- /dev/null +++ b/reading-platform-backend/dist/src/modules/course/course.controller.js @@ -0,0 +1,223 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CourseController = void 0; +const common_1 = require("@nestjs/common"); +const course_service_1 = require("./course.service"); +const jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +const roles_guard_1 = require("../common/guards/roles.guard"); +const roles_decorator_1 = require("../common/decorators/roles.decorator"); +let CourseController = class CourseController { + constructor(courseService) { + this.courseService = courseService; + } + findAll(query) { + return this.courseService.findAll(query); + } + getReviewList(query) { + return this.courseService.getReviewList(query); + } + findOne(id) { + return this.courseService.findOne(+id); + } + getStats(id) { + return this.courseService.getStats(+id); + } + validate(id) { + return this.courseService.validate(+id); + } + getVersionHistory(id) { + return this.courseService.getVersionHistory(+id); + } + create(createCourseDto, req) { + return this.courseService.create({ + ...createCourseDto, + createdBy: req.user?.userId, + }); + } + update(id, updateCourseDto) { + return this.courseService.update(+id, updateCourseDto); + } + remove(id) { + return this.courseService.remove(+id); + } + submit(id, body, req) { + const userId = req.user?.userId || 0; + return this.courseService.submit(+id, userId, body.copyrightConfirmed); + } + withdraw(id, req) { + const userId = req.user?.userId || 0; + return this.courseService.withdraw(+id, userId); + } + approve(id, body, req) { + const reviewerId = req.user?.userId || 0; + return this.courseService.approve(+id, reviewerId, body); + } + reject(id, body, req) { + const reviewerId = req.user?.userId || 0; + return this.courseService.reject(+id, reviewerId, body); + } + directPublish(id, body, req) { + const userId = req.user?.userId || 0; + return this.courseService.directPublish(+id, userId, body.skipValidation); + } + publish(id) { + return this.courseService.publish(+id); + } + unpublish(id) { + return this.courseService.unpublish(+id); + } + republish(id) { + return this.courseService.republish(+id); + } +}; +exports.CourseController = CourseController; +__decorate([ + (0, common_1.Get)(), + __param(0, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], CourseController.prototype, "findAll", null); +__decorate([ + (0, common_1.Get)('review'), + __param(0, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], CourseController.prototype, "getReviewList", null); +__decorate([ + (0, common_1.Get)(':id'), + __param(0, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", void 0) +], CourseController.prototype, "findOne", null); +__decorate([ + (0, common_1.Get)(':id/stats'), + __param(0, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", void 0) +], CourseController.prototype, "getStats", null); +__decorate([ + (0, common_1.Get)(':id/validate'), + __param(0, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", void 0) +], CourseController.prototype, "validate", null); +__decorate([ + (0, common_1.Get)(':id/versions'), + __param(0, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", void 0) +], CourseController.prototype, "getVersionHistory", null); +__decorate([ + (0, common_1.Post)(), + __param(0, (0, common_1.Body)()), + __param(1, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], CourseController.prototype, "create", null); +__decorate([ + (0, common_1.Put)(':id'), + __param(0, (0, common_1.Param)('id')), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, Object]), + __metadata("design:returntype", void 0) +], CourseController.prototype, "update", null); +__decorate([ + (0, common_1.Delete)(':id'), + __param(0, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", void 0) +], CourseController.prototype, "remove", null); +__decorate([ + (0, common_1.Post)(':id/submit'), + __param(0, (0, common_1.Param)('id')), + __param(1, (0, common_1.Body)()), + __param(2, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, Object, Object]), + __metadata("design:returntype", void 0) +], CourseController.prototype, "submit", null); +__decorate([ + (0, common_1.Post)(':id/withdraw'), + __param(0, (0, common_1.Param)('id')), + __param(1, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, Object]), + __metadata("design:returntype", void 0) +], CourseController.prototype, "withdraw", null); +__decorate([ + (0, common_1.Post)(':id/approve'), + __param(0, (0, common_1.Param)('id')), + __param(1, (0, common_1.Body)()), + __param(2, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, Object, Object]), + __metadata("design:returntype", void 0) +], CourseController.prototype, "approve", null); +__decorate([ + (0, common_1.Post)(':id/reject'), + __param(0, (0, common_1.Param)('id')), + __param(1, (0, common_1.Body)()), + __param(2, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, Object, Object]), + __metadata("design:returntype", void 0) +], CourseController.prototype, "reject", null); +__decorate([ + (0, common_1.Post)(':id/direct-publish'), + (0, roles_decorator_1.Roles)('admin'), + __param(0, (0, common_1.Param)('id')), + __param(1, (0, common_1.Body)()), + __param(2, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, Object, Object]), + __metadata("design:returntype", void 0) +], CourseController.prototype, "directPublish", null); +__decorate([ + (0, common_1.Post)(':id/publish'), + __param(0, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", void 0) +], CourseController.prototype, "publish", null); +__decorate([ + (0, common_1.Post)(':id/unpublish'), + __param(0, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", void 0) +], CourseController.prototype, "unpublish", null); +__decorate([ + (0, common_1.Post)(':id/republish'), + __param(0, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", void 0) +], CourseController.prototype, "republish", null); +exports.CourseController = CourseController = __decorate([ + (0, common_1.Controller)('courses'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('admin'), + __metadata("design:paramtypes", [course_service_1.CourseService]) +], CourseController); +//# sourceMappingURL=course.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/course/course.controller.js.map b/reading-platform-backend/dist/src/modules/course/course.controller.js.map new file mode 100644 index 0000000..018da62 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/course/course.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"course.controller.js","sourceRoot":"","sources":["../../../../src/modules/course/course.controller.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/course/course.module.d.ts b/reading-platform-backend/dist/src/modules/course/course.module.d.ts new file mode 100644 index 0000000..30ef43d --- /dev/null +++ b/reading-platform-backend/dist/src/modules/course/course.module.d.ts @@ -0,0 +1,2 @@ +export declare class CourseModule { +} diff --git a/reading-platform-backend/dist/src/modules/course/course.module.js b/reading-platform-backend/dist/src/modules/course/course.module.js new file mode 100644 index 0000000..0553a33 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/course/course.module.js @@ -0,0 +1,26 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CourseModule = void 0; +const common_1 = require("@nestjs/common"); +const course_controller_1 = require("./course.controller"); +const course_service_1 = require("./course.service"); +const course_validation_service_1 = require("./course-validation.service"); +const prisma_module_1 = require("../../database/prisma.module"); +let CourseModule = class CourseModule { +}; +exports.CourseModule = CourseModule; +exports.CourseModule = CourseModule = __decorate([ + (0, common_1.Module)({ + imports: [prisma_module_1.PrismaModule], + controllers: [course_controller_1.CourseController], + providers: [course_service_1.CourseService, course_validation_service_1.CourseValidationService], + exports: [course_service_1.CourseService, course_validation_service_1.CourseValidationService], + }) +], CourseModule); +//# sourceMappingURL=course.module.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/course/course.module.js.map b/reading-platform-backend/dist/src/modules/course/course.module.js.map new file mode 100644 index 0000000..92aaf8c --- /dev/null +++ b/reading-platform-backend/dist/src/modules/course/course.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"course.module.js","sourceRoot":"","sources":["../../../../src/modules/course/course.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2DAAuD;AACvD,qDAAiD;AACjD,2EAAsE;AACtE,gEAA4D;AAQrD,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,oCAAY;uBAAZ,YAAY;IANxB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,4BAAY,CAAC;QACvB,WAAW,EAAE,CAAC,oCAAgB,CAAC;QAC/B,SAAS,EAAE,CAAC,8BAAa,EAAE,mDAAuB,CAAC;QACnD,OAAO,EAAE,CAAC,8BAAa,EAAE,mDAAuB,CAAC;KAClD,CAAC;GACW,YAAY,CAAG"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/course/course.service.d.ts b/reading-platform-backend/dist/src/modules/course/course.service.d.ts new file mode 100644 index 0000000..6497de3 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/course/course.service.d.ts @@ -0,0 +1,627 @@ +import { PrismaService } from '../../database/prisma.service'; +import { CourseValidationService, ValidationResult } from './course-validation.service'; +export declare class CourseService { + private prisma; + private validationService; + private readonly logger; + constructor(prisma: PrismaService, validationService: CourseValidationService); + findAll(query: any): Promise<{ + items: { + id: number; + status: string; + createdAt: Date; + name: string; + pictureBookName: string; + gradeTags: string; + version: string; + submittedAt: Date; + reviewedAt: Date; + reviewComment: string; + usageCount: number; + teacherCount: number; + avgRating: number; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findOne(id: number): Promise<{ + resources: { + id: number; + createdAt: Date; + sortOrder: number; + courseId: number; + resourceType: string; + resourceName: string; + fileUrl: string; + fileSize: number | null; + mimeType: string | null; + metadata: string | null; + }[]; + scripts: ({ + pages: { + id: number; + createdAt: Date; + updatedAt: Date; + resourceIds: string | null; + pageNumber: number; + scriptId: number; + questions: string | null; + interactionComponent: string | null; + teacherNotes: string | null; + }[]; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + duration: number; + sortOrder: number; + courseId: number; + stepIndex: number; + stepName: string; + stepType: string; + objective: string | null; + teacherScript: string | null; + interactionPoints: string | null; + resourceIds: string | null; + })[]; + activities: { + id: number; + createdAt: Date; + name: string; + duration: number | null; + sortOrder: number; + courseId: number; + domain: string | null; + domainTagId: number | null; + activityType: string; + onlineMaterials: string | null; + offlineMaterials: string | null; + activityGuide: string | null; + objectives: string | null; + }[]; + } & { + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + create(createCourseDto: any): Promise<{ + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + update(id: number, updateCourseDto: any): Promise<{ + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + private syncLessonPlanToScripts; + private syncActivitiesToTable; + private mapActivityType; + remove(id: number): Promise<{ + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + validate(id: number): Promise; + submit(id: number, userId: number, copyrightConfirmed: boolean): Promise<{ + validationSummary: string; + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + withdraw(id: number, userId: number): Promise<{ + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + approve(id: number, reviewerId: number, reviewData: { + checklist?: any; + comment?: string; + }): Promise<{ + authorizedTenantCount: number; + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + reject(id: number, reviewerId: number, reviewData: { + checklist?: any; + comment: string; + }): Promise<{ + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + directPublish(id: number, userId: number, skipValidation?: boolean): Promise<{ + authorizedTenantCount: number; + validationSkipped: boolean; + validationWarnings: import("./course-validation.service").ValidationWarning[]; + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + publish(id: number): Promise<{ + authorizedTenantCount: number; + validationSkipped: boolean; + validationWarnings: import("./course-validation.service").ValidationWarning[]; + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + unpublish(id: number): Promise<{ + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + republish(id: number): Promise<{ + authorizedTenantCount: number; + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + ebookPaths: string | null; + audioPaths: string | null; + videoPaths: string | null; + otherResources: string | null; + pptPath: string | null; + pptName: string | null; + posterPaths: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + gradeTags: string; + domainTags: string; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + getStats(id: number): Promise<{ + courseName: string; + totalLessons: number; + totalTeachers: number; + totalStudents: number; + avgRating: number; + lessonTrend: any[]; + feedbackDistribution: { + designQuality: number; + participation: number; + goalAchievement: number; + totalFeedbacks: number; + }; + recentLessons: any[]; + studentPerformance: { + avgFocus: number; + avgParticipation: number; + avgInterest: number; + avgUnderstanding: number; + }; + }>; + getReviewList(query: any): Promise<{ + items: { + id: number; + status: string; + name: string; + coverImagePath: string; + gradeTags: string; + submittedAt: Date; + submittedBy: number; + reviewedAt: Date; + reviewedBy: number; + reviewComment: string; + }[]; + total: number; + page: number; + pageSize: number; + }>; + getVersionHistory(id: number): Promise<{ + id: number; + version: string; + changeLog: string; + publishedAt: Date; + publishedBy: number; + }[]>; +} diff --git a/reading-platform-backend/dist/src/modules/course/course.service.js b/reading-platform-backend/dist/src/modules/course/course.service.js new file mode 100644 index 0000000..74e6618 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/course/course.service.js @@ -0,0 +1,750 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var CourseService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CourseService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../../database/prisma.service"); +const course_validation_service_1 = require("./course-validation.service"); +let CourseService = CourseService_1 = class CourseService { + constructor(prisma, validationService) { + this.prisma = prisma; + this.validationService = validationService; + this.logger = new common_1.Logger(CourseService_1.name); + } + async findAll(query) { + const { page = 1, pageSize = 10, grade, status, keyword } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = {}; + if (status) { + where.status = status; + } + if (keyword) { + where.name = { contains: keyword }; + } + if (grade) { + const gradeUpper = grade.toUpperCase(); + where.OR = [ + { gradeTags: { contains: gradeUpper } }, + { gradeTags: { contains: grade.toLowerCase() } }, + ]; + } + const [items, total] = await Promise.all([ + this.prisma.course.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + select: { + id: true, + name: true, + pictureBookName: true, + gradeTags: true, + status: true, + version: true, + usageCount: true, + teacherCount: true, + avgRating: true, + createdAt: true, + submittedAt: true, + reviewedAt: true, + reviewComment: true, + }, + }), + this.prisma.course.count({ where }), + ]); + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + async findOne(id) { + const course = await this.prisma.course.findUnique({ + where: { id }, + include: { + resources: { + orderBy: { sortOrder: 'asc' }, + }, + scripts: { + orderBy: { sortOrder: 'asc' }, + include: { + pages: { + orderBy: { pageNumber: 'asc' }, + }, + }, + }, + activities: { + orderBy: { sortOrder: 'asc' }, + }, + }, + }); + if (!course) { + throw new common_1.NotFoundException(`Course #${id} not found`); + } + return course; + } + async create(createCourseDto) { + try { + this.logger.log(`Creating course with data: ${JSON.stringify(createCourseDto)}`); + const result = await this.prisma.course.create({ + data: createCourseDto, + }); + this.logger.log(`Course created successfully with ID: ${result.id}`); + return result; + } + catch (error) { + this.logger.error(`Error creating course: ${error.message}`, error.stack); + throw error; + } + } + async update(id, updateCourseDto) { + const fieldsToClear = [ + 'coverImagePath', + 'ebookPaths', + 'audioPaths', + 'videoPaths', + 'otherResources', + 'pptPath', + 'pptName', + 'posterPaths', + 'tools', + 'studentMaterials', + 'pictureBookName', + 'lessonPlanData', + 'activitiesData', + 'assessmentData', + ]; + const cleanedData = {}; + for (const [key, value] of Object.entries(updateCourseDto)) { + if (fieldsToClear.includes(key) && (value === null || value === '')) { + cleanedData[key] = null; + } + else if (value !== undefined) { + cleanedData[key] = value; + } + } + this.logger.log(`Updating course ${id} with data: ${JSON.stringify(Object.keys(cleanedData))}`); + return this.prisma.$transaction(async (tx) => { + const updatedCourse = await tx.course.update({ + where: { id }, + data: cleanedData, + }); + if (updateCourseDto.lessonPlanData !== undefined) { + await this.syncLessonPlanToScripts(tx, id, updateCourseDto.lessonPlanData); + } + if (updateCourseDto.activitiesData !== undefined) { + await this.syncActivitiesToTable(tx, id, updateCourseDto.activitiesData); + } + return updatedCourse; + }); + } + async syncLessonPlanToScripts(tx, courseId, lessonPlanData) { + await tx.courseScriptPage.deleteMany({ + where: { script: { courseId } }, + }); + await tx.courseScript.deleteMany({ + where: { courseId }, + }); + if (!lessonPlanData) { + this.logger.log(`Course ${courseId}: lessonPlanData is null, cleared scripts`); + return; + } + try { + const lessonPlan = JSON.parse(lessonPlanData); + const phases = lessonPlan.phases || []; + const topLevelScriptPages = lessonPlan.scriptPages || []; + this.logger.log(`=== 同步课程 ${courseId} 的教学脚本 ===`); + this.logger.log(`phases 数量: ${phases.length}`); + this.logger.log(`顶层 scriptPages 数量: ${topLevelScriptPages.length}`); + for (let i = 0; i < phases.length; i++) { + const phase = phases[i]; + this.logger.log(`Phase ${i}: name=${phase.name}, pages=${phase.pages?.length || 0}, enablePageScript=${phase.enablePageScript}`); + const script = await tx.courseScript.create({ + data: { + courseId, + stepIndex: i + 1, + stepName: phase.name || `步骤${i + 1}`, + stepType: phase.type || 'CUSTOM', + duration: phase.duration || 5, + objective: phase.objective || null, + teacherScript: phase.content || null, + interactionPoints: null, + resourceIds: phase.resourceIds ? JSON.stringify(phase.resourceIds) : null, + sortOrder: i, + }, + }); + let pagesToCreate = phase.pages || []; + if (pagesToCreate.length === 0 && topLevelScriptPages.length > 0 && i === 0) { + pagesToCreate = topLevelScriptPages; + } + if (pagesToCreate.length > 0) { + this.logger.log(`为 Phase ${i} 创建 ${pagesToCreate.length} 页逐页脚本`); + for (const page of pagesToCreate) { + await tx.courseScriptPage.create({ + data: { + scriptId: script.id, + pageNumber: page.pageNumber, + questions: page.teacherScript || null, + interactionComponent: page.actions ? JSON.stringify(page.actions) : null, + teacherNotes: page.notes || null, + resourceIds: page.resourceIds ? JSON.stringify(page.resourceIds) : null, + }, + }); + } + } + } + this.logger.log(`Course ${courseId}: synced ${phases.length} scripts from lessonPlanData`); + } + catch (error) { + this.logger.error(`Failed to sync lessonPlanData for course ${courseId}: ${error.message}`); + } + } + async syncActivitiesToTable(tx, courseId, activitiesData) { + await tx.courseActivity.deleteMany({ + where: { courseId }, + }); + if (!activitiesData) { + this.logger.log(`Course ${courseId}: activitiesData is null, cleared activities`); + return; + } + try { + const activities = JSON.parse(activitiesData); + for (let i = 0; i < activities.length; i++) { + const activity = activities[i]; + await tx.courseActivity.create({ + data: { + courseId, + name: activity.name || `活动${i + 1}`, + domain: activity.domain || null, + domainTagId: null, + activityType: this.mapActivityType(activity.type), + duration: activity.duration || 15, + onlineMaterials: activity.content ? JSON.stringify({ content: activity.content }) : null, + offlineMaterials: activity.materials || null, + activityGuide: null, + objectives: null, + sortOrder: i, + }, + }); + } + this.logger.log(`Course ${courseId}: synced ${activities.length} activities from activitiesData`); + } + catch (error) { + this.logger.error(`Failed to sync activitiesData for course ${courseId}: ${error.message}`); + } + } + mapActivityType(type) { + const typeMap = { + 'family': 'FAMILY', + 'art': 'ART', + 'game': 'GAME', + 'outdoor': 'OUTDOOR', + 'other': 'OTHER', + 'handicraft': 'HANDICRAFT', + 'music': 'MUSIC', + 'exploration': 'EXPLORATION', + 'sports': 'SPORTS', + '家庭延伸': 'FAMILY', + '美工活动': 'ART', + '游戏活动': 'GAME', + '户外活动': 'OUTDOOR', + '其他': 'OTHER', + '手工活动': 'HANDICRAFT', + '音乐活动': 'MUSIC', + '探索活动': 'EXPLORATION', + '运动活动': 'SPORTS', + '亲子活动': 'FAMILY', + }; + return typeMap[type || ''] || 'OTHER'; + } + async remove(id) { + return this.prisma.course.delete({ + where: { id }, + }); + } + async validate(id) { + const course = await this.findOne(id); + return this.validationService.validateForSubmit(course); + } + async submit(id, userId, copyrightConfirmed) { + const course = await this.prisma.course.findUnique({ + where: { id }, + }); + if (!course) { + throw new common_1.NotFoundException(`Course #${id} not found`); + } + if (course.status !== 'DRAFT' && course.status !== 'REJECTED') { + throw new common_1.BadRequestException(`课程状态为 ${course.status},无法提交审核`); + } + const validationResult = await this.validationService.validateForSubmit(course); + if (!validationResult.valid) { + throw new common_1.BadRequestException({ + message: '课程内容不完整,请检查以下问题', + errors: validationResult.errors, + warnings: validationResult.warnings, + }); + } + if (!copyrightConfirmed) { + throw new common_1.BadRequestException('请确认版权合规'); + } + const updatedCourse = await this.prisma.course.update({ + where: { id }, + data: { + status: 'PENDING', + submittedAt: new Date(), + submittedBy: userId, + }, + }); + this.logger.log(`Course ${id} submitted for review by user ${userId}`); + return { + ...updatedCourse, + validationSummary: this.validationService.getValidationSummary(validationResult), + }; + } + async withdraw(id, userId) { + const course = await this.prisma.course.findUnique({ + where: { id }, + }); + if (!course) { + throw new common_1.NotFoundException(`Course #${id} not found`); + } + if (course.status !== 'PENDING') { + throw new common_1.BadRequestException(`课程状态为 ${course.status},无法撤销`); + } + const updatedCourse = await this.prisma.course.update({ + where: { id }, + data: { + status: 'DRAFT', + submittedAt: null, + submittedBy: null, + }, + }); + this.logger.log(`Course ${id} review withdrawn by user ${userId}`); + return updatedCourse; + } + async approve(id, reviewerId, reviewData) { + const course = await this.prisma.course.findUnique({ + where: { id }, + }); + if (!course) { + throw new common_1.NotFoundException(`Course #${id} not found`); + } + if (course.status !== 'PENDING') { + throw new common_1.BadRequestException(`课程状态为 ${course.status},无法审核`); + } + if (course.submittedBy === reviewerId) { + throw new common_1.BadRequestException('不能审核自己提交的课程'); + } + const result = await this.prisma.$transaction(async (tx) => { + const updatedCourse = await tx.course.update({ + where: { id }, + data: { + status: 'PUBLISHED', + reviewedAt: new Date(), + reviewedBy: reviewerId, + reviewComment: reviewData.comment || null, + reviewChecklist: reviewData.checklist ? JSON.stringify(reviewData.checklist) : null, + publishedAt: new Date(), + }, + }); + await tx.courseVersion.create({ + data: { + courseId: id, + version: course.version, + snapshotData: JSON.stringify(course), + changeLog: reviewData.comment || '审核通过发布', + publishedBy: reviewerId, + }, + }); + return updatedCourse; + }); + const activeTenants = await this.prisma.tenant.findMany({ + where: { status: 'ACTIVE' }, + select: { id: true }, + }); + this.logger.log(`Publishing course ${id} to ${activeTenants.length} active tenants`); + for (const tenant of activeTenants) { + await this.prisma.tenantCourse.upsert({ + where: { + tenantId_courseId: { + tenantId: tenant.id, + courseId: id, + }, + }, + update: { + authorized: true, + authorizedAt: new Date(), + }, + create: { + tenantId: tenant.id, + courseId: id, + authorized: true, + authorizedAt: new Date(), + }, + }); + } + this.logger.log(`Course ${id} approved and published by reviewer ${reviewerId}`); + return { + ...result, + authorizedTenantCount: activeTenants.length, + }; + } + async reject(id, reviewerId, reviewData) { + const course = await this.prisma.course.findUnique({ + where: { id }, + }); + if (!course) { + throw new common_1.NotFoundException(`Course #${id} not found`); + } + if (course.status !== 'PENDING') { + throw new common_1.BadRequestException(`课程状态为 ${course.status},无法审核`); + } + if (course.submittedBy === reviewerId) { + throw new common_1.BadRequestException('不能审核自己提交的课程'); + } + if (!reviewData.comment || reviewData.comment.trim().length === 0) { + throw new common_1.BadRequestException('请填写驳回原因'); + } + const updatedCourse = await this.prisma.course.update({ + where: { id }, + data: { + status: 'REJECTED', + reviewedAt: new Date(), + reviewedBy: reviewerId, + reviewComment: reviewData.comment, + reviewChecklist: reviewData.checklist ? JSON.stringify(reviewData.checklist) : null, + }, + }); + this.logger.log(`Course ${id} rejected by reviewer ${reviewerId}: ${reviewData.comment}`); + return updatedCourse; + } + async directPublish(id, userId, skipValidation = false) { + const course = await this.prisma.course.findUnique({ + where: { id }, + }); + if (!course) { + throw new common_1.NotFoundException(`Course #${id} not found`); + } + if (course.status === 'PUBLISHED') { + throw new common_1.BadRequestException('课程已发布'); + } + const validationResult = await this.validationService.validateForSubmit(course); + if (!skipValidation && !validationResult.valid) { + throw new common_1.BadRequestException({ + message: '课程内容不完整,请检查以下问题', + errors: validationResult.errors, + warnings: validationResult.warnings, + }); + } + const result = await this.prisma.$transaction(async (tx) => { + const updatedCourse = await tx.course.update({ + where: { id }, + data: { + status: 'PUBLISHED', + publishedAt: new Date(), + reviewedAt: new Date(), + reviewedBy: userId, + reviewComment: '超级管理员直接发布', + }, + }); + await tx.courseVersion.create({ + data: { + courseId: id, + version: course.version, + snapshotData: JSON.stringify(course), + changeLog: '超级管理员直接发布', + publishedBy: userId, + }, + }); + return updatedCourse; + }); + const activeTenants = await this.prisma.tenant.findMany({ + where: { status: 'ACTIVE' }, + select: { id: true }, + }); + for (const tenant of activeTenants) { + await this.prisma.tenantCourse.upsert({ + where: { + tenantId_courseId: { + tenantId: tenant.id, + courseId: id, + }, + }, + update: { + authorized: true, + authorizedAt: new Date(), + }, + create: { + tenantId: tenant.id, + courseId: id, + authorized: true, + authorizedAt: new Date(), + }, + }); + } + this.logger.log(`Course ${id} directly published by super admin ${userId}`); + return { + ...result, + authorizedTenantCount: activeTenants.length, + validationSkipped: skipValidation && !validationResult.valid, + validationWarnings: validationResult.warnings, + }; + } + async publish(id) { + return this.directPublish(id, 0, false); + } + async unpublish(id) { + const course = await this.prisma.course.findUnique({ + where: { id }, + }); + if (!course) { + throw new common_1.NotFoundException(`Course #${id} not found`); + } + if (course.status !== 'PUBLISHED') { + throw new common_1.BadRequestException(`课程状态为 ${course.status},无法下架`); + } + const updatedCourse = await this.prisma.course.update({ + where: { id }, + data: { + status: 'ARCHIVED', + }, + }); + await this.prisma.tenantCourse.updateMany({ + where: { courseId: id }, + data: { + authorized: false, + }, + }); + this.logger.log(`Course ${id} unpublished`); + return updatedCourse; + } + async republish(id) { + const course = await this.prisma.course.findUnique({ + where: { id }, + }); + if (!course) { + throw new common_1.NotFoundException(`Course #${id} not found`); + } + if (course.status !== 'ARCHIVED') { + throw new common_1.BadRequestException(`课程状态为 ${course.status},无法重新发布`); + } + const updatedCourse = await this.prisma.course.update({ + where: { id }, + data: { + status: 'PUBLISHED', + }, + }); + const activeTenants = await this.prisma.tenant.findMany({ + where: { status: 'ACTIVE' }, + select: { id: true }, + }); + for (const tenant of activeTenants) { + await this.prisma.tenantCourse.upsert({ + where: { + tenantId_courseId: { + tenantId: tenant.id, + courseId: id, + }, + }, + update: { + authorized: true, + authorizedAt: new Date(), + }, + create: { + tenantId: tenant.id, + courseId: id, + authorized: true, + authorizedAt: new Date(), + }, + }); + } + this.logger.log(`Course ${id} republished`); + return { + ...updatedCourse, + authorizedTenantCount: activeTenants.length, + }; + } + async getStats(id) { + const course = await this.prisma.course.findUnique({ + where: { id }, + select: { + id: true, + name: true, + usageCount: true, + teacherCount: true, + avgRating: true, + }, + }); + if (!course) { + throw new common_1.NotFoundException(`Course #${id} not found`); + } + const lessons = await this.prisma.lesson.findMany({ + where: { courseId: id }, + include: { + teacher: { + select: { id: true, name: true }, + }, + class: { + select: { id: true, name: true }, + }, + }, + orderBy: { createdAt: 'desc' }, + take: 10, + }); + const feedbacks = await this.prisma.lessonFeedback.findMany({ + where: { + lesson: { + courseId: id, + }, + }, + }); + const calculateAverage = (field) => { + const validFeedbacks = feedbacks.filter((f) => f[field] != null); + if (validFeedbacks.length === 0) + return 0; + const sum = validFeedbacks.reduce((acc, f) => acc + f[field], 0); + return sum / validFeedbacks.length; + }; + const studentRecords = await this.prisma.studentRecord.findMany({ + where: { + lesson: { + courseId: id, + }, + }, + }); + const calculateStudentAvg = (field) => { + const validRecords = studentRecords.filter((r) => r[field] != null); + if (validRecords.length === 0) + return 0; + const sum = validRecords.reduce((acc, r) => acc + r[field], 0); + return sum / validRecords.length; + }; + const now = new Date(); + const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + const recentLessons = await this.prisma.lesson.findMany({ + where: { + courseId: id, + createdAt: { gte: weekAgo }, + }, + select: { + createdAt: true, + }, + }); + const lessonTrend = []; + for (let i = 6; i >= 0; i--) { + const date = new Date(now.getTime() - i * 24 * 60 * 60 * 1000); + const dateStr = date.toLocaleDateString('zh-CN', { weekday: 'short' }); + const count = recentLessons.filter((lesson) => { + const lessonDate = new Date(lesson.createdAt); + return lessonDate.toDateString() === date.toDateString(); + }).length; + lessonTrend.push({ date: dateStr, count }); + } + const uniqueStudentIds = new Set(); + lessons.forEach((lesson) => { + uniqueStudentIds.add(lesson.classId); + }); + return { + courseName: course.name, + totalLessons: course.usageCount || lessons.length, + totalTeachers: course.teacherCount || new Set(lessons.map((l) => l.teacherId)).size, + totalStudents: uniqueStudentIds.size, + avgRating: course.avgRating || 0, + lessonTrend, + feedbackDistribution: { + designQuality: calculateAverage('designQuality'), + participation: calculateAverage('participation'), + goalAchievement: calculateAverage('goalAchievement'), + totalFeedbacks: feedbacks.length, + }, + recentLessons: lessons.map((lesson) => ({ + ...lesson, + date: lesson.createdAt, + })), + studentPerformance: { + avgFocus: calculateStudentAvg('focus'), + avgParticipation: calculateStudentAvg('participation'), + avgInterest: calculateStudentAvg('interest'), + avgUnderstanding: calculateStudentAvg('understanding'), + }, + }; + } + async getReviewList(query) { + const { page = 1, pageSize = 10, status, submittedBy } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = { + status: { in: ['PENDING', 'REJECTED'] }, + }; + if (status) { + where.status = status; + } + if (submittedBy) { + where.submittedBy = +submittedBy; + } + const [items, total] = await Promise.all([ + this.prisma.course.findMany({ + where, + skip, + take, + orderBy: { submittedAt: 'desc' }, + select: { + id: true, + name: true, + status: true, + submittedAt: true, + submittedBy: true, + reviewedAt: true, + reviewedBy: true, + reviewComment: true, + coverImagePath: true, + gradeTags: true, + }, + }), + this.prisma.course.count({ where }), + ]); + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + async getVersionHistory(id) { + const course = await this.prisma.course.findUnique({ + where: { id }, + }); + if (!course) { + throw new common_1.NotFoundException(`Course #${id} not found`); + } + const versions = await this.prisma.courseVersion.findMany({ + where: { courseId: id }, + orderBy: { publishedAt: 'desc' }, + }); + return versions.map((v) => ({ + id: v.id, + version: v.version, + changeLog: v.changeLog, + publishedAt: v.publishedAt, + publishedBy: v.publishedBy, + })); + } +}; +exports.CourseService = CourseService; +exports.CourseService = CourseService = CourseService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService, + course_validation_service_1.CourseValidationService]) +], CourseService); +//# sourceMappingURL=course.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/course/course.service.js.map b/reading-platform-backend/dist/src/modules/course/course.service.js.map new file mode 100644 index 0000000..7f14b01 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/course/course.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"course.service.js","sourceRoot":"","sources":["../../../../src/modules/course/course.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/export/export.controller.d.ts b/reading-platform-backend/dist/src/modules/export/export.controller.d.ts new file mode 100644 index 0000000..be64f96 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/export/export.controller.d.ts @@ -0,0 +1,10 @@ +import { Response } from 'express'; +import { ExportService } from './export.service'; +export declare class ExportController { + private readonly exportService; + constructor(exportService: ExportService); + exportTeachers(req: any, res: Response): Promise; + exportStudents(req: any, res: Response): Promise; + exportLessons(req: any, res: Response): Promise; + exportGrowthRecords(req: any, studentId: string, res: Response): Promise; +} diff --git a/reading-platform-backend/dist/src/modules/export/export.controller.js b/reading-platform-backend/dist/src/modules/export/export.controller.js new file mode 100644 index 0000000..caa3980 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/export/export.controller.js @@ -0,0 +1,90 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ExportController = void 0; +const common_1 = require("@nestjs/common"); +const export_service_1 = require("./export.service"); +const jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +const roles_guard_1 = require("../common/guards/roles.guard"); +const roles_decorator_1 = require("../common/decorators/roles.decorator"); +let ExportController = class ExportController { + constructor(exportService) { + this.exportService = exportService; + } + async exportTeachers(req, res) { + const buffer = await this.exportService.exportTeachers(req.user.tenantId); + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + res.setHeader('Content-Disposition', `attachment; filename=teachers_${Date.now()}.xlsx`); + res.send(buffer); + } + async exportStudents(req, res) { + const buffer = await this.exportService.exportStudents(req.user.tenantId); + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + res.setHeader('Content-Disposition', `attachment; filename=students_${Date.now()}.xlsx`); + res.send(buffer); + } + async exportLessons(req, res) { + const buffer = await this.exportService.exportLessons(req.user.tenantId); + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + res.setHeader('Content-Disposition', `attachment; filename=lessons_${Date.now()}.xlsx`); + res.send(buffer); + } + async exportGrowthRecords(req, studentId, res) { + const buffer = await this.exportService.exportGrowthRecords(req.user.tenantId, studentId ? +studentId : undefined); + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + res.setHeader('Content-Disposition', `attachment; filename=growth_records_${Date.now()}.xlsx`); + res.send(buffer); + } +}; +exports.ExportController = ExportController; +__decorate([ + (0, common_1.Get)('teachers'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Res)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", Promise) +], ExportController.prototype, "exportTeachers", null); +__decorate([ + (0, common_1.Get)('students'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Res)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", Promise) +], ExportController.prototype, "exportStudents", null); +__decorate([ + (0, common_1.Get)('lessons'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Res)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", Promise) +], ExportController.prototype, "exportLessons", null); +__decorate([ + (0, common_1.Get)('growth-records'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)('studentId')), + __param(2, (0, common_1.Res)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", Promise) +], ExportController.prototype, "exportGrowthRecords", null); +exports.ExportController = ExportController = __decorate([ + (0, common_1.Controller)('school/export'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('school'), + __metadata("design:paramtypes", [export_service_1.ExportService]) +], ExportController); +//# sourceMappingURL=export.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/export/export.controller.js.map b/reading-platform-backend/dist/src/modules/export/export.controller.js.map new file mode 100644 index 0000000..a1ff88b --- /dev/null +++ b/reading-platform-backend/dist/src/modules/export/export.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"export.controller.js","sourceRoot":"","sources":["../../../../src/modules/export/export.controller.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/export/export.module.d.ts b/reading-platform-backend/dist/src/modules/export/export.module.d.ts new file mode 100644 index 0000000..b9723eb --- /dev/null +++ b/reading-platform-backend/dist/src/modules/export/export.module.d.ts @@ -0,0 +1,2 @@ +export declare class ExportModule { +} diff --git a/reading-platform-backend/dist/src/modules/export/export.module.js b/reading-platform-backend/dist/src/modules/export/export.module.js new file mode 100644 index 0000000..17a55ef --- /dev/null +++ b/reading-platform-backend/dist/src/modules/export/export.module.js @@ -0,0 +1,23 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ExportModule = void 0; +const common_1 = require("@nestjs/common"); +const export_controller_1 = require("./export.controller"); +const export_service_1 = require("./export.service"); +let ExportModule = class ExportModule { +}; +exports.ExportModule = ExportModule; +exports.ExportModule = ExportModule = __decorate([ + (0, common_1.Module)({ + controllers: [export_controller_1.ExportController], + providers: [export_service_1.ExportService], + exports: [export_service_1.ExportService], + }) +], ExportModule); +//# sourceMappingURL=export.module.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/export/export.module.js.map b/reading-platform-backend/dist/src/modules/export/export.module.js.map new file mode 100644 index 0000000..82770ab --- /dev/null +++ b/reading-platform-backend/dist/src/modules/export/export.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"export.module.js","sourceRoot":"","sources":["../../../../src/modules/export/export.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2DAAuD;AACvD,qDAAiD;AAO1C,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,oCAAY;uBAAZ,YAAY;IALxB,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,oCAAgB,CAAC;QAC/B,SAAS,EAAE,CAAC,8BAAa,CAAC;QAC1B,OAAO,EAAE,CAAC,8BAAa,CAAC;KACzB,CAAC;GACW,YAAY,CAAG"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/export/export.service.d.ts b/reading-platform-backend/dist/src/modules/export/export.service.d.ts new file mode 100644 index 0000000..6e4c952 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/export/export.service.d.ts @@ -0,0 +1,10 @@ +import { PrismaService } from '../../database/prisma.service'; +export declare class ExportService { + private prisma; + private readonly logger; + constructor(prisma: PrismaService); + exportTeachers(tenantId: number): Promise; + exportStudents(tenantId: number): Promise; + exportLessons(tenantId: number): Promise; + exportGrowthRecords(tenantId: number, studentId?: number): Promise; +} diff --git a/reading-platform-backend/dist/src/modules/export/export.service.js b/reading-platform-backend/dist/src/modules/export/export.service.js new file mode 100644 index 0000000..22dbe6a --- /dev/null +++ b/reading-platform-backend/dist/src/modules/export/export.service.js @@ -0,0 +1,260 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var ExportService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ExportService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../../database/prisma.service"); +const ExcelJS = __importStar(require("exceljs")); +let ExportService = ExportService_1 = class ExportService { + constructor(prisma) { + this.prisma = prisma; + this.logger = new common_1.Logger(ExportService_1.name); + } + async exportTeachers(tenantId) { + const teachers = await this.prisma.teacher.findMany({ + where: { tenantId }, + include: { + classes: { + select: { name: true }, + }, + }, + orderBy: { createdAt: 'desc' }, + }); + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet('教师列表'); + worksheet.columns = [ + { header: 'ID', key: 'id', width: 8 }, + { header: '姓名', key: 'name', width: 15 }, + { header: '手机号', key: 'phone', width: 15 }, + { header: '邮箱', key: 'email', width: 25 }, + { header: '登录账号', key: 'loginAccount', width: 15 }, + { header: '负责班级', key: 'classes', width: 20 }, + { header: '授课次数', key: 'lessonCount', width: 10 }, + { header: '状态', key: 'status', width: 10 }, + { header: '创建时间', key: 'createdAt', width: 20 }, + ]; + worksheet.getRow(1).font = { bold: true }; + worksheet.getRow(1).fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFE0E0E0' }, + }; + teachers.forEach((teacher) => { + worksheet.addRow({ + id: teacher.id, + name: teacher.name, + phone: teacher.phone, + email: teacher.email || '-', + loginAccount: teacher.loginAccount, + classes: teacher.classes.map((c) => c.name).join('、') || '-', + lessonCount: teacher.lessonCount, + status: teacher.status === 'ACTIVE' ? '正常' : '停用', + createdAt: teacher.createdAt.toLocaleDateString('zh-CN'), + }); + }); + const buffer = await workbook.xlsx.writeBuffer(); + return Buffer.from(buffer); + } + async exportStudents(tenantId) { + const students = await this.prisma.student.findMany({ + where: { tenantId }, + include: { + class: { + select: { name: true }, + }, + }, + orderBy: [{ classId: 'asc' }, { createdAt: 'asc' }], + }); + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet('学生列表'); + worksheet.columns = [ + { header: 'ID', key: 'id', width: 8 }, + { header: '姓名', key: 'name', width: 15 }, + { header: '性别', key: 'gender', width: 8 }, + { header: '班级', key: 'className', width: 15 }, + { header: '生日', key: 'birthDate', width: 15 }, + { header: '家长姓名', key: 'parentName', width: 15 }, + { header: '家长电话', key: 'parentPhone', width: 15 }, + { header: '阅读次数', key: 'readingCount', width: 10 }, + { header: '创建时间', key: 'createdAt', width: 20 }, + ]; + worksheet.getRow(1).font = { bold: true }; + worksheet.getRow(1).fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFE0E0E0' }, + }; + students.forEach((student) => { + worksheet.addRow({ + id: student.id, + name: student.name, + gender: student.gender || '-', + className: student.class?.name || '-', + birthDate: student.birthDate + ? new Date(student.birthDate).toLocaleDateString('zh-CN') + : '-', + parentName: student.parentName || '-', + parentPhone: student.parentPhone || '-', + readingCount: student.readingCount, + createdAt: student.createdAt.toLocaleDateString('zh-CN'), + }); + }); + const buffer = await workbook.xlsx.writeBuffer(); + return Buffer.from(buffer); + } + async exportLessons(tenantId) { + const lessons = await this.prisma.lesson.findMany({ + where: { tenantId }, + include: { + course: { + select: { name: true, pictureBookName: true }, + }, + class: { + select: { name: true }, + }, + teacher: { + select: { name: true }, + }, + }, + orderBy: { createdAt: 'desc' }, + }); + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet('授课记录'); + worksheet.columns = [ + { header: 'ID', key: 'id', width: 8 }, + { header: '课程名称', key: 'courseName', width: 25 }, + { header: '绘本名称', key: 'pictureBookName', width: 20 }, + { header: '授课班级', key: 'className', width: 15 }, + { header: '授课教师', key: 'teacherName', width: 12 }, + { header: '计划时间', key: 'plannedDatetime', width: 18 }, + { header: '开始时间', key: 'startDatetime', width: 18 }, + { header: '结束时间', key: 'endDatetime', width: 18 }, + { header: '实际时长(分钟)', key: 'actualDuration', width: 12 }, + { header: '状态', key: 'status', width: 10 }, + ]; + worksheet.getRow(1).font = { bold: true }; + worksheet.getRow(1).fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFE0E0E0' }, + }; + const statusMap = { + PLANNED: '已计划', + IN_PROGRESS: '进行中', + COMPLETED: '已完成', + CANCELLED: '已取消', + }; + lessons.forEach((lesson) => { + worksheet.addRow({ + id: lesson.id, + courseName: lesson.course?.name || '-', + pictureBookName: lesson.course?.pictureBookName || '-', + className: lesson.class?.name || '-', + teacherName: lesson.teacher?.name || '-', + plannedDatetime: lesson.plannedDatetime + ? new Date(lesson.plannedDatetime).toLocaleString('zh-CN') + : '-', + startDatetime: lesson.startDatetime + ? new Date(lesson.startDatetime).toLocaleString('zh-CN') + : '-', + endDatetime: lesson.endDatetime + ? new Date(lesson.endDatetime).toLocaleString('zh-CN') + : '-', + actualDuration: lesson.actualDuration || '-', + status: statusMap[lesson.status] || lesson.status, + }); + }); + const buffer = await workbook.xlsx.writeBuffer(); + return Buffer.from(buffer); + } + async exportGrowthRecords(tenantId, studentId) { + const where = { tenantId }; + if (studentId) { + where.studentId = studentId; + } + const records = await this.prisma.growthRecord.findMany({ + where, + include: { + student: { + select: { name: true }, + }, + class: { + select: { name: true }, + }, + }, + orderBy: { recordDate: 'desc' }, + }); + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet('成长档案'); + worksheet.columns = [ + { header: 'ID', key: 'id', width: 8 }, + { header: '学生姓名', key: 'studentName', width: 15 }, + { header: '班级', key: 'className', width: 15 }, + { header: '标题', key: 'title', width: 25 }, + { header: '内容', key: 'content', width: 50 }, + { header: '记录类型', key: 'recordType', width: 12 }, + { header: '记录日期', key: 'recordDate', width: 15 }, + { header: '创建时间', key: 'createdAt', width: 20 }, + ]; + worksheet.getRow(1).font = { bold: true }; + worksheet.getRow(1).fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFE0E0E0' }, + }; + records.forEach((record) => { + worksheet.addRow({ + id: record.id, + studentName: record.student?.name || '-', + className: record.class?.name || '-', + title: record.title, + content: record.content || '-', + recordType: record.recordType, + recordDate: record.recordDate + ? new Date(record.recordDate).toLocaleDateString('zh-CN') + : '-', + createdAt: record.createdAt.toLocaleDateString('zh-CN'), + }); + }); + const buffer = await workbook.xlsx.writeBuffer(); + return Buffer.from(buffer); + } +}; +exports.ExportService = ExportService; +exports.ExportService = ExportService = ExportService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], ExportService); +//# sourceMappingURL=export.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/export/export.service.js.map b/reading-platform-backend/dist/src/modules/export/export.service.js.map new file mode 100644 index 0000000..7384ecf --- /dev/null +++ b/reading-platform-backend/dist/src/modules/export/export.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"export.service.js","sourceRoot":"","sources":["../../../../src/modules/export/export.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/file-upload/file-upload.controller.d.ts b/reading-platform-backend/dist/src/modules/file-upload/file-upload.controller.d.ts new file mode 100644 index 0000000..2bcd364 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/file-upload/file-upload.controller.d.ts @@ -0,0 +1,23 @@ +import { FileUploadService } from './file-upload.service'; +export declare class FileUploadController { + private readonly fileUploadService; + private readonly logger; + constructor(fileUploadService: FileUploadService); + uploadFile(file: Express.Multer.File, body: { + type?: string; + courseId?: string; + }): Promise<{ + success: boolean; + filePath: string; + fileName: string; + originalName: string; + fileSize: number; + mimeType: string; + }>; + deleteFile(body: { + filePath: string; + }): Promise<{ + success: boolean; + message: string; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/file-upload/file-upload.controller.js b/reading-platform-backend/dist/src/modules/file-upload/file-upload.controller.js new file mode 100644 index 0000000..67a13b1 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/file-upload/file-upload.controller.js @@ -0,0 +1,89 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +var FileUploadController_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.FileUploadController = void 0; +const common_1 = require("@nestjs/common"); +const platform_express_1 = require("@nestjs/platform-express"); +const multer_1 = require("multer"); +const file_upload_service_1 = require("./file-upload.service"); +let FileUploadController = FileUploadController_1 = class FileUploadController { + constructor(fileUploadService) { + this.fileUploadService = fileUploadService; + this.logger = new common_1.Logger(FileUploadController_1.name); + } + async uploadFile(file, body) { + this.logger.log(`Uploading file: ${file.originalname}, type: ${body.type || 'unknown'}`); + if (!file) { + throw new common_1.BadRequestException('没有上传文件'); + } + const fileType = body.type || 'other'; + const validationResult = this.fileUploadService.validateFile(file, fileType); + if (!validationResult.valid) { + throw new common_1.BadRequestException(validationResult.error); + } + const savedFile = await this.fileUploadService.saveFile(file, fileType, body.courseId); + this.logger.log(`File uploaded successfully: ${savedFile.filePath}`); + return { + success: true, + filePath: savedFile.filePath, + fileName: savedFile.fileName, + originalName: file.originalname, + fileSize: file.size, + mimeType: file.mimetype, + }; + } + async deleteFile(body) { + this.logger.log(`Deleting file: ${body.filePath}`); + if (!body.filePath) { + throw new common_1.BadRequestException('缺少文件路径'); + } + const result = await this.fileUploadService.deleteFile(body.filePath); + if (!result.success) { + throw new common_1.BadRequestException(result.error); + } + this.logger.log(`File deleted successfully: ${body.filePath}`); + return { + success: true, + message: '文件删除成功', + }; + } +}; +exports.FileUploadController = FileUploadController; +__decorate([ + (0, common_1.Post)('upload'), + (0, common_1.UseInterceptors)((0, platform_express_1.FileInterceptor)('file', { + storage: (0, multer_1.memoryStorage)(), + limits: { + fileSize: 300 * 1024 * 1024, + }, + })), + __param(0, (0, common_1.UploadedFile)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", Promise) +], FileUploadController.prototype, "uploadFile", null); +__decorate([ + (0, common_1.Delete)('delete'), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], FileUploadController.prototype, "deleteFile", null); +exports.FileUploadController = FileUploadController = FileUploadController_1 = __decorate([ + (0, common_1.Controller)('files'), + __metadata("design:paramtypes", [file_upload_service_1.FileUploadService]) +], FileUploadController); +//# sourceMappingURL=file-upload.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/file-upload/file-upload.controller.js.map b/reading-platform-backend/dist/src/modules/file-upload/file-upload.controller.js.map new file mode 100644 index 0000000..2353ea5 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/file-upload/file-upload.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"file-upload.controller.js","sourceRoot":"","sources":["../../../../src/modules/file-upload/file-upload.controller.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/file-upload/file-upload.module.d.ts b/reading-platform-backend/dist/src/modules/file-upload/file-upload.module.d.ts new file mode 100644 index 0000000..24e98ef --- /dev/null +++ b/reading-platform-backend/dist/src/modules/file-upload/file-upload.module.d.ts @@ -0,0 +1,2 @@ +export declare class FileUploadModule { +} diff --git a/reading-platform-backend/dist/src/modules/file-upload/file-upload.module.js b/reading-platform-backend/dist/src/modules/file-upload/file-upload.module.js new file mode 100644 index 0000000..91deefb --- /dev/null +++ b/reading-platform-backend/dist/src/modules/file-upload/file-upload.module.js @@ -0,0 +1,23 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.FileUploadModule = void 0; +const common_1 = require("@nestjs/common"); +const file_upload_controller_1 = require("./file-upload.controller"); +const file_upload_service_1 = require("./file-upload.service"); +let FileUploadModule = class FileUploadModule { +}; +exports.FileUploadModule = FileUploadModule; +exports.FileUploadModule = FileUploadModule = __decorate([ + (0, common_1.Module)({ + controllers: [file_upload_controller_1.FileUploadController], + providers: [file_upload_service_1.FileUploadService], + exports: [file_upload_service_1.FileUploadService], + }) +], FileUploadModule); +//# sourceMappingURL=file-upload.module.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/file-upload/file-upload.module.js.map b/reading-platform-backend/dist/src/modules/file-upload/file-upload.module.js.map new file mode 100644 index 0000000..dc16bde --- /dev/null +++ b/reading-platform-backend/dist/src/modules/file-upload/file-upload.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"file-upload.module.js","sourceRoot":"","sources":["../../../../src/modules/file-upload/file-upload.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,qEAAgE;AAChE,+DAA0D;AAOnD,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;CAAG,CAAA;AAAnB,4CAAgB;2BAAhB,gBAAgB;IAL5B,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,6CAAoB,CAAC;QACnC,SAAS,EAAE,CAAC,uCAAiB,CAAC;QAC9B,OAAO,EAAE,CAAC,uCAAiB,CAAC;KAC7B,CAAC;GACW,gBAAgB,CAAG"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/file-upload/file-upload.service.d.ts b/reading-platform-backend/dist/src/modules/file-upload/file-upload.service.d.ts new file mode 100644 index 0000000..24007dc --- /dev/null +++ b/reading-platform-backend/dist/src/modules/file-upload/file-upload.service.d.ts @@ -0,0 +1,18 @@ +export declare class FileUploadService { + private readonly logger; + private readonly uploadBasePath; + constructor(); + private ensureDirectoriesExist; + validateFile(file: Express.Multer.File, type: string): { + valid: boolean; + error?: string; + }; + saveFile(file: Express.Multer.File, type: string, courseId?: string): Promise<{ + filePath: string; + fileName: string; + }>; + deleteFile(filePath: string): Promise<{ + success: boolean; + error?: string; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/file-upload/file-upload.service.js b/reading-platform-backend/dist/src/modules/file-upload/file-upload.service.js new file mode 100644 index 0000000..d948e14 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/file-upload/file-upload.service.js @@ -0,0 +1,161 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var FileUploadService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.FileUploadService = void 0; +const common_1 = require("@nestjs/common"); +const path_1 = require("path"); +const fs_1 = require("fs"); +const FILE_TYPE_CONFIG = { + cover: { + allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp'], + maxSize: 10 * 1024 * 1024, + folder: 'covers', + }, + ebook: { + allowedMimeTypes: [ + 'application/pdf', + 'application/vnd.ms-powerpoint', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + ], + maxSize: 300 * 1024 * 1024, + folder: 'ebooks', + }, + audio: { + allowedMimeTypes: ['audio/mpeg', 'audio/wav', 'audio/mp4', 'audio/m4a'], + maxSize: 300 * 1024 * 1024, + folder: 'audio', + }, + video: { + allowedMimeTypes: ['video/mp4', 'video/webm'], + maxSize: 300 * 1024 * 1024, + folder: 'videos', + }, + ppt: { + allowedMimeTypes: [ + 'application/vnd.ms-powerpoint', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + ], + maxSize: 300 * 1024 * 1024, + folder: (0, path_1.join)('materials', 'ppt'), + }, + poster: { + allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp'], + maxSize: 10 * 1024 * 1024, + folder: (0, path_1.join)('materials', 'posters'), + }, + other: { + allowedMimeTypes: [ + 'application/pdf', + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'image/jpeg', + 'image/png', + 'image/gif', + 'image/webp', + ], + maxSize: 300 * 1024 * 1024, + folder: 'other', + }, +}; +let FileUploadService = FileUploadService_1 = class FileUploadService { + constructor() { + this.logger = new common_1.Logger(FileUploadService_1.name); + this.uploadBasePath = (0, path_1.join)(process.cwd(), 'uploads', 'courses'); + this.ensureDirectoriesExist(); + } + async ensureDirectoriesExist() { + const directories = Object.values(FILE_TYPE_CONFIG).map((config) => (0, path_1.join)(this.uploadBasePath, config.folder)); + for (const dir of directories) { + try { + await fs_1.promises.mkdir(dir, { recursive: true }); + this.logger.log(`Ensured directory exists: ${dir}`); + } + catch (error) { + this.logger.error(`Failed to create directory ${dir}:`, error); + } + } + } + validateFile(file, type) { + const config = FILE_TYPE_CONFIG[type] || FILE_TYPE_CONFIG.other; + if (file.size > config.maxSize) { + const maxSizeMB = (config.maxSize / (1024 * 1024)).toFixed(0); + return { + valid: false, + error: `文件大小超过限制,最大允许 ${maxSizeMB}MB`, + }; + } + if (!config.allowedMimeTypes.includes(file.mimetype)) { + return { + valid: false, + error: `不支持的文件类型: ${file.mimetype}`, + }; + } + return { valid: true }; + } + async saveFile(file, type, courseId) { + const config = FILE_TYPE_CONFIG[type] || FILE_TYPE_CONFIG.other; + const timestamp = Date.now(); + const randomStr = Math.random().toString(36).substring(2, 8); + const originalExt = (0, path_1.extname)(file.originalname) || ''; + const courseIdPrefix = courseId ? `${courseId}_` : ''; + const newFileName = `${courseIdPrefix}${timestamp}_${randomStr}${originalExt}`; + const targetDir = (0, path_1.join)(this.uploadBasePath, config.folder); + const targetPath = (0, path_1.join)(targetDir, newFileName); + try { + await fs_1.promises.writeFile(targetPath, file.buffer); + const relativePath = `/uploads/courses/${config.folder}/${newFileName}`; + this.logger.log(`File saved: ${targetPath}`); + return { + filePath: relativePath, + fileName: newFileName, + }; + } + catch (error) { + this.logger.error('Failed to save file:', error); + throw new common_1.BadRequestException('文件保存失败'); + } + } + async deleteFile(filePath) { + try { + if (!filePath.startsWith('/uploads/')) { + return { success: false, error: '非法的文件路径' }; + } + const normalizedPath = filePath.replace(/\.\./g, ''); + const fullPath = (0, path_1.join)(process.cwd(), normalizedPath); + if (!fullPath.startsWith((0, path_1.join)(process.cwd(), 'uploads'))) { + return { success: false, error: '非法的文件路径' }; + } + try { + await fs_1.promises.access(fullPath); + } + catch { + this.logger.warn(`File not found: ${fullPath}`); + return { success: true, error: '文件不存在' }; + } + await fs_1.promises.unlink(fullPath); + this.logger.log(`File deleted: ${fullPath}`); + return { success: true }; + } + catch (error) { + this.logger.error('Failed to delete file:', error); + return { success: false, error: '文件删除失败' }; + } + } +}; +exports.FileUploadService = FileUploadService; +exports.FileUploadService = FileUploadService = FileUploadService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", []) +], FileUploadService); +//# sourceMappingURL=file-upload.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/file-upload/file-upload.service.js.map b/reading-platform-backend/dist/src/modules/file-upload/file-upload.service.js.map new file mode 100644 index 0000000..ffb742c --- /dev/null +++ b/reading-platform-backend/dist/src/modules/file-upload/file-upload.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"file-upload.service.js","sourceRoot":"","sources":["../../../../src/modules/file-upload/file-upload.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAAyE;AACzE,+BAA+C;AAC/C,2BAAoC;AAGpC,MAAM,gBAAgB,GAAG;IACvB,KAAK,EAAE;QACL,gBAAgB,EAAE,CAAC,YAAY,EAAE,WAAW,EAAE,YAAY,CAAC;QAC3D,OAAO,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;QACzB,MAAM,EAAE,QAAQ;KACjB;IACD,KAAK,EAAE;QACL,gBAAgB,EAAE;YAChB,iBAAiB;YACjB,+BAA+B;YAC/B,2EAA2E;SAC5E;QACD,OAAO,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI;QAC1B,MAAM,EAAE,QAAQ;KACjB;IACD,KAAK,EAAE;QACL,gBAAgB,EAAE,CAAC,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,CAAC;QACvE,OAAO,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI;QAC1B,MAAM,EAAE,OAAO;KAChB;IACD,KAAK,EAAE;QACL,gBAAgB,EAAE,CAAC,WAAW,EAAE,YAAY,CAAC;QAC7C,OAAO,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI;QAC1B,MAAM,EAAE,QAAQ;KACjB;IACD,GAAG,EAAE;QACH,gBAAgB,EAAE;YAChB,+BAA+B;YAC/B,2EAA2E;SAC5E;QACD,OAAO,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI;QAC1B,MAAM,EAAE,IAAA,WAAI,EAAC,WAAW,EAAE,KAAK,CAAC;KACjC;IACD,MAAM,EAAE;QACN,gBAAgB,EAAE,CAAC,YAAY,EAAE,WAAW,EAAE,YAAY,CAAC;QAC3D,OAAO,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;QACzB,MAAM,EAAE,IAAA,WAAI,EAAC,WAAW,EAAE,SAAS,CAAC;KACrC;IACD,KAAK,EAAE;QACL,gBAAgB,EAAE;YAChB,iBAAiB;YACjB,oBAAoB;YACpB,yEAAyE;YACzE,0BAA0B;YAC1B,mEAAmE;YACnE,YAAY;YACZ,WAAW;YACX,WAAW;YACX,YAAY;SACb;QACD,OAAO,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI;QAC1B,MAAM,EAAE,OAAO;KAChB;CACF,CAAC;AAGK,IAAM,iBAAiB,yBAAvB,MAAM,iBAAiB;IAI5B;QAHiB,WAAM,GAAG,IAAI,eAAM,CAAC,mBAAiB,CAAC,IAAI,CAAC,CAAC;QAC5C,mBAAc,GAAG,IAAA,WAAI,EAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;QAG1E,IAAI,CAAC,sBAAsB,EAAE,CAAC;IAChC,CAAC;IAKO,KAAK,CAAC,sBAAsB;QAClC,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CACjE,IAAA,WAAI,EAAC,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,CACzC,CAAC;QAEF,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;YAC9B,IAAI,CAAC;gBACH,MAAM,aAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBACzC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,6BAA6B,GAAG,EAAE,CAAC,CAAC;YACtD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,GAAG,GAAG,EAAE,KAAK,CAAC,CAAC;YACjE,CAAC;QACH,CAAC;IACH,CAAC;IAKD,YAAY,CACV,IAAyB,EACzB,IAAY;QAEZ,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAqC,CAAC,IAAI,gBAAgB,CAAC,KAAK,CAAC;QAGjG,IAAI,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YAC/B,MAAM,SAAS,GAAG,CAAC,MAAM,CAAC,OAAO,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC9D,OAAO;gBACL,KAAK,EAAE,KAAK;gBACZ,KAAK,EAAE,iBAAiB,SAAS,IAAI;aACtC,CAAC;QACJ,CAAC;QAGD,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACrD,OAAO;gBACL,KAAK,EAAE,KAAK;gBACZ,KAAK,EAAE,aAAa,IAAI,CAAC,QAAQ,EAAE;aACpC,CAAC;QACJ,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACzB,CAAC;IAKD,KAAK,CAAC,QAAQ,CACZ,IAAyB,EACzB,IAAY,EACZ,QAAiB;QAEjB,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAqC,CAAC,IAAI,gBAAgB,CAAC,KAAK,CAAC;QAGjG,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7D,MAAM,WAAW,GAAG,IAAA,cAAO,EAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QACrD,MAAM,cAAc,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACtD,MAAM,WAAW,GAAG,GAAG,cAAc,GAAG,SAAS,IAAI,SAAS,GAAG,WAAW,EAAE,CAAC;QAG/E,MAAM,SAAS,GAAG,IAAA,WAAI,EAAC,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAC3D,MAAM,UAAU,GAAG,IAAA,WAAI,EAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QAEhD,IAAI,CAAC;YAEH,MAAM,aAAE,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YAG5C,MAAM,YAAY,GAAG,oBAAoB,MAAM,CAAC,MAAM,IAAI,WAAW,EAAE,CAAC;YAExE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,UAAU,EAAE,CAAC,CAAC;YAE7C,OAAO;gBACL,QAAQ,EAAE,YAAY;gBACtB,QAAQ,EAAE,WAAW;aACtB,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAsB,EAAE,KAAK,CAAC,CAAC;YACjD,MAAM,IAAI,4BAAmB,CAAC,QAAQ,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IAKD,KAAK,CAAC,UAAU,CAAC,QAAgB;QAC/B,IAAI,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;gBACtC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;YAC9C,CAAC;YAGD,MAAM,cAAc,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACrD,MAAM,QAAQ,GAAG,IAAA,WAAI,EAAC,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,CAAC,CAAC;YAGrD,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAA,WAAI,EAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC;gBACzD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;YAC9C,CAAC;YAGD,IAAI,CAAC;gBACH,MAAM,aAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC5B,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,QAAQ,EAAE,CAAC,CAAC;gBAChD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;YAC3C,CAAC;YAGD,MAAM,aAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC1B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,iBAAiB,QAAQ,EAAE,CAAC,CAAC;YAE7C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;YACnD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;QAC7C,CAAC;IACH,CAAC;CACF,CAAA;AApIY,8CAAiB;4BAAjB,iBAAiB;IAD7B,IAAA,mBAAU,GAAE;;GACA,iBAAiB,CAoI7B"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/growth/dto/create-growth.dto.d.ts b/reading-platform-backend/dist/src/modules/growth/dto/create-growth.dto.d.ts new file mode 100644 index 0000000..2ee52ee --- /dev/null +++ b/reading-platform-backend/dist/src/modules/growth/dto/create-growth.dto.d.ts @@ -0,0 +1,27 @@ +export declare enum RecordType { + STUDENT = "STUDENT", + CLASS = "CLASS" +} +export declare class CreateGrowthRecordDto { + studentId: number; + classId?: number; + recordType: RecordType; + title: string; + content?: string; + images?: string[]; + recordDate: string; +} +export declare class UpdateGrowthRecordDto { + title?: string; + content?: string; + images?: string[]; + recordDate?: string; +} +export declare class QueryGrowthRecordDto { + page?: number; + pageSize?: number; + studentId?: number; + classId?: number; + recordType?: string; + keyword?: string; +} diff --git a/reading-platform-backend/dist/src/modules/growth/dto/create-growth.dto.js b/reading-platform-backend/dist/src/modules/growth/dto/create-growth.dto.js new file mode 100644 index 0000000..239ea72 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/growth/dto/create-growth.dto.js @@ -0,0 +1,114 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.QueryGrowthRecordDto = exports.UpdateGrowthRecordDto = exports.CreateGrowthRecordDto = exports.RecordType = void 0; +const class_validator_1 = require("class-validator"); +var RecordType; +(function (RecordType) { + RecordType["STUDENT"] = "STUDENT"; + RecordType["CLASS"] = "CLASS"; +})(RecordType || (exports.RecordType = RecordType = {})); +class CreateGrowthRecordDto { +} +exports.CreateGrowthRecordDto = CreateGrowthRecordDto; +__decorate([ + (0, class_validator_1.IsInt)(), + (0, class_validator_1.IsNotEmpty)({ message: '学生ID不能为空' }), + __metadata("design:type", Number) +], CreateGrowthRecordDto.prototype, "studentId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], CreateGrowthRecordDto.prototype, "classId", void 0); +__decorate([ + (0, class_validator_1.IsEnum)(RecordType), + __metadata("design:type", String) +], CreateGrowthRecordDto.prototype, "recordType", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '标题不能为空' }), + __metadata("design:type", String) +], CreateGrowthRecordDto.prototype, "title", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateGrowthRecordDto.prototype, "content", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsArray)(), + (0, class_validator_1.IsString)({ each: true }), + __metadata("design:type", Array) +], CreateGrowthRecordDto.prototype, "images", void 0); +__decorate([ + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], CreateGrowthRecordDto.prototype, "recordDate", void 0); +class UpdateGrowthRecordDto { +} +exports.UpdateGrowthRecordDto = UpdateGrowthRecordDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '标题不能为空' }), + __metadata("design:type", String) +], UpdateGrowthRecordDto.prototype, "title", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateGrowthRecordDto.prototype, "content", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsArray)(), + (0, class_validator_1.IsString)({ each: true }), + __metadata("design:type", Array) +], UpdateGrowthRecordDto.prototype, "images", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], UpdateGrowthRecordDto.prototype, "recordDate", void 0); +class QueryGrowthRecordDto { +} +exports.QueryGrowthRecordDto = QueryGrowthRecordDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], QueryGrowthRecordDto.prototype, "page", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], QueryGrowthRecordDto.prototype, "pageSize", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], QueryGrowthRecordDto.prototype, "studentId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], QueryGrowthRecordDto.prototype, "classId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], QueryGrowthRecordDto.prototype, "recordType", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], QueryGrowthRecordDto.prototype, "keyword", void 0); +//# sourceMappingURL=create-growth.dto.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/growth/dto/create-growth.dto.js.map b/reading-platform-backend/dist/src/modules/growth/dto/create-growth.dto.js.map new file mode 100644 index 0000000..2e15bb4 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/growth/dto/create-growth.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"create-growth.dto.js","sourceRoot":"","sources":["../../../../../src/modules/growth/dto/create-growth.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAyG;AAEzG,IAAY,UAGX;AAHD,WAAY,UAAU;IACpB,iCAAmB,CAAA;IACnB,6BAAe,CAAA;AACjB,CAAC,EAHW,UAAU,0BAAV,UAAU,QAGrB;AAED,MAAa,qBAAqB;CA2BjC;AA3BD,sDA2BC;AAxBC;IAFC,IAAA,uBAAK,GAAE;IACP,IAAA,4BAAU,EAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;;wDAClB;AAIlB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;;sDACS;AAGjB;IADC,IAAA,wBAAM,EAAC,UAAU,CAAC;;yDACI;AAIvB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,EAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;;oDACpB;AAId;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;sDACM;AAKjB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,yBAAO,GAAE;IACT,IAAA,0BAAQ,EAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;;qDACP;AAGlB;IADC,IAAA,8BAAY,GAAE;;yDACI;AAGrB,MAAa,qBAAqB;CAkBjC;AAlBD,sDAkBC;AAdC;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,EAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;;oDACnB;AAIf;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;sDACM;AAKjB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,yBAAO,GAAE;IACT,IAAA,0BAAQ,EAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;;qDACP;AAIlB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,8BAAY,GAAE;;yDACK;AAGtB,MAAa,oBAAoB;CAwBhC;AAxBD,oDAwBC;AArBC;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;;kDACM;AAId;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;;sDACU;AAIlB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;;uDACW;AAInB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;;qDACS;AAIjB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;wDACS;AAIpB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;qDACM"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/growth/growth.controller.d.ts b/reading-platform-backend/dist/src/modules/growth/growth.controller.d.ts new file mode 100644 index 0000000..176f6c2 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/growth/growth.controller.d.ts @@ -0,0 +1,277 @@ +import { GrowthService } from './growth.service'; +import { CreateGrowthRecordDto, UpdateGrowthRecordDto } from './dto/create-growth.dto'; +export declare class GrowthController { + private readonly growthService; + constructor(growthService: GrowthService); + findAll(req: any, query: any): Promise<{ + items: { + images: any[]; + student: { + id: number; + name: string; + gender: string; + }; + class: { + id: number; + name: string; + grade: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findOne(req: any, id: string): Promise<{ + images: any[]; + student: { + id: number; + name: string; + gender: string; + birthDate: Date; + }; + class: { + id: number; + name: string; + grade: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }>; + create(req: any, dto: CreateGrowthRecordDto): Promise<{ + images: any[]; + student: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }>; + update(req: any, id: string, dto: UpdateGrowthRecordDto): Promise<{ + images: any[]; + student: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }>; + delete(req: any, id: string): Promise<{ + message: string; + }>; + findByStudent(req: any, studentId: string, query: any): Promise<{ + items: { + images: any[]; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findByClass(req: any, classId: string, query: any): Promise<{ + items: { + images: any[]; + student: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }[]; + total: number; + page: number; + pageSize: number; + }>; +} +export declare class TeacherGrowthController { + private readonly growthService; + constructor(growthService: GrowthService); + findAll(req: any, query: any): Promise<{ + items: { + images: any[]; + student: { + id: number; + name: string; + gender: string; + }; + class: { + id: number; + name: string; + grade: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findOne(req: any, id: string): Promise<{ + images: any[]; + student: { + id: number; + name: string; + gender: string; + birthDate: Date; + }; + class: { + id: number; + name: string; + grade: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }>; + create(req: any, dto: CreateGrowthRecordDto): Promise<{ + images: any[]; + student: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }>; + update(req: any, id: string, dto: UpdateGrowthRecordDto): Promise<{ + images: any[]; + student: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }>; + delete(req: any, id: string): Promise<{ + message: string; + }>; + findByClass(req: any, classId: string, query: any): Promise<{ + items: { + images: any[]; + student: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }[]; + total: number; + page: number; + pageSize: number; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/growth/growth.controller.js b/reading-platform-backend/dist/src/modules/growth/growth.controller.js new file mode 100644 index 0000000..dea746e --- /dev/null +++ b/reading-platform-backend/dist/src/modules/growth/growth.controller.js @@ -0,0 +1,194 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TeacherGrowthController = exports.GrowthController = void 0; +const common_1 = require("@nestjs/common"); +const growth_service_1 = require("./growth.service"); +const create_growth_dto_1 = require("./dto/create-growth.dto"); +const jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +const roles_guard_1 = require("../common/guards/roles.guard"); +const roles_decorator_1 = require("../common/decorators/roles.decorator"); +let GrowthController = class GrowthController { + constructor(growthService) { + this.growthService = growthService; + } + findAll(req, query) { + return this.growthService.findAll(req.user.tenantId, query); + } + findOne(req, id) { + return this.growthService.findOne(req.user.tenantId, +id); + } + create(req, dto) { + return this.growthService.create(req.user.tenantId, req.user.userId, dto); + } + update(req, id, dto) { + return this.growthService.update(req.user.tenantId, +id, dto); + } + delete(req, id) { + return this.growthService.delete(req.user.tenantId, +id); + } + findByStudent(req, studentId, query) { + return this.growthService.findByStudent(req.user.tenantId, +studentId, query); + } + findByClass(req, classId, query) { + return this.growthService.findByClass(req.user.tenantId, +classId, query); + } +}; +exports.GrowthController = GrowthController; +__decorate([ + (0, common_1.Get)('growth-records'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], GrowthController.prototype, "findAll", null); +__decorate([ + (0, common_1.Get)('growth-records/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], GrowthController.prototype, "findOne", null); +__decorate([ + (0, common_1.Post)('growth-records'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, create_growth_dto_1.CreateGrowthRecordDto]), + __metadata("design:returntype", void 0) +], GrowthController.prototype, "create", null); +__decorate([ + (0, common_1.Put)('growth-records/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, create_growth_dto_1.UpdateGrowthRecordDto]), + __metadata("design:returntype", void 0) +], GrowthController.prototype, "update", null); +__decorate([ + (0, common_1.Delete)('growth-records/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], GrowthController.prototype, "delete", null); +__decorate([ + (0, common_1.Get)('students/:studentId/growth-records'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('studentId')), + __param(2, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", void 0) +], GrowthController.prototype, "findByStudent", null); +__decorate([ + (0, common_1.Get)('classes/:classId/growth-records'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('classId')), + __param(2, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", void 0) +], GrowthController.prototype, "findByClass", null); +exports.GrowthController = GrowthController = __decorate([ + (0, common_1.Controller)('school'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('school'), + __metadata("design:paramtypes", [growth_service_1.GrowthService]) +], GrowthController); +let TeacherGrowthController = class TeacherGrowthController { + constructor(growthService) { + this.growthService = growthService; + } + findAll(req, query) { + return this.growthService.findAllForTeacher(req.user.tenantId, req.user.userId, query); + } + findOne(req, id) { + return this.growthService.findOneForTeacher(req.user.tenantId, req.user.userId, +id); + } + create(req, dto) { + return this.growthService.createForTeacher(req.user.tenantId, req.user.userId, dto); + } + update(req, id, dto) { + return this.growthService.updateForTeacher(req.user.tenantId, req.user.userId, +id, dto); + } + delete(req, id) { + return this.growthService.deleteForTeacher(req.user.tenantId, req.user.userId, +id); + } + findByClass(req, classId, query) { + return this.growthService.findByClass(req.user.tenantId, +classId, query); + } +}; +exports.TeacherGrowthController = TeacherGrowthController; +__decorate([ + (0, common_1.Get)('growth-records'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], TeacherGrowthController.prototype, "findAll", null); +__decorate([ + (0, common_1.Get)('growth-records/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], TeacherGrowthController.prototype, "findOne", null); +__decorate([ + (0, common_1.Post)('growth-records'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, create_growth_dto_1.CreateGrowthRecordDto]), + __metadata("design:returntype", void 0) +], TeacherGrowthController.prototype, "create", null); +__decorate([ + (0, common_1.Put)('growth-records/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, create_growth_dto_1.UpdateGrowthRecordDto]), + __metadata("design:returntype", void 0) +], TeacherGrowthController.prototype, "update", null); +__decorate([ + (0, common_1.Delete)('growth-records/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], TeacherGrowthController.prototype, "delete", null); +__decorate([ + (0, common_1.Get)('classes/:classId/growth-records'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('classId')), + __param(2, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", void 0) +], TeacherGrowthController.prototype, "findByClass", null); +exports.TeacherGrowthController = TeacherGrowthController = __decorate([ + (0, common_1.Controller)('teacher'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('teacher'), + __metadata("design:paramtypes", [growth_service_1.GrowthService]) +], TeacherGrowthController); +//# sourceMappingURL=growth.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/growth/growth.controller.js.map b/reading-platform-backend/dist/src/modules/growth/growth.controller.js.map new file mode 100644 index 0000000..a59af03 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/growth/growth.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"growth.controller.js","sourceRoot":"","sources":["../../../../src/modules/growth/growth.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAWwB;AACxB,qDAAiD;AACjD,+DAAuF;AACvF,oEAA+D;AAC/D,8DAA0D;AAC1D,0EAA6D;AAKtD,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAC3B,YAA6B,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;IAAG,CAAC;IAG7D,OAAO,CAAY,GAAQ,EAAW,KAAU;QAC9C,OAAO,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC9D,CAAC;IAGD,OAAO,CAAY,GAAQ,EAAe,EAAU;QAClD,OAAO,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5D,CAAC;IAGD,MAAM,CAAY,GAAQ,EAAU,GAA0B;QAC5D,OAAO,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5E,CAAC;IAGD,MAAM,CACO,GAAQ,EACN,EAAU,EACf,GAA0B;QAElC,OAAO,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAChE,CAAC;IAGD,MAAM,CAAY,GAAQ,EAAe,EAAU;QACjD,OAAO,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3D,CAAC;IAGD,aAAa,CACA,GAAQ,EACC,SAAiB,EAC5B,KAAU;QAEnB,OAAO,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAChF,CAAC;IAGD,WAAW,CACE,GAAQ,EACD,OAAe,EACxB,KAAU;QAEnB,OAAO,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC5E,CAAC;CACF,CAAA;AAjDY,4CAAgB;AAI3B;IADC,IAAA,YAAG,EAAC,gBAAgB,CAAC;IACb,WAAA,IAAA,gBAAO,GAAE,CAAA;IAAY,WAAA,IAAA,cAAK,GAAE,CAAA;;;;+CAEpC;AAGD;IADC,IAAA,YAAG,EAAC,oBAAoB,CAAC;IACjB,WAAA,IAAA,gBAAO,GAAE,CAAA;IAAY,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;+CAExC;AAGD;IADC,IAAA,aAAI,EAAC,gBAAgB,CAAC;IACf,WAAA,IAAA,gBAAO,GAAE,CAAA;IAAY,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAM,yCAAqB;;8CAE7D;AAGD;IADC,IAAA,YAAG,EAAC,oBAAoB,CAAC;IAEvB,WAAA,IAAA,gBAAO,GAAE,CAAA;IACT,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IACX,WAAA,IAAA,aAAI,GAAE,CAAA;;qDAAM,yCAAqB;;8CAGnC;AAGD;IADC,IAAA,eAAM,EAAC,oBAAoB,CAAC;IACrB,WAAA,IAAA,gBAAO,GAAE,CAAA;IAAY,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;8CAEvC;AAGD;IADC,IAAA,YAAG,EAAC,oCAAoC,CAAC;IAEvC,WAAA,IAAA,gBAAO,GAAE,CAAA;IACT,WAAA,IAAA,cAAK,EAAC,WAAW,CAAC,CAAA;IAClB,WAAA,IAAA,cAAK,GAAE,CAAA;;;;qDAGT;AAGD;IADC,IAAA,YAAG,EAAC,iCAAiC,CAAC;IAEpC,WAAA,IAAA,gBAAO,GAAE,CAAA;IACT,WAAA,IAAA,cAAK,EAAC,SAAS,CAAC,CAAA;IAChB,WAAA,IAAA,cAAK,GAAE,CAAA;;;;mDAGT;2BAhDU,gBAAgB;IAH5B,IAAA,mBAAU,EAAC,QAAQ,CAAC;IACpB,IAAA,kBAAS,EAAC,6BAAY,EAAE,wBAAU,CAAC;IACnC,IAAA,uBAAK,EAAC,QAAQ,CAAC;qCAE8B,8BAAa;GAD9C,gBAAgB,CAiD5B;AAMM,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;IAClC,YAA6B,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;IAAG,CAAC;IAG7D,OAAO,CAAY,GAAQ,EAAW,KAAU;QAC9C,OAAO,IAAI,CAAC,aAAa,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACzF,CAAC;IAGD,OAAO,CAAY,GAAQ,EAAe,EAAU;QAClD,OAAO,IAAI,CAAC,aAAa,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IACvF,CAAC;IAGD,MAAM,CAAY,GAAQ,EAAU,GAA0B;QAC5D,OAAO,IAAI,CAAC,aAAa,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACtF,CAAC;IAGD,MAAM,CACO,GAAQ,EACN,EAAU,EACf,GAA0B;QAElC,OAAO,IAAI,CAAC,aAAa,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAC3F,CAAC;IAGD,MAAM,CAAY,GAAQ,EAAe,EAAU;QACjD,OAAO,IAAI,CAAC,aAAa,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IACtF,CAAC;IAGD,WAAW,CACE,GAAQ,EACD,OAAe,EACxB,KAAU;QAEnB,OAAO,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC5E,CAAC;CACF,CAAA;AAxCY,0DAAuB;AAIlC;IADC,IAAA,YAAG,EAAC,gBAAgB,CAAC;IACb,WAAA,IAAA,gBAAO,GAAE,CAAA;IAAY,WAAA,IAAA,cAAK,GAAE,CAAA;;;;sDAEpC;AAGD;IADC,IAAA,YAAG,EAAC,oBAAoB,CAAC;IACjB,WAAA,IAAA,gBAAO,GAAE,CAAA;IAAY,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;sDAExC;AAGD;IADC,IAAA,aAAI,EAAC,gBAAgB,CAAC;IACf,WAAA,IAAA,gBAAO,GAAE,CAAA;IAAY,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAM,yCAAqB;;qDAE7D;AAGD;IADC,IAAA,YAAG,EAAC,oBAAoB,CAAC;IAEvB,WAAA,IAAA,gBAAO,GAAE,CAAA;IACT,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IACX,WAAA,IAAA,aAAI,GAAE,CAAA;;qDAAM,yCAAqB;;qDAGnC;AAGD;IADC,IAAA,eAAM,EAAC,oBAAoB,CAAC;IACrB,WAAA,IAAA,gBAAO,GAAE,CAAA;IAAY,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;qDAEvC;AAGD;IADC,IAAA,YAAG,EAAC,iCAAiC,CAAC;IAEpC,WAAA,IAAA,gBAAO,GAAE,CAAA;IACT,WAAA,IAAA,cAAK,EAAC,SAAS,CAAC,CAAA;IAChB,WAAA,IAAA,cAAK,GAAE,CAAA;;;;0DAGT;kCAvCU,uBAAuB;IAHnC,IAAA,mBAAU,EAAC,SAAS,CAAC;IACrB,IAAA,kBAAS,EAAC,6BAAY,EAAE,wBAAU,CAAC;IACnC,IAAA,uBAAK,EAAC,SAAS,CAAC;qCAE6B,8BAAa;GAD9C,uBAAuB,CAwCnC"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/growth/growth.module.d.ts b/reading-platform-backend/dist/src/modules/growth/growth.module.d.ts new file mode 100644 index 0000000..c83a9bd --- /dev/null +++ b/reading-platform-backend/dist/src/modules/growth/growth.module.d.ts @@ -0,0 +1,2 @@ +export declare class GrowthModule { +} diff --git a/reading-platform-backend/dist/src/modules/growth/growth.module.js b/reading-platform-backend/dist/src/modules/growth/growth.module.js new file mode 100644 index 0000000..bbc25f7 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/growth/growth.module.js @@ -0,0 +1,23 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.GrowthModule = void 0; +const common_1 = require("@nestjs/common"); +const growth_controller_1 = require("./growth.controller"); +const growth_service_1 = require("./growth.service"); +let GrowthModule = class GrowthModule { +}; +exports.GrowthModule = GrowthModule; +exports.GrowthModule = GrowthModule = __decorate([ + (0, common_1.Module)({ + controllers: [growth_controller_1.GrowthController, growth_controller_1.TeacherGrowthController], + providers: [growth_service_1.GrowthService], + exports: [growth_service_1.GrowthService], + }) +], GrowthModule); +//# sourceMappingURL=growth.module.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/growth/growth.module.js.map b/reading-platform-backend/dist/src/modules/growth/growth.module.js.map new file mode 100644 index 0000000..eb17fe3 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/growth/growth.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"growth.module.js","sourceRoot":"","sources":["../../../../src/modules/growth/growth.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2DAAgF;AAChF,qDAAiD;AAO1C,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,oCAAY;uBAAZ,YAAY;IALxB,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,oCAAgB,EAAE,2CAAuB,CAAC;QACxD,SAAS,EAAE,CAAC,8BAAa,CAAC;QAC1B,OAAO,EAAE,CAAC,8BAAa,CAAC;KACzB,CAAC;GACW,YAAY,CAAG"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/growth/growth.service.d.ts b/reading-platform-backend/dist/src/modules/growth/growth.service.d.ts new file mode 100644 index 0000000..0ba8df3 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/growth/growth.service.d.ts @@ -0,0 +1,252 @@ +import { PrismaService } from '../../database/prisma.service'; +import { CreateGrowthRecordDto, UpdateGrowthRecordDto } from './dto/create-growth.dto'; +export declare class GrowthService { + private prisma; + private readonly logger; + constructor(prisma: PrismaService); + private parseJsonArray; + findAll(tenantId: number, query: any): Promise<{ + items: { + images: any[]; + student: { + id: number; + name: string; + gender: string; + }; + class: { + id: number; + name: string; + grade: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findAllForTeacher(tenantId: number, teacherId: number, query: any): Promise<{ + items: { + images: any[]; + student: { + id: number; + name: string; + gender: string; + }; + class: { + id: number; + name: string; + grade: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findOneForTeacher(tenantId: number, teacherId: number, id: number): Promise<{ + images: any[]; + student: { + id: number; + name: string; + gender: string; + birthDate: Date; + }; + class: { + id: number; + name: string; + grade: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }>; + createForTeacher(tenantId: number, teacherId: number, dto: CreateGrowthRecordDto): Promise<{ + images: any[]; + student: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }>; + updateForTeacher(tenantId: number, teacherId: number, id: number, dto: UpdateGrowthRecordDto): Promise<{ + images: any[]; + student: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }>; + deleteForTeacher(tenantId: number, teacherId: number, id: number): Promise<{ + message: string; + }>; + findOne(tenantId: number, id: number): Promise<{ + images: any[]; + student: { + id: number; + name: string; + gender: string; + birthDate: Date; + }; + class: { + id: number; + name: string; + grade: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }>; + create(tenantId: number, userId: number, dto: CreateGrowthRecordDto): Promise<{ + images: any[]; + student: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }>; + update(tenantId: number, id: number, dto: UpdateGrowthRecordDto): Promise<{ + images: any[]; + student: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }>; + delete(tenantId: number, id: number): Promise<{ + message: string; + }>; + findByStudent(tenantId: number, studentId: number, query: any): Promise<{ + items: { + images: any[]; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findByClass(tenantId: number, classId: number, query: any): Promise<{ + items: { + images: any[]; + student: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }[]; + total: number; + page: number; + pageSize: number; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/growth/growth.service.js b/reading-platform-backend/dist/src/modules/growth/growth.service.js new file mode 100644 index 0000000..cf56810 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/growth/growth.service.js @@ -0,0 +1,544 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var GrowthService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.GrowthService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../../database/prisma.service"); +let GrowthService = GrowthService_1 = class GrowthService { + constructor(prisma) { + this.prisma = prisma; + this.logger = new common_1.Logger(GrowthService_1.name); + } + parseJsonArray(value) { + if (!value) + return []; + if (typeof value === 'string') { + try { + return JSON.parse(value); + } + catch { + return []; + } + } + return Array.isArray(value) ? value : []; + } + async findAll(tenantId, query) { + const { page = 1, pageSize = 10, studentId, classId, recordType, keyword } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = { + tenantId: tenantId, + }; + if (studentId) { + where.studentId = +studentId; + } + if (classId) { + where.classId = +classId; + } + if (recordType) { + where.recordType = recordType; + } + if (keyword) { + where.OR = [ + { title: { contains: keyword } }, + { content: { contains: keyword } }, + ]; + } + const [items, total] = await Promise.all([ + this.prisma.growthRecord.findMany({ + where, + skip, + take, + orderBy: { recordDate: 'desc' }, + include: { + student: { + select: { + id: true, + name: true, + gender: true, + }, + }, + class: { + select: { + id: true, + name: true, + grade: true, + }, + }, + }, + }), + this.prisma.growthRecord.count({ where }), + ]); + return { + items: items.map((item) => ({ + ...item, + images: this.parseJsonArray(item.images), + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + async findAllForTeacher(tenantId, teacherId, query) { + const { page = 1, pageSize = 10, studentId, classId, recordType, keyword } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const classTeachers = await this.prisma.classTeacher.findMany({ + where: { teacherId }, + select: { classId: true }, + }); + const classIds = classTeachers.map((ct) => ct.classId); + if (classIds.length === 0) { + return { + items: [], + total: 0, + page: +page, + pageSize: +pageSize, + }; + } + const where = { + tenantId: tenantId, + classId: { in: classIds }, + }; + if (studentId) { + where.studentId = +studentId; + } + if (classId) { + if (!classIds.includes(+classId)) { + throw new common_1.ForbiddenException('您没有权限查看该班级的档案'); + } + where.classId = +classId; + } + if (recordType) { + where.recordType = recordType; + } + if (keyword) { + where.OR = [ + { title: { contains: keyword } }, + { content: { contains: keyword } }, + ]; + } + const [items, total] = await Promise.all([ + this.prisma.growthRecord.findMany({ + where, + skip, + take, + orderBy: { recordDate: 'desc' }, + include: { + student: { + select: { + id: true, + name: true, + gender: true, + }, + }, + class: { + select: { + id: true, + name: true, + grade: true, + }, + }, + }, + }), + this.prisma.growthRecord.count({ where }), + ]); + return { + items: items.map((item) => ({ + ...item, + images: this.parseJsonArray(item.images), + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + async findOneForTeacher(tenantId, teacherId, id) { + const record = await this.prisma.growthRecord.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + include: { + student: { + select: { + id: true, + name: true, + gender: true, + birthDate: true, + }, + }, + class: { + select: { + id: true, + name: true, + grade: true, + }, + }, + }, + }); + if (!record) { + throw new common_1.NotFoundException('成长档案不存在'); + } + const classTeacher = await this.prisma.classTeacher.findFirst({ + where: { teacherId, classId: record.classId }, + }); + if (!classTeacher) { + throw new common_1.ForbiddenException('您没有权限查看此档案'); + } + return { + ...record, + images: this.parseJsonArray(record.images), + }; + } + async createForTeacher(tenantId, teacherId, dto) { + const student = await this.prisma.student.findFirst({ + where: { + id: dto.studentId, + tenantId: tenantId, + }, + }); + if (!student) { + throw new common_1.NotFoundException('学生不存在'); + } + const classId = dto.classId || student.classId; + const classTeacher = await this.prisma.classTeacher.findFirst({ + where: { teacherId, classId }, + }); + if (!classTeacher) { + throw new common_1.ForbiddenException('您没有权限为此班级创建档案'); + } + const record = await this.prisma.growthRecord.create({ + data: { + tenantId: tenantId, + studentId: dto.studentId, + classId: classId, + recordType: dto.recordType, + title: dto.title, + content: dto.content, + images: JSON.stringify(dto.images || []), + recordDate: new Date(dto.recordDate), + createdBy: teacherId, + }, + include: { + student: { + select: { + id: true, + name: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + this.logger.log(`Growth record created by teacher: ${record.id}`); + return { + ...record, + images: this.parseJsonArray(record.images), + }; + } + async updateForTeacher(tenantId, teacherId, id, dto) { + const existing = await this.prisma.growthRecord.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + if (!existing) { + throw new common_1.NotFoundException('成长档案不存在'); + } + const classTeacher = await this.prisma.classTeacher.findFirst({ + where: { teacherId, classId: existing.classId }, + }); + if (!classTeacher) { + throw new common_1.ForbiddenException('您没有权限更新此档案'); + } + const record = await this.prisma.growthRecord.update({ + where: { id: id }, + data: { + title: dto.title, + content: dto.content, + images: dto.images ? JSON.stringify(dto.images) : undefined, + recordDate: dto.recordDate ? new Date(dto.recordDate) : undefined, + }, + include: { + student: { + select: { + id: true, + name: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + this.logger.log(`Growth record updated by teacher: ${id}`); + return { + ...record, + images: this.parseJsonArray(record.images), + }; + } + async deleteForTeacher(tenantId, teacherId, id) { + const existing = await this.prisma.growthRecord.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + if (!existing) { + throw new common_1.NotFoundException('成长档案不存在'); + } + const classTeacher = await this.prisma.classTeacher.findFirst({ + where: { teacherId, classId: existing.classId }, + }); + if (!classTeacher) { + throw new common_1.ForbiddenException('您没有权限删除此档案'); + } + await this.prisma.growthRecord.delete({ + where: { id: id }, + }); + this.logger.log(`Growth record deleted by teacher: ${id}`); + return { message: '删除成功' }; + } + async findOne(tenantId, id) { + const record = await this.prisma.growthRecord.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + include: { + student: { + select: { + id: true, + name: true, + gender: true, + birthDate: true, + }, + }, + class: { + select: { + id: true, + name: true, + grade: true, + }, + }, + }, + }); + if (!record) { + throw new common_1.NotFoundException('成长档案不存在'); + } + return { + ...record, + images: this.parseJsonArray(record.images), + }; + } + async create(tenantId, userId, dto) { + const student = await this.prisma.student.findFirst({ + where: { + id: dto.studentId, + tenantId: tenantId, + }, + }); + if (!student) { + throw new common_1.NotFoundException('学生不存在'); + } + const record = await this.prisma.growthRecord.create({ + data: { + tenantId: tenantId, + studentId: dto.studentId, + classId: dto.classId || student.classId, + recordType: dto.recordType, + title: dto.title, + content: dto.content, + images: JSON.stringify(dto.images || []), + recordDate: new Date(dto.recordDate), + createdBy: userId, + }, + include: { + student: { + select: { + id: true, + name: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + this.logger.log(`Growth record created: ${record.id}`); + return { + ...record, + images: this.parseJsonArray(record.images), + }; + } + async update(tenantId, id, dto) { + const existing = await this.prisma.growthRecord.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + if (!existing) { + throw new common_1.NotFoundException('成长档案不存在'); + } + const record = await this.prisma.growthRecord.update({ + where: { id: id }, + data: { + title: dto.title, + content: dto.content, + images: dto.images ? JSON.stringify(dto.images) : undefined, + recordDate: dto.recordDate ? new Date(dto.recordDate) : undefined, + }, + include: { + student: { + select: { + id: true, + name: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + this.logger.log(`Growth record updated: ${id}`); + return { + ...record, + images: this.parseJsonArray(record.images), + }; + } + async delete(tenantId, id) { + const existing = await this.prisma.growthRecord.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + if (!existing) { + throw new common_1.NotFoundException('成长档案不存在'); + } + await this.prisma.growthRecord.delete({ + where: { id: id }, + }); + this.logger.log(`Growth record deleted: ${id}`); + return { message: '删除成功' }; + } + async findByStudent(tenantId, studentId, query) { + const { page = 1, pageSize = 10 } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const student = await this.prisma.student.findFirst({ + where: { + id: studentId, + tenantId: tenantId, + }, + }); + if (!student) { + throw new common_1.NotFoundException('学生不存在'); + } + const [items, total] = await Promise.all([ + this.prisma.growthRecord.findMany({ + where: { + studentId: studentId, + tenantId: tenantId, + }, + skip, + take, + orderBy: { recordDate: 'desc' }, + }), + this.prisma.growthRecord.count({ + where: { + studentId: studentId, + tenantId: tenantId, + }, + }), + ]); + return { + items: items.map((item) => ({ + ...item, + images: this.parseJsonArray(item.images), + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + async findByClass(tenantId, classId, query) { + const { page = 1, pageSize = 10, recordDate } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const classEntity = await this.prisma.class.findFirst({ + where: { + id: classId, + tenantId: tenantId, + }, + }); + if (!classEntity) { + throw new common_1.NotFoundException('班级不存在'); + } + const where = { + classId: classId, + tenantId: tenantId, + recordType: 'CLASS', + }; + if (recordDate) { + where.recordDate = new Date(recordDate); + } + const [items, total] = await Promise.all([ + this.prisma.growthRecord.findMany({ + where, + skip, + take, + orderBy: { recordDate: 'desc' }, + include: { + student: { + select: { + id: true, + name: true, + }, + }, + }, + }), + this.prisma.growthRecord.count({ where }), + ]); + return { + items: items.map((item) => ({ + ...item, + images: this.parseJsonArray(item.images), + })), + total, + page: +page, + pageSize: +pageSize, + }; + } +}; +exports.GrowthService = GrowthService; +exports.GrowthService = GrowthService = GrowthService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], GrowthService); +//# sourceMappingURL=growth.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/growth/growth.service.js.map b/reading-platform-backend/dist/src/modules/growth/growth.service.js.map new file mode 100644 index 0000000..a59125e --- /dev/null +++ b/reading-platform-backend/dist/src/modules/growth/growth.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"growth.service.js","sourceRoot":"","sources":["../../../../src/modules/growth/growth.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/lesson/dto/create-lesson.dto.d.ts b/reading-platform-backend/dist/src/modules/lesson/dto/create-lesson.dto.d.ts new file mode 100644 index 0000000..b24e64b --- /dev/null +++ b/reading-platform-backend/dist/src/modules/lesson/dto/create-lesson.dto.d.ts @@ -0,0 +1,5 @@ +export declare class CreateLessonDto { + courseId: number; + classId: number; + plannedDatetime?: string; +} diff --git a/reading-platform-backend/dist/src/modules/lesson/dto/create-lesson.dto.js b/reading-platform-backend/dist/src/modules/lesson/dto/create-lesson.dto.js new file mode 100644 index 0000000..531e589 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/lesson/dto/create-lesson.dto.js @@ -0,0 +1,30 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CreateLessonDto = void 0; +const class_validator_1 = require("class-validator"); +class CreateLessonDto { +} +exports.CreateLessonDto = CreateLessonDto; +__decorate([ + (0, class_validator_1.IsNumber)(), + __metadata("design:type", Number) +], CreateLessonDto.prototype, "courseId", void 0); +__decorate([ + (0, class_validator_1.IsNumber)(), + __metadata("design:type", Number) +], CreateLessonDto.prototype, "classId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateLessonDto.prototype, "plannedDatetime", void 0); +//# sourceMappingURL=create-lesson.dto.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/lesson/dto/create-lesson.dto.js.map b/reading-platform-backend/dist/src/modules/lesson/dto/create-lesson.dto.js.map new file mode 100644 index 0000000..b554728 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/lesson/dto/create-lesson.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"create-lesson.dto.js","sourceRoot":"","sources":["../../../../../src/modules/lesson/dto/create-lesson.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAiE;AAEjE,MAAa,eAAe;CAU3B;AAVD,0CAUC;AARC;IADC,IAAA,0BAAQ,GAAE;;iDACM;AAGjB;IADC,IAAA,0BAAQ,GAAE;;gDACK;AAIhB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;wDACc"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/lesson/dto/finish-lesson.dto.d.ts b/reading-platform-backend/dist/src/modules/lesson/dto/finish-lesson.dto.d.ts new file mode 100644 index 0000000..7c9064f --- /dev/null +++ b/reading-platform-backend/dist/src/modules/lesson/dto/finish-lesson.dto.d.ts @@ -0,0 +1,6 @@ +export declare class FinishLessonDto { + overallRating?: string; + participationRating?: string; + completionNote?: string; + actualDuration?: number; +} diff --git a/reading-platform-backend/dist/src/modules/lesson/dto/finish-lesson.dto.js b/reading-platform-backend/dist/src/modules/lesson/dto/finish-lesson.dto.js new file mode 100644 index 0000000..a2cf2d9 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/lesson/dto/finish-lesson.dto.js @@ -0,0 +1,37 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.FinishLessonDto = void 0; +const class_validator_1 = require("class-validator"); +class FinishLessonDto { +} +exports.FinishLessonDto = FinishLessonDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], FinishLessonDto.prototype, "overallRating", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], FinishLessonDto.prototype, "participationRating", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], FinishLessonDto.prototype, "completionNote", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsNumber)(), + __metadata("design:type", Number) +], FinishLessonDto.prototype, "actualDuration", void 0); +//# sourceMappingURL=finish-lesson.dto.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/lesson/dto/finish-lesson.dto.js.map b/reading-platform-backend/dist/src/modules/lesson/dto/finish-lesson.dto.js.map new file mode 100644 index 0000000..ca4ccbe --- /dev/null +++ b/reading-platform-backend/dist/src/modules/lesson/dto/finish-lesson.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"finish-lesson.dto.js","sourceRoot":"","sources":["../../../../../src/modules/lesson/dto/finish-lesson.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAiE;AAEjE,MAAa,eAAe;CAgB3B;AAhBD,0CAgBC;AAbC;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;sDACY;AAIvB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;4DACkB;AAI7B;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;uDACa;AAIxB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;uDACa"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/lesson/lesson.controller.d.ts b/reading-platform-backend/dist/src/modules/lesson/lesson.controller.d.ts new file mode 100644 index 0000000..89d89e4 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/lesson/lesson.controller.d.ts @@ -0,0 +1,485 @@ +import { LessonService } from './lesson.service'; +import { CreateLessonDto } from './dto/create-lesson.dto'; +import { FinishLessonDto } from './dto/finish-lesson.dto'; +export declare class LessonController { + private readonly lessonService; + constructor(lessonService: LessonService); + findAll(req: any, query: any): Promise<{ + items: ({ + course: { + id: number; + name: string; + pictureBookName: string; + }; + class: { + id: number; + name: string; + }; + } & { + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number; + courseId: number; + schedulePlanId: number | null; + plannedDatetime: Date | null; + startDatetime: Date | null; + endDatetime: Date | null; + actualDuration: number | null; + overallRating: string | null; + participationRating: string | null; + completionNote: string | null; + })[]; + total: number; + page: number; + pageSize: number; + }>; + findOne(req: any, id: string): Promise<{ + course: { + ebookPaths: any; + audioPaths: any; + videoPaths: any; + posterPaths: any; + scripts: { + interactionPoints: any; + resourceIds: any; + pages: { + resourceIds: any; + id: number; + createdAt: Date; + updatedAt: Date; + pageNumber: number; + scriptId: number; + questions: string | null; + interactionComponent: string | null; + teacherNotes: string | null; + }[]; + id: number; + createdAt: Date; + updatedAt: Date; + duration: number; + sortOrder: number; + courseId: number; + stepIndex: number; + stepName: string; + stepType: string; + objective: string | null; + teacherScript: string | null; + }[]; + activities: { + onlineMaterials: any; + objectives: any; + id: number; + createdAt: Date; + name: string; + duration: number | null; + sortOrder: number; + courseId: number; + domain: string | null; + domainTagId: number | null; + activityType: string; + offlineMaterials: string | null; + activityGuide: string | null; + }[]; + id: number; + name: string; + pictureBookName: string; + pptPath: string; + pptName: string; + duration: number; + }; + class: { + id: number; + name: string; + students: { + id: number; + name: string; + gender: string; + }[]; + }; + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number; + courseId: number; + schedulePlanId: number | null; + plannedDatetime: Date | null; + startDatetime: Date | null; + endDatetime: Date | null; + actualDuration: number | null; + overallRating: string | null; + participationRating: string | null; + completionNote: string | null; + }>; + create(req: any, dto: CreateLessonDto): Promise<{ + course: { + id: number; + name: string; + pictureBookName: string; + duration: number; + }; + class: { + id: number; + name: string; + }; + } & { + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number; + courseId: number; + schedulePlanId: number | null; + plannedDatetime: Date | null; + startDatetime: Date | null; + endDatetime: Date | null; + actualDuration: number | null; + overallRating: string | null; + participationRating: string | null; + completionNote: string | null; + }>; + start(req: any, id: string): Promise<{ + course: { + ebookPaths: any; + audioPaths: any; + videoPaths: any; + posterPaths: any; + scripts: { + interactionPoints: any; + resourceIds: any; + pages: { + resourceIds: any; + id: number; + createdAt: Date; + updatedAt: Date; + pageNumber: number; + scriptId: number; + questions: string | null; + interactionComponent: string | null; + teacherNotes: string | null; + }[]; + id: number; + createdAt: Date; + updatedAt: Date; + duration: number; + sortOrder: number; + courseId: number; + stepIndex: number; + stepName: string; + stepType: string; + objective: string | null; + teacherScript: string | null; + }[]; + activities: { + onlineMaterials: any; + objectives: any; + id: number; + createdAt: Date; + name: string; + duration: number | null; + sortOrder: number; + courseId: number; + domain: string | null; + domainTagId: number | null; + activityType: string; + offlineMaterials: string | null; + activityGuide: string | null; + }[]; + id: number; + name: string; + pictureBookName: string; + pptPath: string; + pptName: string; + duration: number; + }; + class: { + id: number; + name: string; + students: { + id: number; + name: string; + gender: string; + }[]; + }; + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number; + courseId: number; + schedulePlanId: number | null; + plannedDatetime: Date | null; + startDatetime: Date | null; + endDatetime: Date | null; + actualDuration: number | null; + overallRating: string | null; + participationRating: string | null; + completionNote: string | null; + }>; + finish(req: any, id: string, dto: FinishLessonDto): Promise<{ + course: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + } & { + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number; + courseId: number; + schedulePlanId: number | null; + plannedDatetime: Date | null; + startDatetime: Date | null; + endDatetime: Date | null; + actualDuration: number | null; + overallRating: string | null; + participationRating: string | null; + completionNote: string | null; + }>; + cancel(req: any, id: string): Promise<{ + course: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + } & { + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number; + courseId: number; + schedulePlanId: number | null; + plannedDatetime: Date | null; + startDatetime: Date | null; + endDatetime: Date | null; + actualDuration: number | null; + overallRating: string | null; + participationRating: string | null; + completionNote: string | null; + }>; + saveStudentRecord(req: any, id: string, studentId: string, data: any): Promise<{ + id: number; + createdAt: Date; + updatedAt: Date; + studentId: number; + lessonId: number; + participation: number | null; + focus: number | null; + interest: number | null; + understanding: number | null; + domainAchievements: string | null; + notes: string | null; + }>; + getStudentRecords(req: any, id: string): Promise<{ + lesson: { + id: number; + status: string; + className: string; + }; + students: { + record: { + student: { + id: number; + name: string; + gender: string; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + studentId: number; + lessonId: number; + participation: number | null; + focus: number | null; + interest: number | null; + understanding: number | null; + domainAchievements: string | null; + notes: string | null; + }; + id: number; + name: string; + gender: string; + }[]; + }>; + batchSaveStudentRecords(req: any, id: string, data: { + records: Array<{ + studentId: number; + focus?: number; + participation?: number; + interest?: number; + understanding?: number; + notes?: string; + }>; + }): Promise<{ + count: number; + records: any[]; + }>; + submitFeedback(req: any, id: string, data: any): Promise<{ + stepFeedbacks: any; + activitiesDone: any; + lesson: { + id: number; + course: { + id: number; + name: string; + }; + }; + id: number; + createdAt: Date; + updatedAt: Date; + teacherId: number; + lessonId: number; + designQuality: number | null; + participation: number | null; + goalAchievement: number | null; + pros: string | null; + suggestions: string | null; + }>; + getFeedback(req: any, id: string): Promise<{ + stepFeedbacks: any; + activitiesDone: any; + lesson: { + id: number; + course: { + id: number; + name: string; + pictureBookName: string; + }; + class: { + id: number; + name: string; + }; + }; + id: number; + createdAt: Date; + updatedAt: Date; + teacherId: number; + lessonId: number; + designQuality: number | null; + participation: number | null; + goalAchievement: number | null; + pros: string | null; + suggestions: string | null; + }>; +} +export declare class TeacherFeedbackController { + private readonly lessonService; + constructor(lessonService: LessonService); + findAll(req: any, query: any): Promise<{ + items: { + stepFeedbacks: any; + activitiesDone: any; + teacher: { + id: number; + name: string; + }; + lesson: { + id: number; + course: { + id: number; + name: string; + pictureBookName: string; + }; + class: { + id: number; + name: string; + }; + startDatetime: Date; + actualDuration: number; + }; + id: number; + createdAt: Date; + updatedAt: Date; + teacherId: number; + lessonId: number; + designQuality: number | null; + participation: number | null; + goalAchievement: number | null; + pros: string | null; + suggestions: string | null; + }[]; + total: number; + page: number; + pageSize: number; + }>; + getStats(req: any): Promise<{ + totalFeedbacks: number; + avgDesignQuality: number; + avgParticipation: number; + avgGoalAchievement: number; + }>; +} +export declare class SchoolFeedbackController { + private readonly lessonService; + constructor(lessonService: LessonService); + findAll(req: any, query: any): Promise<{ + items: { + stepFeedbacks: any; + activitiesDone: any; + teacher: { + id: number; + name: string; + }; + lesson: { + id: number; + course: { + id: number; + name: string; + pictureBookName: string; + }; + class: { + id: number; + name: string; + }; + startDatetime: Date; + actualDuration: number; + }; + id: number; + createdAt: Date; + updatedAt: Date; + teacherId: number; + lessonId: number; + designQuality: number | null; + participation: number | null; + goalAchievement: number | null; + pros: string | null; + suggestions: string | null; + }[]; + total: number; + page: number; + pageSize: number; + }>; + getStats(req: any): Promise<{ + totalFeedbacks: number; + avgDesignQuality: number; + avgParticipation: number; + avgGoalAchievement: number; + courseStats: Record; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/lesson/lesson.controller.js b/reading-platform-backend/dist/src/modules/lesson/lesson.controller.js new file mode 100644 index 0000000..d16a4a7 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/lesson/lesson.controller.js @@ -0,0 +1,230 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SchoolFeedbackController = exports.TeacherFeedbackController = exports.LessonController = void 0; +const common_1 = require("@nestjs/common"); +const lesson_service_1 = require("./lesson.service"); +const create_lesson_dto_1 = require("./dto/create-lesson.dto"); +const finish_lesson_dto_1 = require("./dto/finish-lesson.dto"); +const jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +const roles_guard_1 = require("../common/guards/roles.guard"); +const roles_decorator_1 = require("../common/decorators/roles.decorator"); +let LessonController = class LessonController { + constructor(lessonService) { + this.lessonService = lessonService; + } + findAll(req, query) { + return this.lessonService.findByTeacher(req.user.userId, query); + } + findOne(req, id) { + return this.lessonService.findOne(+id, req.user.userId); + } + create(req, dto) { + return this.lessonService.create(req.user.userId, req.user.tenantId, dto); + } + start(req, id) { + return this.lessonService.start(+id, req.user.userId); + } + finish(req, id, dto) { + return this.lessonService.finish(+id, req.user.userId, dto); + } + cancel(req, id) { + return this.lessonService.cancel(+id, req.user.userId); + } + saveStudentRecord(req, id, studentId, data) { + return this.lessonService.saveStudentRecord(+id, req.user.userId, +studentId, data); + } + getStudentRecords(req, id) { + return this.lessonService.getStudentRecords(+id, req.user.userId); + } + batchSaveStudentRecords(req, id, data) { + return this.lessonService.batchSaveStudentRecords(+id, req.user.userId, data.records); + } + submitFeedback(req, id, data) { + return this.lessonService.submitFeedback(+id, req.user.userId, data); + } + getFeedback(req, id) { + return this.lessonService.getFeedback(+id, req.user.userId); + } +}; +exports.LessonController = LessonController; +__decorate([ + (0, common_1.Get)(), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], LessonController.prototype, "findAll", null); +__decorate([ + (0, common_1.Get)(':id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], LessonController.prototype, "findOne", null); +__decorate([ + (0, common_1.Post)(), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, create_lesson_dto_1.CreateLessonDto]), + __metadata("design:returntype", void 0) +], LessonController.prototype, "create", null); +__decorate([ + (0, common_1.Post)(':id/start'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], LessonController.prototype, "start", null); +__decorate([ + (0, common_1.Post)(':id/finish'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, finish_lesson_dto_1.FinishLessonDto]), + __metadata("design:returntype", void 0) +], LessonController.prototype, "finish", null); +__decorate([ + (0, common_1.Post)(':id/cancel'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], LessonController.prototype, "cancel", null); +__decorate([ + (0, common_1.Post)(':id/students/:studentId/record'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Param)('studentId')), + __param(3, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, String, Object]), + __metadata("design:returntype", void 0) +], LessonController.prototype, "saveStudentRecord", null); +__decorate([ + (0, common_1.Get)(':id/student-records'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], LessonController.prototype, "getStudentRecords", null); +__decorate([ + (0, common_1.Post)(':id/student-records/batch'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", void 0) +], LessonController.prototype, "batchSaveStudentRecords", null); +__decorate([ + (0, common_1.Post)(':id/feedback'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", void 0) +], LessonController.prototype, "submitFeedback", null); +__decorate([ + (0, common_1.Get)(':id/feedback'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], LessonController.prototype, "getFeedback", null); +exports.LessonController = LessonController = __decorate([ + (0, common_1.Controller)('teacher/lessons'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('teacher'), + __metadata("design:paramtypes", [lesson_service_1.LessonService]) +], LessonController); +let TeacherFeedbackController = class TeacherFeedbackController { + constructor(lessonService) { + this.lessonService = lessonService; + } + findAll(req, query) { + return this.lessonService.getFeedbacksByTenant(req.user.tenantId, { + ...query, + teacherId: req.user.userId, + }); + } + getStats(req) { + return this.lessonService.getTeacherFeedbackStats(req.user.userId); + } +}; +exports.TeacherFeedbackController = TeacherFeedbackController; +__decorate([ + (0, common_1.Get)(), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], TeacherFeedbackController.prototype, "findAll", null); +__decorate([ + (0, common_1.Get)('stats'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], TeacherFeedbackController.prototype, "getStats", null); +exports.TeacherFeedbackController = TeacherFeedbackController = __decorate([ + (0, common_1.Controller)('teacher/feedbacks'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('teacher'), + __metadata("design:paramtypes", [lesson_service_1.LessonService]) +], TeacherFeedbackController); +let SchoolFeedbackController = class SchoolFeedbackController { + constructor(lessonService) { + this.lessonService = lessonService; + } + findAll(req, query) { + return this.lessonService.getFeedbacksByTenant(req.user.tenantId, query); + } + getStats(req) { + return this.lessonService.getFeedbackStats(req.user.tenantId); + } +}; +exports.SchoolFeedbackController = SchoolFeedbackController; +__decorate([ + (0, common_1.Get)(), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], SchoolFeedbackController.prototype, "findAll", null); +__decorate([ + (0, common_1.Get)('stats'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], SchoolFeedbackController.prototype, "getStats", null); +exports.SchoolFeedbackController = SchoolFeedbackController = __decorate([ + (0, common_1.Controller)('school/feedbacks'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('school'), + __metadata("design:paramtypes", [lesson_service_1.LessonService]) +], SchoolFeedbackController); +//# sourceMappingURL=lesson.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/lesson/lesson.controller.js.map b/reading-platform-backend/dist/src/modules/lesson/lesson.controller.js.map new file mode 100644 index 0000000..89a16b8 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/lesson/lesson.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"lesson.controller.js","sourceRoot":"","sources":["../../../../src/modules/lesson/lesson.controller.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/lesson/lesson.module.d.ts b/reading-platform-backend/dist/src/modules/lesson/lesson.module.d.ts new file mode 100644 index 0000000..6ee04b9 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/lesson/lesson.module.d.ts @@ -0,0 +1,2 @@ +export declare class LessonModule { +} diff --git a/reading-platform-backend/dist/src/modules/lesson/lesson.module.js b/reading-platform-backend/dist/src/modules/lesson/lesson.module.js new file mode 100644 index 0000000..58084fc --- /dev/null +++ b/reading-platform-backend/dist/src/modules/lesson/lesson.module.js @@ -0,0 +1,23 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.LessonModule = void 0; +const common_1 = require("@nestjs/common"); +const lesson_controller_1 = require("./lesson.controller"); +const lesson_service_1 = require("./lesson.service"); +let LessonModule = class LessonModule { +}; +exports.LessonModule = LessonModule; +exports.LessonModule = LessonModule = __decorate([ + (0, common_1.Module)({ + controllers: [lesson_controller_1.LessonController, lesson_controller_1.SchoolFeedbackController, lesson_controller_1.TeacherFeedbackController], + providers: [lesson_service_1.LessonService], + exports: [lesson_service_1.LessonService], + }) +], LessonModule); +//# sourceMappingURL=lesson.module.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/lesson/lesson.module.js.map b/reading-platform-backend/dist/src/modules/lesson/lesson.module.js.map new file mode 100644 index 0000000..f633152 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/lesson/lesson.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"lesson.module.js","sourceRoot":"","sources":["../../../../src/modules/lesson/lesson.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2DAA4G;AAC5G,qDAAiD;AAO1C,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,oCAAY;uBAAZ,YAAY;IALxB,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,oCAAgB,EAAE,4CAAwB,EAAE,6CAAyB,CAAC;QACpF,SAAS,EAAE,CAAC,8BAAa,CAAC;QAC1B,OAAO,EAAE,CAAC,8BAAa,CAAC;KACzB,CAAC;GACW,YAAY,CAAG"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/lesson/lesson.service.d.ts b/reading-platform-backend/dist/src/modules/lesson/lesson.service.d.ts new file mode 100644 index 0000000..8402f81 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/lesson/lesson.service.d.ts @@ -0,0 +1,453 @@ +import { PrismaService } from '../../database/prisma.service'; +import { CreateLessonDto } from './dto/create-lesson.dto'; +import { FinishLessonDto } from './dto/finish-lesson.dto'; +export declare class LessonService { + private prisma; + private readonly logger; + constructor(prisma: PrismaService); + create(teacherId: number, tenantId: number, dto: CreateLessonDto): Promise<{ + course: { + id: number; + name: string; + pictureBookName: string; + duration: number; + }; + class: { + id: number; + name: string; + }; + } & { + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number; + courseId: number; + schedulePlanId: number | null; + plannedDatetime: Date | null; + startDatetime: Date | null; + endDatetime: Date | null; + actualDuration: number | null; + overallRating: string | null; + participationRating: string | null; + completionNote: string | null; + }>; + start(lessonId: number, teacherId: number): Promise<{ + course: { + ebookPaths: any; + audioPaths: any; + videoPaths: any; + posterPaths: any; + scripts: { + interactionPoints: any; + resourceIds: any; + pages: { + resourceIds: any; + id: number; + createdAt: Date; + updatedAt: Date; + pageNumber: number; + scriptId: number; + questions: string | null; + interactionComponent: string | null; + teacherNotes: string | null; + }[]; + id: number; + createdAt: Date; + updatedAt: Date; + duration: number; + sortOrder: number; + courseId: number; + stepIndex: number; + stepName: string; + stepType: string; + objective: string | null; + teacherScript: string | null; + }[]; + activities: { + onlineMaterials: any; + objectives: any; + id: number; + createdAt: Date; + name: string; + duration: number | null; + sortOrder: number; + courseId: number; + domain: string | null; + domainTagId: number | null; + activityType: string; + offlineMaterials: string | null; + activityGuide: string | null; + }[]; + id: number; + name: string; + pictureBookName: string; + pptPath: string; + pptName: string; + duration: number; + }; + class: { + id: number; + name: string; + students: { + id: number; + name: string; + gender: string; + }[]; + }; + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number; + courseId: number; + schedulePlanId: number | null; + plannedDatetime: Date | null; + startDatetime: Date | null; + endDatetime: Date | null; + actualDuration: number | null; + overallRating: string | null; + participationRating: string | null; + completionNote: string | null; + }>; + finish(lessonId: number, teacherId: number, dto: FinishLessonDto): Promise<{ + course: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + } & { + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number; + courseId: number; + schedulePlanId: number | null; + plannedDatetime: Date | null; + startDatetime: Date | null; + endDatetime: Date | null; + actualDuration: number | null; + overallRating: string | null; + participationRating: string | null; + completionNote: string | null; + }>; + cancel(lessonId: number, teacherId: number): Promise<{ + course: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + } & { + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number; + courseId: number; + schedulePlanId: number | null; + plannedDatetime: Date | null; + startDatetime: Date | null; + endDatetime: Date | null; + actualDuration: number | null; + overallRating: string | null; + participationRating: string | null; + completionNote: string | null; + }>; + findOne(lessonId: number, teacherId: number): Promise<{ + course: { + ebookPaths: any; + audioPaths: any; + videoPaths: any; + posterPaths: any; + scripts: { + interactionPoints: any; + resourceIds: any; + pages: { + resourceIds: any; + id: number; + createdAt: Date; + updatedAt: Date; + pageNumber: number; + scriptId: number; + questions: string | null; + interactionComponent: string | null; + teacherNotes: string | null; + }[]; + id: number; + createdAt: Date; + updatedAt: Date; + duration: number; + sortOrder: number; + courseId: number; + stepIndex: number; + stepName: string; + stepType: string; + objective: string | null; + teacherScript: string | null; + }[]; + activities: { + onlineMaterials: any; + objectives: any; + id: number; + createdAt: Date; + name: string; + duration: number | null; + sortOrder: number; + courseId: number; + domain: string | null; + domainTagId: number | null; + activityType: string; + offlineMaterials: string | null; + activityGuide: string | null; + }[]; + id: number; + name: string; + pictureBookName: string; + pptPath: string; + pptName: string; + duration: number; + }; + class: { + id: number; + name: string; + students: { + id: number; + name: string; + gender: string; + }[]; + }; + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number; + courseId: number; + schedulePlanId: number | null; + plannedDatetime: Date | null; + startDatetime: Date | null; + endDatetime: Date | null; + actualDuration: number | null; + overallRating: string | null; + participationRating: string | null; + completionNote: string | null; + }>; + findByTeacher(teacherId: number, query: any): Promise<{ + items: ({ + course: { + id: number; + name: string; + pictureBookName: string; + }; + class: { + id: number; + name: string; + }; + } & { + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number; + courseId: number; + schedulePlanId: number | null; + plannedDatetime: Date | null; + startDatetime: Date | null; + endDatetime: Date | null; + actualDuration: number | null; + overallRating: string | null; + participationRating: string | null; + completionNote: string | null; + })[]; + total: number; + page: number; + pageSize: number; + }>; + saveStudentRecord(lessonId: number, teacherId: number, studentId: number, data: { + focus?: number; + participation?: number; + interest?: number; + understanding?: number; + notes?: string; + }): Promise<{ + id: number; + createdAt: Date; + updatedAt: Date; + studentId: number; + lessonId: number; + participation: number | null; + focus: number | null; + interest: number | null; + understanding: number | null; + domainAchievements: string | null; + notes: string | null; + }>; + getStudentRecords(lessonId: number, teacherId: number): Promise<{ + lesson: { + id: number; + status: string; + className: string; + }; + students: { + record: { + student: { + id: number; + name: string; + gender: string; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + studentId: number; + lessonId: number; + participation: number | null; + focus: number | null; + interest: number | null; + understanding: number | null; + domainAchievements: string | null; + notes: string | null; + }; + id: number; + name: string; + gender: string; + }[]; + }>; + batchSaveStudentRecords(lessonId: number, teacherId: number, records: Array<{ + studentId: number; + focus?: number; + participation?: number; + interest?: number; + understanding?: number; + notes?: string; + }>): Promise<{ + count: number; + records: any[]; + }>; + submitFeedback(lessonId: number, teacherId: number, data: { + designQuality?: number; + participation?: number; + goalAchievement?: number; + stepFeedbacks?: any; + pros?: string; + suggestions?: string; + activitiesDone?: any; + }): Promise<{ + stepFeedbacks: any; + activitiesDone: any; + lesson: { + id: number; + course: { + id: number; + name: string; + }; + }; + id: number; + createdAt: Date; + updatedAt: Date; + teacherId: number; + lessonId: number; + designQuality: number | null; + participation: number | null; + goalAchievement: number | null; + pros: string | null; + suggestions: string | null; + }>; + getFeedback(lessonId: number, teacherId: number): Promise<{ + stepFeedbacks: any; + activitiesDone: any; + lesson: { + id: number; + course: { + id: number; + name: string; + pictureBookName: string; + }; + class: { + id: number; + name: string; + }; + }; + id: number; + createdAt: Date; + updatedAt: Date; + teacherId: number; + lessonId: number; + designQuality: number | null; + participation: number | null; + goalAchievement: number | null; + pros: string | null; + suggestions: string | null; + }>; + getFeedbacksByTenant(tenantId: number, query: any): Promise<{ + items: { + stepFeedbacks: any; + activitiesDone: any; + teacher: { + id: number; + name: string; + }; + lesson: { + id: number; + course: { + id: number; + name: string; + pictureBookName: string; + }; + class: { + id: number; + name: string; + }; + startDatetime: Date; + actualDuration: number; + }; + id: number; + createdAt: Date; + updatedAt: Date; + teacherId: number; + lessonId: number; + designQuality: number | null; + participation: number | null; + goalAchievement: number | null; + pros: string | null; + suggestions: string | null; + }[]; + total: number; + page: number; + pageSize: number; + }>; + getFeedbackStats(tenantId: number): Promise<{ + totalFeedbacks: number; + avgDesignQuality: number; + avgParticipation: number; + avgGoalAchievement: number; + courseStats: Record; + }>; + getTeacherFeedbackStats(teacherId: number): Promise<{ + totalFeedbacks: number; + avgDesignQuality: number; + avgParticipation: number; + avgGoalAchievement: number; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/lesson/lesson.service.js b/reading-platform-backend/dist/src/modules/lesson/lesson.service.js new file mode 100644 index 0000000..7f2441f --- /dev/null +++ b/reading-platform-backend/dist/src/modules/lesson/lesson.service.js @@ -0,0 +1,762 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var LessonService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.LessonService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../../database/prisma.service"); +let LessonService = LessonService_1 = class LessonService { + constructor(prisma) { + this.prisma = prisma; + this.logger = new common_1.Logger(LessonService_1.name); + } + async create(teacherId, tenantId, dto) { + const course = await this.prisma.course.findUnique({ + where: { id: dto.courseId }, + }); + if (!course) { + throw new common_1.NotFoundException('课程不存在'); + } + if (course.status !== 'PUBLISHED') { + throw new common_1.ForbiddenException('该课程未发布'); + } + const tenantCourse = await this.prisma.tenantCourse.findUnique({ + where: { + tenantId_courseId: { + tenantId: tenantId, + courseId: dto.courseId, + }, + }, + }); + if (!tenantCourse || !tenantCourse.authorized) { + throw new common_1.ForbiddenException('您的学校未获得此课程的授权'); + } + const classEntity = await this.prisma.class.findFirst({ + where: { + id: dto.classId, + tenantId: tenantId, + teacherId: teacherId, + }, + }); + if (!classEntity) { + throw new common_1.ForbiddenException('无权操作此班级'); + } + const lesson = await this.prisma.lesson.create({ + data: { + tenantId: tenantId, + teacherId: teacherId, + classId: dto.classId, + courseId: dto.courseId, + plannedDatetime: dto.plannedDatetime ? new Date(dto.plannedDatetime) : null, + status: 'PLANNED', + }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + duration: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + this.logger.log(`Lesson created: ${lesson.id} by teacher ${teacherId}`); + return lesson; + } + async start(lessonId, teacherId) { + const lesson = await this.prisma.lesson.findUnique({ + where: { id: lessonId }, + }); + if (!lesson) { + throw new common_1.NotFoundException('授课记录不存在'); + } + if (lesson.teacherId !== teacherId) { + throw new common_1.ForbiddenException('无权操作此授课记录'); + } + if (lesson.status !== 'PLANNED') { + throw new common_1.ForbiddenException('该授课记录已开始或已完成'); + } + const updatedLesson = await this.prisma.lesson.update({ + where: { id: lessonId }, + data: { + status: 'IN_PROGRESS', + startDatetime: new Date(), + }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + duration: true, + pptPath: true, + pptName: true, + ebookPaths: true, + audioPaths: true, + videoPaths: true, + posterPaths: true, + scripts: { + orderBy: { sortOrder: 'asc' }, + include: { + pages: { + orderBy: { pageNumber: 'asc' }, + }, + }, + }, + activities: { + orderBy: { sortOrder: 'asc' }, + }, + }, + }, + class: { + select: { + id: true, + name: true, + students: { + select: { + id: true, + name: true, + gender: true, + }, + }, + }, + }, + }, + }); + this.logger.log(`Lesson started: ${lessonId}`); + return { + ...updatedLesson, + course: { + ...updatedLesson.course, + ebookPaths: updatedLesson.course.ebookPaths ? JSON.parse(updatedLesson.course.ebookPaths) : [], + audioPaths: updatedLesson.course.audioPaths ? JSON.parse(updatedLesson.course.audioPaths) : [], + videoPaths: updatedLesson.course.videoPaths ? JSON.parse(updatedLesson.course.videoPaths) : [], + posterPaths: updatedLesson.course.posterPaths ? JSON.parse(updatedLesson.course.posterPaths) : [], + scripts: updatedLesson.course.scripts.map((script) => ({ + ...script, + interactionPoints: script.interactionPoints ? JSON.parse(script.interactionPoints) : null, + resourceIds: script.resourceIds ? JSON.parse(script.resourceIds) : null, + pages: script.pages?.map((page) => ({ + ...page, + resourceIds: page.resourceIds ? JSON.parse(page.resourceIds) : null, + })), + })), + activities: updatedLesson.course.activities.map((activity) => ({ + ...activity, + onlineMaterials: activity.onlineMaterials ? JSON.parse(activity.onlineMaterials) : null, + objectives: activity.objectives ? JSON.parse(activity.objectives) : null, + })), + }, + }; + } + async finish(lessonId, teacherId, dto) { + const lesson = await this.prisma.lesson.findUnique({ + where: { id: lessonId }, + }); + if (!lesson) { + throw new common_1.NotFoundException('授课记录不存在'); + } + if (lesson.teacherId !== teacherId) { + throw new common_1.ForbiddenException('无权操作此授课记录'); + } + if (lesson.status !== 'IN_PROGRESS') { + throw new common_1.ForbiddenException('该授课记录未开始或已完成'); + } + let actualDuration = dto.actualDuration; + if (!actualDuration && lesson.startDatetime) { + const endTime = new Date(); + actualDuration = Math.round((endTime.getTime() - lesson.startDatetime.getTime()) / 60000); + } + const updatedLesson = await this.prisma.lesson.update({ + where: { id: lessonId }, + data: { + status: 'COMPLETED', + endDatetime: new Date(), + actualDuration: actualDuration, + overallRating: dto.overallRating, + participationRating: dto.participationRating, + completionNote: dto.completionNote, + }, + include: { + course: { + select: { + id: true, + name: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + await this.prisma.course.update({ + where: { id: lesson.courseId }, + data: { + usageCount: { increment: 1 }, + }, + }); + await this.prisma.teacher.update({ + where: { id: teacherId }, + data: { + lessonCount: { increment: 1 }, + }, + }); + this.logger.log(`Lesson finished: ${lessonId}, duration: ${actualDuration} minutes`); + return updatedLesson; + } + async cancel(lessonId, teacherId) { + const lesson = await this.prisma.lesson.findUnique({ + where: { id: lessonId }, + }); + if (!lesson) { + throw new common_1.NotFoundException('授课记录不存在'); + } + if (lesson.teacherId !== teacherId) { + throw new common_1.ForbiddenException('无权操作此授课记录'); + } + if (lesson.status !== 'PLANNED') { + throw new common_1.ForbiddenException('只有已计划的课程可以取消'); + } + const updatedLesson = await this.prisma.lesson.update({ + where: { id: lessonId }, + data: { + status: 'CANCELLED', + }, + include: { + course: { + select: { + id: true, + name: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + this.logger.log(`Lesson cancelled: ${lessonId}`); + return updatedLesson; + } + async findOne(lessonId, teacherId) { + const lesson = await this.prisma.lesson.findUnique({ + where: { id: lessonId }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + duration: true, + pptPath: true, + pptName: true, + ebookPaths: true, + audioPaths: true, + videoPaths: true, + posterPaths: true, + scripts: { + orderBy: { sortOrder: 'asc' }, + include: { + pages: { + orderBy: { pageNumber: 'asc' }, + }, + }, + }, + activities: { + orderBy: { sortOrder: 'asc' }, + }, + }, + }, + class: { + select: { + id: true, + name: true, + students: { + select: { + id: true, + name: true, + gender: true, + }, + }, + }, + }, + }, + }); + if (!lesson) { + throw new common_1.NotFoundException('授课记录不存在'); + } + if (lesson.teacherId !== teacherId) { + throw new common_1.ForbiddenException('无权查看此授课记录'); + } + return { + ...lesson, + course: { + ...lesson.course, + ebookPaths: lesson.course.ebookPaths ? JSON.parse(lesson.course.ebookPaths) : [], + audioPaths: lesson.course.audioPaths ? JSON.parse(lesson.course.audioPaths) : [], + videoPaths: lesson.course.videoPaths ? JSON.parse(lesson.course.videoPaths) : [], + posterPaths: lesson.course.posterPaths ? JSON.parse(lesson.course.posterPaths) : [], + scripts: lesson.course.scripts.map((script) => ({ + ...script, + interactionPoints: script.interactionPoints ? JSON.parse(script.interactionPoints) : null, + resourceIds: script.resourceIds ? JSON.parse(script.resourceIds) : null, + pages: script.pages?.map((page) => ({ + ...page, + resourceIds: page.resourceIds ? JSON.parse(page.resourceIds) : null, + })), + })), + activities: lesson.course.activities.map((activity) => ({ + ...activity, + onlineMaterials: activity.onlineMaterials ? JSON.parse(activity.onlineMaterials) : null, + objectives: activity.objectives ? JSON.parse(activity.objectives) : null, + })), + }, + }; + } + async findByTeacher(teacherId, query) { + const { page = 1, pageSize = 10, status, courseId } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = { + teacherId: teacherId, + }; + if (status) { + where.status = status; + } + if (courseId) { + where.courseId = +courseId; + } + const [items, total] = await Promise.all([ + this.prisma.lesson.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }), + this.prisma.lesson.count({ where }), + ]); + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + async saveStudentRecord(lessonId, teacherId, studentId, data) { + const lesson = await this.prisma.lesson.findUnique({ + where: { id: lessonId }, + }); + if (!lesson) { + throw new common_1.NotFoundException('授课记录不存在'); + } + if (lesson.teacherId !== teacherId) { + throw new common_1.ForbiddenException('无权操作此授课记录'); + } + const student = await this.prisma.student.findFirst({ + where: { + id: studentId, + classId: lesson.classId, + }, + }); + if (!student) { + throw new common_1.NotFoundException('学生不存在或不在此班级'); + } + const record = await this.prisma.studentRecord.upsert({ + where: { + lessonId_studentId: { + lessonId: lessonId, + studentId: studentId, + }, + }, + update: data, + create: { + lessonId: lessonId, + studentId: studentId, + ...data, + }, + }); + return record; + } + async getStudentRecords(lessonId, teacherId) { + const lesson = await this.prisma.lesson.findUnique({ + where: { id: lessonId }, + include: { + class: { + select: { + id: true, + name: true, + students: { + select: { + id: true, + name: true, + gender: true, + }, + }, + }, + }, + }, + }); + if (!lesson) { + throw new common_1.NotFoundException('授课记录不存在'); + } + if (lesson.teacherId !== teacherId) { + throw new common_1.ForbiddenException('无权操作此授课记录'); + } + const records = await this.prisma.studentRecord.findMany({ + where: { lessonId }, + include: { + student: { + select: { + id: true, + name: true, + gender: true, + }, + }, + }, + }); + const studentRecords = lesson.class.students.map((student) => { + const record = records.find((r) => r.studentId === student.id); + return { + ...student, + record: record || null, + }; + }); + return { + lesson: { + id: lesson.id, + status: lesson.status, + className: lesson.class.name, + }, + students: studentRecords, + }; + } + async batchSaveStudentRecords(lessonId, teacherId, records) { + const lesson = await this.prisma.lesson.findUnique({ + where: { id: lessonId }, + }); + if (!lesson) { + throw new common_1.NotFoundException('授课记录不存在'); + } + if (lesson.teacherId !== teacherId) { + throw new common_1.ForbiddenException('无权操作此授课记录'); + } + const results = []; + for (const record of records) { + const saved = await this.prisma.studentRecord.upsert({ + where: { + lessonId_studentId: { + lessonId: lessonId, + studentId: record.studentId, + }, + }, + update: { + focus: record.focus, + participation: record.participation, + interest: record.interest, + understanding: record.understanding, + notes: record.notes, + }, + create: { + lessonId: lessonId, + studentId: record.studentId, + focus: record.focus, + participation: record.participation, + interest: record.interest, + understanding: record.understanding, + notes: record.notes, + }, + }); + results.push(saved); + const existingRecord = await this.prisma.studentRecord.findFirst({ + where: { + studentId: record.studentId, + lessonId: { not: lessonId }, + }, + }); + if (!existingRecord) { + await this.prisma.student.update({ + where: { id: record.studentId }, + data: { readingCount: { increment: 1 } }, + }); + } + } + this.logger.log(`Batch saved ${results.length} student records for lesson ${lessonId}`); + return { count: results.length, records: results }; + } + async submitFeedback(lessonId, teacherId, data) { + const lesson = await this.prisma.lesson.findUnique({ + where: { id: lessonId }, + }); + if (!lesson) { + throw new common_1.NotFoundException('授课记录不存在'); + } + if (lesson.teacherId !== teacherId) { + throw new common_1.ForbiddenException('无权操作此授课记录'); + } + const feedback = await this.prisma.lessonFeedback.upsert({ + where: { + lessonId_teacherId: { + lessonId: lessonId, + teacherId: teacherId, + }, + }, + update: { + designQuality: data.designQuality, + participation: data.participation, + goalAchievement: data.goalAchievement, + stepFeedbacks: data.stepFeedbacks ? JSON.stringify(data.stepFeedbacks) : null, + pros: data.pros, + suggestions: data.suggestions, + activitiesDone: data.activitiesDone ? JSON.stringify(data.activitiesDone) : null, + }, + create: { + lessonId: lessonId, + teacherId: teacherId, + designQuality: data.designQuality, + participation: data.participation, + goalAchievement: data.goalAchievement, + stepFeedbacks: data.stepFeedbacks ? JSON.stringify(data.stepFeedbacks) : null, + pros: data.pros, + suggestions: data.suggestions, + activitiesDone: data.activitiesDone ? JSON.stringify(data.activitiesDone) : null, + }, + include: { + lesson: { + select: { + id: true, + course: { + select: { + id: true, + name: true, + }, + }, + }, + }, + }, + }); + await this.prisma.teacher.update({ + where: { id: teacherId }, + data: { + feedbackCount: { increment: 1 }, + }, + }); + this.logger.log(`Feedback submitted for lesson: ${lessonId}`); + return { + ...feedback, + stepFeedbacks: feedback.stepFeedbacks ? JSON.parse(feedback.stepFeedbacks) : null, + activitiesDone: feedback.activitiesDone ? JSON.parse(feedback.activitiesDone) : null, + }; + } + async getFeedback(lessonId, teacherId) { + const feedback = await this.prisma.lessonFeedback.findUnique({ + where: { + lessonId_teacherId: { + lessonId: lessonId, + teacherId: teacherId, + }, + }, + include: { + lesson: { + select: { + id: true, + course: { + select: { + id: true, + name: true, + pictureBookName: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }, + }, + }); + if (!feedback) { + return null; + } + return { + ...feedback, + stepFeedbacks: feedback.stepFeedbacks ? JSON.parse(feedback.stepFeedbacks) : null, + activitiesDone: feedback.activitiesDone ? JSON.parse(feedback.activitiesDone) : null, + }; + } + async getFeedbacksByTenant(tenantId, query) { + const { page = 1, pageSize = 10, teacherId, courseId } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = { + lesson: { + tenantId: tenantId, + }, + }; + if (teacherId) { + where.teacherId = +teacherId; + } + if (courseId) { + where.lesson = { + ...where.lesson, + courseId: +courseId, + }; + } + const [items, total] = await Promise.all([ + this.prisma.lessonFeedback.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + include: { + lesson: { + select: { + id: true, + startDatetime: true, + actualDuration: true, + course: { + select: { + id: true, + name: true, + pictureBookName: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }, + teacher: { + select: { + id: true, + name: true, + }, + }, + }, + }), + this.prisma.lessonFeedback.count({ where }), + ]); + return { + items: items.map((item) => ({ + ...item, + stepFeedbacks: item.stepFeedbacks ? JSON.parse(item.stepFeedbacks) : null, + activitiesDone: item.activitiesDone ? JSON.parse(item.activitiesDone) : null, + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + async getFeedbackStats(tenantId) { + const feedbacks = await this.prisma.lessonFeedback.findMany({ + where: { + lesson: { + tenantId: tenantId, + }, + }, + select: { + designQuality: true, + participation: true, + goalAchievement: true, + lesson: { + select: { + courseId: true, + }, + }, + }, + }); + const totalFeedbacks = feedbacks.length; + const avgDesignQuality = feedbacks.reduce((sum, f) => sum + (f.designQuality || 0), 0) / (totalFeedbacks || 1); + const avgParticipation = feedbacks.reduce((sum, f) => sum + (f.participation || 0), 0) / (totalFeedbacks || 1); + const avgGoalAchievement = feedbacks.reduce((sum, f) => sum + (f.goalAchievement || 0), 0) / (totalFeedbacks || 1); + const courseStats = {}; + feedbacks.forEach((f) => { + const courseId = f.lesson.courseId; + if (!courseStats[courseId]) { + courseStats[courseId] = { count: 0, avgRating: 0 }; + } + courseStats[courseId].count++; + courseStats[courseId].avgRating += (f.designQuality || 0 + f.participation || 0 + f.goalAchievement || 0) / 3; + }); + Object.keys(courseStats).forEach((courseId) => { + courseStats[+courseId].avgRating /= courseStats[+courseId].count; + }); + return { + totalFeedbacks, + avgDesignQuality: Math.round(avgDesignQuality * 10) / 10, + avgParticipation: Math.round(avgParticipation * 10) / 10, + avgGoalAchievement: Math.round(avgGoalAchievement * 10) / 10, + courseStats, + }; + } + async getTeacherFeedbackStats(teacherId) { + const feedbacks = await this.prisma.lessonFeedback.findMany({ + where: { teacherId }, + select: { + designQuality: true, + participation: true, + goalAchievement: true, + lesson: { + select: { + courseId: true, + }, + }, + }, + }); + const totalFeedbacks = feedbacks.length; + const avgDesignQuality = feedbacks.reduce((sum, f) => sum + (f.designQuality || 0), 0) / (totalFeedbacks || 1); + const avgParticipation = feedbacks.reduce((sum, f) => sum + (f.participation || 0), 0) / (totalFeedbacks || 1); + const avgGoalAchievement = feedbacks.reduce((sum, f) => sum + (f.goalAchievement || 0), 0) / (totalFeedbacks || 1); + return { + totalFeedbacks, + avgDesignQuality: Math.round(avgDesignQuality * 10) / 10, + avgParticipation: Math.round(avgParticipation * 10) / 10, + avgGoalAchievement: Math.round(avgGoalAchievement * 10) / 10, + }; + } +}; +exports.LessonService = LessonService; +exports.LessonService = LessonService = LessonService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], LessonService); +//# sourceMappingURL=lesson.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/lesson/lesson.service.js.map b/reading-platform-backend/dist/src/modules/lesson/lesson.service.js.map new file mode 100644 index 0000000..04493a1 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/lesson/lesson.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"lesson.service.js","sourceRoot":"","sources":["../../../../src/modules/lesson/lesson.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/notification/notification.controller.d.ts b/reading-platform-backend/dist/src/modules/notification/notification.controller.d.ts new file mode 100644 index 0000000..7232037 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/notification/notification.controller.d.ts @@ -0,0 +1,121 @@ +import { NotificationService } from './notification.service'; +export declare class SchoolNotificationController { + private readonly notificationService; + constructor(notificationService: NotificationService); + getNotifications(req: any, query: any): Promise<{ + items: { + id: number; + tenantId: number; + title: string; + createdAt: Date; + content: string; + recipientType: string; + recipientId: number; + notificationType: string; + relatedType: string | null; + relatedId: number | null; + isRead: boolean; + readAt: Date | null; + }[]; + total: number; + unreadCount: number; + page: number; + pageSize: number; + }>; + getUnreadCount(req: any): Promise; + markAsRead(req: any, id: string): Promise<{ + id: number; + tenantId: number; + title: string; + createdAt: Date; + content: string; + recipientType: string; + recipientId: number; + notificationType: string; + relatedType: string | null; + relatedId: number | null; + isRead: boolean; + readAt: Date | null; + }>; + markAllAsRead(req: any): Promise; +} +export declare class TeacherNotificationController { + private readonly notificationService; + constructor(notificationService: NotificationService); + getNotifications(req: any, query: any): Promise<{ + items: { + id: number; + tenantId: number; + title: string; + createdAt: Date; + content: string; + recipientType: string; + recipientId: number; + notificationType: string; + relatedType: string | null; + relatedId: number | null; + isRead: boolean; + readAt: Date | null; + }[]; + total: number; + unreadCount: number; + page: number; + pageSize: number; + }>; + getUnreadCount(req: any): Promise; + markAsRead(req: any, id: string): Promise<{ + id: number; + tenantId: number; + title: string; + createdAt: Date; + content: string; + recipientType: string; + recipientId: number; + notificationType: string; + relatedType: string | null; + relatedId: number | null; + isRead: boolean; + readAt: Date | null; + }>; + markAllAsRead(req: any): Promise; +} +export declare class ParentNotificationController { + private readonly notificationService; + constructor(notificationService: NotificationService); + getNotifications(req: any, query: any): Promise<{ + items: { + id: number; + tenantId: number; + title: string; + createdAt: Date; + content: string; + recipientType: string; + recipientId: number; + notificationType: string; + relatedType: string | null; + relatedId: number | null; + isRead: boolean; + readAt: Date | null; + }[]; + total: number; + unreadCount: number; + page: number; + pageSize: number; + }>; + getUnreadCount(req: any): Promise; + markAsRead(req: any, id: string): Promise<{ + id: number; + tenantId: number; + title: string; + createdAt: Date; + content: string; + recipientType: string; + recipientId: number; + notificationType: string; + relatedType: string | null; + relatedId: number | null; + isRead: boolean; + readAt: Date | null; + }>; + markAllAsRead(req: any): Promise; +} diff --git a/reading-platform-backend/dist/src/modules/notification/notification.controller.js b/reading-platform-backend/dist/src/modules/notification/notification.controller.js new file mode 100644 index 0000000..1195c53 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/notification/notification.controller.js @@ -0,0 +1,183 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ParentNotificationController = exports.TeacherNotificationController = exports.SchoolNotificationController = void 0; +const common_1 = require("@nestjs/common"); +const notification_service_1 = require("./notification.service"); +const jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +const roles_guard_1 = require("../common/guards/roles.guard"); +const roles_decorator_1 = require("../common/decorators/roles.decorator"); +let SchoolNotificationController = class SchoolNotificationController { + constructor(notificationService) { + this.notificationService = notificationService; + } + getNotifications(req, query) { + return this.notificationService.getNotifications(req.user.tenantId, 'SCHOOL', req.user.userId, query); + } + getUnreadCount(req) { + return this.notificationService.getUnreadCount(req.user.tenantId, 'SCHOOL', req.user.userId); + } + markAsRead(req, id) { + return this.notificationService.markAsRead(req.user.tenantId, +id, 'SCHOOL', req.user.userId); + } + markAllAsRead(req) { + return this.notificationService.markAllAsRead(req.user.tenantId, 'SCHOOL', req.user.userId); + } +}; +exports.SchoolNotificationController = SchoolNotificationController; +__decorate([ + (0, common_1.Get)(), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], SchoolNotificationController.prototype, "getNotifications", null); +__decorate([ + (0, common_1.Get)('unread-count'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], SchoolNotificationController.prototype, "getUnreadCount", null); +__decorate([ + (0, common_1.Put)(':id/read'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolNotificationController.prototype, "markAsRead", null); +__decorate([ + (0, common_1.Put)('read-all'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], SchoolNotificationController.prototype, "markAllAsRead", null); +exports.SchoolNotificationController = SchoolNotificationController = __decorate([ + (0, common_1.Controller)('school/notifications'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('school'), + __metadata("design:paramtypes", [notification_service_1.NotificationService]) +], SchoolNotificationController); +let TeacherNotificationController = class TeacherNotificationController { + constructor(notificationService) { + this.notificationService = notificationService; + } + getNotifications(req, query) { + return this.notificationService.getNotifications(req.user.tenantId, 'TEACHER', req.user.userId, query); + } + getUnreadCount(req) { + return this.notificationService.getUnreadCount(req.user.tenantId, 'TEACHER', req.user.userId); + } + markAsRead(req, id) { + return this.notificationService.markAsRead(req.user.tenantId, +id, 'TEACHER', req.user.userId); + } + markAllAsRead(req) { + return this.notificationService.markAllAsRead(req.user.tenantId, 'TEACHER', req.user.userId); + } +}; +exports.TeacherNotificationController = TeacherNotificationController; +__decorate([ + (0, common_1.Get)(), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], TeacherNotificationController.prototype, "getNotifications", null); +__decorate([ + (0, common_1.Get)('unread-count'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], TeacherNotificationController.prototype, "getUnreadCount", null); +__decorate([ + (0, common_1.Put)(':id/read'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], TeacherNotificationController.prototype, "markAsRead", null); +__decorate([ + (0, common_1.Put)('read-all'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], TeacherNotificationController.prototype, "markAllAsRead", null); +exports.TeacherNotificationController = TeacherNotificationController = __decorate([ + (0, common_1.Controller)('teacher/notifications'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('teacher'), + __metadata("design:paramtypes", [notification_service_1.NotificationService]) +], TeacherNotificationController); +let ParentNotificationController = class ParentNotificationController { + constructor(notificationService) { + this.notificationService = notificationService; + } + getNotifications(req, query) { + return this.notificationService.getNotifications(req.user.tenantId, 'PARENT', req.user.userId, query); + } + getUnreadCount(req) { + return this.notificationService.getUnreadCount(req.user.tenantId, 'PARENT', req.user.userId); + } + markAsRead(req, id) { + return this.notificationService.markAsRead(req.user.tenantId, +id, 'PARENT', req.user.userId); + } + markAllAsRead(req) { + return this.notificationService.markAllAsRead(req.user.tenantId, 'PARENT', req.user.userId); + } +}; +exports.ParentNotificationController = ParentNotificationController; +__decorate([ + (0, common_1.Get)(), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], ParentNotificationController.prototype, "getNotifications", null); +__decorate([ + (0, common_1.Get)('unread-count'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], ParentNotificationController.prototype, "getUnreadCount", null); +__decorate([ + (0, common_1.Put)(':id/read'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], ParentNotificationController.prototype, "markAsRead", null); +__decorate([ + (0, common_1.Put)('read-all'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], ParentNotificationController.prototype, "markAllAsRead", null); +exports.ParentNotificationController = ParentNotificationController = __decorate([ + (0, common_1.Controller)('parent/notifications'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('parent'), + __metadata("design:paramtypes", [notification_service_1.NotificationService]) +], ParentNotificationController); +//# sourceMappingURL=notification.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/notification/notification.controller.js.map b/reading-platform-backend/dist/src/modules/notification/notification.controller.js.map new file mode 100644 index 0000000..caf5ba5 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/notification/notification.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"notification.controller.js","sourceRoot":"","sources":["../../../../src/modules/notification/notification.controller.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/notification/notification.module.d.ts b/reading-platform-backend/dist/src/modules/notification/notification.module.d.ts new file mode 100644 index 0000000..1634b39 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/notification/notification.module.d.ts @@ -0,0 +1,2 @@ +export declare class NotificationModule { +} diff --git a/reading-platform-backend/dist/src/modules/notification/notification.module.js b/reading-platform-backend/dist/src/modules/notification/notification.module.js new file mode 100644 index 0000000..30dfd44 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/notification/notification.module.js @@ -0,0 +1,30 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.NotificationModule = void 0; +const common_1 = require("@nestjs/common"); +const schedule_1 = require("@nestjs/schedule"); +const notification_controller_1 = require("./notification.controller"); +const notification_service_1 = require("./notification.service"); +const schedule_notification_service_1 = require("./schedule-notification.service"); +let NotificationModule = class NotificationModule { +}; +exports.NotificationModule = NotificationModule; +exports.NotificationModule = NotificationModule = __decorate([ + (0, common_1.Module)({ + imports: [schedule_1.ScheduleModule.forRoot()], + controllers: [ + notification_controller_1.SchoolNotificationController, + notification_controller_1.TeacherNotificationController, + notification_controller_1.ParentNotificationController, + ], + providers: [notification_service_1.NotificationService, schedule_notification_service_1.ScheduleNotificationService], + exports: [notification_service_1.NotificationService, schedule_notification_service_1.ScheduleNotificationService], + }) +], NotificationModule); +//# sourceMappingURL=notification.module.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/notification/notification.module.js.map b/reading-platform-backend/dist/src/modules/notification/notification.module.js.map new file mode 100644 index 0000000..33ed11c --- /dev/null +++ b/reading-platform-backend/dist/src/modules/notification/notification.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"notification.module.js","sourceRoot":"","sources":["../../../../src/modules/notification/notification.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,+CAAkD;AAClD,uEAImC;AACnC,iEAA6D;AAC7D,mFAA8E;AAYvE,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;CAAG,CAAA;AAArB,gDAAkB;6BAAlB,kBAAkB;IAV9B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,yBAAc,CAAC,OAAO,EAAE,CAAC;QACnC,WAAW,EAAE;YACX,sDAA4B;YAC5B,uDAA6B;YAC7B,sDAA4B;SAC7B;QACD,SAAS,EAAE,CAAC,0CAAmB,EAAE,2DAA2B,CAAC;QAC7D,OAAO,EAAE,CAAC,0CAAmB,EAAE,2DAA2B,CAAC;KAC5D,CAAC;GACW,kBAAkB,CAAG"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/notification/notification.service.d.ts b/reading-platform-backend/dist/src/modules/notification/notification.service.d.ts new file mode 100644 index 0000000..1f6c7dc --- /dev/null +++ b/reading-platform-backend/dist/src/modules/notification/notification.service.d.ts @@ -0,0 +1,75 @@ +import { PrismaService } from '../../database/prisma.service'; +export declare class NotificationService { + private prisma; + private readonly logger; + constructor(prisma: PrismaService); + createNotification(data: { + tenantId: number; + recipientType: 'TEACHER' | 'SCHOOL' | 'PARENT'; + recipientId: number; + title: string; + content: string; + notificationType: 'SYSTEM' | 'TASK' | 'LESSON' | 'GROWTH'; + relatedType?: string; + relatedId?: number; + }): Promise<{ + id: number; + tenantId: number; + title: string; + createdAt: Date; + content: string; + recipientType: string; + recipientId: number; + notificationType: string; + relatedType: string | null; + relatedId: number | null; + isRead: boolean; + readAt: Date | null; + }>; + createBatchNotifications(notifications: Array<{ + tenantId: number; + recipientType: 'TEACHER' | 'SCHOOL' | 'PARENT'; + recipientId: number; + title: string; + content: string; + notificationType: 'SYSTEM' | 'TASK' | 'LESSON' | 'GROWTH'; + relatedType?: string; + relatedId?: number; + }>): Promise; + getNotifications(tenantId: number, recipientType: string, recipientId: number, query: any): Promise<{ + items: { + id: number; + tenantId: number; + title: string; + createdAt: Date; + content: string; + recipientType: string; + recipientId: number; + notificationType: string; + relatedType: string | null; + relatedId: number | null; + isRead: boolean; + readAt: Date | null; + }[]; + total: number; + unreadCount: number; + page: number; + pageSize: number; + }>; + getUnreadCount(tenantId: number, recipientType: string, recipientId: number): Promise; + markAsRead(tenantId: number, notificationId: number, recipientType: string, recipientId: number): Promise<{ + id: number; + tenantId: number; + title: string; + createdAt: Date; + content: string; + recipientType: string; + recipientId: number; + notificationType: string; + relatedType: string | null; + relatedId: number | null; + isRead: boolean; + readAt: Date | null; + }>; + markAllAsRead(tenantId: number, recipientType: string, recipientId: number): Promise; +} diff --git a/reading-platform-backend/dist/src/modules/notification/notification.service.js b/reading-platform-backend/dist/src/modules/notification/notification.service.js new file mode 100644 index 0000000..12a6503 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/notification/notification.service.js @@ -0,0 +1,131 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var NotificationService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.NotificationService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../../database/prisma.service"); +let NotificationService = NotificationService_1 = class NotificationService { + constructor(prisma) { + this.prisma = prisma; + this.logger = new common_1.Logger(NotificationService_1.name); + } + async createNotification(data) { + const notification = await this.prisma.notification.create({ + data: { + tenantId: data.tenantId, + recipientType: data.recipientType, + recipientId: data.recipientId, + title: data.title, + content: data.content, + notificationType: data.notificationType, + relatedType: data.relatedType, + relatedId: data.relatedId, + }, + }); + this.logger.log(`Notification created: ${notification.id} for ${data.recipientType}:${data.recipientId}`); + return notification; + } + async createBatchNotifications(notifications) { + const results = await this.prisma.notification.createMany({ + data: notifications, + }); + this.logger.log(`Batch notifications created: ${results.count}`); + return results; + } + async getNotifications(tenantId, recipientType, recipientId, query) { + const { page = 1, pageSize = 20, isRead, notificationType } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = { + tenantId, + recipientType, + recipientId, + }; + if (isRead !== undefined) { + where.isRead = isRead === 'true'; + } + if (notificationType) { + where.notificationType = notificationType; + } + const [items, total, unreadCount] = await Promise.all([ + this.prisma.notification.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + }), + this.prisma.notification.count({ where }), + this.prisma.notification.count({ + where: { ...where, isRead: false }, + }), + ]); + return { + items, + total, + unreadCount, + page: +page, + pageSize: +pageSize, + }; + } + async getUnreadCount(tenantId, recipientType, recipientId) { + return this.prisma.notification.count({ + where: { + tenantId, + recipientType, + recipientId, + isRead: false, + }, + }); + } + async markAsRead(tenantId, notificationId, recipientType, recipientId) { + const notification = await this.prisma.notification.findFirst({ + where: { + id: notificationId, + tenantId, + recipientType, + recipientId, + }, + }); + if (!notification) { + return null; + } + return this.prisma.notification.update({ + where: { id: notificationId }, + data: { + isRead: true, + readAt: new Date(), + }, + }); + } + async markAllAsRead(tenantId, recipientType, recipientId) { + const result = await this.prisma.notification.updateMany({ + where: { + tenantId, + recipientType, + recipientId, + isRead: false, + }, + data: { + isRead: true, + readAt: new Date(), + }, + }); + this.logger.log(`Marked ${result.count} notifications as read for ${recipientType}:${recipientId}`); + return result; + } +}; +exports.NotificationService = NotificationService; +exports.NotificationService = NotificationService = NotificationService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], NotificationService); +//# sourceMappingURL=notification.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/notification/notification.service.js.map b/reading-platform-backend/dist/src/modules/notification/notification.service.js.map new file mode 100644 index 0000000..907f531 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/notification/notification.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"notification.service.js","sourceRoot":"","sources":["../../../../src/modules/notification/notification.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/notification/schedule-notification.service.d.ts b/reading-platform-backend/dist/src/modules/notification/schedule-notification.service.d.ts new file mode 100644 index 0000000..7519e1f --- /dev/null +++ b/reading-platform-backend/dist/src/modules/notification/schedule-notification.service.d.ts @@ -0,0 +1,32 @@ +import { PrismaService } from '../../database/prisma.service'; +import { NotificationService } from './notification.service'; +export declare class ScheduleNotificationService { + private prisma; + private notificationService; + private readonly logger; + private isProcessing; + constructor(prisma: PrismaService, notificationService: NotificationService); + handleUpcomingScheduleReminders(): Promise; + private sendScheduleReminder; + triggerReminderCheck(): Promise<{ + message: string; + }>; + resetAllReminders(): Promise<{ + message: string; + }>; + handleTaskDeadlineReminders(): Promise; + sendManualTaskReminder(tenantId: number, taskId: number): Promise<{ + success: boolean; + message: string; + remindedCount?: undefined; + students?: undefined; + } | { + success: boolean; + message: string; + remindedCount: number; + students: { + id: number; + name: string; + }[]; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/notification/schedule-notification.service.js b/reading-platform-backend/dist/src/modules/notification/schedule-notification.service.js new file mode 100644 index 0000000..7c5b77f --- /dev/null +++ b/reading-platform-backend/dist/src/modules/notification/schedule-notification.service.js @@ -0,0 +1,288 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var ScheduleNotificationService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ScheduleNotificationService = void 0; +const common_1 = require("@nestjs/common"); +const schedule_1 = require("@nestjs/schedule"); +const prisma_service_1 = require("../../database/prisma.service"); +const notification_service_1 = require("./notification.service"); +let ScheduleNotificationService = ScheduleNotificationService_1 = class ScheduleNotificationService { + constructor(prisma, notificationService) { + this.prisma = prisma; + this.notificationService = notificationService; + this.logger = new common_1.Logger(ScheduleNotificationService_1.name); + this.isProcessing = false; + } + async handleUpcomingScheduleReminders() { + if (this.isProcessing) { + this.logger.debug('Previous reminder task still processing, skipping...'); + return; + } + this.isProcessing = true; + try { + const now = new Date(); + const thirtyMinutesLater = new Date(now.getTime() + 30 * 60 * 1000); + const upcomingSchedules = await this.prisma.schedulePlan.findMany({ + where: { + status: 'ACTIVE', + reminderSent: false, + scheduledDate: { + gte: new Date(now.toISOString().split('T')[0] + 'T00:00:00.000Z'), + lt: new Date(now.toISOString().split('T')[0] + 'T23:59:59.999Z'), + }, + teacherId: { not: null }, + }, + include: { + teacher: { + select: { id: true, name: true }, + }, + course: { + select: { name: true, pictureBookName: true }, + }, + class: { + select: { name: true }, + }, + }, + }); + const schedulesToRemind = upcomingSchedules.filter((schedule) => { + if (!schedule.scheduledTime) + return false; + const [startTime] = schedule.scheduledTime.split('-'); + if (!startTime) + return false; + const [hours, minutes] = startTime.split(':').map(Number); + const scheduleStartTime = new Date(now); + scheduleStartTime.setHours(hours, minutes, 0, 0); + return scheduleStartTime >= now && scheduleStartTime <= thirtyMinutesLater; + }); + this.logger.log(`Found ${schedulesToRemind.length} schedules to send reminders for`); + for (const schedule of schedulesToRemind) { + try { + await this.sendScheduleReminder(schedule); + } + catch (error) { + this.logger.error(`Failed to send reminder for schedule ${schedule.id}:`, error); + } + } + } + catch (error) { + this.logger.error('Error in schedule reminder task:', error); + } + finally { + this.isProcessing = false; + } + } + async sendScheduleReminder(schedule) { + if (!schedule.teacherId || !schedule.teacher) { + return; + } + const courseName = schedule.course?.pictureBookName || schedule.course?.name || '课程'; + const className = schedule.class?.name || '班级'; + const timeStr = schedule.scheduledTime || '时间待定'; + await this.notificationService.createNotification({ + tenantId: schedule.tenantId, + recipientType: 'TEACHER', + recipientId: schedule.teacherId, + title: '课程提醒', + content: `您即将在 ${timeStr} 为「${className}」讲授《${courseName}》,请做好准备。`, + notificationType: 'LESSON', + relatedType: 'SchedulePlan', + relatedId: schedule.id, + }); + await this.prisma.schedulePlan.update({ + where: { id: schedule.id }, + data: { + reminderSent: true, + reminderSentAt: new Date(), + }, + }); + this.logger.log(`Reminder sent for schedule ${schedule.id} to teacher ${schedule.teacherId}`); + } + async triggerReminderCheck() { + this.logger.log('Manual trigger of reminder check'); + await this.handleUpcomingScheduleReminders(); + return { message: 'Reminder check triggered' }; + } + async resetAllReminders() { + const result = await this.prisma.schedulePlan.updateMany({ + where: { + reminderSent: true, + }, + data: { + reminderSent: false, + reminderSentAt: null, + }, + }); + this.logger.log(`Reset ${result.count} schedule reminders`); + return { message: `Reset ${result.count} reminders` }; + } + async handleTaskDeadlineReminders() { + this.logger.log('Starting task deadline reminder check...'); + try { + const now = new Date(); + const threeDaysLater = new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000); + const oneDayLater = new Date(now.getTime() + 24 * 60 * 60 * 1000); + const tasksToRemind = await this.prisma.task.findMany({ + where: { + status: 'PUBLISHED', + endDate: { + gte: now, + lte: threeDaysLater, + }, + }, + include: { + course: { + select: { name: true, pictureBookName: true }, + }, + completions: { + where: { + status: { not: 'COMPLETED' }, + }, + include: { + student: { + select: { + id: true, + name: true, + parents: { + include: { + parent: { + select: { id: true }, + }, + }, + }, + }, + }, + }, + }, + }, + }); + this.logger.log(`Found ${tasksToRemind.length} tasks with upcoming deadlines`); + let reminderCount = 0; + for (const task of tasksToRemind) { + const daysRemaining = Math.ceil((task.endDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000)); + if (![3, 1, 0].includes(daysRemaining)) + continue; + const taskName = task.course?.pictureBookName || task.course?.name || task.title; + for (const completion of task.completions) { + for (const parentRelation of completion.student.parents) { + try { + await this.notificationService.createNotification({ + tenantId: task.tenantId, + recipientType: 'PARENT', + recipientId: parentRelation.parent.id, + title: '任务即将截止', + content: `「${completion.student.name}」的任务《${taskName}》将在${daysRemaining === 0 ? '今天' : daysRemaining + '天后'}截止,请尽快完成。`, + notificationType: 'TASK', + relatedType: 'Task', + relatedId: task.id, + }); + reminderCount++; + } + catch (error) { + this.logger.error(`Failed to send task reminder to parent ${parentRelation.parent.id}:`, error); + } + } + } + } + this.logger.log(`Sent ${reminderCount} task reminder notifications`); + } + catch (error) { + this.logger.error('Error in task deadline reminder task:', error); + } + } + async sendManualTaskReminder(tenantId, taskId) { + const task = await this.prisma.task.findFirst({ + where: { id: taskId, tenantId }, + include: { + course: { + select: { name: true, pictureBookName: true }, + }, + completions: { + where: { + status: { not: 'COMPLETED' }, + }, + include: { + student: { + select: { + id: true, + name: true, + parents: { + include: { + parent: { + select: { id: true, name: true }, + }, + }, + }, + }, + }, + }, + }, + }, + }); + if (!task) { + return { success: false, message: '任务不存在' }; + } + const taskName = task.course?.pictureBookName || task.course?.name || task.title; + let reminderCount = 0; + const remindedStudents = []; + for (const completion of task.completions) { + for (const parentRelation of completion.student.parents) { + try { + await this.notificationService.createNotification({ + tenantId: task.tenantId, + recipientType: 'PARENT', + recipientId: parentRelation.parent.id, + title: '阅读任务提醒', + content: `老师提醒您:「${completion.student.name}」的任务《${taskName}》尚未完成,请督促孩子尽快完成阅读任务。`, + notificationType: 'TASK', + relatedType: 'Task', + relatedId: task.id, + }); + reminderCount++; + } + catch (error) { + this.logger.error(`Failed to send manual task reminder to parent ${parentRelation.parent.id}:`, error); + } + } + remindedStudents.push({ + id: completion.student.id, + name: completion.student.name, + }); + } + this.logger.log(`Manual task reminder sent for task ${taskId} to ${reminderCount} parents`); + return { + success: true, + message: `已发送${reminderCount}条提醒`, + remindedCount: reminderCount, + students: remindedStudents, + }; + } +}; +exports.ScheduleNotificationService = ScheduleNotificationService; +__decorate([ + (0, schedule_1.Cron)(schedule_1.CronExpression.EVERY_30_MINUTES), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", Promise) +], ScheduleNotificationService.prototype, "handleUpcomingScheduleReminders", null); +__decorate([ + (0, schedule_1.Cron)('0 9 * * *'), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", Promise) +], ScheduleNotificationService.prototype, "handleTaskDeadlineReminders", null); +exports.ScheduleNotificationService = ScheduleNotificationService = ScheduleNotificationService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService, + notification_service_1.NotificationService]) +], ScheduleNotificationService); +//# sourceMappingURL=schedule-notification.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/notification/schedule-notification.service.js.map b/reading-platform-backend/dist/src/modules/notification/schedule-notification.service.js.map new file mode 100644 index 0000000..924ecf9 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/notification/schedule-notification.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"schedule-notification.service.js","sourceRoot":"","sources":["../../../../src/modules/notification/schedule-notification.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/parent/parent.controller.d.ts b/reading-platform-backend/dist/src/modules/parent/parent.controller.d.ts new file mode 100644 index 0000000..dff18cf --- /dev/null +++ b/reading-platform-backend/dist/src/modules/parent/parent.controller.d.ts @@ -0,0 +1,136 @@ +import { ParentService } from './parent.service'; +export declare class ParentController { + private readonly parentService; + constructor(parentService: ParentService); + getChildren(req: any): Promise<{ + id: number; + name: string; + gender: string; + birthDate: Date; + relationship: string; + class: { + id: number; + name: string; + grade: string; + }; + readingCount: number; + lessonCount: number; + }[]>; + getChildProfile(req: any, id: string): Promise<{ + stats: { + lessonRecords: number; + growthRecords: number; + taskCompletions: number; + }; + class: { + id: number; + name: string; + grade: string; + }; + id: number; + tenantId: number; + createdAt: Date; + updatedAt: Date; + name: string; + classId: number; + gender: string | null; + birthDate: Date | null; + parentPhone: string | null; + parentName: string | null; + readingCount: number; + lessonCount: number; + }>; + getChildLessons(req: any, id: string, query: any): Promise<{ + items: ({ + lesson: { + id: number; + course: { + id: number; + name: string; + pictureBookName: string; + }; + startDatetime: Date; + endDatetime: Date; + actualDuration: number; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + studentId: number; + lessonId: number; + participation: number | null; + focus: number | null; + interest: number | null; + understanding: number | null; + domainAchievements: string | null; + notes: string | null; + })[]; + total: number; + page: number; + pageSize: number; + }>; + getChildTasks(req: any, id: string, query: any): Promise<{ + items: ({ + task: { + id: number; + title: string; + description: string; + taskType: string; + startDate: Date; + endDate: Date; + course: { + id: number; + name: string; + }; + }; + } & { + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + })[]; + total: number; + page: number; + pageSize: number; + }>; + submitTaskFeedback(req: any, studentId: string, taskId: string, body: { + feedback: string; + }): Promise<{ + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + }>; + getChildGrowthRecords(req: any, id: string, query: any): Promise<{ + items: { + images: any; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }[]; + total: number; + page: number; + pageSize: number; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/parent/parent.controller.js b/reading-platform-backend/dist/src/modules/parent/parent.controller.js new file mode 100644 index 0000000..3e1f0bb --- /dev/null +++ b/reading-platform-backend/dist/src/modules/parent/parent.controller.js @@ -0,0 +1,103 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ParentController = void 0; +const common_1 = require("@nestjs/common"); +const parent_service_1 = require("./parent.service"); +const jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +const roles_guard_1 = require("../common/guards/roles.guard"); +const roles_decorator_1 = require("../common/decorators/roles.decorator"); +let ParentController = class ParentController { + constructor(parentService) { + this.parentService = parentService; + } + getChildren(req) { + return this.parentService.getChildren(req.user.userId, req.user.tenantId); + } + getChildProfile(req, id) { + return this.parentService.getChildProfile(req.user.userId, +id, req.user.tenantId); + } + getChildLessons(req, id, query) { + return this.parentService.getChildLessons(req.user.userId, +id, req.user.tenantId, query); + } + getChildTasks(req, id, query) { + return this.parentService.getChildTasks(req.user.userId, +id, req.user.tenantId, query); + } + submitTaskFeedback(req, studentId, taskId, body) { + return this.parentService.submitTaskFeedback(req.user.userId, +studentId, +taskId, req.user.tenantId, body.feedback); + } + getChildGrowthRecords(req, id, query) { + return this.parentService.getChildGrowthRecords(req.user.userId, +id, req.user.tenantId, query); + } +}; +exports.ParentController = ParentController; +__decorate([ + (0, common_1.Get)('children'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], ParentController.prototype, "getChildren", null); +__decorate([ + (0, common_1.Get)('children/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], ParentController.prototype, "getChildProfile", null); +__decorate([ + (0, common_1.Get)('children/:id/lessons'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", void 0) +], ParentController.prototype, "getChildLessons", null); +__decorate([ + (0, common_1.Get)('children/:id/tasks'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", void 0) +], ParentController.prototype, "getChildTasks", null); +__decorate([ + (0, common_1.Put)('children/:studentId/tasks/:taskId/feedback'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('studentId')), + __param(2, (0, common_1.Param)('taskId')), + __param(3, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, String, Object]), + __metadata("design:returntype", void 0) +], ParentController.prototype, "submitTaskFeedback", null); +__decorate([ + (0, common_1.Get)('children/:id/growth-records'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", void 0) +], ParentController.prototype, "getChildGrowthRecords", null); +exports.ParentController = ParentController = __decorate([ + (0, common_1.Controller)('parent'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('parent'), + __metadata("design:paramtypes", [parent_service_1.ParentService]) +], ParentController); +//# sourceMappingURL=parent.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/parent/parent.controller.js.map b/reading-platform-backend/dist/src/modules/parent/parent.controller.js.map new file mode 100644 index 0000000..b179723 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/parent/parent.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"parent.controller.js","sourceRoot":"","sources":["../../../../src/modules/parent/parent.controller.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/parent/parent.module.d.ts b/reading-platform-backend/dist/src/modules/parent/parent.module.d.ts new file mode 100644 index 0000000..fc10500 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/parent/parent.module.d.ts @@ -0,0 +1,2 @@ +export declare class ParentModule { +} diff --git a/reading-platform-backend/dist/src/modules/parent/parent.module.js b/reading-platform-backend/dist/src/modules/parent/parent.module.js new file mode 100644 index 0000000..474ff98 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/parent/parent.module.js @@ -0,0 +1,23 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ParentModule = void 0; +const common_1 = require("@nestjs/common"); +const parent_controller_1 = require("./parent.controller"); +const parent_service_1 = require("./parent.service"); +let ParentModule = class ParentModule { +}; +exports.ParentModule = ParentModule; +exports.ParentModule = ParentModule = __decorate([ + (0, common_1.Module)({ + controllers: [parent_controller_1.ParentController], + providers: [parent_service_1.ParentService], + exports: [parent_service_1.ParentService], + }) +], ParentModule); +//# sourceMappingURL=parent.module.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/parent/parent.module.js.map b/reading-platform-backend/dist/src/modules/parent/parent.module.js.map new file mode 100644 index 0000000..c716d7a --- /dev/null +++ b/reading-platform-backend/dist/src/modules/parent/parent.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"parent.module.js","sourceRoot":"","sources":["../../../../src/modules/parent/parent.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2DAAuD;AACvD,qDAAiD;AAO1C,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,oCAAY;uBAAZ,YAAY;IALxB,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,oCAAgB,CAAC;QAC/B,SAAS,EAAE,CAAC,8BAAa,CAAC;QAC1B,OAAO,EAAE,CAAC,8BAAa,CAAC;KACzB,CAAC;GACW,YAAY,CAAG"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/parent/parent.service.d.ts b/reading-platform-backend/dist/src/modules/parent/parent.service.d.ts new file mode 100644 index 0000000..b20dd55 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/parent/parent.service.d.ts @@ -0,0 +1,135 @@ +import { PrismaService } from '../../database/prisma.service'; +export declare class ParentService { + private prisma; + private readonly logger; + constructor(prisma: PrismaService); + getChildren(parentId: number, tenantId: number): Promise<{ + id: number; + name: string; + gender: string; + birthDate: Date; + relationship: string; + class: { + id: number; + name: string; + grade: string; + }; + readingCount: number; + lessonCount: number; + }[]>; + getChildProfile(parentId: number, studentId: number, tenantId: number): Promise<{ + stats: { + lessonRecords: number; + growthRecords: number; + taskCompletions: number; + }; + class: { + id: number; + name: string; + grade: string; + }; + id: number; + tenantId: number; + createdAt: Date; + updatedAt: Date; + name: string; + classId: number; + gender: string | null; + birthDate: Date | null; + parentPhone: string | null; + parentName: string | null; + readingCount: number; + lessonCount: number; + }>; + getChildLessons(parentId: number, studentId: number, tenantId: number, query: any): Promise<{ + items: ({ + lesson: { + id: number; + course: { + id: number; + name: string; + pictureBookName: string; + }; + startDatetime: Date; + endDatetime: Date; + actualDuration: number; + }; + } & { + id: number; + createdAt: Date; + updatedAt: Date; + studentId: number; + lessonId: number; + participation: number | null; + focus: number | null; + interest: number | null; + understanding: number | null; + domainAchievements: string | null; + notes: string | null; + })[]; + total: number; + page: number; + pageSize: number; + }>; + getChildTasks(parentId: number, studentId: number, tenantId: number, query: any): Promise<{ + items: ({ + task: { + id: number; + title: string; + description: string; + taskType: string; + startDate: Date; + endDate: Date; + course: { + id: number; + name: string; + }; + }; + } & { + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + })[]; + total: number; + page: number; + pageSize: number; + }>; + submitTaskFeedback(parentId: number, studentId: number, taskId: number, tenantId: number, feedback: string): Promise<{ + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + }>; + getChildGrowthRecords(parentId: number, studentId: number, tenantId: number, query: any): Promise<{ + items: { + images: any; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + createdBy: number; + createdAt: Date; + updatedAt: Date; + classId: number | null; + studentId: number; + recordType: string; + content: string | null; + recordDate: Date; + }[]; + total: number; + page: number; + pageSize: number; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/parent/parent.service.js b/reading-platform-backend/dist/src/modules/parent/parent.service.js new file mode 100644 index 0000000..3555822 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/parent/parent.service.js @@ -0,0 +1,271 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var ParentService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ParentService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../../database/prisma.service"); +let ParentService = ParentService_1 = class ParentService { + constructor(prisma) { + this.prisma = prisma; + this.logger = new common_1.Logger(ParentService_1.name); + } + async getChildren(parentId, tenantId) { + const parent = await this.prisma.parent.findFirst({ + where: { id: parentId, tenantId }, + include: { + children: { + include: { + student: { + include: { + class: { + select: { + id: true, + name: true, + grade: true, + }, + }, + }, + }, + }, + }, + }, + }); + if (!parent) { + throw new common_1.NotFoundException('家长不存在'); + } + return parent.children.map((c) => ({ + id: c.student.id, + name: c.student.name, + gender: c.student.gender, + birthDate: c.student.birthDate, + relationship: c.relationship, + class: c.student.class, + readingCount: c.student.readingCount, + lessonCount: c.student.lessonCount, + })); + } + async getChildProfile(parentId, studentId, tenantId) { + const relation = await this.prisma.parentStudent.findFirst({ + where: { parentId, studentId }, + }); + if (!relation) { + throw new common_1.ForbiddenException('您没有查看该学生信息的权限'); + } + const student = await this.prisma.student.findFirst({ + where: { id: studentId, tenantId }, + include: { + class: { + select: { + id: true, + name: true, + grade: true, + }, + }, + }, + }); + if (!student) { + throw new common_1.NotFoundException('学生不存在'); + } + const [lessonRecords, growthRecords, taskCompletions] = await Promise.all([ + this.prisma.studentRecord.count({ + where: { studentId }, + }), + this.prisma.growthRecord.count({ + where: { studentId }, + }), + this.prisma.taskCompletion.count({ + where: { + studentId, + status: 'COMPLETED', + }, + }), + ]); + return { + ...student, + stats: { + lessonRecords, + growthRecords, + taskCompletions, + }, + }; + } + async getChildLessons(parentId, studentId, tenantId, query) { + const relation = await this.prisma.parentStudent.findFirst({ + where: { parentId, studentId }, + }); + if (!relation) { + throw new common_1.ForbiddenException('您没有查看该学生信息的权限'); + } + const { page = 1, pageSize = 10 } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = { studentId }; + const [items, total] = await Promise.all([ + this.prisma.studentRecord.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + include: { + lesson: { + select: { + id: true, + startDatetime: true, + endDatetime: true, + actualDuration: true, + course: { + select: { + id: true, + name: true, + pictureBookName: true, + }, + }, + }, + }, + }, + }), + this.prisma.studentRecord.count({ where }), + ]); + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + async getChildTasks(parentId, studentId, tenantId, query) { + const relation = await this.prisma.parentStudent.findFirst({ + where: { parentId, studentId }, + }); + if (!relation) { + throw new common_1.ForbiddenException('您没有查看该学生信息的权限'); + } + const { page = 1, pageSize = 10, status } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = { + studentId, + task: { tenantId, status: 'PUBLISHED' }, + }; + if (status) { + where.status = status; + } + const [items, total] = await Promise.all([ + this.prisma.taskCompletion.findMany({ + where, + skip, + take, + orderBy: { task: { createdAt: 'desc' } }, + include: { + task: { + select: { + id: true, + title: true, + description: true, + taskType: true, + startDate: true, + endDate: true, + course: { + select: { + id: true, + name: true, + }, + }, + }, + }, + }, + }), + this.prisma.taskCompletion.count({ where }), + ]); + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + async submitTaskFeedback(parentId, studentId, taskId, tenantId, feedback) { + const relation = await this.prisma.parentStudent.findFirst({ + where: { parentId, studentId }, + }); + if (!relation) { + throw new common_1.ForbiddenException('您没有操作该学生信息的权限'); + } + const completion = await this.prisma.taskCompletion.findFirst({ + where: { + taskId, + studentId, + task: { tenantId }, + }, + }); + if (!completion) { + throw new common_1.NotFoundException('任务记录不存在'); + } + const updated = await this.prisma.taskCompletion.update({ + where: { + taskId_studentId: { taskId, studentId }, + }, + data: { + parentFeedback: feedback, + }, + }); + this.logger.log(`Parent feedback submitted: task=${taskId}, student=${studentId}`); + return updated; + } + async getChildGrowthRecords(parentId, studentId, tenantId, query) { + const relation = await this.prisma.parentStudent.findFirst({ + where: { parentId, studentId }, + }); + if (!relation) { + throw new common_1.ForbiddenException('您没有查看该学生信息的权限'); + } + const { page = 1, pageSize = 10 } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = { + studentId, + tenantId, + }; + const [items, total] = await Promise.all([ + this.prisma.growthRecord.findMany({ + where, + skip, + take, + orderBy: { recordDate: 'desc' }, + include: { + class: { + select: { + id: true, + name: true, + }, + }, + }, + }), + this.prisma.growthRecord.count({ where }), + ]); + return { + items: items.map((item) => ({ + ...item, + images: item.images ? JSON.parse(item.images) : [], + })), + total, + page: +page, + pageSize: +pageSize, + }; + } +}; +exports.ParentService = ParentService; +exports.ParentService = ParentService = ParentService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], ParentService); +//# sourceMappingURL=parent.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/parent/parent.service.js.map b/reading-platform-backend/dist/src/modules/parent/parent.service.js.map new file mode 100644 index 0000000..b03f60d --- /dev/null +++ b/reading-platform-backend/dist/src/modules/parent/parent.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"parent.service.js","sourceRoot":"","sources":["../../../../src/modules/parent/parent.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/resource/dto/create-resource.dto.d.ts b/reading-platform-backend/dist/src/modules/resource/dto/create-resource.dto.d.ts new file mode 100644 index 0000000..169c975 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/resource/dto/create-resource.dto.d.ts @@ -0,0 +1,53 @@ +export declare enum LibraryType { + PICTURE_BOOK = "PICTURE_BOOK", + MATERIAL = "MATERIAL", + TEMPLATE = "TEMPLATE" +} +export declare enum FileType { + IMAGE = "IMAGE", + PDF = "PDF", + VIDEO = "VIDEO", + AUDIO = "AUDIO", + PPT = "PPT", + OTHER = "OTHER" +} +export declare class CreateLibraryDto { + name: string; + libraryType: LibraryType; + description?: string; + coverImage?: string; +} +export declare class UpdateLibraryDto { + name?: string; + description?: string; + coverImage?: string; + sortOrder?: number; +} +export declare class CreateResourceItemDto { + libraryId: number; + title: string; + description?: string; + fileType: FileType; + filePath: string; + fileSize?: number; + tags?: string[]; +} +export declare class UpdateResourceItemDto { + title?: string; + description?: string; + tags?: string[]; + sortOrder?: number; +} +export declare class QueryLibraryDto { + page?: number; + pageSize?: number; + libraryType?: string; + keyword?: string; +} +export declare class QueryResourceItemDto { + page?: number; + pageSize?: number; + libraryId?: number; + fileType?: string; + keyword?: string; +} diff --git a/reading-platform-backend/dist/src/modules/resource/dto/create-resource.dto.js b/reading-platform-backend/dist/src/modules/resource/dto/create-resource.dto.js new file mode 100644 index 0000000..d1f45eb --- /dev/null +++ b/reading-platform-backend/dist/src/modules/resource/dto/create-resource.dto.js @@ -0,0 +1,191 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.QueryResourceItemDto = exports.QueryLibraryDto = exports.UpdateResourceItemDto = exports.CreateResourceItemDto = exports.UpdateLibraryDto = exports.CreateLibraryDto = exports.FileType = exports.LibraryType = void 0; +const class_validator_1 = require("class-validator"); +var LibraryType; +(function (LibraryType) { + LibraryType["PICTURE_BOOK"] = "PICTURE_BOOK"; + LibraryType["MATERIAL"] = "MATERIAL"; + LibraryType["TEMPLATE"] = "TEMPLATE"; +})(LibraryType || (exports.LibraryType = LibraryType = {})); +var FileType; +(function (FileType) { + FileType["IMAGE"] = "IMAGE"; + FileType["PDF"] = "PDF"; + FileType["VIDEO"] = "VIDEO"; + FileType["AUDIO"] = "AUDIO"; + FileType["PPT"] = "PPT"; + FileType["OTHER"] = "OTHER"; +})(FileType || (exports.FileType = FileType = {})); +class CreateLibraryDto { +} +exports.CreateLibraryDto = CreateLibraryDto; +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '资源库名称不能为空' }), + __metadata("design:type", String) +], CreateLibraryDto.prototype, "name", void 0); +__decorate([ + (0, class_validator_1.IsEnum)(LibraryType), + __metadata("design:type", String) +], CreateLibraryDto.prototype, "libraryType", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateLibraryDto.prototype, "description", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateLibraryDto.prototype, "coverImage", void 0); +class UpdateLibraryDto { +} +exports.UpdateLibraryDto = UpdateLibraryDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '资源库名称不能为空' }), + __metadata("design:type", String) +], UpdateLibraryDto.prototype, "name", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateLibraryDto.prototype, "description", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateLibraryDto.prototype, "coverImage", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + (0, class_validator_1.Min)(0), + __metadata("design:type", Number) +], UpdateLibraryDto.prototype, "sortOrder", void 0); +class CreateResourceItemDto { +} +exports.CreateResourceItemDto = CreateResourceItemDto; +__decorate([ + (0, class_validator_1.IsInt)(), + (0, class_validator_1.IsNotEmpty)({ message: '资源库ID不能为空' }), + __metadata("design:type", Number) +], CreateResourceItemDto.prototype, "libraryId", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '资源名称不能为空' }), + __metadata("design:type", String) +], CreateResourceItemDto.prototype, "title", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateResourceItemDto.prototype, "description", void 0); +__decorate([ + (0, class_validator_1.IsEnum)(FileType), + __metadata("design:type", String) +], CreateResourceItemDto.prototype, "fileType", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '文件路径不能为空' }), + __metadata("design:type", String) +], CreateResourceItemDto.prototype, "filePath", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], CreateResourceItemDto.prototype, "fileSize", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsArray)(), + (0, class_validator_1.IsString)({ each: true }), + __metadata("design:type", Array) +], CreateResourceItemDto.prototype, "tags", void 0); +class UpdateResourceItemDto { +} +exports.UpdateResourceItemDto = UpdateResourceItemDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '资源名称不能为空' }), + __metadata("design:type", String) +], UpdateResourceItemDto.prototype, "title", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateResourceItemDto.prototype, "description", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsArray)(), + (0, class_validator_1.IsString)({ each: true }), + __metadata("design:type", Array) +], UpdateResourceItemDto.prototype, "tags", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + (0, class_validator_1.Min)(0), + __metadata("design:type", Number) +], UpdateResourceItemDto.prototype, "sortOrder", void 0); +class QueryLibraryDto { +} +exports.QueryLibraryDto = QueryLibraryDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], QueryLibraryDto.prototype, "page", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], QueryLibraryDto.prototype, "pageSize", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], QueryLibraryDto.prototype, "libraryType", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], QueryLibraryDto.prototype, "keyword", void 0); +class QueryResourceItemDto { +} +exports.QueryResourceItemDto = QueryResourceItemDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], QueryResourceItemDto.prototype, "page", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], QueryResourceItemDto.prototype, "pageSize", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], QueryResourceItemDto.prototype, "libraryId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], QueryResourceItemDto.prototype, "fileType", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], QueryResourceItemDto.prototype, "keyword", void 0); +//# sourceMappingURL=create-resource.dto.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/resource/dto/create-resource.dto.js.map b/reading-platform-backend/dist/src/modules/resource/dto/create-resource.dto.js.map new file mode 100644 index 0000000..421537f --- /dev/null +++ b/reading-platform-backend/dist/src/modules/resource/dto/create-resource.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"create-resource.dto.js","sourceRoot":"","sources":["../../../../../src/modules/resource/dto/create-resource.dto.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/resource/resource.controller.d.ts b/reading-platform-backend/dist/src/modules/resource/resource.controller.d.ts new file mode 100644 index 0000000..9bd54a6 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/resource/resource.controller.d.ts @@ -0,0 +1,157 @@ +import { ResourceService } from './resource.service'; +import { CreateLibraryDto, UpdateLibraryDto, CreateResourceItemDto, UpdateResourceItemDto } from './dto/create-resource.dto'; +export declare class ResourceController { + private readonly resourceService; + constructor(resourceService: ResourceService); + findAllLibraries(query: any): Promise<{ + items: { + itemCount: number; + _count: any; + id: number; + tenantId: number | null; + description: string | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + sortOrder: number; + libraryType: string; + coverImage: string | null; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findLibrary(id: string): Promise<{ + items: { + tags: any[]; + id: number; + title: string; + description: string | null; + createdAt: Date; + sortOrder: number; + fileSize: number | null; + libraryId: number; + fileType: string; + filePath: string; + }[]; + id: number; + tenantId: number | null; + description: string | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + sortOrder: number; + libraryType: string; + coverImage: string | null; + }>; + createLibrary(dto: CreateLibraryDto, req: any): Promise<{ + id: number; + tenantId: number | null; + description: string | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + sortOrder: number; + libraryType: string; + coverImage: string | null; + }>; + updateLibrary(id: string, dto: UpdateLibraryDto): Promise<{ + id: number; + tenantId: number | null; + description: string | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + sortOrder: number; + libraryType: string; + coverImage: string | null; + }>; + deleteLibrary(id: string): Promise<{ + message: string; + }>; + findAllItems(query: any): Promise<{ + items: { + tags: any[]; + library: { + id: number; + name: string; + libraryType: string; + }; + id: number; + title: string; + description: string | null; + createdAt: Date; + sortOrder: number; + fileSize: number | null; + libraryId: number; + fileType: string; + filePath: string; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findItem(id: string): Promise<{ + tags: any[]; + library: { + id: number; + name: string; + libraryType: string; + }; + id: number; + title: string; + description: string | null; + createdAt: Date; + sortOrder: number; + fileSize: number | null; + libraryId: number; + fileType: string; + filePath: string; + }>; + createItem(dto: CreateResourceItemDto): Promise<{ + tags: any[]; + id: number; + title: string; + description: string | null; + createdAt: Date; + sortOrder: number; + fileSize: number | null; + libraryId: number; + fileType: string; + filePath: string; + }>; + updateItem(id: string, dto: UpdateResourceItemDto): Promise<{ + tags: any[]; + id: number; + title: string; + description: string | null; + createdAt: Date; + sortOrder: number; + fileSize: number | null; + libraryId: number; + fileType: string; + filePath: string; + }>; + deleteItem(id: string): Promise<{ + message: string; + }>; + batchDeleteItems(body: { + ids: number[]; + }): Promise<{ + message: string; + }>; + getStats(): Promise<{ + totalLibraries: number; + totalItems: number; + itemsByType: Record; + itemsByLibraryType: Record; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/resource/resource.controller.js b/reading-platform-backend/dist/src/modules/resource/resource.controller.js new file mode 100644 index 0000000..149a762 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/resource/resource.controller.js @@ -0,0 +1,156 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ResourceController = void 0; +const common_1 = require("@nestjs/common"); +const resource_service_1 = require("./resource.service"); +const create_resource_dto_1 = require("./dto/create-resource.dto"); +const jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +const roles_guard_1 = require("../common/guards/roles.guard"); +const roles_decorator_1 = require("../common/decorators/roles.decorator"); +let ResourceController = class ResourceController { + constructor(resourceService) { + this.resourceService = resourceService; + } + findAllLibraries(query) { + return this.resourceService.findAllLibraries(query); + } + findLibrary(id) { + return this.resourceService.findLibrary(+id); + } + createLibrary(dto, req) { + return this.resourceService.createLibrary(dto, req.user.userId); + } + updateLibrary(id, dto) { + return this.resourceService.updateLibrary(+id, dto); + } + deleteLibrary(id) { + return this.resourceService.deleteLibrary(+id); + } + findAllItems(query) { + return this.resourceService.findAllItems(query); + } + findItem(id) { + return this.resourceService.findItem(+id); + } + createItem(dto) { + return this.resourceService.createItem(dto); + } + updateItem(id, dto) { + return this.resourceService.updateItem(+id, dto); + } + deleteItem(id) { + return this.resourceService.deleteItem(+id); + } + batchDeleteItems(body) { + return this.resourceService.batchDeleteItems(body.ids); + } + getStats() { + return this.resourceService.getStats(); + } +}; +exports.ResourceController = ResourceController; +__decorate([ + (0, common_1.Get)('libraries'), + __param(0, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], ResourceController.prototype, "findAllLibraries", null); +__decorate([ + (0, common_1.Get)('libraries/:id'), + __param(0, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", void 0) +], ResourceController.prototype, "findLibrary", null); +__decorate([ + (0, common_1.Post)('libraries'), + __param(0, (0, common_1.Body)()), + __param(1, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [create_resource_dto_1.CreateLibraryDto, Object]), + __metadata("design:returntype", void 0) +], ResourceController.prototype, "createLibrary", null); +__decorate([ + (0, common_1.Put)('libraries/:id'), + __param(0, (0, common_1.Param)('id')), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, create_resource_dto_1.UpdateLibraryDto]), + __metadata("design:returntype", void 0) +], ResourceController.prototype, "updateLibrary", null); +__decorate([ + (0, common_1.Delete)('libraries/:id'), + __param(0, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", void 0) +], ResourceController.prototype, "deleteLibrary", null); +__decorate([ + (0, common_1.Get)('items'), + __param(0, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], ResourceController.prototype, "findAllItems", null); +__decorate([ + (0, common_1.Get)('items/:id'), + __param(0, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", void 0) +], ResourceController.prototype, "findItem", null); +__decorate([ + (0, common_1.Post)('items'), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [create_resource_dto_1.CreateResourceItemDto]), + __metadata("design:returntype", void 0) +], ResourceController.prototype, "createItem", null); +__decorate([ + (0, common_1.Put)('items/:id'), + __param(0, (0, common_1.Param)('id')), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, create_resource_dto_1.UpdateResourceItemDto]), + __metadata("design:returntype", void 0) +], ResourceController.prototype, "updateItem", null); +__decorate([ + (0, common_1.Delete)('items/:id'), + __param(0, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", void 0) +], ResourceController.prototype, "deleteItem", null); +__decorate([ + (0, common_1.Post)('items/batch-delete'), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], ResourceController.prototype, "batchDeleteItems", null); +__decorate([ + (0, common_1.Get)('stats'), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", void 0) +], ResourceController.prototype, "getStats", null); +exports.ResourceController = ResourceController = __decorate([ + (0, common_1.Controller)('admin/resources'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('admin'), + __metadata("design:paramtypes", [resource_service_1.ResourceService]) +], ResourceController); +//# sourceMappingURL=resource.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/resource/resource.controller.js.map b/reading-platform-backend/dist/src/modules/resource/resource.controller.js.map new file mode 100644 index 0000000..bd0ec2d --- /dev/null +++ b/reading-platform-backend/dist/src/modules/resource/resource.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"resource.controller.js","sourceRoot":"","sources":["../../../../src/modules/resource/resource.controller.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/resource/resource.module.d.ts b/reading-platform-backend/dist/src/modules/resource/resource.module.d.ts new file mode 100644 index 0000000..50d39ca --- /dev/null +++ b/reading-platform-backend/dist/src/modules/resource/resource.module.d.ts @@ -0,0 +1,2 @@ +export declare class ResourceModule { +} diff --git a/reading-platform-backend/dist/src/modules/resource/resource.module.js b/reading-platform-backend/dist/src/modules/resource/resource.module.js new file mode 100644 index 0000000..8db9b49 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/resource/resource.module.js @@ -0,0 +1,23 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ResourceModule = void 0; +const common_1 = require("@nestjs/common"); +const resource_controller_1 = require("./resource.controller"); +const resource_service_1 = require("./resource.service"); +let ResourceModule = class ResourceModule { +}; +exports.ResourceModule = ResourceModule; +exports.ResourceModule = ResourceModule = __decorate([ + (0, common_1.Module)({ + controllers: [resource_controller_1.ResourceController], + providers: [resource_service_1.ResourceService], + exports: [resource_service_1.ResourceService], + }) +], ResourceModule); +//# sourceMappingURL=resource.module.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/resource/resource.module.js.map b/reading-platform-backend/dist/src/modules/resource/resource.module.js.map new file mode 100644 index 0000000..4ccab78 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/resource/resource.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"resource.module.js","sourceRoot":"","sources":["../../../../src/modules/resource/resource.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,+DAA2D;AAC3D,yDAAqD;AAO9C,IAAM,cAAc,GAApB,MAAM,cAAc;CAAG,CAAA;AAAjB,wCAAc;yBAAd,cAAc;IAL1B,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,wCAAkB,CAAC;QACjC,SAAS,EAAE,CAAC,kCAAe,CAAC;QAC5B,OAAO,EAAE,CAAC,kCAAe,CAAC;KAC3B,CAAC;GACW,cAAc,CAAG"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/resource/resource.service.d.ts b/reading-platform-backend/dist/src/modules/resource/resource.service.d.ts new file mode 100644 index 0000000..86e3afc --- /dev/null +++ b/reading-platform-backend/dist/src/modules/resource/resource.service.d.ts @@ -0,0 +1,157 @@ +import { PrismaService } from '../../database/prisma.service'; +import { CreateLibraryDto, UpdateLibraryDto, CreateResourceItemDto, UpdateResourceItemDto } from './dto/create-resource.dto'; +export declare class ResourceService { + private prisma; + private readonly logger; + constructor(prisma: PrismaService); + private parseJsonArray; + findAllLibraries(query: any): Promise<{ + items: { + itemCount: number; + _count: any; + id: number; + tenantId: number | null; + description: string | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + sortOrder: number; + libraryType: string; + coverImage: string | null; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findLibrary(id: number): Promise<{ + items: { + tags: any[]; + id: number; + title: string; + description: string | null; + createdAt: Date; + sortOrder: number; + fileSize: number | null; + libraryId: number; + fileType: string; + filePath: string; + }[]; + id: number; + tenantId: number | null; + description: string | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + sortOrder: number; + libraryType: string; + coverImage: string | null; + }>; + createLibrary(dto: CreateLibraryDto, userId: number): Promise<{ + id: number; + tenantId: number | null; + description: string | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + sortOrder: number; + libraryType: string; + coverImage: string | null; + }>; + updateLibrary(id: number, dto: UpdateLibraryDto): Promise<{ + id: number; + tenantId: number | null; + description: string | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + sortOrder: number; + libraryType: string; + coverImage: string | null; + }>; + deleteLibrary(id: number): Promise<{ + message: string; + }>; + findAllItems(query: any): Promise<{ + items: { + tags: any[]; + library: { + id: number; + name: string; + libraryType: string; + }; + id: number; + title: string; + description: string | null; + createdAt: Date; + sortOrder: number; + fileSize: number | null; + libraryId: number; + fileType: string; + filePath: string; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findItem(id: number): Promise<{ + tags: any[]; + library: { + id: number; + name: string; + libraryType: string; + }; + id: number; + title: string; + description: string | null; + createdAt: Date; + sortOrder: number; + fileSize: number | null; + libraryId: number; + fileType: string; + filePath: string; + }>; + createItem(dto: CreateResourceItemDto): Promise<{ + tags: any[]; + id: number; + title: string; + description: string | null; + createdAt: Date; + sortOrder: number; + fileSize: number | null; + libraryId: number; + fileType: string; + filePath: string; + }>; + updateItem(id: number, dto: UpdateResourceItemDto): Promise<{ + tags: any[]; + id: number; + title: string; + description: string | null; + createdAt: Date; + sortOrder: number; + fileSize: number | null; + libraryId: number; + fileType: string; + filePath: string; + }>; + deleteItem(id: number): Promise<{ + message: string; + }>; + batchDeleteItems(ids: number[]): Promise<{ + message: string; + }>; + getStats(): Promise<{ + totalLibraries: number; + totalItems: number; + itemsByType: Record; + itemsByLibraryType: Record; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/resource/resource.service.js b/reading-platform-backend/dist/src/modules/resource/resource.service.js new file mode 100644 index 0000000..fbb4978 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/resource/resource.service.js @@ -0,0 +1,311 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var ResourceService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ResourceService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../../database/prisma.service"); +let ResourceService = ResourceService_1 = class ResourceService { + constructor(prisma) { + this.prisma = prisma; + this.logger = new common_1.Logger(ResourceService_1.name); + } + parseJsonArray(value) { + if (!value) + return []; + if (typeof value === 'string') { + try { + return JSON.parse(value); + } + catch { + return []; + } + } + return Array.isArray(value) ? value : []; + } + async findAllLibraries(query) { + const { page = 1, pageSize = 10, libraryType, keyword } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = {}; + if (libraryType) { + where.libraryType = libraryType; + } + if (keyword) { + where.name = { contains: keyword }; + } + const [items, total] = await Promise.all([ + this.prisma.resourceLibrary.findMany({ + where, + skip, + take, + orderBy: [{ sortOrder: 'asc' }, { createdAt: 'desc' }], + include: { + _count: { + select: { + items: true, + }, + }, + }, + }), + this.prisma.resourceLibrary.count({ where }), + ]); + return { + items: items.map((lib) => ({ + ...lib, + itemCount: lib._count.items, + _count: undefined, + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + async findLibrary(id) { + const library = await this.prisma.resourceLibrary.findUnique({ + where: { id }, + include: { + items: { + orderBy: { sortOrder: 'asc' }, + take: 100, + }, + }, + }); + if (!library) { + throw new common_1.NotFoundException('资源库不存在'); + } + return { + ...library, + items: library.items.map((item) => ({ + ...item, + tags: this.parseJsonArray(item.tags), + })), + }; + } + async createLibrary(dto, userId) { + const library = await this.prisma.resourceLibrary.create({ + data: { + name: dto.name, + libraryType: dto.libraryType, + description: dto.description, + coverImage: dto.coverImage, + createdBy: userId, + }, + }); + this.logger.log(`Library created: ${library.id}`); + return library; + } + async updateLibrary(id, dto) { + const library = await this.prisma.resourceLibrary.findUnique({ + where: { id }, + }); + if (!library) { + throw new common_1.NotFoundException('资源库不存在'); + } + const updated = await this.prisma.resourceLibrary.update({ + where: { id }, + data: { + name: dto.name, + description: dto.description, + coverImage: dto.coverImage, + sortOrder: dto.sortOrder, + }, + }); + this.logger.log(`Library updated: ${id}`); + return updated; + } + async deleteLibrary(id) { + const library = await this.prisma.resourceLibrary.findUnique({ + where: { id }, + include: { + _count: { + select: { + items: true, + }, + }, + }, + }); + if (!library) { + throw new common_1.NotFoundException('资源库不存在'); + } + await this.prisma.resourceLibrary.delete({ + where: { id }, + }); + this.logger.log(`Library deleted: ${id}`); + return { message: '删除成功' }; + } + async findAllItems(query) { + const { page = 1, pageSize = 20, libraryId, fileType, keyword } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = {}; + if (libraryId) { + where.libraryId = +libraryId; + } + if (fileType) { + where.fileType = fileType; + } + if (keyword) { + where.OR = [ + { title: { contains: keyword } }, + { description: { contains: keyword } }, + ]; + } + const [items, total] = await Promise.all([ + this.prisma.resourceItem.findMany({ + where, + skip, + take, + orderBy: [{ sortOrder: 'asc' }, { createdAt: 'desc' }], + include: { + library: { + select: { + id: true, + name: true, + libraryType: true, + }, + }, + }, + }), + this.prisma.resourceItem.count({ where }), + ]); + return { + items: items.map((item) => ({ + ...item, + tags: this.parseJsonArray(item.tags), + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + async findItem(id) { + const item = await this.prisma.resourceItem.findUnique({ + where: { id }, + include: { + library: { + select: { + id: true, + name: true, + libraryType: true, + }, + }, + }, + }); + if (!item) { + throw new common_1.NotFoundException('资源项目不存在'); + } + return { + ...item, + tags: this.parseJsonArray(item.tags), + }; + } + async createItem(dto) { + const library = await this.prisma.resourceLibrary.findUnique({ + where: { id: dto.libraryId }, + }); + if (!library) { + throw new common_1.NotFoundException('资源库不存在'); + } + const item = await this.prisma.resourceItem.create({ + data: { + libraryId: dto.libraryId, + title: dto.title, + description: dto.description, + fileType: dto.fileType, + filePath: dto.filePath, + fileSize: dto.fileSize, + tags: JSON.stringify(dto.tags || []), + }, + }); + this.logger.log(`Resource item created: ${item.id}`); + return { + ...item, + tags: this.parseJsonArray(item.tags), + }; + } + async updateItem(id, dto) { + const item = await this.prisma.resourceItem.findUnique({ + where: { id }, + }); + if (!item) { + throw new common_1.NotFoundException('资源项目不存在'); + } + const updated = await this.prisma.resourceItem.update({ + where: { id }, + data: { + title: dto.title, + description: dto.description, + tags: dto.tags ? JSON.stringify(dto.tags) : undefined, + sortOrder: dto.sortOrder, + }, + }); + this.logger.log(`Resource item updated: ${id}`); + return { + ...updated, + tags: this.parseJsonArray(updated.tags), + }; + } + async deleteItem(id) { + const item = await this.prisma.resourceItem.findUnique({ + where: { id }, + }); + if (!item) { + throw new common_1.NotFoundException('资源项目不存在'); + } + await this.prisma.resourceItem.delete({ + where: { id }, + }); + this.logger.log(`Resource item deleted: ${id}`); + return { message: '删除成功' }; + } + async batchDeleteItems(ids) { + await this.prisma.resourceItem.deleteMany({ + where: { + id: { in: ids }, + }, + }); + this.logger.log(`Batch deleted ${ids.length} resource items`); + return { message: `成功删除 ${ids.length} 个资源` }; + } + async getStats() { + const [totalLibraries, totalItems, itemsByType, itemsByLibraryType] = await Promise.all([ + this.prisma.resourceLibrary.count(), + this.prisma.resourceItem.count(), + this.prisma.resourceItem.groupBy({ + by: ['fileType'], + _count: true, + }), + this.prisma.resourceLibrary.groupBy({ + by: ['libraryType'], + _count: true, + }), + ]); + return { + totalLibraries, + totalItems, + itemsByType: itemsByType.reduce((acc, item) => { + acc[item.fileType] = item._count; + return acc; + }, {}), + itemsByLibraryType: itemsByLibraryType.reduce((acc, lib) => { + acc[lib.libraryType] = lib._count; + return acc; + }, {}), + }; + } +}; +exports.ResourceService = ResourceService; +exports.ResourceService = ResourceService = ResourceService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], ResourceService); +//# sourceMappingURL=resource.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/resource/resource.service.js.map b/reading-platform-backend/dist/src/modules/resource/resource.service.js.map new file mode 100644 index 0000000..8932b28 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/resource/resource.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"resource.service.js","sourceRoot":"","sources":["../../../../src/modules/resource/resource.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/dto/class-teacher.dto.d.ts b/reading-platform-backend/dist/src/modules/school/dto/class-teacher.dto.d.ts new file mode 100644 index 0000000..abb4f41 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/dto/class-teacher.dto.d.ts @@ -0,0 +1,14 @@ +export type TeacherRole = 'MAIN' | 'ASSIST' | 'CARE'; +export declare class AddClassTeacherDto { + teacherId: number; + role: TeacherRole; + isPrimary?: boolean; +} +export declare class UpdateClassTeacherDto { + role?: TeacherRole; + isPrimary?: boolean; +} +export declare class TransferStudentDto { + toClassId: number; + reason?: string; +} diff --git a/reading-platform-backend/dist/src/modules/school/dto/class-teacher.dto.js b/reading-platform-backend/dist/src/modules/school/dto/class-teacher.dto.js new file mode 100644 index 0000000..ab7b6cd --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/dto/class-teacher.dto.js @@ -0,0 +1,57 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TransferStudentDto = exports.UpdateClassTeacherDto = exports.AddClassTeacherDto = void 0; +const class_validator_1 = require("class-validator"); +class AddClassTeacherDto { +} +exports.AddClassTeacherDto = AddClassTeacherDto; +__decorate([ + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], AddClassTeacherDto.prototype, "teacherId", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsIn)(['MAIN', 'ASSIST', 'CARE']), + __metadata("design:type", String) +], AddClassTeacherDto.prototype, "role", void 0); +__decorate([ + (0, class_validator_1.IsBoolean)(), + (0, class_validator_1.IsOptional)(), + __metadata("design:type", Boolean) +], AddClassTeacherDto.prototype, "isPrimary", void 0); +class UpdateClassTeacherDto { +} +exports.UpdateClassTeacherDto = UpdateClassTeacherDto; +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsIn)(['MAIN', 'ASSIST', 'CARE']), + (0, class_validator_1.IsOptional)(), + __metadata("design:type", String) +], UpdateClassTeacherDto.prototype, "role", void 0); +__decorate([ + (0, class_validator_1.IsBoolean)(), + (0, class_validator_1.IsOptional)(), + __metadata("design:type", Boolean) +], UpdateClassTeacherDto.prototype, "isPrimary", void 0); +class TransferStudentDto { +} +exports.TransferStudentDto = TransferStudentDto; +__decorate([ + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], TransferStudentDto.prototype, "toClassId", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsOptional)(), + __metadata("design:type", String) +], TransferStudentDto.prototype, "reason", void 0); +//# sourceMappingURL=class-teacher.dto.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/dto/class-teacher.dto.js.map b/reading-platform-backend/dist/src/modules/school/dto/class-teacher.dto.js.map new file mode 100644 index 0000000..6ecd19b --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/dto/class-teacher.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"class-teacher.dto.js","sourceRoot":"","sources":["../../../../../src/modules/school/dto/class-teacher.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAA+E;AAM/E,MAAa,kBAAkB;CAW9B;AAXD,gDAWC;AATC;IADC,IAAA,uBAAK,GAAE;;qDACU;AAIlB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,sBAAI,EAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;;gDACf;AAIlB;IAFC,IAAA,2BAAS,GAAE;IACX,IAAA,4BAAU,GAAE;;qDACO;AAItB,MAAa,qBAAqB;CASjC;AATD,sDASC;AALC;IAHC,IAAA,0BAAQ,GAAE;IACV,IAAA,sBAAI,EAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAChC,IAAA,4BAAU,GAAE;;mDACM;AAInB;IAFC,IAAA,2BAAS,GAAE;IACX,IAAA,4BAAU,GAAE;;wDACO;AAItB,MAAa,kBAAkB;CAO9B;AAPD,gDAOC;AALC;IADC,IAAA,uBAAK,GAAE;;qDACU;AAIlB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;kDACG"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/dto/create-class.dto.d.ts b/reading-platform-backend/dist/src/modules/school/dto/create-class.dto.d.ts new file mode 100644 index 0000000..2ac8590 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/dto/create-class.dto.d.ts @@ -0,0 +1,10 @@ +export declare class CreateClassDto { + name: string; + grade: string; + teacherId?: number; +} +export declare class UpdateClassDto { + name?: string; + grade?: string; + teacherId?: number; +} diff --git a/reading-platform-backend/dist/src/modules/school/dto/create-class.dto.js b/reading-platform-backend/dist/src/modules/school/dto/create-class.dto.js new file mode 100644 index 0000000..d868486 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/dto/create-class.dto.js @@ -0,0 +1,52 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.UpdateClassDto = exports.CreateClassDto = void 0; +const class_validator_1 = require("class-validator"); +class CreateClassDto { +} +exports.CreateClassDto = CreateClassDto; +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '班级名称不能为空' }), + __metadata("design:type", String) +], CreateClassDto.prototype, "name", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '年级不能为空' }), + __metadata("design:type", String) +], CreateClassDto.prototype, "grade", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], CreateClassDto.prototype, "teacherId", void 0); +class UpdateClassDto { +} +exports.UpdateClassDto = UpdateClassDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '班级名称不能为空' }), + __metadata("design:type", String) +], UpdateClassDto.prototype, "name", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '年级不能为空' }), + __metadata("design:type", String) +], UpdateClassDto.prototype, "grade", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], UpdateClassDto.prototype, "teacherId", void 0); +//# sourceMappingURL=create-class.dto.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/dto/create-class.dto.js.map b/reading-platform-backend/dist/src/modules/school/dto/create-class.dto.js.map new file mode 100644 index 0000000..652dcbb --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/dto/create-class.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"create-class.dto.js","sourceRoot":"","sources":["../../../../../src/modules/school/dto/create-class.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAmF;AAEnF,MAAa,cAAc;CAY1B;AAZD,wCAYC;AATC;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,EAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;;4CACvB;AAIb;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,EAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;;6CACpB;AAId;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;;iDACW;AAGrB,MAAa,cAAc;CAc1B;AAdD,wCAcC;AAVC;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,EAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;;4CACtB;AAKd;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,EAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;;6CACnB;AAIf;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;;iDACW"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/dto/create-student.dto.d.ts b/reading-platform-backend/dist/src/modules/school/dto/create-student.dto.d.ts new file mode 100644 index 0000000..8976da2 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/dto/create-student.dto.d.ts @@ -0,0 +1,16 @@ +export declare class CreateStudentDto { + name: string; + gender?: string; + birthDate?: string; + classId: number; + parentName?: string; + parentPhone?: string; +} +export declare class UpdateStudentDto { + name?: string; + gender?: string; + birthDate?: string; + classId?: number; + parentName?: string; + parentPhone?: string; +} diff --git a/reading-platform-backend/dist/src/modules/school/dto/create-student.dto.js b/reading-platform-backend/dist/src/modules/school/dto/create-student.dto.js new file mode 100644 index 0000000..572eabc --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/dto/create-student.dto.js @@ -0,0 +1,83 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.UpdateStudentDto = exports.CreateStudentDto = void 0; +const class_validator_1 = require("class-validator"); +class CreateStudentDto { +} +exports.CreateStudentDto = CreateStudentDto; +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '姓名不能为空' }), + __metadata("design:type", String) +], CreateStudentDto.prototype, "name", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsIn)(['男', '女'], { message: '性别只能是男或女' }), + __metadata("design:type", String) +], CreateStudentDto.prototype, "gender", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateStudentDto.prototype, "birthDate", void 0); +__decorate([ + (0, class_validator_1.IsInt)(), + (0, class_validator_1.IsNotEmpty)({ message: '班级不能为空' }), + __metadata("design:type", Number) +], CreateStudentDto.prototype, "classId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateStudentDto.prototype, "parentName", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.Matches)(/^1[3-9]\d{9}$/, { message: '请输入正确的手机号' }), + __metadata("design:type", String) +], CreateStudentDto.prototype, "parentPhone", void 0); +class UpdateStudentDto { +} +exports.UpdateStudentDto = UpdateStudentDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '姓名不能为空' }), + __metadata("design:type", String) +], UpdateStudentDto.prototype, "name", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsIn)(['男', '女'], { message: '性别只能是男或女' }), + __metadata("design:type", String) +], UpdateStudentDto.prototype, "gender", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateStudentDto.prototype, "birthDate", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], UpdateStudentDto.prototype, "classId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateStudentDto.prototype, "parentName", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.Matches)(/^1[3-9]\d{9}$/, { message: '请输入正确的手机号' }), + __metadata("design:type", String) +], UpdateStudentDto.prototype, "parentPhone", void 0); +//# sourceMappingURL=create-student.dto.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/dto/create-student.dto.js.map b/reading-platform-backend/dist/src/modules/school/dto/create-student.dto.js.map new file mode 100644 index 0000000..d07a5a5 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/dto/create-student.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"create-student.dto.js","sourceRoot":"","sources":["../../../../../src/modules/school/dto/create-student.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAyF;AAEzF,MAAa,gBAAgB;CAyB5B;AAzBD,4CAyBC;AAtBC;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,EAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;;8CACrB;AAIb;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,sBAAI,EAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;;gDAC1B;AAIhB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;mDACQ;AAInB;IAFC,IAAA,uBAAK,GAAE;IACP,IAAA,4BAAU,EAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;;iDAClB;AAIhB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;oDACS;AAKpB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,yBAAO,EAAC,eAAe,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;;qDAC9B;AAGvB,MAAa,gBAAgB;CA0B5B;AA1BD,4CA0BC;AAtBC;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,EAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;;8CACpB;AAId;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,sBAAI,EAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;;gDAC1B;AAIhB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;mDACQ;AAInB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;;iDACS;AAIjB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;oDACS;AAKpB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,yBAAO,EAAC,eAAe,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;;qDAC9B"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/dto/create-teacher.dto.d.ts b/reading-platform-backend/dist/src/modules/school/dto/create-teacher.dto.d.ts new file mode 100644 index 0000000..db6b247 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/dto/create-teacher.dto.d.ts @@ -0,0 +1,14 @@ +export declare class CreateTeacherDto { + name: string; + phone: string; + email?: string; + loginAccount: string; + password?: string; + classIds?: number[]; +} +export declare class UpdateTeacherDto { + name?: string; + phone?: string; + email?: string; + classIds?: number[]; +} diff --git a/reading-platform-backend/dist/src/modules/school/dto/create-teacher.dto.js b/reading-platform-backend/dist/src/modules/school/dto/create-teacher.dto.js new file mode 100644 index 0000000..cf65b19 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/dto/create-teacher.dto.js @@ -0,0 +1,76 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.UpdateTeacherDto = exports.CreateTeacherDto = void 0; +const class_validator_1 = require("class-validator"); +class CreateTeacherDto { +} +exports.CreateTeacherDto = CreateTeacherDto; +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '姓名不能为空' }), + __metadata("design:type", String) +], CreateTeacherDto.prototype, "name", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '手机号不能为空' }), + (0, class_validator_1.Matches)(/^1[3-9]\d{9}$/, { message: '请输入正确的手机号' }), + __metadata("design:type", String) +], CreateTeacherDto.prototype, "phone", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsEmail)({}, { message: '请输入正确的邮箱格式' }), + __metadata("design:type", String) +], CreateTeacherDto.prototype, "email", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '登录账号不能为空' }), + __metadata("design:type", String) +], CreateTeacherDto.prototype, "loginAccount", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.MinLength)(6, { message: '密码至少6位' }), + __metadata("design:type", String) +], CreateTeacherDto.prototype, "password", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsArray)(), + (0, class_validator_1.IsInt)({ each: true }), + __metadata("design:type", Array) +], CreateTeacherDto.prototype, "classIds", void 0); +class UpdateTeacherDto { +} +exports.UpdateTeacherDto = UpdateTeacherDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '姓名不能为空' }), + __metadata("design:type", String) +], UpdateTeacherDto.prototype, "name", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.Matches)(/^1[3-9]\d{9}$/, { message: '请输入正确的手机号' }), + __metadata("design:type", String) +], UpdateTeacherDto.prototype, "phone", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsEmail)({}, { message: '请输入正确的邮箱格式' }), + __metadata("design:type", String) +], UpdateTeacherDto.prototype, "email", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsArray)(), + (0, class_validator_1.IsInt)({ each: true }), + __metadata("design:type", Array) +], UpdateTeacherDto.prototype, "classIds", void 0); +//# sourceMappingURL=create-teacher.dto.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/dto/create-teacher.dto.js.map b/reading-platform-backend/dist/src/modules/school/dto/create-teacher.dto.js.map new file mode 100644 index 0000000..08a11cf --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/dto/create-teacher.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"create-teacher.dto.js","sourceRoot":"","sources":["../../../../../src/modules/school/dto/create-teacher.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAgH;AAEhH,MAAa,gBAAgB;CA2B5B;AA3BD,4CA2BC;AAxBC;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,EAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;;8CACrB;AAKb;IAHC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,EAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;IAClC,IAAA,yBAAO,EAAC,eAAe,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;;+CACrC;AAId;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,yBAAO,EAAC,EAAE,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;;+CACxB;AAIf;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,EAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;;sDACf;AAKrB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,2BAAS,EAAC,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;;kDAClB;AAKlB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,yBAAO,GAAE;IACT,IAAA,uBAAK,EAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;;kDACF;AAGtB,MAAa,gBAAgB;CAmB5B;AAnBD,4CAmBC;AAfC;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,EAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;;8CACpB;AAKd;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,yBAAO,EAAC,eAAe,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;;+CACpC;AAIf;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,yBAAO,EAAC,EAAE,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;;+CACxB;AAKf;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,yBAAO,GAAE;IACT,IAAA,uBAAK,EAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;;kDACF"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/dto/import-students.dto.d.ts b/reading-platform-backend/dist/src/modules/school/dto/import-students.dto.d.ts new file mode 100644 index 0000000..ac38ed5 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/dto/import-students.dto.d.ts @@ -0,0 +1,3 @@ +export declare class ImportStudentsDto { + classId?: number; +} diff --git a/reading-platform-backend/dist/src/modules/school/dto/import-students.dto.js b/reading-platform-backend/dist/src/modules/school/dto/import-students.dto.js new file mode 100644 index 0000000..a7ac049 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/dto/import-students.dto.js @@ -0,0 +1,22 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ImportStudentsDto = void 0; +const class_validator_1 = require("class-validator"); +class ImportStudentsDto { +} +exports.ImportStudentsDto = ImportStudentsDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], ImportStudentsDto.prototype, "classId", void 0); +//# sourceMappingURL=import-students.dto.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/dto/import-students.dto.js.map b/reading-platform-backend/dist/src/modules/school/dto/import-students.dto.js.map new file mode 100644 index 0000000..9176d1b --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/dto/import-students.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"import-students.dto.js","sourceRoot":"","sources":["../../../../../src/modules/school/dto/import-students.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAoD;AAEpD,MAAa,iBAAiB;CAI7B;AAJD,8CAIC;AADC;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,uBAAK,GAAE;;kDACS"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/dto/schedule.dto.d.ts b/reading-platform-backend/dist/src/modules/school/dto/schedule.dto.d.ts new file mode 100644 index 0000000..91b95bd --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/dto/schedule.dto.d.ts @@ -0,0 +1,49 @@ +export declare class CreateScheduleDto { + classId: number; + courseId: number; + teacherId?: number; + scheduledDate?: string; + scheduledTime?: string; + weekDay?: number; + repeatType: string; + repeatEndDate?: string; + note?: string; +} +export declare class UpdateScheduleDto { + teacherId?: number; + scheduledDate?: string; + scheduledTime?: string; + weekDay?: number; + repeatType?: string; + repeatEndDate?: string; + note?: string; + status?: string; +} +export declare class QueryScheduleDto { + classId?: number; + teacherId?: number; + courseId?: number; + startDate?: string; + endDate?: string; + status?: string; + source?: string; + page?: number; + pageSize?: number; +} +export declare class TimetableQueryDto { + startDate: string; + endDate: string; + classId?: number; + teacherId?: number; +} +export declare class BatchScheduleItemDto { + classId: number; + courseId: number; + teacherId?: number; + scheduledDate: string; + scheduledTime?: string; + note?: string; +} +export declare class BatchCreateScheduleDto { + schedules: BatchScheduleItemDto[]; +} diff --git a/reading-platform-backend/dist/src/modules/school/dto/schedule.dto.js b/reading-platform-backend/dist/src/modules/school/dto/schedule.dto.js new file mode 100644 index 0000000..ceb6e83 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/dto/schedule.dto.js @@ -0,0 +1,222 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BatchCreateScheduleDto = exports.BatchScheduleItemDto = exports.TimetableQueryDto = exports.QueryScheduleDto = exports.UpdateScheduleDto = exports.CreateScheduleDto = void 0; +const class_validator_1 = require("class-validator"); +class CreateScheduleDto { +} +exports.CreateScheduleDto = CreateScheduleDto; +__decorate([ + (0, class_validator_1.IsInt)(), + (0, class_validator_1.IsNotEmpty)(), + __metadata("design:type", Number) +], CreateScheduleDto.prototype, "classId", void 0); +__decorate([ + (0, class_validator_1.IsInt)(), + (0, class_validator_1.IsNotEmpty)(), + __metadata("design:type", Number) +], CreateScheduleDto.prototype, "courseId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], CreateScheduleDto.prototype, "teacherId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], CreateScheduleDto.prototype, "scheduledDate", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateScheduleDto.prototype, "scheduledTime", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + (0, class_validator_1.Min)(0), + (0, class_validator_1.Max)(6), + __metadata("design:type", Number) +], CreateScheduleDto.prototype, "weekDay", void 0); +__decorate([ + (0, class_validator_1.IsEnum)(['NONE', 'DAILY', 'WEEKLY']), + __metadata("design:type", String) +], CreateScheduleDto.prototype, "repeatType", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], CreateScheduleDto.prototype, "repeatEndDate", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateScheduleDto.prototype, "note", void 0); +class UpdateScheduleDto { +} +exports.UpdateScheduleDto = UpdateScheduleDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], UpdateScheduleDto.prototype, "teacherId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], UpdateScheduleDto.prototype, "scheduledDate", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateScheduleDto.prototype, "scheduledTime", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + (0, class_validator_1.Min)(0), + (0, class_validator_1.Max)(6), + __metadata("design:type", Number) +], UpdateScheduleDto.prototype, "weekDay", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsEnum)(['NONE', 'DAILY', 'WEEKLY']), + __metadata("design:type", String) +], UpdateScheduleDto.prototype, "repeatType", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], UpdateScheduleDto.prototype, "repeatEndDate", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateScheduleDto.prototype, "note", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateScheduleDto.prototype, "status", void 0); +class QueryScheduleDto { +} +exports.QueryScheduleDto = QueryScheduleDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], QueryScheduleDto.prototype, "classId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], QueryScheduleDto.prototype, "teacherId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], QueryScheduleDto.prototype, "courseId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], QueryScheduleDto.prototype, "startDate", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], QueryScheduleDto.prototype, "endDate", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], QueryScheduleDto.prototype, "status", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], QueryScheduleDto.prototype, "source", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + (0, class_validator_1.Min)(1), + __metadata("design:type", Number) +], QueryScheduleDto.prototype, "page", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + (0, class_validator_1.Min)(1), + (0, class_validator_1.Max)(100), + __metadata("design:type", Number) +], QueryScheduleDto.prototype, "pageSize", void 0); +class TimetableQueryDto { +} +exports.TimetableQueryDto = TimetableQueryDto; +__decorate([ + (0, class_validator_1.IsNotEmpty)(), + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], TimetableQueryDto.prototype, "startDate", void 0); +__decorate([ + (0, class_validator_1.IsNotEmpty)(), + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], TimetableQueryDto.prototype, "endDate", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], TimetableQueryDto.prototype, "classId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], TimetableQueryDto.prototype, "teacherId", void 0); +class BatchScheduleItemDto { +} +exports.BatchScheduleItemDto = BatchScheduleItemDto; +__decorate([ + (0, class_validator_1.IsInt)(), + (0, class_validator_1.IsNotEmpty)(), + __metadata("design:type", Number) +], BatchScheduleItemDto.prototype, "classId", void 0); +__decorate([ + (0, class_validator_1.IsInt)(), + (0, class_validator_1.IsNotEmpty)(), + __metadata("design:type", Number) +], BatchScheduleItemDto.prototype, "courseId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], BatchScheduleItemDto.prototype, "teacherId", void 0); +__decorate([ + (0, class_validator_1.IsDateString)(), + (0, class_validator_1.IsNotEmpty)(), + __metadata("design:type", String) +], BatchScheduleItemDto.prototype, "scheduledDate", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], BatchScheduleItemDto.prototype, "scheduledTime", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], BatchScheduleItemDto.prototype, "note", void 0); +class BatchCreateScheduleDto { +} +exports.BatchCreateScheduleDto = BatchCreateScheduleDto; +__decorate([ + (0, class_validator_1.IsNotEmpty)(), + __metadata("design:type", Array) +], BatchCreateScheduleDto.prototype, "schedules", void 0); +//# sourceMappingURL=schedule.dto.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/dto/schedule.dto.js.map b/reading-platform-backend/dist/src/modules/school/dto/schedule.dto.js.map new file mode 100644 index 0000000..f82f503 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/dto/schedule.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"schedule.dto.js","sourceRoot":"","sources":["../../../../../src/modules/school/dto/schedule.dto.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/export.controller.d.ts b/reading-platform-backend/dist/src/modules/school/export.controller.d.ts new file mode 100644 index 0000000..6081f70 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/export.controller.d.ts @@ -0,0 +1,12 @@ +import { Response } from 'express'; +import { ExportService } from './export.service'; +export declare class ExportController { + private readonly exportService; + constructor(exportService: ExportService); + exportLessons(req: any, startDate?: string, endDate?: string, res?: Response): Promise; + exportTeacherStats(req: any, startDate?: string, endDate?: string, res?: Response): Promise; + exportStudentStats(req: any, classId?: string, res?: Response): Promise; + private sendExcelResponse; + private getDateRangeFilename; + private formatDate; +} diff --git a/reading-platform-backend/dist/src/modules/school/export.controller.js b/reading-platform-backend/dist/src/modules/school/export.controller.js new file mode 100644 index 0000000..af74b88 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/export.controller.js @@ -0,0 +1,101 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ExportController = void 0; +const common_1 = require("@nestjs/common"); +const export_service_1 = require("./export.service"); +const jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +const roles_guard_1 = require("../common/guards/roles.guard"); +const roles_decorator_1 = require("../common/decorators/roles.decorator"); +let ExportController = class ExportController { + constructor(exportService) { + this.exportService = exportService; + } + async exportLessons(req, startDate, endDate, res) { + const buffer = await this.exportService.exportLessons(req.user.tenantId, startDate, endDate); + const filename = `授课记录_${this.getDateRangeFilename(startDate, endDate)}.xlsx`; + this.sendExcelResponse(res, buffer, filename); + } + async exportTeacherStats(req, startDate, endDate, res) { + const buffer = await this.exportService.exportTeacherStats(req.user.tenantId, startDate, endDate); + const filename = `教师绩效统计_${this.getDateRangeFilename(startDate, endDate)}.xlsx`; + this.sendExcelResponse(res, buffer, filename); + } + async exportStudentStats(req, classId, res) { + const buffer = await this.exportService.exportStudentStats(req.user.tenantId, classId ? parseInt(classId, 10) : undefined); + const filename = `学生统计_${this.formatDate(new Date())}.xlsx`; + this.sendExcelResponse(res, buffer, filename); + } + sendExcelResponse(res, buffer, filename) { + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(filename)}"`); + res.setHeader('Content-Length', buffer.length); + res.send(buffer); + } + getDateRangeFilename(startDate, endDate) { + if (startDate && endDate) { + return `${startDate}_${endDate}`; + } + else if (startDate) { + return `${startDate}_至今`; + } + else if (endDate) { + return `至${endDate}`; + } + return this.formatDate(new Date()); + } + formatDate(date) { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}${month}${day}`; + } +}; +exports.ExportController = ExportController; +__decorate([ + (0, common_1.Get)('export/lessons'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)('startDate')), + __param(2, (0, common_1.Query)('endDate')), + __param(3, (0, common_1.Res)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, String, Object]), + __metadata("design:returntype", Promise) +], ExportController.prototype, "exportLessons", null); +__decorate([ + (0, common_1.Get)('export/teacher-stats'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)('startDate')), + __param(2, (0, common_1.Query)('endDate')), + __param(3, (0, common_1.Res)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, String, Object]), + __metadata("design:returntype", Promise) +], ExportController.prototype, "exportTeacherStats", null); +__decorate([ + (0, common_1.Get)('export/student-stats'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)('classId')), + __param(2, (0, common_1.Res)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", Promise) +], ExportController.prototype, "exportStudentStats", null); +exports.ExportController = ExportController = __decorate([ + (0, common_1.Controller)('school'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('school'), + __metadata("design:paramtypes", [export_service_1.ExportService]) +], ExportController); +//# sourceMappingURL=export.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/export.controller.js.map b/reading-platform-backend/dist/src/modules/school/export.controller.js.map new file mode 100644 index 0000000..af98faf --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/export.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"export.controller.js","sourceRoot":"","sources":["../../../../src/modules/school/export.controller.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/export.service.d.ts b/reading-platform-backend/dist/src/modules/school/export.service.d.ts new file mode 100644 index 0000000..8b8c4f6 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/export.service.d.ts @@ -0,0 +1,12 @@ +import { PrismaService } from '../../database/prisma.service'; +export declare class ExportService { + private prisma; + private readonly logger; + constructor(prisma: PrismaService); + exportLessons(tenantId: number, startDate?: string, endDate?: string): Promise; + exportTeacherStats(tenantId: number, startDate?: string, endDate?: string): Promise; + exportStudentStats(tenantId: number, classId?: number): Promise; + private generateExcelBuffer; + private calculateColumnWidths; + private getStatusText; +} diff --git a/reading-platform-backend/dist/src/modules/school/export.service.js b/reading-platform-backend/dist/src/modules/school/export.service.js new file mode 100644 index 0000000..636e10a --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/export.service.js @@ -0,0 +1,263 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var ExportService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ExportService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../../database/prisma.service"); +const XLSX = __importStar(require("xlsx")); +let ExportService = ExportService_1 = class ExportService { + constructor(prisma) { + this.prisma = prisma; + this.logger = new common_1.Logger(ExportService_1.name); + } + async exportLessons(tenantId, startDate, endDate) { + const where = { + tenantId, + status: 'COMPLETED', + }; + if (startDate || endDate) { + where.createdAt = {}; + if (startDate) + where.createdAt.gte = new Date(startDate); + if (endDate) + where.createdAt.lte = new Date(endDate); + } + const lessons = await this.prisma.lesson.findMany({ + where, + orderBy: { + createdAt: 'desc', + }, + include: { + course: { + select: { + name: true, + pictureBookName: true, + duration: true, + }, + }, + teacher: { + select: { + name: true, + }, + }, + class: { + select: { + name: true, + grade: true, + }, + }, + }, + }); + const data = lessons.map((lesson, index) => ({ + '序号': index + 1, + '课程名称': lesson.course?.name || '', + '绘本名称': lesson.course?.pictureBookName || '', + '授课教师': lesson.teacher?.name || '', + '班级': lesson.class?.name || '', + '年级': lesson.class?.grade || '', + '计划时长(分钟)': lesson.course?.duration || '', + '实际时长(分钟)': lesson.actualDuration || '', + '授课日期': lesson.startDatetime + ? new Date(lesson.startDatetime).toLocaleDateString('zh-CN') + : '', + '开始时间': lesson.startDatetime + ? new Date(lesson.startDatetime).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }) + : '', + '结束时间': lesson.endDatetime + ? new Date(lesson.endDatetime).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }) + : '', + '状态': this.getStatusText(lesson.status), + '备注': lesson.completionNote || '', + })); + return this.generateExcelBuffer(data, '授课记录'); + } + async exportTeacherStats(tenantId, startDate, endDate) { + const teachers = await this.prisma.teacher.findMany({ + where: { + tenantId, + status: 'ACTIVE', + }, + select: { + id: true, + name: true, + phone: true, + email: true, + lessonCount: true, + createdAt: true, + }, + }); + const lessonWhere = { + tenantId, + status: 'COMPLETED', + }; + if (startDate || endDate) { + lessonWhere.createdAt = {}; + if (startDate) + lessonWhere.createdAt.gte = new Date(startDate); + if (endDate) + lessonWhere.createdAt.lte = new Date(endDate); + } + const teacherStats = await Promise.all(teachers.map(async (teacher) => { + const periodLessons = await this.prisma.lesson.count({ + where: { + ...lessonWhere, + teacherId: teacher.id, + }, + }); + const feedbackWhere = { + teacherId: teacher.id, + }; + if (startDate || endDate) { + feedbackWhere.lesson = { + endDatetime: {}, + }; + if (startDate) + feedbackWhere.lesson.endDatetime.gte = new Date(startDate); + if (endDate) + feedbackWhere.lesson.endDatetime.lte = new Date(endDate); + } + const feedbacks = await this.prisma.lessonFeedback.findMany({ + where: feedbackWhere, + select: { + designQuality: true, + participation: true, + goalAchievement: true, + }, + }); + let avgRating = 0; + if (feedbacks.length > 0) { + const totalRating = feedbacks.reduce((sum, f) => { + const ratings = [f.designQuality, f.participation, f.goalAchievement].filter((r) => r !== null); + const avg = ratings.length > 0 ? ratings.reduce((s, r) => s + r, 0) / ratings.length : 0; + return sum + avg; + }, 0); + avgRating = Math.round((totalRating / feedbacks.length) * 100) / 100; + } + const classCount = await this.prisma.classTeacher.count({ + where: { + teacherId: teacher.id, + }, + }); + return { + '教师姓名': teacher.name, + '联系电话': teacher.phone, + '邮箱': teacher.email || '', + '关联班级数': classCount, + '累计授课次数': teacher.lessonCount, + '本期授课次数': periodLessons, + '平均评分': avgRating || '暂无评分', + '入职日期': new Date(teacher.createdAt).toLocaleDateString('zh-CN'), + }; + })); + teacherStats.sort((a, b) => b['本期授课次数'] - a['本期授课次数']); + const dataWithRank = teacherStats.map((item, index) => ({ + '排名': index + 1, + ...item, + })); + return this.generateExcelBuffer(dataWithRank, '教师绩效'); + } + async exportStudentStats(tenantId, classId) { + const where = { tenantId }; + if (classId) { + where.classId = classId; + } + const students = await this.prisma.student.findMany({ + where, + include: { + class: { + select: { + name: true, + grade: true, + }, + }, + }, + orderBy: { + createdAt: 'desc', + }, + }); + const data = students.map((student, index) => ({ + '序号': index + 1, + '学生姓名': student.name, + '性别': student.gender === 'MALE' ? '男' : student.gender === 'FEMALE' ? '女' : '', + '出生日期': student.birthDate + ? new Date(student.birthDate).toLocaleDateString('zh-CN') + : '', + '班级': student.class?.name || '', + '年级': student.class?.grade || '', + '家长姓名': student.parentName || '', + '联系电话': student.parentPhone || '', + '参与课程数': student.lessonCount, + '阅读记录数': student.readingCount, + '入校日期': new Date(student.createdAt).toLocaleDateString('zh-CN'), + })); + return this.generateExcelBuffer(data, '学生统计'); + } + generateExcelBuffer(data, sheetName) { + const worksheet = XLSX.utils.json_to_sheet(data); + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, sheetName); + const colWidths = this.calculateColumnWidths(data); + worksheet['!cols'] = colWidths; + return XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }); + } + calculateColumnWidths(data) { + if (data.length === 0) + return []; + const headers = Object.keys(data[0]); + return headers.map((header) => { + let maxWidth = header.length; + data.forEach((row) => { + const value = String(row[header] || ''); + maxWidth = Math.max(maxWidth, value.length); + }); + return { wch: Math.min(maxWidth + 2, 50) }; + }); + } + getStatusText(status) { + const statusMap = { + PLANNED: '已计划', + IN_PROGRESS: '进行中', + COMPLETED: '已完成', + CANCELLED: '已取消', + }; + return statusMap[status] || status; + } +}; +exports.ExportService = ExportService; +exports.ExportService = ExportService = ExportService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], ExportService); +//# sourceMappingURL=export.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/export.service.js.map b/reading-platform-backend/dist/src/modules/school/export.service.js.map new file mode 100644 index 0000000..22d186d --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/export.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"export.service.js","sourceRoot":"","sources":["../../../../src/modules/school/export.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/package.controller.d.ts b/reading-platform-backend/dist/src/modules/school/package.controller.d.ts new file mode 100644 index 0000000..cc63f20 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/package.controller.d.ts @@ -0,0 +1,34 @@ +import { PrismaService } from '../../database/prisma.service'; +export declare class PackageController { + private prisma; + constructor(prisma: PrismaService); + getPackageInfo(req: any): Promise<{ + packageType: string; + teacherQuota: number; + studentQuota: number; + storageQuota: number; + teacherCount: number; + studentCount: number; + storageUsed: number; + startDate: string; + expireDate: string; + status: string; + }>; + getPackageUsage(req: any): Promise<{ + teacher: { + used: number; + quota: number; + percentage: number; + }; + student: { + used: number; + quota: number; + percentage: number; + }; + storage: { + used: number; + quota: number; + percentage: number; + }; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/school/package.controller.js b/reading-platform-backend/dist/src/modules/school/package.controller.js new file mode 100644 index 0000000..87f645b --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/package.controller.js @@ -0,0 +1,120 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PackageController = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../../database/prisma.service"); +const jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +const roles_guard_1 = require("../common/guards/roles.guard"); +const roles_decorator_1 = require("../common/decorators/roles.decorator"); +let PackageController = class PackageController { + constructor(prisma) { + this.prisma = prisma; + } + async getPackageInfo(req) { + const tenantId = req.user.tenantId; + const [tenant, teacherCount, studentCount] = await Promise.all([ + this.prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { + id: true, + name: true, + packageType: true, + teacherQuota: true, + studentQuota: true, + storageQuota: true, + storageUsed: true, + startDate: true, + expireDate: true, + status: true, + }, + }), + this.prisma.teacher.count({ where: { tenantId } }), + this.prisma.student.count({ where: { tenantId } }), + ]); + if (!tenant) { + return null; + } + return { + packageType: tenant.packageType, + teacherQuota: tenant.teacherQuota, + studentQuota: tenant.studentQuota, + storageQuota: Number(tenant.storageQuota), + teacherCount: teacherCount, + studentCount: studentCount, + storageUsed: Number(tenant.storageUsed), + startDate: tenant.startDate, + expireDate: tenant.expireDate, + status: tenant.status, + }; + } + async getPackageUsage(req) { + const tenantId = req.user.tenantId; + const [tenant, teacherCount, studentCount] = await Promise.all([ + this.prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { + teacherQuota: true, + studentQuota: true, + storageQuota: true, + storageUsed: true, + }, + }), + this.prisma.teacher.count({ where: { tenantId } }), + this.prisma.student.count({ where: { tenantId } }), + ]); + if (!tenant) { + return null; + } + return { + teacher: { + used: teacherCount, + quota: tenant.teacherQuota, + percentage: tenant.teacherQuota > 0 ? Math.round((teacherCount / tenant.teacherQuota) * 100) : 0, + }, + student: { + used: studentCount, + quota: tenant.studentQuota, + percentage: tenant.studentQuota > 0 ? Math.round((studentCount / tenant.studentQuota) * 100) : 0, + }, + storage: { + used: Number(tenant.storageUsed), + quota: Number(tenant.storageQuota), + percentage: Number(tenant.storageQuota) > 0 ? Math.round((Number(tenant.storageUsed) / Number(tenant.storageQuota)) * 100) : 0, + }, + }; + } +}; +exports.PackageController = PackageController; +__decorate([ + (0, common_1.Get)('package'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], PackageController.prototype, "getPackageInfo", null); +__decorate([ + (0, common_1.Get)('package/usage'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", Promise) +], PackageController.prototype, "getPackageUsage", null); +exports.PackageController = PackageController = __decorate([ + (0, common_1.Controller)('school'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('school'), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], PackageController); +//# sourceMappingURL=package.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/package.controller.js.map b/reading-platform-backend/dist/src/modules/school/package.controller.js.map new file mode 100644 index 0000000..db7eb77 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/package.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"package.controller.js","sourceRoot":"","sources":["../../../../src/modules/school/package.controller.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/school.controller.d.ts b/reading-platform-backend/dist/src/modules/school/school.controller.d.ts new file mode 100644 index 0000000..def0e46 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/school.controller.d.ts @@ -0,0 +1,858 @@ +import { SchoolService } from './school.service'; +import { CreateTeacherDto, UpdateTeacherDto } from './dto/create-teacher.dto'; +import { CreateStudentDto, UpdateStudentDto } from './dto/create-student.dto'; +import { CreateClassDto, UpdateClassDto } from './dto/create-class.dto'; +import { AddClassTeacherDto, UpdateClassTeacherDto, TransferStudentDto } from './dto/class-teacher.dto'; +import { CreateScheduleDto, UpdateScheduleDto, QueryScheduleDto, TimetableQueryDto } from './dto/schedule.dto'; +export declare class SchoolController { + private readonly schoolService; + constructor(schoolService: SchoolService); + findTeachers(req: any, query: any): Promise<{ + items: { + classIds: any[]; + classNames: string; + passwordHash: any; + classes: { + id: number; + name: string; + }[]; + id: number; + tenantId: number; + name: string; + phone: string; + email: string | null; + loginAccount: string; + status: string; + lessonCount: number; + feedbackCount: number; + createdAt: Date; + updatedAt: Date; + lastLoginAt: Date | null; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findTeacher(req: any, id: string): Promise<{ + classIds: any[]; + classNames: string; + passwordHash: any; + classes: { + id: number; + name: string; + }[]; + id: number; + tenantId: number; + name: string; + phone: string; + email: string | null; + loginAccount: string; + status: string; + lessonCount: number; + feedbackCount: number; + createdAt: Date; + updatedAt: Date; + lastLoginAt: Date | null; + }>; + createTeacher(req: any, dto: CreateTeacherDto): Promise<{ + classIds: any[]; + classNames: string; + passwordHash: any; + classes: { + id: number; + name: string; + }[]; + id: number; + tenantId: number; + name: string; + phone: string; + email: string | null; + loginAccount: string; + status: string; + lessonCount: number; + feedbackCount: number; + createdAt: Date; + updatedAt: Date; + lastLoginAt: Date | null; + }>; + updateTeacher(req: any, id: string, dto: UpdateTeacherDto): Promise<{ + classIds: any[]; + classNames: string; + passwordHash: any; + classes: { + id: number; + name: string; + }[]; + id: number; + tenantId: number; + name: string; + phone: string; + email: string | null; + loginAccount: string; + status: string; + lessonCount: number; + feedbackCount: number; + createdAt: Date; + updatedAt: Date; + lastLoginAt: Date | null; + }>; + deleteTeacher(req: any, id: string): Promise<{ + message: string; + }>; + resetTeacherPassword(req: any, id: string): Promise<{ + tempPassword: string; + }>; + findStudents(req: any, query: any): Promise<{ + items: { + className: string; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + name: string; + lessonCount: number; + createdAt: Date; + updatedAt: Date; + classId: number; + gender: string | null; + birthDate: Date | null; + parentPhone: string | null; + parentName: string | null; + readingCount: number; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findStudent(req: any, id: string): Promise<{ + className: string; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + name: string; + lessonCount: number; + createdAt: Date; + updatedAt: Date; + classId: number; + gender: string | null; + birthDate: Date | null; + parentPhone: string | null; + parentName: string | null; + readingCount: number; + }>; + createStudent(req: any, dto: CreateStudentDto): Promise<{ + className: string; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + name: string; + lessonCount: number; + createdAt: Date; + updatedAt: Date; + classId: number; + gender: string | null; + birthDate: Date | null; + parentPhone: string | null; + parentName: string | null; + readingCount: number; + }>; + updateStudent(req: any, id: string, dto: UpdateStudentDto): Promise<{ + className: string; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + name: string; + lessonCount: number; + createdAt: Date; + updatedAt: Date; + classId: number; + gender: string | null; + birthDate: Date | null; + parentPhone: string | null; + parentName: string | null; + readingCount: number; + }>; + deleteStudent(req: any, id: string): Promise<{ + message: string; + }>; + transferStudent(req: any, id: string, dto: TransferStudentDto): Promise<{ + message: string; + }>; + getStudentClassHistory(req: any, id: string): Promise<{ + id: number; + fromClass: { + id: number; + name: string; + grade: string; + }; + toClass: { + id: number; + name: string; + grade: string; + }; + reason: string; + operatedBy: number; + createdAt: Date; + }[]>; + importStudents(req: any, file: Express.Multer.File, defaultClassId?: string): Promise<{ + success: number; + failed: number; + errors: { + row: number; + message: string; + }[]; + }>; + getImportTemplate(): { + headers: string[]; + example: string[]; + notes: string[]; + }; + findClasses(req: any): Promise<{ + id: number; + name: string; + grade: string; + teacherId: number; + teacherName: string; + studentCount: number; + lessonCount: number; + createdAt: Date; + updatedAt: Date; + teachers: { + id: number; + teacherId: number; + teacherName: string; + teacherPhone: string; + role: string; + isPrimary: boolean; + }[]; + }[]>; + findClass(req: any, id: string): Promise<{ + id: number; + name: string; + grade: string; + teacherId: number; + teacherName: string; + studentCount: number; + lessonCount: number; + students: { + id: number; + name: string; + lessonCount: number; + gender: string; + birthDate: Date; + parentPhone: string; + parentName: string; + }[]; + createdAt: Date; + updatedAt: Date; + }>; + findClassStudents(req: any, id: string, query: any): Promise<{ + items: { + id: number; + tenantId: number; + name: string; + lessonCount: number; + createdAt: Date; + updatedAt: Date; + classId: number; + gender: string | null; + birthDate: Date | null; + parentPhone: string | null; + parentName: string | null; + readingCount: number; + }[]; + total: number; + page: number; + pageSize: number; + }>; + createClass(req: any, dto: CreateClassDto): Promise<{ + id: number; + name: string; + grade: string; + teacherId: number; + teacherName: string; + studentCount: number; + lessonCount: number; + }>; + updateClass(req: any, id: string, dto: UpdateClassDto): Promise<{ + id: number; + name: string; + grade: string; + teacherId: number; + teacherName: string; + studentCount: number; + lessonCount: number; + }>; + deleteClass(req: any, id: string): Promise<{ + message: string; + }>; + findClassTeachers(req: any, id: string): Promise<{ + id: number; + teacherId: number; + teacherName: string; + teacherPhone: string; + teacherEmail: string; + role: string; + isPrimary: boolean; + createdAt: Date; + }[]>; + addClassTeacher(req: any, id: string, dto: AddClassTeacherDto): Promise<{ + id: number; + teacherId: number; + teacherName: string; + teacherPhone: string; + role: string; + isPrimary: boolean; + }>; + updateClassTeacher(req: any, id: string, teacherId: string, dto: UpdateClassTeacherDto): Promise<{ + id: number; + teacherId: number; + teacherName: string; + teacherPhone: string; + role: string; + isPrimary: boolean; + }>; + removeClassTeacher(req: any, id: string, teacherId: string): Promise<{ + message: string; + }>; + findParents(req: any, query: any): Promise<{ + items: { + childrenCount: number; + children: { + relationship: string; + id: number; + name: string; + class: { + id: number; + name: string; + }; + }[]; + id: number; + tenantId: number; + name: string; + phone: string; + email: string | null; + loginAccount: string; + passwordHash: string; + status: string; + createdAt: Date; + updatedAt: Date; + lastLoginAt: Date | null; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findParent(req: any, id: string): Promise<{ + children: { + relationship: string; + id: number; + name: string; + class: { + id: number; + name: string; + }; + gender: string; + }[]; + id: number; + tenantId: number; + name: string; + phone: string; + email: string | null; + loginAccount: string; + passwordHash: string; + status: string; + createdAt: Date; + updatedAt: Date; + lastLoginAt: Date | null; + }>; + createParent(req: any, dto: any): Promise<{ + id: number; + tenantId: number; + name: string; + phone: string; + email: string | null; + loginAccount: string; + passwordHash: string; + status: string; + createdAt: Date; + updatedAt: Date; + lastLoginAt: Date | null; + }>; + updateParent(req: any, id: string, dto: any): Promise<{ + id: number; + tenantId: number; + name: string; + phone: string; + email: string | null; + loginAccount: string; + passwordHash: string; + status: string; + createdAt: Date; + updatedAt: Date; + lastLoginAt: Date | null; + }>; + deleteParent(req: any, id: string): Promise<{ + message: string; + }>; + resetParentPassword(req: any, id: string): Promise<{ + tempPassword: string; + }>; + addChildToParent(req: any, parentId: string, studentId: string, body: { + relationship: string; + }): Promise<{ + student: { + id: number; + name: string; + }; + } & { + id: number; + createdAt: Date; + parentId: number; + studentId: number; + relationship: string; + }>; + removeChildFromParent(req: any, parentId: string, studentId: string): Promise<{ + message: string; + }>; + findCourses(req: any): Promise<{ + id: number; + name: string; + pictureBookName: string; + pictureUrl: string; + gradeTags: any[]; + domainTags: any[]; + duration: number; + usageCount: number; + authorized: boolean; + }[]>; + findCourse(req: any, id: string): Promise<{ + authorized: boolean; + gradeTags: any; + domainTags: any; + ebookPaths: any; + audioPaths: any; + videoPaths: any; + otherResources: any; + posterPaths: any; + tenantCourses: any; + teacherCount: number; + scripts: { + interactionPoints: any; + resourceIds: any; + pages: { + resourceIds: any; + id: number; + createdAt: Date; + updatedAt: Date; + pageNumber: number; + scriptId: number; + questions: string | null; + interactionComponent: string | null; + teacherNotes: string | null; + }[]; + id: number; + createdAt: Date; + updatedAt: Date; + courseId: number; + duration: number; + sortOrder: number; + stepIndex: number; + stepName: string; + stepType: string; + objective: string | null; + teacherScript: string | null; + }[]; + activities: { + onlineMaterials: any; + objectives: any; + id: number; + name: string; + createdAt: Date; + courseId: number; + duration: number | null; + sortOrder: number; + domain: string | null; + domainTagId: number | null; + activityType: string; + offlineMaterials: string | null; + activityGuide: string | null; + }[]; + resources: { + id: number; + createdAt: Date; + courseId: number; + sortOrder: number; + resourceType: string; + resourceName: string; + fileUrl: string; + fileSize: number | null; + mimeType: string | null; + metadata: string | null; + }[]; + id: number; + name: string; + status: string; + createdAt: Date; + updatedAt: Date; + parentId: number | null; + description: string | null; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + pptPath: string | null; + pptName: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + isLatest: boolean; + usageCount: number; + avgRating: number; + createdBy: number | null; + publishedAt: Date | null; + }>; + findSchedules(req: any, query: QueryScheduleDto): Promise<{ + items: { + className: string; + courseName: string; + teacherName: string; + teacher: { + id: number; + name: string; + phone: string; + }; + class: { + id: number; + name: string; + grade: string; + }; + course: { + id: number; + name: string; + pictureBookName: string; + duration: number; + }; + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + teacherId: number | null; + classId: number; + courseId: number; + createdBy: number; + scheduledDate: Date | null; + scheduledTime: string | null; + weekDay: number | null; + repeatType: string; + repeatEndDate: Date | null; + source: string; + note: string | null; + reminderSent: boolean; + reminderSentAt: Date | null; + }[]; + total: number; + page: number; + pageSize: number; + }>; + getTimetable(req: any, query: TimetableQueryDto): Promise<{ + date: string; + weekDay: number; + schedules: any[]; + }[]>; + findSchedule(req: any, id: string): Promise<{ + className: string; + courseName: string; + teacherName: string; + teacher: { + id: number; + name: string; + phone: string; + }; + class: { + id: number; + name: string; + grade: string; + }; + course: { + id: number; + name: string; + pictureBookName: string; + duration: number; + }; + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + teacherId: number | null; + classId: number; + courseId: number; + createdBy: number; + scheduledDate: Date | null; + scheduledTime: string | null; + weekDay: number | null; + repeatType: string; + repeatEndDate: Date | null; + source: string; + note: string | null; + reminderSent: boolean; + reminderSentAt: Date | null; + }>; + createSchedule(req: any, dto: CreateScheduleDto): Promise<{ + className: string; + courseName: string; + teacherName: string; + teacher: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + course: { + id: number; + name: string; + }; + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + teacherId: number | null; + classId: number; + courseId: number; + createdBy: number; + scheduledDate: Date | null; + scheduledTime: string | null; + weekDay: number | null; + repeatType: string; + repeatEndDate: Date | null; + source: string; + note: string | null; + reminderSent: boolean; + reminderSentAt: Date | null; + }>; + updateSchedule(req: any, id: string, dto: UpdateScheduleDto): Promise<{ + className: string; + courseName: string; + teacherName: string; + teacher: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + course: { + id: number; + name: string; + }; + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + teacherId: number | null; + classId: number; + courseId: number; + createdBy: number; + scheduledDate: Date | null; + scheduledTime: string | null; + weekDay: number | null; + repeatType: string; + repeatEndDate: Date | null; + source: string; + note: string | null; + reminderSent: boolean; + reminderSentAt: Date | null; + }>; + cancelSchedule(req: any, id: string): Promise<{ + message: string; + }>; + batchCreateSchedules(req: any, dto: { + schedules: any[]; + }): Promise<{ + success: number; + failed: number; + results: any[]; + errors: any[]; + }>; + getScheduleTemplates(req: any, query: any): Promise<{ + courseName: string; + className: string; + teacherName: string; + teacher: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + grade: string; + }; + course: { + id: number; + name: string; + pictureBookName: string; + }; + id: number; + tenantId: number; + name: string; + createdAt: Date; + updatedAt: Date; + teacherId: number | null; + classId: number | null; + courseId: number; + duration: number; + scheduledTime: string | null; + weekDay: number | null; + isDefault: boolean; + }[]>; + getScheduleTemplate(req: any, id: string): Promise<{ + courseName: string; + className: string; + teacherName: string; + teacher: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + grade: string; + }; + course: { + id: number; + name: string; + pictureBookName: string; + duration: number; + }; + id: number; + tenantId: number; + name: string; + createdAt: Date; + updatedAt: Date; + teacherId: number | null; + classId: number | null; + courseId: number; + duration: number; + scheduledTime: string | null; + weekDay: number | null; + isDefault: boolean; + }>; + createScheduleTemplate(req: any, dto: any): Promise<{ + courseName: string; + className: string; + teacherName: string; + teacher: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + course: { + id: number; + name: string; + }; + id: number; + tenantId: number; + name: string; + createdAt: Date; + updatedAt: Date; + teacherId: number | null; + classId: number | null; + courseId: number; + duration: number; + scheduledTime: string | null; + weekDay: number | null; + isDefault: boolean; + }>; + updateScheduleTemplate(req: any, id: string, dto: any): Promise<{ + courseName: string; + className: string; + teacherName: string; + teacher: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + course: { + id: number; + name: string; + }; + id: number; + tenantId: number; + name: string; + createdAt: Date; + updatedAt: Date; + teacherId: number | null; + classId: number | null; + courseId: number; + duration: number; + scheduledTime: string | null; + weekDay: number | null; + isDefault: boolean; + }>; + deleteScheduleTemplate(req: any, id: string): Promise<{ + message: string; + }>; + applyScheduleTemplate(req: any, id: string, dto: any): Promise<{ + className: string; + courseName: string; + class: { + id: number; + name: string; + }; + course: { + id: number; + name: string; + }; + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + teacherId: number | null; + classId: number; + courseId: number; + createdBy: number; + scheduledDate: Date | null; + scheduledTime: string | null; + weekDay: number | null; + repeatType: string; + repeatEndDate: Date | null; + source: string; + note: string | null; + reminderSent: boolean; + reminderSentAt: Date | null; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/school/school.controller.js b/reading-platform-backend/dist/src/modules/school/school.controller.js new file mode 100644 index 0000000..aee4820 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/school.controller.js @@ -0,0 +1,600 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SchoolController = void 0; +const common_1 = require("@nestjs/common"); +const platform_express_1 = require("@nestjs/platform-express"); +const school_service_1 = require("./school.service"); +const create_teacher_dto_1 = require("./dto/create-teacher.dto"); +const create_student_dto_1 = require("./dto/create-student.dto"); +const create_class_dto_1 = require("./dto/create-class.dto"); +const class_teacher_dto_1 = require("./dto/class-teacher.dto"); +const schedule_dto_1 = require("./dto/schedule.dto"); +const jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +const roles_guard_1 = require("../common/guards/roles.guard"); +const roles_decorator_1 = require("../common/decorators/roles.decorator"); +const log_operation_decorator_1 = require("../common/decorators/log-operation.decorator"); +const log_interceptor_1 = require("../common/interceptors/log.interceptor"); +let SchoolController = class SchoolController { + constructor(schoolService) { + this.schoolService = schoolService; + } + findTeachers(req, query) { + return this.schoolService.findTeachers(req.user.tenantId, query); + } + findTeacher(req, id) { + return this.schoolService.findTeacher(req.user.tenantId, +id); + } + createTeacher(req, dto) { + return this.schoolService.createTeacher(req.user.tenantId, dto); + } + updateTeacher(req, id, dto) { + return this.schoolService.updateTeacher(req.user.tenantId, +id, dto); + } + deleteTeacher(req, id) { + return this.schoolService.deleteTeacher(req.user.tenantId, +id); + } + resetTeacherPassword(req, id) { + return this.schoolService.resetTeacherPassword(req.user.tenantId, +id); + } + findStudents(req, query) { + return this.schoolService.findStudents(req.user.tenantId, query); + } + findStudent(req, id) { + return this.schoolService.findStudent(req.user.tenantId, +id); + } + createStudent(req, dto) { + return this.schoolService.createStudent(req.user.tenantId, dto); + } + updateStudent(req, id, dto) { + return this.schoolService.updateStudent(req.user.tenantId, +id, dto); + } + deleteStudent(req, id) { + return this.schoolService.deleteStudent(req.user.tenantId, +id); + } + transferStudent(req, id, dto) { + return this.schoolService.transferStudent(req.user.tenantId, +id, dto); + } + getStudentClassHistory(req, id) { + return this.schoolService.getStudentClassHistory(req.user.tenantId, +id); + } + async importStudents(req, file, defaultClassId) { + if (!file) { + throw new common_1.BadRequestException('请上传文件'); + } + const studentsData = await this.schoolService.parseStudentImportFile(file); + return this.schoolService.importStudents(req.user.tenantId, studentsData, defaultClassId ? +defaultClassId : undefined); + } + getImportTemplate() { + return { + headers: ['姓名', '性别', '出生日期', '班级ID', '家长姓名', '家长电话'], + example: ['张小明', '男', '2020-01-15', '1', '张三', '13800138000'], + notes: [ + '姓名为必填项', + '性别可选:男/女,默认为男', + '出生日期格式:YYYY-MM-DD', + '班级ID为必填项,可在班级管理中查看', + '家长姓名和家长电话为选填项', + ], + }; + } + findClasses(req) { + return this.schoolService.findClasses(req.user.tenantId); + } + findClass(req, id) { + return this.schoolService.findClass(req.user.tenantId, +id); + } + findClassStudents(req, id, query) { + return this.schoolService.findClassStudents(req.user.tenantId, +id, query); + } + createClass(req, dto) { + return this.schoolService.createClass(req.user.tenantId, dto); + } + updateClass(req, id, dto) { + return this.schoolService.updateClass(req.user.tenantId, +id, dto); + } + deleteClass(req, id) { + return this.schoolService.deleteClass(req.user.tenantId, +id); + } + findClassTeachers(req, id) { + return this.schoolService.findClassTeachers(req.user.tenantId, +id); + } + addClassTeacher(req, id, dto) { + return this.schoolService.addClassTeacher(req.user.tenantId, +id, dto); + } + updateClassTeacher(req, id, teacherId, dto) { + return this.schoolService.updateClassTeacher(req.user.tenantId, +id, +teacherId, dto); + } + removeClassTeacher(req, id, teacherId) { + return this.schoolService.removeClassTeacher(req.user.tenantId, +id, +teacherId); + } + findParents(req, query) { + return this.schoolService.findParents(req.user.tenantId, query); + } + findParent(req, id) { + return this.schoolService.findParent(req.user.tenantId, +id); + } + createParent(req, dto) { + return this.schoolService.createParent(req.user.tenantId, dto); + } + updateParent(req, id, dto) { + return this.schoolService.updateParent(req.user.tenantId, +id, dto); + } + deleteParent(req, id) { + return this.schoolService.deleteParent(req.user.tenantId, +id); + } + resetParentPassword(req, id) { + return this.schoolService.resetParentPassword(req.user.tenantId, +id); + } + addChildToParent(req, parentId, studentId, body) { + return this.schoolService.addChildToParent(req.user.tenantId, +parentId, +studentId, body.relationship); + } + removeChildFromParent(req, parentId, studentId) { + return this.schoolService.removeChildFromParent(req.user.tenantId, +parentId, +studentId); + } + findCourses(req) { + return this.schoolService.findCourses(req.user.tenantId); + } + findCourse(req, id) { + return this.schoolService.findCourse(req.user.tenantId, +id); + } + findSchedules(req, query) { + return this.schoolService.findSchedules(req.user.tenantId, query); + } + getTimetable(req, query) { + return this.schoolService.getTimetable(req.user.tenantId, query); + } + findSchedule(req, id) { + return this.schoolService.findSchedule(req.user.tenantId, +id); + } + createSchedule(req, dto) { + return this.schoolService.createSchedule(req.user.tenantId, dto, req.user.userId); + } + updateSchedule(req, id, dto) { + return this.schoolService.updateSchedule(req.user.tenantId, +id, dto); + } + cancelSchedule(req, id) { + return this.schoolService.cancelSchedule(req.user.tenantId, +id); + } + batchCreateSchedules(req, dto) { + return this.schoolService.batchCreateSchedules(req.user.tenantId, dto.schedules); + } + getScheduleTemplates(req, query) { + return this.schoolService.getScheduleTemplates(req.user.tenantId, query); + } + getScheduleTemplate(req, id) { + return this.schoolService.getScheduleTemplate(req.user.tenantId, +id); + } + createScheduleTemplate(req, dto) { + return this.schoolService.createScheduleTemplate(req.user.tenantId, dto); + } + updateScheduleTemplate(req, id, dto) { + return this.schoolService.updateScheduleTemplate(req.user.tenantId, +id, dto); + } + deleteScheduleTemplate(req, id) { + return this.schoolService.deleteScheduleTemplate(req.user.tenantId, +id); + } + applyScheduleTemplate(req, id, dto) { + return this.schoolService.applyScheduleTemplate(req.user.tenantId, +id, dto); + } +}; +exports.SchoolController = SchoolController; +__decorate([ + (0, common_1.Get)('teachers'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "findTeachers", null); +__decorate([ + (0, common_1.Get)('teachers/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "findTeacher", null); +__decorate([ + (0, common_1.Post)('teachers'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, create_teacher_dto_1.CreateTeacherDto]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "createTeacher", null); +__decorate([ + (0, common_1.Put)('teachers/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, create_teacher_dto_1.UpdateTeacherDto]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "updateTeacher", null); +__decorate([ + (0, common_1.Delete)('teachers/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "deleteTeacher", null); +__decorate([ + (0, common_1.Post)('teachers/:id/reset-password'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "resetTeacherPassword", null); +__decorate([ + (0, common_1.Get)('students'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "findStudents", null); +__decorate([ + (0, common_1.Get)('students/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "findStudent", null); +__decorate([ + (0, common_1.Post)('students'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, create_student_dto_1.CreateStudentDto]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "createStudent", null); +__decorate([ + (0, common_1.Put)('students/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, create_student_dto_1.UpdateStudentDto]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "updateStudent", null); +__decorate([ + (0, common_1.Delete)('students/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "deleteStudent", null); +__decorate([ + (0, common_1.Post)('students/:id/transfer'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, class_teacher_dto_1.TransferStudentDto]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "transferStudent", null); +__decorate([ + (0, common_1.Get)('students/:id/history'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "getStudentClassHistory", null); +__decorate([ + (0, common_1.Post)('students/import'), + (0, common_1.UseInterceptors)((0, platform_express_1.FileInterceptor)('file')), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.UploadedFile)()), + __param(2, (0, common_1.Query)('defaultClassId')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object, String]), + __metadata("design:returntype", Promise) +], SchoolController.prototype, "importStudents", null); +__decorate([ + (0, common_1.Get)('students/import/template'), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "getImportTemplate", null); +__decorate([ + (0, common_1.Get)('classes'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "findClasses", null); +__decorate([ + (0, common_1.Get)('classes/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "findClass", null); +__decorate([ + (0, common_1.Get)('classes/:id/students'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "findClassStudents", null); +__decorate([ + (0, common_1.Post)('classes'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, create_class_dto_1.CreateClassDto]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "createClass", null); +__decorate([ + (0, common_1.Put)('classes/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, create_class_dto_1.UpdateClassDto]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "updateClass", null); +__decorate([ + (0, common_1.Delete)('classes/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "deleteClass", null); +__decorate([ + (0, common_1.Get)('classes/:id/teachers'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "findClassTeachers", null); +__decorate([ + (0, common_1.Post)('classes/:id/teachers'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, class_teacher_dto_1.AddClassTeacherDto]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "addClassTeacher", null); +__decorate([ + (0, common_1.Put)('classes/:id/teachers/:teacherId'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Param)('teacherId')), + __param(3, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, String, class_teacher_dto_1.UpdateClassTeacherDto]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "updateClassTeacher", null); +__decorate([ + (0, common_1.Delete)('classes/:id/teachers/:teacherId'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Param)('teacherId')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "removeClassTeacher", null); +__decorate([ + (0, common_1.Get)('parents'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "findParents", null); +__decorate([ + (0, common_1.Get)('parents/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "findParent", null); +__decorate([ + (0, common_1.Post)('parents'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "createParent", null); +__decorate([ + (0, common_1.Put)('parents/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "updateParent", null); +__decorate([ + (0, common_1.Delete)('parents/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "deleteParent", null); +__decorate([ + (0, common_1.Post)('parents/:id/reset-password'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "resetParentPassword", null); +__decorate([ + (0, common_1.Post)('parents/:parentId/children/:studentId'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('parentId')), + __param(2, (0, common_1.Param)('studentId')), + __param(3, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, String, Object]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "addChildToParent", null); +__decorate([ + (0, common_1.Delete)('parents/:parentId/children/:studentId'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('parentId')), + __param(2, (0, common_1.Param)('studentId')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "removeChildFromParent", null); +__decorate([ + (0, common_1.Get)('courses'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "findCourses", null); +__decorate([ + (0, common_1.Get)('courses/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "findCourse", null); +__decorate([ + (0, common_1.Get)('schedules'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, schedule_dto_1.QueryScheduleDto]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "findSchedules", null); +__decorate([ + (0, common_1.Get)('schedules/timetable'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, schedule_dto_1.TimetableQueryDto]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "getTimetable", null); +__decorate([ + (0, common_1.Get)('schedules/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "findSchedule", null); +__decorate([ + (0, common_1.Post)('schedules'), + (0, log_operation_decorator_1.LogOperation)({ module: '排课管理', action: '创建排课', description: '创建新的课程排期' }), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, schedule_dto_1.CreateScheduleDto]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "createSchedule", null); +__decorate([ + (0, common_1.Put)('schedules/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, schedule_dto_1.UpdateScheduleDto]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "updateSchedule", null); +__decorate([ + (0, common_1.Delete)('schedules/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "cancelSchedule", null); +__decorate([ + (0, common_1.Post)('schedules/batch'), + (0, log_operation_decorator_1.LogOperation)({ module: '排课管理', action: '批量创建排课', description: '批量创建课程排期' }), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "batchCreateSchedules", null); +__decorate([ + (0, common_1.Get)('schedule-templates'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "getScheduleTemplates", null); +__decorate([ + (0, common_1.Get)('schedule-templates/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "getScheduleTemplate", null); +__decorate([ + (0, common_1.Post)('schedule-templates'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "createScheduleTemplate", null); +__decorate([ + (0, common_1.Put)('schedule-templates/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "updateScheduleTemplate", null); +__decorate([ + (0, common_1.Delete)('schedule-templates/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "deleteScheduleTemplate", null); +__decorate([ + (0, common_1.Post)('schedule-templates/:id/apply'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", void 0) +], SchoolController.prototype, "applyScheduleTemplate", null); +exports.SchoolController = SchoolController = __decorate([ + (0, common_1.Controller)('school'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, common_1.UseInterceptors)(log_interceptor_1.LogInterceptor), + (0, roles_decorator_1.Roles)('school'), + __metadata("design:paramtypes", [school_service_1.SchoolService]) +], SchoolController); +//# sourceMappingURL=school.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/school.controller.js.map b/reading-platform-backend/dist/src/modules/school/school.controller.js.map new file mode 100644 index 0000000..14c6b31 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/school.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"school.controller.js","sourceRoot":"","sources":["../../../../src/modules/school/school.controller.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/school.module.d.ts b/reading-platform-backend/dist/src/modules/school/school.module.d.ts new file mode 100644 index 0000000..5f084c5 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/school.module.d.ts @@ -0,0 +1,2 @@ +export declare class SchoolModule { +} diff --git a/reading-platform-backend/dist/src/modules/school/school.module.js b/reading-platform-backend/dist/src/modules/school/school.module.js new file mode 100644 index 0000000..4019074 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/school.module.js @@ -0,0 +1,30 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SchoolModule = void 0; +const common_1 = require("@nestjs/common"); +const school_controller_1 = require("./school.controller"); +const school_service_1 = require("./school.service"); +const stats_controller_1 = require("./stats.controller"); +const stats_service_1 = require("./stats.service"); +const package_controller_1 = require("./package.controller"); +const settings_controller_1 = require("./settings.controller"); +const settings_service_1 = require("./settings.service"); +const export_controller_1 = require("./export.controller"); +const export_service_1 = require("./export.service"); +let SchoolModule = class SchoolModule { +}; +exports.SchoolModule = SchoolModule; +exports.SchoolModule = SchoolModule = __decorate([ + (0, common_1.Module)({ + controllers: [school_controller_1.SchoolController, stats_controller_1.StatsController, package_controller_1.PackageController, settings_controller_1.SettingsController, export_controller_1.ExportController], + providers: [school_service_1.SchoolService, stats_service_1.StatsService, settings_service_1.SettingsService, export_service_1.ExportService], + exports: [school_service_1.SchoolService, stats_service_1.StatsService, settings_service_1.SettingsService, export_service_1.ExportService], + }) +], SchoolModule); +//# sourceMappingURL=school.module.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/school.module.js.map b/reading-platform-backend/dist/src/modules/school/school.module.js.map new file mode 100644 index 0000000..3aec9a2 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/school.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"school.module.js","sourceRoot":"","sources":["../../../../src/modules/school/school.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2DAAuD;AACvD,qDAAiD;AACjD,yDAAqD;AACrD,mDAA+C;AAC/C,6DAAyD;AACzD,+DAA2D;AAC3D,yDAAqD;AACrD,2DAAuD;AACvD,qDAAiD;AAO1C,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,oCAAY;uBAAZ,YAAY;IALxB,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,oCAAgB,EAAE,kCAAe,EAAE,sCAAiB,EAAE,wCAAkB,EAAE,oCAAgB,CAAC;QACzG,SAAS,EAAE,CAAC,8BAAa,EAAE,4BAAY,EAAE,kCAAe,EAAE,8BAAa,CAAC;QACxE,OAAO,EAAE,CAAC,8BAAa,EAAE,4BAAY,EAAE,kCAAe,EAAE,8BAAa,CAAC;KACvE,CAAC;GACW,YAAY,CAAG"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/school.service.d.ts b/reading-platform-backend/dist/src/modules/school/school.service.d.ts new file mode 100644 index 0000000..1099237 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/school.service.d.ts @@ -0,0 +1,899 @@ +import { PrismaService } from '../../database/prisma.service'; +import { CreateTeacherDto, UpdateTeacherDto } from './dto/create-teacher.dto'; +import { CreateStudentDto, UpdateStudentDto } from './dto/create-student.dto'; +import { CreateClassDto, UpdateClassDto } from './dto/create-class.dto'; +import { AddClassTeacherDto, UpdateClassTeacherDto, TransferStudentDto } from './dto/class-teacher.dto'; +import { CreateScheduleDto, UpdateScheduleDto, QueryScheduleDto, TimetableQueryDto } from './dto/schedule.dto'; +export declare class SchoolService { + private prisma; + private readonly logger; + constructor(prisma: PrismaService); + private parseJsonArray; + findTeachers(tenantId: number, query: any): Promise<{ + items: { + classIds: any[]; + classNames: string; + passwordHash: any; + classes: { + id: number; + name: string; + }[]; + id: number; + tenantId: number; + name: string; + phone: string; + email: string | null; + loginAccount: string; + status: string; + lessonCount: number; + feedbackCount: number; + createdAt: Date; + updatedAt: Date; + lastLoginAt: Date | null; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findTeacher(tenantId: number, id: number): Promise<{ + classIds: any[]; + classNames: string; + passwordHash: any; + classes: { + id: number; + name: string; + }[]; + id: number; + tenantId: number; + name: string; + phone: string; + email: string | null; + loginAccount: string; + status: string; + lessonCount: number; + feedbackCount: number; + createdAt: Date; + updatedAt: Date; + lastLoginAt: Date | null; + }>; + createTeacher(tenantId: number, dto: CreateTeacherDto): Promise<{ + classIds: any[]; + classNames: string; + passwordHash: any; + classes: { + id: number; + name: string; + }[]; + id: number; + tenantId: number; + name: string; + phone: string; + email: string | null; + loginAccount: string; + status: string; + lessonCount: number; + feedbackCount: number; + createdAt: Date; + updatedAt: Date; + lastLoginAt: Date | null; + }>; + updateTeacher(tenantId: number, id: number, dto: UpdateTeacherDto): Promise<{ + classIds: any[]; + classNames: string; + passwordHash: any; + classes: { + id: number; + name: string; + }[]; + id: number; + tenantId: number; + name: string; + phone: string; + email: string | null; + loginAccount: string; + status: string; + lessonCount: number; + feedbackCount: number; + createdAt: Date; + updatedAt: Date; + lastLoginAt: Date | null; + }>; + deleteTeacher(tenantId: number, id: number): Promise<{ + message: string; + }>; + resetTeacherPassword(tenantId: number, id: number): Promise<{ + tempPassword: string; + }>; + findStudents(tenantId: number, query: any): Promise<{ + items: { + className: string; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + name: string; + lessonCount: number; + createdAt: Date; + updatedAt: Date; + classId: number; + gender: string | null; + birthDate: Date | null; + parentPhone: string | null; + parentName: string | null; + readingCount: number; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findStudent(tenantId: number, id: number): Promise<{ + className: string; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + name: string; + lessonCount: number; + createdAt: Date; + updatedAt: Date; + classId: number; + gender: string | null; + birthDate: Date | null; + parentPhone: string | null; + parentName: string | null; + readingCount: number; + }>; + createStudent(tenantId: number, dto: CreateStudentDto): Promise<{ + className: string; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + name: string; + lessonCount: number; + createdAt: Date; + updatedAt: Date; + classId: number; + gender: string | null; + birthDate: Date | null; + parentPhone: string | null; + parentName: string | null; + readingCount: number; + }>; + updateStudent(tenantId: number, id: number, dto: UpdateStudentDto): Promise<{ + className: string; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + name: string; + lessonCount: number; + createdAt: Date; + updatedAt: Date; + classId: number; + gender: string | null; + birthDate: Date | null; + parentPhone: string | null; + parentName: string | null; + readingCount: number; + }>; + deleteStudent(tenantId: number, id: number): Promise<{ + message: string; + }>; + importStudents(tenantId: number, studentsData: any[], defaultClassId?: number): Promise<{ + success: number; + failed: number; + errors: { + row: number; + message: string; + }[]; + }>; + parseStudentImportFile(file: Express.Multer.File): Promise; + private formatDate; + findClasses(tenantId: number): Promise<{ + id: number; + name: string; + grade: string; + teacherId: number; + teacherName: string; + studentCount: number; + lessonCount: number; + createdAt: Date; + updatedAt: Date; + teachers: { + id: number; + teacherId: number; + teacherName: string; + teacherPhone: string; + role: string; + isPrimary: boolean; + }[]; + }[]>; + findClass(tenantId: number, id: number): Promise<{ + id: number; + name: string; + grade: string; + teacherId: number; + teacherName: string; + studentCount: number; + lessonCount: number; + students: { + id: number; + name: string; + lessonCount: number; + gender: string; + birthDate: Date; + parentPhone: string; + parentName: string; + }[]; + createdAt: Date; + updatedAt: Date; + }>; + findClassStudents(tenantId: number, classId: number, query: any): Promise<{ + items: { + id: number; + tenantId: number; + name: string; + lessonCount: number; + createdAt: Date; + updatedAt: Date; + classId: number; + gender: string | null; + birthDate: Date | null; + parentPhone: string | null; + parentName: string | null; + readingCount: number; + }[]; + total: number; + page: number; + pageSize: number; + }>; + createClass(tenantId: number, dto: CreateClassDto): Promise<{ + id: number; + name: string; + grade: string; + teacherId: number; + teacherName: string; + studentCount: number; + lessonCount: number; + }>; + updateClass(tenantId: number, id: number, dto: UpdateClassDto): Promise<{ + id: number; + name: string; + grade: string; + teacherId: number; + teacherName: string; + studentCount: number; + lessonCount: number; + }>; + deleteClass(tenantId: number, id: number): Promise<{ + message: string; + }>; + findParents(tenantId: number, query: any): Promise<{ + items: { + childrenCount: number; + children: { + relationship: string; + id: number; + name: string; + class: { + id: number; + name: string; + }; + }[]; + id: number; + tenantId: number; + name: string; + phone: string; + email: string | null; + loginAccount: string; + passwordHash: string; + status: string; + createdAt: Date; + updatedAt: Date; + lastLoginAt: Date | null; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findParent(tenantId: number, id: number): Promise<{ + children: { + relationship: string; + id: number; + name: string; + class: { + id: number; + name: string; + }; + gender: string; + }[]; + id: number; + tenantId: number; + name: string; + phone: string; + email: string | null; + loginAccount: string; + passwordHash: string; + status: string; + createdAt: Date; + updatedAt: Date; + lastLoginAt: Date | null; + }>; + createParent(tenantId: number, dto: { + name: string; + phone: string; + email?: string; + loginAccount: string; + password: string; + }): Promise<{ + id: number; + tenantId: number; + name: string; + phone: string; + email: string | null; + loginAccount: string; + passwordHash: string; + status: string; + createdAt: Date; + updatedAt: Date; + lastLoginAt: Date | null; + }>; + updateParent(tenantId: number, id: number, dto: { + name?: string; + phone?: string; + email?: string; + status?: string; + password?: string; + }): Promise<{ + id: number; + tenantId: number; + name: string; + phone: string; + email: string | null; + loginAccount: string; + passwordHash: string; + status: string; + createdAt: Date; + updatedAt: Date; + lastLoginAt: Date | null; + }>; + deleteParent(tenantId: number, id: number): Promise<{ + message: string; + }>; + resetParentPassword(tenantId: number, id: number): Promise<{ + tempPassword: string; + }>; + addChildToParent(tenantId: number, parentId: number, studentId: number, relationship: string): Promise<{ + student: { + id: number; + name: string; + }; + } & { + id: number; + createdAt: Date; + parentId: number; + studentId: number; + relationship: string; + }>; + removeChildFromParent(tenantId: number, parentId: number, studentId: number): Promise<{ + message: string; + }>; + findCourses(tenantId: number): Promise<{ + id: number; + name: string; + pictureBookName: string; + pictureUrl: string; + gradeTags: any[]; + domainTags: any[]; + duration: number; + usageCount: number; + authorized: boolean; + }[]>; + findCourse(tenantId: number, courseId: number): Promise<{ + authorized: boolean; + gradeTags: any; + domainTags: any; + ebookPaths: any; + audioPaths: any; + videoPaths: any; + otherResources: any; + posterPaths: any; + tenantCourses: any; + teacherCount: number; + scripts: { + interactionPoints: any; + resourceIds: any; + pages: { + resourceIds: any; + id: number; + createdAt: Date; + updatedAt: Date; + pageNumber: number; + scriptId: number; + questions: string | null; + interactionComponent: string | null; + teacherNotes: string | null; + }[]; + id: number; + createdAt: Date; + updatedAt: Date; + courseId: number; + duration: number; + sortOrder: number; + stepIndex: number; + stepName: string; + stepType: string; + objective: string | null; + teacherScript: string | null; + }[]; + activities: { + onlineMaterials: any; + objectives: any; + id: number; + name: string; + createdAt: Date; + courseId: number; + duration: number | null; + sortOrder: number; + domain: string | null; + domainTagId: number | null; + activityType: string; + offlineMaterials: string | null; + activityGuide: string | null; + }[]; + resources: { + id: number; + createdAt: Date; + courseId: number; + sortOrder: number; + resourceType: string; + resourceName: string; + fileUrl: string; + fileSize: number | null; + mimeType: string | null; + metadata: string | null; + }[]; + id: number; + name: string; + status: string; + createdAt: Date; + updatedAt: Date; + parentId: number | null; + description: string | null; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + pptPath: string | null; + pptName: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + isLatest: boolean; + usageCount: number; + avgRating: number; + createdBy: number | null; + publishedAt: Date | null; + }>; + findClassTeachers(tenantId: number, classId: number): Promise<{ + id: number; + teacherId: number; + teacherName: string; + teacherPhone: string; + teacherEmail: string; + role: string; + isPrimary: boolean; + createdAt: Date; + }[]>; + addClassTeacher(tenantId: number, classId: number, dto: AddClassTeacherDto): Promise<{ + id: number; + teacherId: number; + teacherName: string; + teacherPhone: string; + role: string; + isPrimary: boolean; + }>; + updateClassTeacher(tenantId: number, classId: number, teacherId: number, dto: UpdateClassTeacherDto): Promise<{ + id: number; + teacherId: number; + teacherName: string; + teacherPhone: string; + role: string; + isPrimary: boolean; + }>; + removeClassTeacher(tenantId: number, classId: number, teacherId: number): Promise<{ + message: string; + }>; + transferStudent(tenantId: number, studentId: number, dto: TransferStudentDto): Promise<{ + message: string; + }>; + getStudentClassHistory(tenantId: number, studentId: number): Promise<{ + id: number; + fromClass: { + id: number; + name: string; + grade: string; + }; + toClass: { + id: number; + name: string; + grade: string; + }; + reason: string; + operatedBy: number; + createdAt: Date; + }[]>; + private parseTimeToMinutes; + private isTimeOverlapping; + private checkScheduleConflict; + findSchedules(tenantId: number, query: QueryScheduleDto): Promise<{ + items: { + className: string; + courseName: string; + teacherName: string; + teacher: { + id: number; + name: string; + phone: string; + }; + class: { + id: number; + name: string; + grade: string; + }; + course: { + id: number; + name: string; + pictureBookName: string; + duration: number; + }; + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + teacherId: number | null; + classId: number; + courseId: number; + createdBy: number; + scheduledDate: Date | null; + scheduledTime: string | null; + weekDay: number | null; + repeatType: string; + repeatEndDate: Date | null; + source: string; + note: string | null; + reminderSent: boolean; + reminderSentAt: Date | null; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findSchedule(tenantId: number, id: number): Promise<{ + className: string; + courseName: string; + teacherName: string; + teacher: { + id: number; + name: string; + phone: string; + }; + class: { + id: number; + name: string; + grade: string; + }; + course: { + id: number; + name: string; + pictureBookName: string; + duration: number; + }; + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + teacherId: number | null; + classId: number; + courseId: number; + createdBy: number; + scheduledDate: Date | null; + scheduledTime: string | null; + weekDay: number | null; + repeatType: string; + repeatEndDate: Date | null; + source: string; + note: string | null; + reminderSent: boolean; + reminderSentAt: Date | null; + }>; + createSchedule(tenantId: number, dto: CreateScheduleDto, userId: number): Promise<{ + className: string; + courseName: string; + teacherName: string; + teacher: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + course: { + id: number; + name: string; + }; + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + teacherId: number | null; + classId: number; + courseId: number; + createdBy: number; + scheduledDate: Date | null; + scheduledTime: string | null; + weekDay: number | null; + repeatType: string; + repeatEndDate: Date | null; + source: string; + note: string | null; + reminderSent: boolean; + reminderSentAt: Date | null; + }>; + updateSchedule(tenantId: number, id: number, dto: UpdateScheduleDto): Promise<{ + className: string; + courseName: string; + teacherName: string; + teacher: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + course: { + id: number; + name: string; + }; + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + teacherId: number | null; + classId: number; + courseId: number; + createdBy: number; + scheduledDate: Date | null; + scheduledTime: string | null; + weekDay: number | null; + repeatType: string; + repeatEndDate: Date | null; + source: string; + note: string | null; + reminderSent: boolean; + reminderSentAt: Date | null; + }>; + cancelSchedule(tenantId: number, id: number): Promise<{ + message: string; + }>; + getTimetable(tenantId: number, query: TimetableQueryDto): Promise<{ + date: string; + weekDay: number; + schedules: any[]; + }[]>; + batchCreateSchedules(tenantId: number, schedules: Array<{ + classId: number; + courseId: number; + teacherId?: number; + scheduledDate: string; + scheduledTime?: string; + note?: string; + }>): Promise<{ + success: number; + failed: number; + results: any[]; + errors: any[]; + }>; + getScheduleTemplates(tenantId: number, query?: { + classId?: number; + courseId?: number; + }): Promise<{ + courseName: string; + className: string; + teacherName: string; + teacher: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + grade: string; + }; + course: { + id: number; + name: string; + pictureBookName: string; + }; + id: number; + tenantId: number; + name: string; + createdAt: Date; + updatedAt: Date; + teacherId: number | null; + classId: number | null; + courseId: number; + duration: number; + scheduledTime: string | null; + weekDay: number | null; + isDefault: boolean; + }[]>; + getScheduleTemplate(tenantId: number, id: number): Promise<{ + courseName: string; + className: string; + teacherName: string; + teacher: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + grade: string; + }; + course: { + id: number; + name: string; + pictureBookName: string; + duration: number; + }; + id: number; + tenantId: number; + name: string; + createdAt: Date; + updatedAt: Date; + teacherId: number | null; + classId: number | null; + courseId: number; + duration: number; + scheduledTime: string | null; + weekDay: number | null; + isDefault: boolean; + }>; + createScheduleTemplate(tenantId: number, data: { + name: string; + courseId: number; + classId?: number; + teacherId?: number; + scheduledTime?: string; + weekDay?: number; + duration?: number; + isDefault?: boolean; + }): Promise<{ + courseName: string; + className: string; + teacherName: string; + teacher: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + course: { + id: number; + name: string; + }; + id: number; + tenantId: number; + name: string; + createdAt: Date; + updatedAt: Date; + teacherId: number | null; + classId: number | null; + courseId: number; + duration: number; + scheduledTime: string | null; + weekDay: number | null; + isDefault: boolean; + }>; + updateScheduleTemplate(tenantId: number, id: number, data: { + name?: string; + classId?: number; + teacherId?: number; + scheduledTime?: string; + weekDay?: number; + duration?: number; + isDefault?: boolean; + }): Promise<{ + courseName: string; + className: string; + teacherName: string; + teacher: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + course: { + id: number; + name: string; + }; + id: number; + tenantId: number; + name: string; + createdAt: Date; + updatedAt: Date; + teacherId: number | null; + classId: number | null; + courseId: number; + duration: number; + scheduledTime: string | null; + weekDay: number | null; + isDefault: boolean; + }>; + deleteScheduleTemplate(tenantId: number, id: number): Promise<{ + message: string; + }>; + applyScheduleTemplate(tenantId: number, templateId: number, data: { + scheduledDate: string; + classId?: number; + teacherId?: number; + }): Promise<{ + className: string; + courseName: string; + class: { + id: number; + name: string; + }; + course: { + id: number; + name: string; + }; + id: number; + tenantId: number; + status: string; + createdAt: Date; + updatedAt: Date; + teacherId: number | null; + classId: number; + courseId: number; + createdBy: number; + scheduledDate: Date | null; + scheduledTime: string | null; + weekDay: number | null; + repeatType: string; + repeatEndDate: Date | null; + source: string; + note: string | null; + reminderSent: boolean; + reminderSentAt: Date | null; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/school/school.service.js b/reading-platform-backend/dist/src/modules/school/school.service.js new file mode 100644 index 0000000..dd6575c --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/school.service.js @@ -0,0 +1,1952 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var SchoolService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SchoolService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../../database/prisma.service"); +const bcrypt = __importStar(require("bcrypt")); +const xlsx = __importStar(require("xlsx")); +let SchoolService = SchoolService_1 = class SchoolService { + constructor(prisma) { + this.prisma = prisma; + this.logger = new common_1.Logger(SchoolService_1.name); + } + parseJsonArray(value) { + if (!value) + return []; + if (typeof value === 'string') { + try { + return JSON.parse(value); + } + catch { + return []; + } + } + return Array.isArray(value) ? value : []; + } + async findTeachers(tenantId, query) { + const { page = 1, pageSize = 10, keyword, status } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = { + tenantId: tenantId, + }; + if (keyword) { + where.OR = [ + { name: { contains: keyword } }, + { phone: { contains: keyword } }, + { loginAccount: { contains: keyword } }, + ]; + } + if (status) { + where.status = status; + } + const [items, total] = await Promise.all([ + this.prisma.teacher.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + include: { + classes: { + select: { + id: true, + name: true, + }, + }, + }, + }), + this.prisma.teacher.count({ where }), + ]); + const parsedItems = items.map((teacher) => ({ + ...teacher, + classIds: this.parseJsonArray(teacher.classIds), + classNames: teacher.classes.map((c) => c.name).join(', '), + passwordHash: undefined, + })); + return { + items: parsedItems, + total, + page: +page, + pageSize: +pageSize, + }; + } + async findTeacher(tenantId, id) { + const teacher = await this.prisma.teacher.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + include: { + classes: { + select: { + id: true, + name: true, + }, + }, + }, + }); + if (!teacher) { + throw new common_1.NotFoundException('教师不存在'); + } + return { + ...teacher, + classIds: this.parseJsonArray(teacher.classIds), + classNames: teacher.classes.map((c) => c.name).join(', '), + passwordHash: undefined, + }; + } + async createTeacher(tenantId, dto) { + const tenant = await this.prisma.tenant.findUnique({ + where: { id: tenantId }, + }); + if (!tenant) { + throw new common_1.NotFoundException('学校不存在'); + } + const teacherCount = await this.prisma.teacher.count({ + where: { tenantId: tenantId }, + }); + if (teacherCount >= tenant.teacherQuota) { + throw new common_1.ForbiddenException('教师配额已满,无法添加更多教师'); + } + const existingTeacher = await this.prisma.teacher.findUnique({ + where: { loginAccount: dto.loginAccount }, + }); + if (existingTeacher) { + throw new common_1.ConflictException('登录账号已存在'); + } + const passwordHash = await bcrypt.hash(dto.password || '123456', 10); + const teacher = await this.prisma.teacher.create({ + data: { + tenantId: tenantId, + name: dto.name, + phone: dto.phone, + email: dto.email, + loginAccount: dto.loginAccount, + passwordHash: passwordHash, + classIds: JSON.stringify(dto.classIds || []), + status: 'ACTIVE', + }, + include: { + classes: { + select: { + id: true, + name: true, + }, + }, + }, + }); + if (dto.classIds && dto.classIds.length > 0) { + await this.prisma.class.updateMany({ + where: { + id: { in: dto.classIds }, + tenantId: tenantId, + }, + data: { + teacherId: teacher.id, + }, + }); + } + await this.prisma.tenant.update({ + where: { id: tenantId }, + data: { + teacherCount: { increment: 1 }, + }, + }); + this.logger.log(`Teacher created: ${teacher.id} by tenant ${tenantId}`); + return { + ...teacher, + classIds: this.parseJsonArray(teacher.classIds), + classNames: teacher.classes.map((c) => c.name).join(', '), + passwordHash: undefined, + }; + } + async updateTeacher(tenantId, id, dto) { + const existingTeacher = await this.prisma.teacher.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + if (!existingTeacher) { + throw new common_1.NotFoundException('教师不存在'); + } + const teacher = await this.prisma.teacher.update({ + where: { id: id }, + data: { + name: dto.name, + phone: dto.phone, + email: dto.email, + classIds: dto.classIds ? JSON.stringify(dto.classIds) : undefined, + }, + include: { + classes: { + select: { + id: true, + name: true, + }, + }, + }, + }); + await this.prisma.class.updateMany({ + where: { + teacherId: id, + tenantId: tenantId, + }, + data: { + teacherId: null, + }, + }); + if (dto.classIds && dto.classIds.length > 0) { + await this.prisma.class.updateMany({ + where: { + id: { in: dto.classIds }, + tenantId: tenantId, + }, + data: { + teacherId: id, + }, + }); + } + this.logger.log(`Teacher updated: ${id}`); + return { + ...teacher, + classIds: this.parseJsonArray(teacher.classIds), + classNames: teacher.classes.map((c) => c.name).join(', '), + passwordHash: undefined, + }; + } + async deleteTeacher(tenantId, id) { + const teacher = await this.prisma.teacher.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + if (!teacher) { + throw new common_1.NotFoundException('教师不存在'); + } + await this.prisma.teacher.delete({ + where: { id: id }, + }); + await this.prisma.tenant.update({ + where: { id: tenantId }, + data: { + teacherCount: { decrement: 1 }, + }, + }); + this.logger.log(`Teacher deleted: ${id}`); + return { message: '删除成功' }; + } + async resetTeacherPassword(tenantId, id) { + const teacher = await this.prisma.teacher.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + if (!teacher) { + throw new common_1.NotFoundException('教师不存在'); + } + const tempPassword = Math.random().toString(36).slice(-8); + const passwordHash = await bcrypt.hash(tempPassword, 10); + await this.prisma.teacher.update({ + where: { id: id }, + data: { + passwordHash: passwordHash, + }, + }); + this.logger.log(`Teacher password reset: ${id}`); + return { tempPassword }; + } + async findStudents(tenantId, query) { + const { page = 1, pageSize = 10, classId, keyword } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = { + tenantId: tenantId, + }; + if (classId) { + where.classId = +classId; + } + if (keyword) { + where.OR = [ + { name: { contains: keyword } }, + { parentName: { contains: keyword } }, + { parentPhone: { contains: keyword } }, + ]; + } + const [items, total] = await Promise.all([ + this.prisma.student.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + include: { + class: { + select: { + id: true, + name: true, + }, + }, + }, + }), + this.prisma.student.count({ where }), + ]); + const parsedItems = items.map((student) => ({ + ...student, + className: student.class?.name, + })); + return { + items: parsedItems, + total, + page: +page, + pageSize: +pageSize, + }; + } + async findStudent(tenantId, id) { + const student = await this.prisma.student.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + include: { + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + if (!student) { + throw new common_1.NotFoundException('学生不存在'); + } + return { + ...student, + className: student.class?.name, + }; + } + async createStudent(tenantId, dto) { + const tenant = await this.prisma.tenant.findUnique({ + where: { id: tenantId }, + }); + if (!tenant) { + throw new common_1.NotFoundException('学校不存在'); + } + const studentCount = await this.prisma.student.count({ + where: { tenantId: tenantId }, + }); + if (studentCount >= tenant.studentQuota) { + throw new common_1.ForbiddenException('学生配额已满,无法添加更多学生'); + } + const classEntity = await this.prisma.class.findFirst({ + where: { + id: dto.classId, + tenantId: tenantId, + }, + }); + if (!classEntity) { + throw new common_1.NotFoundException('班级不存在'); + } + const student = await this.prisma.student.create({ + data: { + tenantId: tenantId, + classId: dto.classId, + name: dto.name, + gender: dto.gender, + birthDate: dto.birthDate ? new Date(dto.birthDate) : null, + parentName: dto.parentName, + parentPhone: dto.parentPhone, + }, + include: { + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + await this.prisma.class.update({ + where: { id: dto.classId }, + data: { + studentCount: { increment: 1 }, + }, + }); + await this.prisma.tenant.update({ + where: { id: tenantId }, + data: { + studentCount: { increment: 1 }, + }, + }); + this.logger.log(`Student created: ${student.id} by tenant ${tenantId}`); + return { + ...student, + className: student.class?.name, + }; + } + async updateStudent(tenantId, id, dto) { + const existingStudent = await this.prisma.student.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + if (!existingStudent) { + throw new common_1.NotFoundException('学生不存在'); + } + if (dto.classId && dto.classId !== existingStudent.classId) { + const newClass = await this.prisma.class.findFirst({ + where: { + id: dto.classId, + tenantId: tenantId, + }, + }); + if (!newClass) { + throw new common_1.NotFoundException('新班级不存在'); + } + } + const student = await this.prisma.student.update({ + where: { id: id }, + data: { + name: dto.name, + gender: dto.gender, + birthDate: dto.birthDate ? new Date(dto.birthDate) : undefined, + classId: dto.classId, + parentName: dto.parentName, + parentPhone: dto.parentPhone, + }, + include: { + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + if (dto.classId && dto.classId !== existingStudent.classId) { + await this.prisma.class.update({ + where: { id: existingStudent.classId }, + data: { + studentCount: { decrement: 1 }, + }, + }); + await this.prisma.class.update({ + where: { id: dto.classId }, + data: { + studentCount: { increment: 1 }, + }, + }); + } + this.logger.log(`Student updated: ${id}`); + return { + ...student, + className: student.class?.name, + }; + } + async deleteStudent(tenantId, id) { + const student = await this.prisma.student.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + if (!student) { + throw new common_1.NotFoundException('学生不存在'); + } + const classId = student.classId; + await this.prisma.student.delete({ + where: { id: id }, + }); + await this.prisma.class.update({ + where: { id: classId }, + data: { + studentCount: { decrement: 1 }, + }, + }); + await this.prisma.tenant.update({ + where: { id: tenantId }, + data: { + studentCount: { decrement: 1 }, + }, + }); + this.logger.log(`Student deleted: ${id}`); + return { message: '删除成功' }; + } + async importStudents(tenantId, studentsData, defaultClassId) { + let success = 0; + let failed = 0; + const errors = []; + for (let i = 0; i < studentsData.length; i++) { + const data = studentsData[i]; + try { + const classId = data.classId || defaultClassId; + if (!classId) { + throw new Error('未指定班级'); + } + await this.createStudent(tenantId, { + name: data.name, + gender: data.gender || '男', + birthDate: data.birthDate, + classId: classId, + parentName: data.parentName, + parentPhone: data.parentPhone, + }); + success++; + } + catch (error) { + failed++; + errors.push({ + row: i + 2, + message: error.message || '导入失败', + }); + } + } + this.logger.log(`Students imported: success=${success}, failed=${failed}`); + return { success, failed, errors }; + } + async parseStudentImportFile(file) { + const workbook = xlsx.read(file.buffer, { type: 'buffer' }); + const sheetName = workbook.SheetNames[0]; + const sheet = workbook.Sheets[sheetName]; + const data = xlsx.utils.sheet_to_json(sheet, { header: 1 }); + if (data.length < 2) { + throw new common_1.BadRequestException('文件内容为空或格式不正确'); + } + const students = []; + for (let i = 1; i < data.length; i++) { + const row = data[i]; + if (!row || row.length === 0 || !row[0]) + continue; + students.push({ + name: String(row[0] || '').trim(), + gender: String(row[1] || '男').trim(), + birthDate: row[2] ? this.formatDate(row[2]) : null, + classId: row[3] ? parseInt(String(row[3]), 10) : null, + parentName: String(row[4] || '').trim() || null, + parentPhone: String(row[5] || '').trim() || null, + }); + } + if (students.length === 0) { + throw new common_1.BadRequestException('未找到有效的学生数据'); + } + return students; + } + formatDate(value) { + if (!value) + return null; + if (typeof value === 'number') { + const date = xlsx.SSF.parse_date_code(value); + if (date) { + return `${date.y}-${String(date.m).padStart(2, '0')}-${String(date.d).padStart(2, '0')}`; + } + } + const dateStr = String(value).trim(); + const date = new Date(dateStr); + if (!isNaN(date.getTime())) { + return date.toISOString().split('T')[0]; + } + return null; + } + async findClasses(tenantId) { + const classes = await this.prisma.class.findMany({ + where: { + tenantId: tenantId, + }, + orderBy: { createdAt: 'desc' }, + include: { + teacher: { + select: { + id: true, + name: true, + }, + }, + classTeachers: { + include: { + teacher: { + select: { + id: true, + name: true, + phone: true, + }, + }, + }, + }, + _count: { + select: { + students: true, + lessons: true, + }, + }, + }, + }); + return classes.map((cls) => ({ + id: cls.id, + name: cls.name, + grade: cls.grade, + teacherId: cls.teacherId, + teacherName: cls.teacher?.name, + studentCount: cls._count.students, + lessonCount: cls._count.lessons, + createdAt: cls.createdAt, + updatedAt: cls.updatedAt, + teachers: cls.classTeachers.map((ct) => ({ + id: ct.id, + teacherId: ct.teacher.id, + teacherName: ct.teacher.name, + teacherPhone: ct.teacher.phone, + role: ct.role, + isPrimary: ct.isPrimary, + })), + })); + } + async findClass(tenantId, id) { + const classEntity = await this.prisma.class.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + include: { + teacher: { + select: { + id: true, + name: true, + }, + }, + students: { + select: { + id: true, + name: true, + gender: true, + birthDate: true, + parentName: true, + parentPhone: true, + lessonCount: true, + }, + }, + }, + }); + if (!classEntity) { + throw new common_1.NotFoundException('班级不存在'); + } + return { + id: classEntity.id, + name: classEntity.name, + grade: classEntity.grade, + teacherId: classEntity.teacherId, + teacherName: classEntity.teacher?.name, + studentCount: classEntity.students.length, + lessonCount: classEntity.lessonCount, + students: classEntity.students, + createdAt: classEntity.createdAt, + updatedAt: classEntity.updatedAt, + }; + } + async findClassStudents(tenantId, classId, query) { + const { page = 1, pageSize = 20, keyword } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const classEntity = await this.prisma.class.findFirst({ + where: { + id: classId, + tenantId: tenantId, + }, + }); + if (!classEntity) { + throw new common_1.NotFoundException('班级不存在'); + } + const where = { + classId: classId, + tenantId: tenantId, + }; + if (keyword) { + where.OR = [ + { name: { contains: keyword } }, + { parentName: { contains: keyword } }, + ]; + } + const [items, total] = await Promise.all([ + this.prisma.student.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + }), + this.prisma.student.count({ where }), + ]); + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + async createClass(tenantId, dto) { + if (dto.teacherId) { + const teacher = await this.prisma.teacher.findFirst({ + where: { + id: dto.teacherId, + tenantId: tenantId, + }, + }); + if (!teacher) { + throw new common_1.NotFoundException('教师不存在'); + } + } + const classEntity = await this.prisma.class.create({ + data: { + tenantId: tenantId, + name: dto.name, + grade: dto.grade, + teacherId: dto.teacherId || null, + }, + include: { + teacher: { + select: { + id: true, + name: true, + }, + }, + }, + }); + this.logger.log(`Class created: ${classEntity.id} by tenant ${tenantId}`); + return { + id: classEntity.id, + name: classEntity.name, + grade: classEntity.grade, + teacherId: classEntity.teacherId, + teacherName: classEntity.teacher?.name, + studentCount: 0, + lessonCount: 0, + }; + } + async updateClass(tenantId, id, dto) { + const existingClass = await this.prisma.class.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + if (!existingClass) { + throw new common_1.NotFoundException('班级不存在'); + } + if (dto.teacherId !== undefined && dto.teacherId !== null) { + const teacher = await this.prisma.teacher.findFirst({ + where: { + id: dto.teacherId, + tenantId: tenantId, + }, + }); + if (!teacher) { + throw new common_1.NotFoundException('教师不存在'); + } + } + const classEntity = await this.prisma.class.update({ + where: { id: id }, + data: { + name: dto.name, + grade: dto.grade, + teacherId: dto.teacherId, + }, + include: { + teacher: { + select: { + id: true, + name: true, + }, + }, + _count: { + select: { + students: true, + lessons: true, + }, + }, + }, + }); + this.logger.log(`Class updated: ${id}`); + return { + id: classEntity.id, + name: classEntity.name, + grade: classEntity.grade, + teacherId: classEntity.teacherId, + teacherName: classEntity.teacher?.name, + studentCount: classEntity._count.students, + lessonCount: classEntity._count.lessons, + }; + } + async deleteClass(tenantId, id) { + const classEntity = await this.prisma.class.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + include: { + _count: { + select: { + students: true, + }, + }, + }, + }); + if (!classEntity) { + throw new common_1.NotFoundException('班级不存在'); + } + if (classEntity._count.students > 0) { + throw new common_1.ForbiddenException('班级内还有学生,请先移除学生'); + } + await this.prisma.class.delete({ + where: { id: id }, + }); + this.logger.log(`Class deleted: ${id}`); + return { message: '删除成功' }; + } + async findParents(tenantId, query) { + const { page = 1, pageSize = 10, keyword, status } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = { + tenantId: tenantId, + }; + if (keyword) { + where.OR = [ + { name: { contains: keyword } }, + { phone: { contains: keyword } }, + { loginAccount: { contains: keyword } }, + ]; + } + if (status) { + where.status = status; + } + const [items, total] = await Promise.all([ + this.prisma.parent.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + include: { + children: { + include: { + student: { + select: { + id: true, + name: true, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }, + }, + }, + }, + }), + this.prisma.parent.count({ where }), + ]); + return { + items: items.map((parent) => ({ + ...parent, + childrenCount: parent.children.length, + children: parent.children.map((c) => ({ + ...c.student, + relationship: c.relationship, + })), + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + async findParent(tenantId, id) { + const parent = await this.prisma.parent.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + include: { + children: { + include: { + student: { + select: { + id: true, + name: true, + gender: true, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }, + }, + }, + }, + }); + if (!parent) { + throw new common_1.NotFoundException('家长不存在'); + } + return { + ...parent, + children: parent.children.map((c) => ({ + ...c.student, + relationship: c.relationship, + })), + }; + } + async createParent(tenantId, dto) { + const existing = await this.prisma.parent.findUnique({ + where: { loginAccount: dto.loginAccount }, + }); + if (existing) { + throw new common_1.ForbiddenException('登录账号已存在'); + } + const hashedPassword = await bcrypt.hash(dto.password, 10); + const parent = await this.prisma.parent.create({ + data: { + tenantId: tenantId, + name: dto.name, + phone: dto.phone, + email: dto.email, + loginAccount: dto.loginAccount, + passwordHash: hashedPassword, + status: 'ACTIVE', + }, + }); + this.logger.log(`Parent created: ${parent.id}`); + return parent; + } + async updateParent(tenantId, id, dto) { + const parent = await this.prisma.parent.findFirst({ + where: { id, tenantId }, + }); + if (!parent) { + throw new common_1.NotFoundException('家长不存在'); + } + const updateData = { + name: dto.name, + phone: dto.phone, + email: dto.email, + status: dto.status, + }; + if (dto.password) { + updateData.passwordHash = await bcrypt.hash(dto.password, 10); + } + const updated = await this.prisma.parent.update({ + where: { id }, + data: updateData, + }); + this.logger.log(`Parent updated: ${id}`); + return updated; + } + async deleteParent(tenantId, id) { + const parent = await this.prisma.parent.findFirst({ + where: { id, tenantId }, + }); + if (!parent) { + throw new common_1.NotFoundException('家长不存在'); + } + await this.prisma.parent.delete({ + where: { id }, + }); + this.logger.log(`Parent deleted: ${id}`); + return { message: '删除成功' }; + } + async resetParentPassword(tenantId, id) { + const parent = await this.prisma.parent.findFirst({ + where: { id, tenantId }, + }); + if (!parent) { + throw new common_1.NotFoundException('家长不存在'); + } + const tempPassword = Math.random().toString(36).slice(-8); + const hashedPassword = await bcrypt.hash(tempPassword, 10); + await this.prisma.parent.update({ + where: { id }, + data: { passwordHash: hashedPassword }, + }); + this.logger.log(`Parent password reset: ${id}`); + return { tempPassword }; + } + async addChildToParent(tenantId, parentId, studentId, relationship) { + const parent = await this.prisma.parent.findFirst({ + where: { id: parentId, tenantId }, + }); + if (!parent) { + throw new common_1.NotFoundException('家长不存在'); + } + const student = await this.prisma.student.findFirst({ + where: { id: studentId, tenantId }, + }); + if (!student) { + throw new common_1.NotFoundException('学生不存在'); + } + const existing = await this.prisma.parentStudent.findUnique({ + where: { + parentId_studentId: { + parentId, + studentId, + }, + }, + }); + if (existing) { + throw new common_1.ForbiddenException('该学生已与此家长关联'); + } + const relation = await this.prisma.parentStudent.create({ + data: { + parentId, + studentId, + relationship, + }, + include: { + student: { + select: { + id: true, + name: true, + }, + }, + }, + }); + this.logger.log(`Child ${studentId} added to parent ${parentId}`); + return relation; + } + async removeChildFromParent(tenantId, parentId, studentId) { + const parent = await this.prisma.parent.findFirst({ + where: { id: parentId, tenantId }, + }); + if (!parent) { + throw new common_1.NotFoundException('家长不存在'); + } + await this.prisma.parentStudent.delete({ + where: { + parentId_studentId: { + parentId, + studentId, + }, + }, + }); + this.logger.log(`Child ${studentId} removed from parent ${parentId}`); + return { message: '解除关联成功' }; + } + async findCourses(tenantId) { + const tenantCourses = await this.prisma.tenantCourse.findMany({ + where: { + tenantId, + authorized: true, + }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + coverImagePath: true, + gradeTags: true, + domainTags: true, + duration: true, + usageCount: true, + status: true, + }, + }, + }, + }); + return tenantCourses.map((tc) => ({ + id: tc.course.id, + name: tc.course.name, + pictureBookName: tc.course.pictureBookName, + pictureUrl: tc.course.coverImagePath, + gradeTags: this.parseJsonArray(tc.course.gradeTags), + domainTags: this.parseJsonArray(tc.course.domainTags), + duration: tc.course.duration || 25, + usageCount: tc.course.usageCount || 0, + authorized: tc.authorized, + })); + } + async findCourse(tenantId, courseId) { + const course = await this.prisma.course.findUnique({ + where: { id: courseId }, + include: { + resources: { + orderBy: { sortOrder: 'asc' }, + }, + scripts: { + orderBy: { sortOrder: 'asc' }, + include: { + pages: { + orderBy: { pageNumber: 'asc' }, + }, + }, + }, + activities: { + orderBy: { sortOrder: 'asc' }, + }, + tenantCourses: { + where: { tenantId }, + }, + }, + }); + if (!course) { + throw new common_1.NotFoundException(`课程 #${courseId} 不存在`); + } + if (course.status !== 'PUBLISHED') { + throw new common_1.ForbiddenException('该课程未发布'); + } + const tenantCourse = course.tenantCourses.find((tc) => tc.tenantId === tenantId); + if (!tenantCourse || !tenantCourse.authorized) { + throw new common_1.ForbiddenException('您的学校未获得此课程的授权'); + } + const teacherCount = await this.prisma.lesson.groupBy({ + by: ['teacherId'], + where: { + courseId, + tenantId, + }, + }); + return { + ...course, + authorized: true, + gradeTags: JSON.parse(course.gradeTags || '[]'), + domainTags: JSON.parse(course.domainTags || '[]'), + ebookPaths: course.ebookPaths ? JSON.parse(course.ebookPaths) : null, + audioPaths: course.audioPaths ? JSON.parse(course.audioPaths) : null, + videoPaths: course.videoPaths ? JSON.parse(course.videoPaths) : null, + otherResources: course.otherResources ? JSON.parse(course.otherResources) : null, + posterPaths: course.posterPaths ? JSON.parse(course.posterPaths) : null, + tenantCourses: undefined, + teacherCount: teacherCount.length, + scripts: course.scripts.map((script) => ({ + ...script, + interactionPoints: script.interactionPoints ? JSON.parse(script.interactionPoints) : null, + resourceIds: script.resourceIds ? JSON.parse(script.resourceIds) : null, + pages: script.pages?.map((page) => ({ + ...page, + resourceIds: page.resourceIds ? JSON.parse(page.resourceIds) : null, + })), + })), + activities: course.activities.map((activity) => ({ + ...activity, + onlineMaterials: activity.onlineMaterials ? JSON.parse(activity.onlineMaterials) : null, + objectives: activity.objectives ? JSON.parse(activity.objectives) : null, + })), + }; + } + async findClassTeachers(tenantId, classId) { + const classEntity = await this.prisma.class.findFirst({ + where: { id: classId, tenantId }, + }); + if (!classEntity) { + throw new common_1.NotFoundException('班级不存在'); + } + const classTeachers = await this.prisma.classTeacher.findMany({ + where: { classId }, + include: { + teacher: { + select: { + id: true, + name: true, + phone: true, + email: true, + }, + }, + }, + orderBy: [ + { isPrimary: 'desc' }, + { createdAt: 'asc' }, + ], + }); + return classTeachers.map((ct) => ({ + id: ct.id, + teacherId: ct.teacher.id, + teacherName: ct.teacher.name, + teacherPhone: ct.teacher.phone, + teacherEmail: ct.teacher.email, + role: ct.role, + isPrimary: ct.isPrimary, + createdAt: ct.createdAt, + })); + } + async addClassTeacher(tenantId, classId, dto) { + const classEntity = await this.prisma.class.findFirst({ + where: { id: classId, tenantId }, + }); + if (!classEntity) { + throw new common_1.NotFoundException('班级不存在'); + } + const teacher = await this.prisma.teacher.findFirst({ + where: { id: dto.teacherId, tenantId }, + }); + if (!teacher) { + throw new common_1.NotFoundException('教师不存在'); + } + const existing = await this.prisma.classTeacher.findUnique({ + where: { + classId_teacherId: { classId, teacherId: dto.teacherId }, + }, + }); + if (existing) { + throw new common_1.ConflictException('该教师已在此班级中'); + } + if (dto.isPrimary) { + await this.prisma.classTeacher.updateMany({ + where: { classId, isPrimary: true }, + data: { isPrimary: false }, + }); + } + const classTeacher = await this.prisma.classTeacher.create({ + data: { + classId, + teacherId: dto.teacherId, + role: dto.role, + isPrimary: dto.isPrimary || false, + }, + include: { + teacher: { + select: { + id: true, + name: true, + phone: true, + }, + }, + }, + }); + if (dto.role === 'MAIN' || dto.isPrimary) { + await this.prisma.class.update({ + where: { id: classId }, + data: { teacherId: dto.teacherId }, + }); + } + this.logger.log(`Teacher ${dto.teacherId} added to class ${classId} as ${dto.role}`); + return { + id: classTeacher.id, + teacherId: classTeacher.teacher.id, + teacherName: classTeacher.teacher.name, + teacherPhone: classTeacher.teacher.phone, + role: classTeacher.role, + isPrimary: classTeacher.isPrimary, + }; + } + async updateClassTeacher(tenantId, classId, teacherId, dto) { + const classEntity = await this.prisma.class.findFirst({ + where: { id: classId, tenantId }, + }); + if (!classEntity) { + throw new common_1.NotFoundException('班级不存在'); + } + const classTeacher = await this.prisma.classTeacher.findUnique({ + where: { + classId_teacherId: { classId, teacherId }, + }, + }); + if (!classTeacher) { + throw new common_1.NotFoundException('该教师不在此班级中'); + } + if (dto.isPrimary) { + await this.prisma.classTeacher.updateMany({ + where: { classId, isPrimary: true }, + data: { isPrimary: false }, + }); + } + const updated = await this.prisma.classTeacher.update({ + where: { + classId_teacherId: { classId, teacherId }, + }, + data: { + role: dto.role, + isPrimary: dto.isPrimary, + }, + include: { + teacher: { + select: { + id: true, + name: true, + phone: true, + }, + }, + }, + }); + if (dto.role === 'MAIN' || dto.isPrimary) { + await this.prisma.class.update({ + where: { id: classId }, + data: { teacherId }, + }); + } + this.logger.log(`Teacher ${teacherId} updated in class ${classId}`); + return { + id: updated.id, + teacherId: updated.teacher.id, + teacherName: updated.teacher.name, + teacherPhone: updated.teacher.phone, + role: updated.role, + isPrimary: updated.isPrimary, + }; + } + async removeClassTeacher(tenantId, classId, teacherId) { + const classEntity = await this.prisma.class.findFirst({ + where: { id: classId, tenantId }, + }); + if (!classEntity) { + throw new common_1.NotFoundException('班级不存在'); + } + const classTeacher = await this.prisma.classTeacher.findUnique({ + where: { + classId_teacherId: { classId, teacherId }, + }, + }); + if (!classTeacher) { + throw new common_1.NotFoundException('该教师不在此班级中'); + } + await this.prisma.classTeacher.delete({ + where: { + classId_teacherId: { classId, teacherId }, + }, + }); + if (classEntity.teacherId === teacherId) { + const nextMainTeacher = await this.prisma.classTeacher.findFirst({ + where: { classId, role: 'MAIN' }, + orderBy: { createdAt: 'asc' }, + }); + await this.prisma.class.update({ + where: { id: classId }, + data: { teacherId: nextMainTeacher?.teacherId || null }, + }); + } + this.logger.log(`Teacher ${teacherId} removed from class ${classId}`); + return { message: '移除成功' }; + } + async transferStudent(tenantId, studentId, dto) { + const student = await this.prisma.student.findFirst({ + where: { id: studentId, tenantId }, + }); + if (!student) { + throw new common_1.NotFoundException('学生不存在'); + } + const toClass = await this.prisma.class.findFirst({ + where: { id: dto.toClassId, tenantId }, + }); + if (!toClass) { + throw new common_1.NotFoundException('目标班级不存在'); + } + if (student.classId === dto.toClassId) { + throw new common_1.BadRequestException('学生已在此班级中'); + } + const fromClassId = student.classId; + await this.prisma.$transaction(async (tx) => { + await tx.student.update({ + where: { id: studentId }, + data: { classId: dto.toClassId }, + }); + await tx.class.update({ + where: { id: fromClassId }, + data: { studentCount: { decrement: 1 } }, + }); + await tx.class.update({ + where: { id: dto.toClassId }, + data: { studentCount: { increment: 1 } }, + }); + await tx.studentClassHistory.create({ + data: { + studentId, + fromClassId, + toClassId: dto.toClassId, + reason: dto.reason, + operatedBy: null, + }, + }); + }); + this.logger.log(`Student ${studentId} transferred from class ${fromClassId} to ${dto.toClassId}`); + return { message: '调班成功' }; + } + async getStudentClassHistory(tenantId, studentId) { + const student = await this.prisma.student.findFirst({ + where: { id: studentId, tenantId }, + }); + if (!student) { + throw new common_1.NotFoundException('学生不存在'); + } + const history = await this.prisma.studentClassHistory.findMany({ + where: { studentId }, + include: { + fromClass: { + select: { id: true, name: true, grade: true }, + }, + toClass: { + select: { id: true, name: true, grade: true }, + }, + }, + orderBy: { createdAt: 'desc' }, + }); + return history.map((h) => ({ + id: h.id, + fromClass: h.fromClass ? { id: h.fromClass.id, name: h.fromClass.name, grade: h.fromClass.grade } : null, + toClass: { id: h.toClass.id, name: h.toClass.name, grade: h.toClass.grade }, + reason: h.reason, + operatedBy: h.operatedBy, + createdAt: h.createdAt, + })); + } + parseTimeToMinutes(timeStr) { + const [hours, minutes] = timeStr.split(':').map(Number); + return hours * 60 + minutes; + } + isTimeOverlapping(time1, time2) { + const [start1, end1] = time1.split('-').map(t => this.parseTimeToMinutes(t.trim())); + const [start2, end2] = time2.split('-').map(t => this.parseTimeToMinutes(t.trim())); + return start1 < end2 && start2 < end1; + } + async checkScheduleConflict(teacherId, scheduledDate, scheduledTime, excludeScheduleId) { + const dateStart = new Date(scheduledDate + 'T00:00:00.000Z'); + const dateEnd = new Date(scheduledDate + 'T23:59:59.999Z'); + const existingSchedules = await this.prisma.schedulePlan.findMany({ + where: { + teacherId, + scheduledDate: { + gte: dateStart, + lte: dateEnd, + }, + status: 'ACTIVE', + ...(excludeScheduleId && { id: { not: excludeScheduleId } }), + }, + include: { + class: { select: { name: true } }, + course: { select: { name: true } }, + }, + }); + for (const schedule of existingSchedules) { + if (schedule.scheduledTime && this.isTimeOverlapping(scheduledTime, schedule.scheduledTime)) { + return { + courseName: schedule.course.name, + className: schedule.class.name, + scheduledTime: schedule.scheduledTime, + }; + } + } + return null; + } + async findSchedules(tenantId, query) { + const { page = 1, pageSize = 20, classId, teacherId, courseId, startDate, endDate, status, source } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = { + tenantId, + }; + if (classId) + where.classId = classId; + if (teacherId) + where.teacherId = teacherId; + if (courseId) + where.courseId = courseId; + if (status) + where.status = status; + if (source) + where.source = source; + if (startDate || endDate) { + where.scheduledDate = {}; + if (startDate) + where.scheduledDate.gte = new Date(startDate); + if (endDate) + where.scheduledDate.lte = new Date(endDate); + } + const [items, total] = await Promise.all([ + this.prisma.schedulePlan.findMany({ + where, + skip, + take, + orderBy: { scheduledDate: 'asc' }, + include: { + class: { select: { id: true, name: true, grade: true } }, + course: { select: { id: true, name: true, pictureBookName: true, duration: true } }, + teacher: { select: { id: true, name: true, phone: true } }, + }, + }), + this.prisma.schedulePlan.count({ where }), + ]); + return { + items: items.map((item) => ({ + ...item, + className: item.class.name, + courseName: item.course.name, + teacherName: item.teacher?.name, + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + async findSchedule(tenantId, id) { + const schedule = await this.prisma.schedulePlan.findFirst({ + where: { id, tenantId }, + include: { + class: { select: { id: true, name: true, grade: true } }, + course: { select: { id: true, name: true, pictureBookName: true, duration: true } }, + teacher: { select: { id: true, name: true, phone: true } }, + }, + }); + if (!schedule) { + throw new common_1.NotFoundException('排课计划不存在'); + } + return { + ...schedule, + className: schedule.class.name, + courseName: schedule.course.name, + teacherName: schedule.teacher?.name, + }; + } + async createSchedule(tenantId, dto, userId) { + const classEntity = await this.prisma.class.findFirst({ + where: { id: dto.classId, tenantId }, + }); + if (!classEntity) { + throw new common_1.NotFoundException('班级不存在'); + } + const tenantCourse = await this.prisma.tenantCourse.findFirst({ + where: { tenantId, courseId: dto.courseId, authorized: true }, + }); + if (!tenantCourse) { + throw new common_1.ForbiddenException('该课程未授权或不存在'); + } + if (dto.teacherId) { + const teacher = await this.prisma.teacher.findFirst({ + where: { id: dto.teacherId, tenantId }, + }); + if (!teacher) { + throw new common_1.NotFoundException('教师不存在'); + } + if (dto.scheduledDate && dto.scheduledTime) { + const conflict = await this.checkScheduleConflict(dto.teacherId, dto.scheduledDate, dto.scheduledTime); + if (conflict) { + throw new common_1.ConflictException(`时间冲突:该教师在 ${conflict.scheduledTime} 已有排课「${conflict.courseName}」(${conflict.className}),请选择其他时间段`); + } + } + } + const schedule = await this.prisma.schedulePlan.create({ + data: { + tenantId, + classId: dto.classId, + courseId: dto.courseId, + teacherId: dto.teacherId, + scheduledDate: dto.scheduledDate ? new Date(dto.scheduledDate) : null, + scheduledTime: dto.scheduledTime, + weekDay: dto.weekDay, + repeatType: dto.repeatType, + repeatEndDate: dto.repeatEndDate ? new Date(dto.repeatEndDate) : null, + source: 'SCHOOL', + createdBy: userId, + note: dto.note, + status: 'ACTIVE', + }, + include: { + class: { select: { id: true, name: true } }, + course: { select: { id: true, name: true } }, + teacher: { select: { id: true, name: true } }, + }, + }); + this.logger.log(`Schedule created: ${schedule.id} by user ${userId}`); + return { + ...schedule, + className: schedule.class.name, + courseName: schedule.course.name, + teacherName: schedule.teacher?.name, + }; + } + async updateSchedule(tenantId, id, dto) { + const schedule = await this.prisma.schedulePlan.findFirst({ + where: { id, tenantId }, + }); + if (!schedule) { + throw new common_1.NotFoundException('排课计划不存在'); + } + if (dto.teacherId !== undefined) { + if (dto.teacherId) { + const teacher = await this.prisma.teacher.findFirst({ + where: { id: dto.teacherId, tenantId }, + }); + if (!teacher) { + throw new common_1.NotFoundException('教师不存在'); + } + } + } + const updated = await this.prisma.schedulePlan.update({ + where: { id }, + data: { + teacherId: dto.teacherId, + scheduledDate: dto.scheduledDate ? new Date(dto.scheduledDate) : undefined, + scheduledTime: dto.scheduledTime, + weekDay: dto.weekDay, + repeatType: dto.repeatType, + repeatEndDate: dto.repeatEndDate ? new Date(dto.repeatEndDate) : undefined, + note: dto.note, + status: dto.status, + }, + include: { + class: { select: { id: true, name: true } }, + course: { select: { id: true, name: true } }, + teacher: { select: { id: true, name: true } }, + }, + }); + this.logger.log(`Schedule updated: ${id}`); + return { + ...updated, + className: updated.class.name, + courseName: updated.course.name, + teacherName: updated.teacher?.name, + }; + } + async cancelSchedule(tenantId, id) { + const schedule = await this.prisma.schedulePlan.findFirst({ + where: { id, tenantId }, + }); + if (!schedule) { + throw new common_1.NotFoundException('排课计划不存在'); + } + await this.prisma.schedulePlan.update({ + where: { id }, + data: { status: 'CANCELLED' }, + }); + this.logger.log(`Schedule cancelled: ${id}`); + return { message: '取消成功' }; + } + async getTimetable(tenantId, query) { + const { startDate, endDate, classId, teacherId } = query; + const where = { + tenantId, + status: 'ACTIVE', + scheduledDate: { + gte: new Date(startDate), + lte: new Date(endDate), + }, + }; + if (classId) + where.classId = classId; + if (teacherId) + where.teacherId = teacherId; + const schedules = await this.prisma.schedulePlan.findMany({ + where, + orderBy: [{ scheduledDate: 'asc' }, { scheduledTime: 'asc' }], + include: { + class: { select: { id: true, name: true, grade: true } }, + course: { select: { id: true, name: true, pictureBookName: true, duration: true } }, + teacher: { select: { id: true, name: true } }, + }, + }); + const timetable = {}; + const start = new Date(startDate); + const end = new Date(endDate); + for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) { + const dateStr = d.toISOString().split('T')[0]; + timetable[dateStr] = []; + } + schedules.forEach((schedule) => { + const dateStr = schedule.scheduledDate.toISOString().split('T')[0]; + if (timetable[dateStr]) { + timetable[dateStr].push({ + ...schedule, + className: schedule.class.name, + courseName: schedule.course.name, + teacherName: schedule.teacher?.name, + }); + } + }); + return Object.entries(timetable).map(([date, items]) => ({ + date, + weekDay: new Date(date).getDay(), + schedules: items, + })); + } + async batchCreateSchedules(tenantId, schedules) { + const results = []; + const errors = []; + for (let i = 0; i < schedules.length; i++) { + const item = schedules[i]; + try { + const classEntity = await this.prisma.class.findFirst({ + where: { id: item.classId, tenantId }, + }); + if (!classEntity) { + errors.push({ index: i, message: '班级不存在或无权限' }); + continue; + } + const tenantCourse = await this.prisma.tenantCourse.findFirst({ + where: { tenantId, courseId: item.courseId, authorized: true }, + }); + if (!tenantCourse) { + errors.push({ index: i, message: '课程未授权或不存在' }); + continue; + } + if (item.teacherId) { + const teacher = await this.prisma.teacher.findFirst({ + where: { id: item.teacherId, tenantId }, + }); + if (!teacher) { + errors.push({ index: i, message: '教师不存在' }); + continue; + } + } + const schedule = await this.prisma.schedulePlan.create({ + data: { + tenantId, + classId: item.classId, + courseId: item.courseId, + teacherId: item.teacherId, + scheduledDate: new Date(item.scheduledDate), + scheduledTime: item.scheduledTime, + repeatType: 'NONE', + source: 'SCHOOL', + createdBy: 0, + status: 'ACTIVE', + note: item.note, + }, + include: { + class: { select: { id: true, name: true } }, + course: { select: { id: true, name: true } }, + teacher: { select: { id: true, name: true } }, + }, + }); + results.push({ + ...schedule, + className: schedule.class.name, + courseName: schedule.course.name, + teacherName: schedule.teacher?.name, + }); + } + catch (error) { + errors.push({ index: i, message: error.message || '创建失败' }); + } + } + this.logger.log(`Batch create schedules: ${results.length} success, ${errors.length} failed`); + return { + success: results.length, + failed: errors.length, + results, + errors, + }; + } + async getScheduleTemplates(tenantId, query) { + const where = { tenantId }; + if (query?.classId) { + where.classId = query.classId; + } + if (query?.courseId) { + where.courseId = query.courseId; + } + const templates = await this.prisma.scheduleTemplate.findMany({ + where, + orderBy: [ + { isDefault: 'desc' }, + { createdAt: 'desc' }, + ], + include: { + course: { + select: { id: true, name: true, pictureBookName: true }, + }, + class: { + select: { id: true, name: true, grade: true }, + }, + teacher: { + select: { id: true, name: true }, + }, + }, + }); + return templates.map((t) => ({ + ...t, + courseName: t.course?.name, + className: t.class?.name, + teacherName: t.teacher?.name, + })); + } + async getScheduleTemplate(tenantId, id) { + const template = await this.prisma.scheduleTemplate.findFirst({ + where: { id, tenantId }, + include: { + course: { + select: { id: true, name: true, pictureBookName: true, duration: true }, + }, + class: { + select: { id: true, name: true, grade: true }, + }, + teacher: { + select: { id: true, name: true }, + }, + }, + }); + if (!template) { + throw new common_1.NotFoundException('模板不存在'); + } + return { + ...template, + courseName: template.course?.name, + className: template.class?.name, + teacherName: template.teacher?.name, + }; + } + async createScheduleTemplate(tenantId, data) { + const tenantCourse = await this.prisma.tenantCourse.findFirst({ + where: { tenantId, courseId: data.courseId, authorized: true }, + }); + if (!tenantCourse) { + throw new common_1.BadRequestException('该课程未授权或不存在'); + } + if (data.isDefault) { + await this.prisma.scheduleTemplate.updateMany({ + where: { + tenantId, + courseId: data.courseId, + isDefault: true, + }, + data: { isDefault: false }, + }); + } + const template = await this.prisma.scheduleTemplate.create({ + data: { + tenantId, + name: data.name, + courseId: data.courseId, + classId: data.classId, + teacherId: data.teacherId, + scheduledTime: data.scheduledTime, + weekDay: data.weekDay, + duration: data.duration || 25, + isDefault: data.isDefault || false, + }, + include: { + course: { select: { id: true, name: true } }, + class: { select: { id: true, name: true } }, + teacher: { select: { id: true, name: true } }, + }, + }); + this.logger.log(`Schedule template created: ${template.id}`); + return { + ...template, + courseName: template.course?.name, + className: template.class?.name, + teacherName: template.teacher?.name, + }; + } + async updateScheduleTemplate(tenantId, id, data) { + const template = await this.prisma.scheduleTemplate.findFirst({ + where: { id, tenantId }, + }); + if (!template) { + throw new common_1.NotFoundException('模板不存在'); + } + if (data.isDefault) { + await this.prisma.scheduleTemplate.updateMany({ + where: { + tenantId, + courseId: template.courseId, + isDefault: true, + id: { not: id }, + }, + data: { isDefault: false }, + }); + } + const updated = await this.prisma.scheduleTemplate.update({ + where: { id }, + data: { + name: data.name, + classId: data.classId, + teacherId: data.teacherId, + scheduledTime: data.scheduledTime, + weekDay: data.weekDay, + duration: data.duration, + isDefault: data.isDefault, + }, + include: { + course: { select: { id: true, name: true } }, + class: { select: { id: true, name: true } }, + teacher: { select: { id: true, name: true } }, + }, + }); + return { + ...updated, + courseName: updated.course?.name, + className: updated.class?.name, + teacherName: updated.teacher?.name, + }; + } + async deleteScheduleTemplate(tenantId, id) { + const template = await this.prisma.scheduleTemplate.findFirst({ + where: { id, tenantId }, + }); + if (!template) { + throw new common_1.NotFoundException('模板不存在'); + } + await this.prisma.scheduleTemplate.delete({ + where: { id }, + }); + this.logger.log(`Schedule template deleted: ${id}`); + return { message: '删除成功' }; + } + async applyScheduleTemplate(tenantId, templateId, data) { + const template = await this.prisma.scheduleTemplate.findFirst({ + where: { id: templateId, tenantId }, + }); + if (!template) { + throw new common_1.NotFoundException('模板不存在'); + } + const schedule = await this.prisma.schedulePlan.create({ + data: { + tenantId, + classId: data.classId || template.classId, + courseId: template.courseId, + teacherId: data.teacherId || template.teacherId, + scheduledDate: new Date(data.scheduledDate), + scheduledTime: template.scheduledTime, + repeatType: 'NONE', + source: 'SCHOOL', + createdBy: 0, + status: 'ACTIVE', + }, + include: { + class: { select: { id: true, name: true } }, + course: { select: { id: true, name: true } }, + }, + }); + this.logger.log(`Schedule created from template ${templateId}: ${schedule.id}`); + return { + ...schedule, + className: schedule.class.name, + courseName: schedule.course.name, + }; + } +}; +exports.SchoolService = SchoolService; +exports.SchoolService = SchoolService = SchoolService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], SchoolService); +//# sourceMappingURL=school.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/school.service.js.map b/reading-platform-backend/dist/src/modules/school/school.service.js.map new file mode 100644 index 0000000..faa5b13 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/school.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"school.service.js","sourceRoot":"","sources":["../../../../src/modules/school/school.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/settings.controller.d.ts b/reading-platform-backend/dist/src/modules/school/settings.controller.d.ts new file mode 100644 index 0000000..0f03145 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/settings.controller.d.ts @@ -0,0 +1,36 @@ +import { SettingsService } from './settings.service'; +export declare class SettingsController { + private readonly settingsService; + constructor(settingsService: SettingsService); + getSettings(req: any): Promise<{ + id: number; + tenantId: number; + createdAt: Date; + updatedAt: Date; + address: string | null; + schoolName: string | null; + schoolLogo: string | null; + notifyOnLesson: boolean; + notifyOnTask: boolean; + notifyOnGrowth: boolean; + }>; + updateSettings(req: any, data: { + schoolName?: string; + schoolLogo?: string; + address?: string; + notifyOnLesson?: boolean; + notifyOnTask?: boolean; + notifyOnGrowth?: boolean; + }): Promise<{ + id: number; + tenantId: number; + createdAt: Date; + updatedAt: Date; + address: string | null; + schoolName: string | null; + schoolLogo: string | null; + notifyOnLesson: boolean; + notifyOnTask: boolean; + notifyOnGrowth: boolean; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/school/settings.controller.js b/reading-platform-backend/dist/src/modules/school/settings.controller.js new file mode 100644 index 0000000..e99c64b --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/settings.controller.js @@ -0,0 +1,54 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SettingsController = void 0; +const common_1 = require("@nestjs/common"); +const settings_service_1 = require("./settings.service"); +const jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +const roles_guard_1 = require("../common/guards/roles.guard"); +const roles_decorator_1 = require("../common/decorators/roles.decorator"); +let SettingsController = class SettingsController { + constructor(settingsService) { + this.settingsService = settingsService; + } + getSettings(req) { + return this.settingsService.getSettings(req.user.tenantId); + } + updateSettings(req, data) { + return this.settingsService.updateSettings(req.user.tenantId, data); + } +}; +exports.SettingsController = SettingsController; +__decorate([ + (0, common_1.Get)(), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], SettingsController.prototype, "getSettings", null); +__decorate([ + (0, common_1.Put)(), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], SettingsController.prototype, "updateSettings", null); +exports.SettingsController = SettingsController = __decorate([ + (0, common_1.Controller)('school/settings'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('school'), + __metadata("design:paramtypes", [settings_service_1.SettingsService]) +], SettingsController); +//# sourceMappingURL=settings.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/settings.controller.js.map b/reading-platform-backend/dist/src/modules/school/settings.controller.js.map new file mode 100644 index 0000000..923c9c1 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/settings.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"settings.controller.js","sourceRoot":"","sources":["../../../../src/modules/school/settings.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAOwB;AACxB,yDAAqD;AACrD,oEAA+D;AAC/D,8DAA0D;AAC1D,0EAA6D;AAKtD,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;IAC7B,YAA6B,eAAgC;QAAhC,oBAAe,GAAf,eAAe,CAAiB;IAAG,CAAC;IAGjE,WAAW,CAAY,GAAQ;QAC7B,OAAO,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7D,CAAC;IAGD,cAAc,CACD,GAAQ,EACX,IAOP;QAED,OAAO,IAAI,CAAC,eAAe,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACtE,CAAC;CACF,CAAA;AAtBY,gDAAkB;AAI7B;IADC,IAAA,YAAG,GAAE;IACO,WAAA,IAAA,gBAAO,GAAE,CAAA;;;;qDAErB;AAGD;IADC,IAAA,YAAG,GAAE;IAEH,WAAA,IAAA,gBAAO,GAAE,CAAA;IACT,WAAA,IAAA,aAAI,GAAE,CAAA;;;;wDAUR;6BArBU,kBAAkB;IAH9B,IAAA,mBAAU,EAAC,iBAAiB,CAAC;IAC7B,IAAA,kBAAS,EAAC,6BAAY,EAAE,wBAAU,CAAC;IACnC,IAAA,uBAAK,EAAC,QAAQ,CAAC;qCAEgC,kCAAe;GADlD,kBAAkB,CAsB9B"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/settings.service.d.ts b/reading-platform-backend/dist/src/modules/school/settings.service.d.ts new file mode 100644 index 0000000..eccd8ec --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/settings.service.d.ts @@ -0,0 +1,36 @@ +import { PrismaService } from '../../database/prisma.service'; +export declare class SettingsService { + private prisma; + constructor(prisma: PrismaService); + getSettings(tenantId: number): Promise<{ + id: number; + tenantId: number; + createdAt: Date; + updatedAt: Date; + address: string | null; + schoolName: string | null; + schoolLogo: string | null; + notifyOnLesson: boolean; + notifyOnTask: boolean; + notifyOnGrowth: boolean; + }>; + updateSettings(tenantId: number, data: { + schoolName?: string; + schoolLogo?: string; + address?: string; + notifyOnLesson?: boolean; + notifyOnTask?: boolean; + notifyOnGrowth?: boolean; + }): Promise<{ + id: number; + tenantId: number; + createdAt: Date; + updatedAt: Date; + address: string | null; + schoolName: string | null; + schoolLogo: string | null; + notifyOnLesson: boolean; + notifyOnTask: boolean; + notifyOnGrowth: boolean; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/school/settings.service.js b/reading-platform-backend/dist/src/modules/school/settings.service.js new file mode 100644 index 0000000..1033b17 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/settings.service.js @@ -0,0 +1,92 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SettingsService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../../database/prisma.service"); +let SettingsService = class SettingsService { + constructor(prisma) { + this.prisma = prisma; + } + async getSettings(tenantId) { + let settings = await this.prisma.systemSettings.findUnique({ + where: { tenantId }, + }); + if (!settings) { + const tenant = await this.prisma.tenant.findUnique({ + where: { id: tenantId }, + }); + if (!tenant) { + throw new common_1.NotFoundException('学校不存在'); + } + settings = await this.prisma.systemSettings.create({ + data: { + tenantId, + schoolName: tenant.name, + schoolLogo: tenant.logoUrl, + address: tenant.address, + notifyOnLesson: true, + notifyOnTask: true, + notifyOnGrowth: false, + }, + }); + } + return settings; + } + async updateSettings(tenantId, data) { + let settings = await this.prisma.systemSettings.findUnique({ + where: { tenantId }, + }); + if (!settings) { + settings = await this.prisma.systemSettings.create({ + data: { + tenantId, + schoolName: data.schoolName, + schoolLogo: data.schoolLogo, + address: data.address, + notifyOnLesson: data.notifyOnLesson ?? true, + notifyOnTask: data.notifyOnTask ?? true, + notifyOnGrowth: data.notifyOnGrowth ?? false, + }, + }); + } + else { + settings = await this.prisma.systemSettings.update({ + where: { tenantId }, + data: { + schoolName: data.schoolName, + schoolLogo: data.schoolLogo, + address: data.address, + notifyOnLesson: data.notifyOnLesson, + notifyOnTask: data.notifyOnTask, + notifyOnGrowth: data.notifyOnGrowth, + }, + }); + } + if (data.schoolName || data.schoolLogo || data.address) { + await this.prisma.tenant.update({ + where: { id: tenantId }, + data: { + name: data.schoolName, + logoUrl: data.schoolLogo, + address: data.address, + }, + }); + } + return settings; + } +}; +exports.SettingsService = SettingsService; +exports.SettingsService = SettingsService = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], SettingsService); +//# sourceMappingURL=settings.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/settings.service.js.map b/reading-platform-backend/dist/src/modules/school/settings.service.js.map new file mode 100644 index 0000000..f1e1aae --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/settings.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"settings.service.js","sourceRoot":"","sources":["../../../../src/modules/school/settings.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/stats.controller.d.ts b/reading-platform-backend/dist/src/modules/school/stats.controller.d.ts new file mode 100644 index 0000000..b2f3385 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/stats.controller.d.ts @@ -0,0 +1,59 @@ +import { StatsService } from './stats.service'; +export declare class StatsController { + private readonly statsService; + constructor(statsService: StatsService); + getStats(req: any): Promise<{ + teacherCount: number; + studentCount: number; + classCount: number; + lessonCount: number; + }>; + getActiveTeachers(req: any, limit?: string): Promise<{ + id: number; + name: string; + lessonCount: number; + }[]>; + getCourseUsageStats(req: any): Promise<{ + courseId: number; + courseName: string; + usageCount: number; + }[]>; + getRecentActivities(req: any, limit?: string): Promise<{ + id: number; + type: string; + title: string; + time: Date; + }[]>; + getLessonTrend(req: any, months?: string): Promise; + getCourseDistribution(req: any): Promise; + getReportOverview(req: any): Promise<{ + totalLessons: number; + activeTeacherCount: number; + usedCourseCount: number; + avgRating: number; + }>; + getTeacherReports(req: any): Promise<{ + id: number; + name: string; + lessonCount: number; + courseCount: number; + feedbackCount: number; + avgRating: number; + }[]>; + getCourseReports(req: any): Promise<{ + id: number; + name: string; + lessonCount: number; + teacherCount: number; + studentCount: number; + avgRating: number; + }[]>; + getStudentReports(req: any): Promise<{ + id: number; + name: string; + className: string; + lessonCount: number; + avgFocus: number; + avgParticipation: number; + }[]>; +} diff --git a/reading-platform-backend/dist/src/modules/school/stats.controller.js b/reading-platform-backend/dist/src/modules/school/stats.controller.js new file mode 100644 index 0000000..5f1d24b --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/stats.controller.js @@ -0,0 +1,136 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.StatsController = void 0; +const common_1 = require("@nestjs/common"); +const stats_service_1 = require("./stats.service"); +const jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +const roles_guard_1 = require("../common/guards/roles.guard"); +const roles_decorator_1 = require("../common/decorators/roles.decorator"); +let StatsController = class StatsController { + constructor(statsService) { + this.statsService = statsService; + } + getStats(req) { + return this.statsService.getSchoolStats(req.user.tenantId); + } + getActiveTeachers(req, limit) { + return this.statsService.getActiveTeachers(req.user.tenantId, limit ? parseInt(limit, 10) : 5); + } + getCourseUsageStats(req) { + return this.statsService.getCourseUsageStats(req.user.tenantId); + } + getRecentActivities(req, limit) { + return this.statsService.getRecentActivities(req.user.tenantId, limit ? parseInt(limit, 10) : 10); + } + getLessonTrend(req, months) { + return this.statsService.getLessonTrend(req.user.tenantId, months ? parseInt(months, 10) : 6); + } + getCourseDistribution(req) { + return this.statsService.getCourseDistribution(req.user.tenantId); + } + getReportOverview(req) { + return this.statsService.getReportOverview(req.user.tenantId); + } + getTeacherReports(req) { + return this.statsService.getTeacherReports(req.user.tenantId); + } + getCourseReports(req) { + return this.statsService.getCourseReports(req.user.tenantId); + } + getStudentReports(req) { + return this.statsService.getStudentReports(req.user.tenantId); + } +}; +exports.StatsController = StatsController; +__decorate([ + (0, common_1.Get)('stats'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], StatsController.prototype, "getStats", null); +__decorate([ + (0, common_1.Get)('stats/teachers'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)('limit')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], StatsController.prototype, "getActiveTeachers", null); +__decorate([ + (0, common_1.Get)('stats/courses'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], StatsController.prototype, "getCourseUsageStats", null); +__decorate([ + (0, common_1.Get)('stats/activities'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)('limit')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], StatsController.prototype, "getRecentActivities", null); +__decorate([ + (0, common_1.Get)('stats/lesson-trend'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)('months')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], StatsController.prototype, "getLessonTrend", null); +__decorate([ + (0, common_1.Get)('stats/course-distribution'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], StatsController.prototype, "getCourseDistribution", null); +__decorate([ + (0, common_1.Get)('reports/overview'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], StatsController.prototype, "getReportOverview", null); +__decorate([ + (0, common_1.Get)('reports/teachers'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], StatsController.prototype, "getTeacherReports", null); +__decorate([ + (0, common_1.Get)('reports/courses'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], StatsController.prototype, "getCourseReports", null); +__decorate([ + (0, common_1.Get)('reports/students'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], StatsController.prototype, "getStudentReports", null); +exports.StatsController = StatsController = __decorate([ + (0, common_1.Controller)('school'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('school'), + __metadata("design:paramtypes", [stats_service_1.StatsService]) +], StatsController); +//# sourceMappingURL=stats.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/stats.controller.js.map b/reading-platform-backend/dist/src/modules/school/stats.controller.js.map new file mode 100644 index 0000000..7a72a11 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/stats.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"stats.controller.js","sourceRoot":"","sources":["../../../../src/modules/school/stats.controller.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/stats.service.d.ts b/reading-platform-backend/dist/src/modules/school/stats.service.d.ts new file mode 100644 index 0000000..595ff6d --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/stats.service.d.ts @@ -0,0 +1,69 @@ +import { PrismaService } from '../../database/prisma.service'; +export interface LessonTrendItem { + month: string; + lessonCount: number; + studentCount: number; +} +export interface CourseDistributionItem { + name: string; + value: number; +} +export declare class StatsService { + private prisma; + private readonly logger; + constructor(prisma: PrismaService); + getSchoolStats(tenantId: number): Promise<{ + teacherCount: number; + studentCount: number; + classCount: number; + lessonCount: number; + }>; + getActiveTeachers(tenantId: number, limit?: number): Promise<{ + id: number; + name: string; + lessonCount: number; + }[]>; + getCourseUsageStats(tenantId: number): Promise<{ + courseId: number; + courseName: string; + usageCount: number; + }[]>; + getRecentActivities(tenantId: number, limit?: number): Promise<{ + id: number; + type: string; + title: string; + time: Date; + }[]>; + getLessonTrend(tenantId: number, months?: number): Promise; + getCourseDistribution(tenantId: number): Promise; + getReportOverview(tenantId: number): Promise<{ + totalLessons: number; + activeTeacherCount: number; + usedCourseCount: number; + avgRating: number; + }>; + getTeacherReports(tenantId: number): Promise<{ + id: number; + name: string; + lessonCount: number; + courseCount: number; + feedbackCount: number; + avgRating: number; + }[]>; + getCourseReports(tenantId: number): Promise<{ + id: number; + name: string; + lessonCount: number; + teacherCount: number; + studentCount: number; + avgRating: number; + }[]>; + getStudentReports(tenantId: number): Promise<{ + id: number; + name: string; + className: string; + lessonCount: number; + avgFocus: number; + avgParticipation: number; + }[]>; +} diff --git a/reading-platform-backend/dist/src/modules/school/stats.service.js b/reading-platform-backend/dist/src/modules/school/stats.service.js new file mode 100644 index 0000000..799965b --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/stats.service.js @@ -0,0 +1,400 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var StatsService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.StatsService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../../database/prisma.service"); +let StatsService = StatsService_1 = class StatsService { + constructor(prisma) { + this.prisma = prisma; + this.logger = new common_1.Logger(StatsService_1.name); + } + async getSchoolStats(tenantId) { + const [teacherCount, studentCount, classCount, lessonCount] = await Promise.all([ + this.prisma.teacher.count({ + where: { tenantId, status: 'ACTIVE' }, + }), + this.prisma.student.count({ + where: { tenantId }, + }), + this.prisma.class.count({ + where: { tenantId }, + }), + this.prisma.lesson.count({ + where: { tenantId, status: 'COMPLETED' }, + }), + ]); + return { + teacherCount, + studentCount, + classCount, + lessonCount, + }; + } + async getActiveTeachers(tenantId, limit = 5) { + const teachers = await this.prisma.teacher.findMany({ + where: { + tenantId, + status: 'ACTIVE', + }, + orderBy: { + lessonCount: 'desc', + }, + take: limit, + select: { + id: true, + name: true, + lessonCount: true, + }, + }); + return teachers; + } + async getCourseUsageStats(tenantId) { + const lessons = await this.prisma.lesson.findMany({ + where: { + tenantId, + status: 'COMPLETED', + }, + select: { + courseId: true, + course: { + select: { + id: true, + name: true, + }, + }, + }, + }); + const courseUsageMap = new Map(); + lessons.forEach((lesson) => { + const courseId = lesson.courseId; + const existing = courseUsageMap.get(courseId); + if (existing) { + existing.usageCount++; + } + else { + courseUsageMap.set(courseId, { + courseId: courseId, + courseName: lesson.course?.name || '未知课程', + usageCount: 1, + }); + } + }); + const result = Array.from(courseUsageMap.values()) + .sort((a, b) => b.usageCount - a.usageCount) + .slice(0, 10); + return result; + } + async getRecentActivities(tenantId, limit = 10) { + const lessons = await this.prisma.lesson.findMany({ + where: { + tenantId, + }, + orderBy: { + createdAt: 'desc', + }, + take: limit, + select: { + id: true, + status: true, + createdAt: true, + course: { + select: { + name: true, + }, + }, + teacher: { + select: { + name: true, + }, + }, + class: { + select: { + name: true, + }, + }, + }, + }); + const activities = lessons.map((lesson) => { + let title = ''; + const teacherName = lesson.teacher?.name || '未知教师'; + const courseName = lesson.course?.name || '未知课程'; + const className = lesson.class?.name || '未知班级'; + switch (lesson.status) { + case 'COMPLETED': + title = `${teacherName}完成《${courseName}》授课`; + break; + case 'IN_PROGRESS': + title = `${teacherName}正在进行《${courseName}》授课`; + break; + case 'PLANNED': + title = `${teacherName}计划在${className}讲授《${courseName}》`; + break; + case 'CANCELLED': + title = `${teacherName}取消了《${courseName}》授课`; + break; + default: + title = `${teacherName}操作了《${courseName}》`; + } + return { + id: lesson.id, + type: lesson.status, + title, + time: lesson.createdAt, + }; + }); + return activities; + } + async getLessonTrend(tenantId, months = 6) { + const result = []; + const now = new Date(); + for (let i = months - 1; i >= 0; i--) { + const startDate = new Date(now.getFullYear(), now.getMonth() - i, 1); + const endDate = new Date(now.getFullYear(), now.getMonth() - i + 1, 0, 23, 59, 59); + const lessonCount = await this.prisma.lesson.count({ + where: { + tenantId, + status: 'COMPLETED', + createdAt: { + gte: startDate, + lte: endDate, + }, + }, + }); + const studentCount = await this.prisma.student.count({ + where: { tenantId }, + }); + const monthLabel = `${startDate.getFullYear()}-${String(startDate.getMonth() + 1).padStart(2, '0')}`; + result.push({ + month: monthLabel, + lessonCount, + studentCount, + }); + } + return result; + } + async getCourseDistribution(tenantId) { + const lessons = await this.prisma.lesson.findMany({ + where: { + tenantId, + status: 'COMPLETED', + }, + select: { + courseId: true, + course: { + select: { + name: true, + }, + }, + }, + }); + const courseMap = new Map(); + lessons.forEach((lesson) => { + const courseName = lesson.course?.name || '未知课程'; + courseMap.set(courseName, (courseMap.get(courseName) || 0) + 1); + }); + const result = Array.from(courseMap.entries()) + .map(([name, value]) => ({ name, value })) + .sort((a, b) => b.value - a.value) + .slice(0, 8); + return result; + } + async getReportOverview(tenantId) { + const totalLessons = await this.prisma.lesson.count({ + where: { tenantId, status: 'COMPLETED' }, + }); + const activeTeachers = await this.prisma.teacher.count({ + where: { + tenantId, + status: 'ACTIVE', + lessonCount: { gt: 0 }, + }, + }); + const usedCourses = await this.prisma.lesson.findMany({ + where: { tenantId, status: 'COMPLETED' }, + select: { courseId: true }, + distinct: ['courseId'], + }); + const feedbacks = await this.prisma.lessonFeedback.findMany({ + where: { + lesson: { tenantId }, + }, + select: { + designQuality: true, + participation: true, + goalAchievement: true, + }, + }); + let avgRating = 0; + if (feedbacks.length > 0) { + const totalRating = feedbacks.reduce((sum, f) => { + const avg = ((f.designQuality || 0) + (f.participation || 0) + (f.goalAchievement || 0)) / 3; + return sum + avg; + }, 0); + avgRating = Number((totalRating / feedbacks.length).toFixed(1)); + } + return { + totalLessons, + activeTeacherCount: activeTeachers, + usedCourseCount: usedCourses.length, + avgRating, + }; + } + async getTeacherReports(tenantId) { + const teachers = await this.prisma.teacher.findMany({ + where: { tenantId, status: 'ACTIVE' }, + select: { + id: true, + name: true, + lessonCount: true, + feedbackCount: true, + lessons: { + where: { status: 'COMPLETED' }, + select: { courseId: true }, + }, + }, + }); + return teachers.map((teacher) => { + const uniqueCourses = new Set(teacher.lessons.map((l) => l.courseId)); + const avgRating = teacher.feedbackCount > 0 ? 4.5 : 0; + return { + id: teacher.id, + name: teacher.name, + lessonCount: teacher.lessonCount, + courseCount: uniqueCourses.size, + feedbackCount: teacher.feedbackCount, + avgRating, + }; + }).sort((a, b) => b.lessonCount - a.lessonCount); + } + async getCourseReports(tenantId) { + const lessons = await this.prisma.lesson.findMany({ + where: { tenantId, status: 'COMPLETED' }, + select: { + courseId: true, + course: { + select: { name: true }, + }, + teacherId: true, + classId: true, + class: { + select: { studentCount: true }, + }, + }, + }); + const courseMap = new Map(); + lessons.forEach((lesson) => { + const courseId = lesson.courseId; + const existing = courseMap.get(courseId); + if (existing) { + existing.lessonCount++; + existing.teacherIds.add(lesson.teacherId); + existing.studentCount += lesson.class?.studentCount || 0; + } + else { + courseMap.set(courseId, { + id: courseId, + name: lesson.course?.name || '未知课程', + lessonCount: 1, + teacherIds: new Set([lesson.teacherId]), + studentCount: lesson.class?.studentCount || 0, + }); + } + }); + const courseRatings = await this.prisma.lessonFeedback.findMany({ + where: { + lesson: { tenantId }, + }, + select: { + designQuality: true, + participation: true, + goalAchievement: true, + lesson: { + select: { courseId: true }, + }, + }, + }); + const ratingMap = new Map(); + courseRatings.forEach((feedback) => { + const courseId = feedback.lesson.courseId; + const avg = ((feedback.designQuality || 0) + (feedback.participation || 0) + (feedback.goalAchievement || 0)) / 3; + const existing = ratingMap.get(courseId); + if (existing) { + existing.total += avg; + existing.count++; + } + else { + ratingMap.set(courseId, { total: avg, count: 1 }); + } + }); + return Array.from(courseMap.values()) + .map((course) => { + const rating = ratingMap.get(course.id); + const avgRating = rating ? Number((rating.total / rating.count).toFixed(1)) : 0; + return { + id: course.id, + name: course.name, + lessonCount: course.lessonCount, + teacherCount: course.teacherIds.size, + studentCount: course.studentCount, + avgRating, + }; + }) + .sort((a, b) => b.lessonCount - a.lessonCount); + } + async getStudentReports(tenantId) { + const students = await this.prisma.student.findMany({ + where: { tenantId }, + select: { + id: true, + name: true, + classId: true, + class: { + select: { name: true }, + }, + records: { + select: { + focus: true, + participation: true, + interest: true, + understanding: true, + }, + }, + }, + }); + return students.map((student) => { + let avgFocus = 0; + let avgParticipation = 0; + const recordCount = student.records.length; + if (recordCount > 0) { + const totalFocus = student.records.reduce((sum, r) => sum + (r.focus || 0), 0); + const totalParticipation = student.records.reduce((sum, r) => sum + (r.participation || 0), 0); + avgFocus = Number((totalFocus / recordCount).toFixed(1)); + avgParticipation = Number((totalParticipation / recordCount).toFixed(1)); + } + return { + id: student.id, + name: student.name, + className: student.class?.name || '未分班', + lessonCount: recordCount, + avgFocus, + avgParticipation, + }; + }).sort((a, b) => b.lessonCount - a.lessonCount); + } +}; +exports.StatsService = StatsService; +exports.StatsService = StatsService = StatsService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], StatsService); +//# sourceMappingURL=stats.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/school/stats.service.js.map b/reading-platform-backend/dist/src/modules/school/stats.service.js.map new file mode 100644 index 0000000..2977b2f --- /dev/null +++ b/reading-platform-backend/dist/src/modules/school/stats.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"stats.service.js","sourceRoot":"","sources":["../../../../src/modules/school/stats.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/task/dto/create-task.dto.d.ts b/reading-platform-backend/dist/src/modules/task/dto/create-task.dto.d.ts new file mode 100644 index 0000000..c07e03d --- /dev/null +++ b/reading-platform-backend/dist/src/modules/task/dto/create-task.dto.d.ts @@ -0,0 +1,71 @@ +export declare enum TaskType { + READING = "READING", + ACTIVITY = "ACTIVITY", + HOMEWORK = "HOMEWORK" +} +export declare enum TargetType { + CLASS = "CLASS", + STUDENT = "STUDENT" +} +export declare enum TaskStatus { + DRAFT = "DRAFT", + PUBLISHED = "PUBLISHED", + ARCHIVED = "ARCHIVED" +} +export declare enum CompletionStatus { + PENDING = "PENDING", + IN_PROGRESS = "IN_PROGRESS", + COMPLETED = "COMPLETED" +} +export declare class CreateTaskDto { + title: string; + description?: string; + taskType: TaskType; + targetType: TargetType; + relatedCourseId?: number; + startDate: string; + endDate: string; + targetIds: number[]; +} +export declare class UpdateTaskDto { + title?: string; + description?: string; + startDate?: string; + endDate?: string; + status?: TaskStatus; + targetIds?: number[]; +} +export declare class UpdateCompletionDto { + status: CompletionStatus; + feedback?: string; + parentFeedback?: string; +} +export declare class QueryTaskDto { + page?: number; + pageSize?: number; + status?: string; + taskType?: string; + keyword?: string; +} +export declare class CreateTaskTemplateDto { + name: string; + description?: string; + taskType: TaskType; + relatedCourseId?: number; + defaultDuration?: number; + isDefault?: boolean; +} +export declare class UpdateTaskTemplateDto { + name?: string; + description?: string; + relatedCourseId?: number; + defaultDuration?: number; + isDefault?: boolean; + status?: string; +} +export declare class CreateFromTemplateDto { + templateId: number; + targetIds: number[]; + targetType: string; + startDate?: string; +} diff --git a/reading-platform-backend/dist/src/modules/task/dto/create-task.dto.js b/reading-platform-backend/dist/src/modules/task/dto/create-task.dto.js new file mode 100644 index 0000000..1704031 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/task/dto/create-task.dto.js @@ -0,0 +1,243 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CreateFromTemplateDto = exports.UpdateTaskTemplateDto = exports.CreateTaskTemplateDto = exports.QueryTaskDto = exports.UpdateCompletionDto = exports.UpdateTaskDto = exports.CreateTaskDto = exports.CompletionStatus = exports.TaskStatus = exports.TargetType = exports.TaskType = void 0; +const class_validator_1 = require("class-validator"); +var TaskType; +(function (TaskType) { + TaskType["READING"] = "READING"; + TaskType["ACTIVITY"] = "ACTIVITY"; + TaskType["HOMEWORK"] = "HOMEWORK"; +})(TaskType || (exports.TaskType = TaskType = {})); +var TargetType; +(function (TargetType) { + TargetType["CLASS"] = "CLASS"; + TargetType["STUDENT"] = "STUDENT"; +})(TargetType || (exports.TargetType = TargetType = {})); +var TaskStatus; +(function (TaskStatus) { + TaskStatus["DRAFT"] = "DRAFT"; + TaskStatus["PUBLISHED"] = "PUBLISHED"; + TaskStatus["ARCHIVED"] = "ARCHIVED"; +})(TaskStatus || (exports.TaskStatus = TaskStatus = {})); +var CompletionStatus; +(function (CompletionStatus) { + CompletionStatus["PENDING"] = "PENDING"; + CompletionStatus["IN_PROGRESS"] = "IN_PROGRESS"; + CompletionStatus["COMPLETED"] = "COMPLETED"; +})(CompletionStatus || (exports.CompletionStatus = CompletionStatus = {})); +class CreateTaskDto { +} +exports.CreateTaskDto = CreateTaskDto; +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '任务标题不能为空' }), + __metadata("design:type", String) +], CreateTaskDto.prototype, "title", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateTaskDto.prototype, "description", void 0); +__decorate([ + (0, class_validator_1.IsEnum)(TaskType), + __metadata("design:type", String) +], CreateTaskDto.prototype, "taskType", void 0); +__decorate([ + (0, class_validator_1.IsEnum)(TargetType), + __metadata("design:type", String) +], CreateTaskDto.prototype, "targetType", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], CreateTaskDto.prototype, "relatedCourseId", void 0); +__decorate([ + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], CreateTaskDto.prototype, "startDate", void 0); +__decorate([ + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], CreateTaskDto.prototype, "endDate", void 0); +__decorate([ + (0, class_validator_1.IsArray)(), + (0, class_validator_1.IsInt)({ each: true }), + __metadata("design:type", Array) +], CreateTaskDto.prototype, "targetIds", void 0); +class UpdateTaskDto { +} +exports.UpdateTaskDto = UpdateTaskDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '任务标题不能为空' }), + __metadata("design:type", String) +], UpdateTaskDto.prototype, "title", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateTaskDto.prototype, "description", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], UpdateTaskDto.prototype, "startDate", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], UpdateTaskDto.prototype, "endDate", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsEnum)(TaskStatus), + __metadata("design:type", String) +], UpdateTaskDto.prototype, "status", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsArray)(), + (0, class_validator_1.IsInt)({ each: true }), + __metadata("design:type", Array) +], UpdateTaskDto.prototype, "targetIds", void 0); +class UpdateCompletionDto { +} +exports.UpdateCompletionDto = UpdateCompletionDto; +__decorate([ + (0, class_validator_1.IsEnum)(CompletionStatus), + __metadata("design:type", String) +], UpdateCompletionDto.prototype, "status", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateCompletionDto.prototype, "feedback", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateCompletionDto.prototype, "parentFeedback", void 0); +class QueryTaskDto { +} +exports.QueryTaskDto = QueryTaskDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], QueryTaskDto.prototype, "page", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], QueryTaskDto.prototype, "pageSize", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], QueryTaskDto.prototype, "status", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], QueryTaskDto.prototype, "taskType", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], QueryTaskDto.prototype, "keyword", void 0); +class CreateTaskTemplateDto { +} +exports.CreateTaskTemplateDto = CreateTaskTemplateDto; +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '模板名称不能为空' }), + __metadata("design:type", String) +], CreateTaskTemplateDto.prototype, "name", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateTaskTemplateDto.prototype, "description", void 0); +__decorate([ + (0, class_validator_1.IsEnum)(TaskType), + __metadata("design:type", String) +], CreateTaskTemplateDto.prototype, "taskType", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], CreateTaskTemplateDto.prototype, "relatedCourseId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + (0, class_validator_1.Min)(1), + __metadata("design:type", Number) +], CreateTaskTemplateDto.prototype, "defaultDuration", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + __metadata("design:type", Boolean) +], CreateTaskTemplateDto.prototype, "isDefault", void 0); +class UpdateTaskTemplateDto { +} +exports.UpdateTaskTemplateDto = UpdateTaskTemplateDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '模板名称不能为空' }), + __metadata("design:type", String) +], UpdateTaskTemplateDto.prototype, "name", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateTaskTemplateDto.prototype, "description", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], UpdateTaskTemplateDto.prototype, "relatedCourseId", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + (0, class_validator_1.Min)(1), + __metadata("design:type", Number) +], UpdateTaskTemplateDto.prototype, "defaultDuration", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + __metadata("design:type", Boolean) +], UpdateTaskTemplateDto.prototype, "isDefault", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateTaskTemplateDto.prototype, "status", void 0); +class CreateFromTemplateDto { +} +exports.CreateFromTemplateDto = CreateFromTemplateDto; +__decorate([ + (0, class_validator_1.IsInt)(), + __metadata("design:type", Number) +], CreateFromTemplateDto.prototype, "templateId", void 0); +__decorate([ + (0, class_validator_1.IsArray)(), + (0, class_validator_1.IsInt)({ each: true }), + __metadata("design:type", Array) +], CreateFromTemplateDto.prototype, "targetIds", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateFromTemplateDto.prototype, "targetType", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], CreateFromTemplateDto.prototype, "startDate", void 0); +//# sourceMappingURL=create-task.dto.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/task/dto/create-task.dto.js.map b/reading-platform-backend/dist/src/modules/task/dto/create-task.dto.js.map new file mode 100644 index 0000000..eda9adc --- /dev/null +++ b/reading-platform-backend/dist/src/modules/task/dto/create-task.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"create-task.dto.js","sourceRoot":"","sources":["../../../../../src/modules/task/dto/create-task.dto.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/task/task.controller.d.ts b/reading-platform-backend/dist/src/modules/task/task.controller.d.ts new file mode 100644 index 0000000..c1fe754 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/task/task.controller.d.ts @@ -0,0 +1,820 @@ +import { TaskService } from './task.service'; +import { CreateTaskDto, UpdateTaskDto, UpdateCompletionDto, CreateTaskTemplateDto, UpdateTaskTemplateDto, CreateFromTemplateDto } from './dto/create-task.dto'; +import { ScheduleNotificationService } from '../notification/schedule-notification.service'; +export declare class SchoolTaskController { + private readonly taskService; + private readonly scheduleNotificationService; + constructor(taskService: TaskService, scheduleNotificationService: ScheduleNotificationService); + findAll(req: any, query: any): Promise<{ + items: { + targetCount: number; + completionCount: number; + _count: any; + course: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }[]; + total: number; + page: number; + pageSize: number; + }>; + getStats(req: any): Promise<{ + totalTasks: number; + publishedTasks: number; + completedTasks: number; + inProgressTasks: number; + pendingCount: number; + totalCompletions: number; + completionRate: number; + }>; + getStatsByType(req: any): Promise>; + getStatsByClass(req: any): Promise<{ + classId: number; + className: string; + grade: string; + total: number; + completed: number; + rate: number; + }[]>; + getMonthlyStats(req: any, months?: string): Promise<{ + month: string; + tasks: number; + completions: number; + completed: number; + rate: number; + }[]>; + findOne(req: any, id: string): Promise<{ + course: { + id: number; + name: string; + }; + targets: ({ + task: { + id: number; + title: string; + }; + } & { + id: number; + classId: number | null; + taskId: number; + studentId: number | null; + })[]; + completions: ({ + student: { + id: number; + name: string; + }; + } & { + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + })[]; + } & { + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }>; + getCompletions(req: any, id: string, query: any): Promise<{ + items: ({ + student: { + id: number; + name: string; + gender: string; + class: { + id: number; + name: string; + }; + }; + } & { + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + })[]; + total: number; + page: number; + pageSize: number; + }>; + create(req: any, dto: CreateTaskDto): Promise<{ + course: { + id: number; + name: string; + }; + targets: ({ + task: { + id: number; + title: string; + }; + } & { + id: number; + classId: number | null; + taskId: number; + studentId: number | null; + })[]; + completions: ({ + student: { + id: number; + name: string; + }; + } & { + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + })[]; + } & { + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }>; + update(req: any, id: string, dto: UpdateTaskDto): Promise<{ + course: { + id: number; + name: string; + }; + targets: ({ + task: { + id: number; + title: string; + }; + } & { + id: number; + classId: number | null; + taskId: number; + studentId: number | null; + })[]; + completions: ({ + student: { + id: number; + name: string; + }; + } & { + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + })[]; + } & { + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }>; + delete(req: any, id: string): Promise<{ + message: string; + }>; + updateCompletion(req: any, taskId: string, studentId: string, dto: UpdateCompletionDto): Promise<{ + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + }>; + sendReminder(req: any, id: string): Promise<{ + success: boolean; + message: string; + remindedCount?: undefined; + students?: undefined; + } | { + success: boolean; + message: string; + remindedCount: number; + students: { + id: number; + name: string; + }[]; + }>; + findAllTemplates(req: any, query: any): Promise<{ + items: ({ + course: { + id: number; + name: string; + pictureBookName: string; + }; + } & { + id: number; + tenantId: number; + description: string | null; + taskType: string; + relatedCourseId: number | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + defaultDuration: number; + isDefault: boolean; + })[]; + total: number; + page: number; + pageSize: number; + }>; + findOneTemplate(req: any, id: string): Promise<{ + course: { + id: number; + name: string; + pictureBookName: string; + }; + } & { + id: number; + tenantId: number; + description: string | null; + taskType: string; + relatedCourseId: number | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + defaultDuration: number; + isDefault: boolean; + }>; + getDefaultTemplate(req: any, taskType: string): Promise<{ + course: { + id: number; + name: string; + pictureBookName: string; + }; + } & { + id: number; + tenantId: number; + description: string | null; + taskType: string; + relatedCourseId: number | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + defaultDuration: number; + isDefault: boolean; + }>; + createTemplate(req: any, dto: CreateTaskTemplateDto): Promise<{ + course: { + id: number; + name: string; + pictureBookName: string; + }; + } & { + id: number; + tenantId: number; + description: string | null; + taskType: string; + relatedCourseId: number | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + defaultDuration: number; + isDefault: boolean; + }>; + updateTemplate(req: any, id: string, dto: UpdateTaskTemplateDto): Promise<{ + course: { + id: number; + name: string; + pictureBookName: string; + }; + } & { + id: number; + tenantId: number; + description: string | null; + taskType: string; + relatedCourseId: number | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + defaultDuration: number; + isDefault: boolean; + }>; + deleteTemplate(req: any, id: string): Promise<{ + message: string; + }>; + createFromTemplate(req: any, dto: CreateFromTemplateDto): Promise<{ + course: { + id: number; + name: string; + }; + targets: ({ + task: { + id: number; + title: string; + }; + } & { + id: number; + classId: number | null; + taskId: number; + studentId: number | null; + })[]; + completions: ({ + student: { + id: number; + name: string; + }; + } & { + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + })[]; + } & { + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }>; +} +export declare class TeacherTaskController { + private readonly taskService; + private readonly scheduleNotificationService; + constructor(taskService: TaskService, scheduleNotificationService: ScheduleNotificationService); + findAll(req: any, query: any): Promise<{ + items: { + targetCount: number; + completionCount: number; + _count: any; + course: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }[]; + total: number; + page: number; + pageSize: number; + }>; + getUpcoming(req: any, query: any): Promise<{ + pendingCount: number; + daysRemaining: number; + _count: any; + course: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }[]>; + getStats(req: any): Promise<{ + totalTasks: number; + publishedTasks: number; + completedTasks: number; + inProgressTasks: number; + pendingCount: number; + totalCompletions: number; + completionRate: number; + }>; + getStatsByType(req: any): Promise>; + getStatsByClass(req: any): Promise<{ + classId: number; + className: string; + grade: string; + total: number; + completed: number; + rate: number; + }[]>; + getMonthlyStats(req: any, months?: string): Promise<{ + month: string; + tasks: number; + completions: number; + completed: number; + rate: number; + }[]>; + findByClass(req: any, classId: string, query: any): Promise<{ + items: { + completionCount: number; + completedCount: number; + progress: number; + _count: any; + course: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findOne(req: any, id: string): Promise<{ + course: { + id: number; + name: string; + }; + targets: ({ + task: { + id: number; + title: string; + }; + } & { + id: number; + classId: number | null; + taskId: number; + studentId: number | null; + })[]; + completions: ({ + student: { + id: number; + name: string; + }; + } & { + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + })[]; + } & { + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }>; + getCompletions(req: any, id: string, query: any): Promise<{ + items: ({ + student: { + id: number; + name: string; + gender: string; + class: { + id: number; + name: string; + }; + }; + } & { + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + })[]; + total: number; + page: number; + pageSize: number; + }>; + create(req: any, dto: CreateTaskDto): Promise<{ + course: { + id: number; + name: string; + }; + targets: ({ + task: { + id: number; + title: string; + }; + } & { + id: number; + classId: number | null; + taskId: number; + studentId: number | null; + })[]; + completions: ({ + student: { + id: number; + name: string; + }; + } & { + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + })[]; + } & { + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }>; + update(req: any, id: string, dto: UpdateTaskDto): Promise<{ + course: { + id: number; + name: string; + }; + targets: ({ + task: { + id: number; + title: string; + }; + } & { + id: number; + classId: number | null; + taskId: number; + studentId: number | null; + })[]; + completions: ({ + student: { + id: number; + name: string; + }; + } & { + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + })[]; + } & { + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }>; + delete(req: any, id: string): Promise<{ + message: string; + }>; + sendReminder(req: any, id: string): Promise<{ + success: boolean; + message: string; + remindedCount?: undefined; + students?: undefined; + } | { + success: boolean; + message: string; + remindedCount: number; + students: { + id: number; + name: string; + }[]; + }>; + updateCompletion(req: any, taskId: string, studentId: string, dto: UpdateCompletionDto): Promise<{ + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + }>; + findAllTemplates(req: any, query: any): Promise<{ + items: ({ + course: { + id: number; + name: string; + pictureBookName: string; + }; + } & { + id: number; + tenantId: number; + description: string | null; + taskType: string; + relatedCourseId: number | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + defaultDuration: number; + isDefault: boolean; + })[]; + total: number; + page: number; + pageSize: number; + }>; + findOneTemplate(req: any, id: string): Promise<{ + course: { + id: number; + name: string; + pictureBookName: string; + }; + } & { + id: number; + tenantId: number; + description: string | null; + taskType: string; + relatedCourseId: number | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + defaultDuration: number; + isDefault: boolean; + }>; + getDefaultTemplate(req: any, taskType: string): Promise<{ + course: { + id: number; + name: string; + pictureBookName: string; + }; + } & { + id: number; + tenantId: number; + description: string | null; + taskType: string; + relatedCourseId: number | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + defaultDuration: number; + isDefault: boolean; + }>; + createFromTemplate(req: any, dto: CreateFromTemplateDto): Promise<{ + course: { + id: number; + name: string; + }; + targets: ({ + task: { + id: number; + title: string; + }; + } & { + id: number; + classId: number | null; + taskId: number; + studentId: number | null; + })[]; + completions: ({ + student: { + id: number; + name: string; + }; + } & { + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + })[]; + } & { + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/task/task.controller.js b/reading-platform-backend/dist/src/modules/task/task.controller.js new file mode 100644 index 0000000..7134097 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/task/task.controller.js @@ -0,0 +1,462 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TeacherTaskController = exports.SchoolTaskController = void 0; +const common_1 = require("@nestjs/common"); +const task_service_1 = require("./task.service"); +const create_task_dto_1 = require("./dto/create-task.dto"); +const jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +const roles_guard_1 = require("../common/guards/roles.guard"); +const roles_decorator_1 = require("../common/decorators/roles.decorator"); +const schedule_notification_service_1 = require("../notification/schedule-notification.service"); +let SchoolTaskController = class SchoolTaskController { + constructor(taskService, scheduleNotificationService) { + this.taskService = taskService; + this.scheduleNotificationService = scheduleNotificationService; + } + findAll(req, query) { + return this.taskService.findAll(req.user.tenantId, query); + } + getStats(req) { + return this.taskService.getStats(req.user.tenantId); + } + getStatsByType(req) { + return this.taskService.getStatsByType(req.user.tenantId); + } + getStatsByClass(req) { + return this.taskService.getStatsByClass(req.user.tenantId); + } + getMonthlyStats(req, months) { + return this.taskService.getMonthlyStats(req.user.tenantId, months ? +months : 6); + } + findOne(req, id) { + return this.taskService.findOne(req.user.tenantId, +id); + } + getCompletions(req, id, query) { + return this.taskService.getCompletions(req.user.tenantId, +id, query); + } + create(req, dto) { + return this.taskService.create(req.user.tenantId, req.user.userId, dto); + } + update(req, id, dto) { + return this.taskService.update(req.user.tenantId, +id, dto); + } + delete(req, id) { + return this.taskService.delete(req.user.tenantId, +id); + } + updateCompletion(req, taskId, studentId, dto) { + return this.taskService.updateCompletion(req.user.tenantId, +taskId, +studentId, dto); + } + sendReminder(req, id) { + return this.scheduleNotificationService.sendManualTaskReminder(req.user.tenantId, +id); + } + findAllTemplates(req, query) { + return this.taskService.findAllTemplates(req.user.tenantId, query); + } + findOneTemplate(req, id) { + return this.taskService.findOneTemplate(req.user.tenantId, +id); + } + getDefaultTemplate(req, taskType) { + return this.taskService.getDefaultTemplate(req.user.tenantId, taskType); + } + createTemplate(req, dto) { + return this.taskService.createTemplate(req.user.tenantId, req.user.userId, dto); + } + updateTemplate(req, id, dto) { + return this.taskService.updateTemplate(req.user.tenantId, +id, dto); + } + deleteTemplate(req, id) { + return this.taskService.deleteTemplate(req.user.tenantId, +id); + } + createFromTemplate(req, dto) { + return this.taskService.createFromTemplate(req.user.tenantId, req.user.userId, dto.templateId, { targetIds: dto.targetIds, targetType: dto.targetType, startDate: dto.startDate }); + } +}; +exports.SchoolTaskController = SchoolTaskController; +__decorate([ + (0, common_1.Get)('tasks'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "findAll", null); +__decorate([ + (0, common_1.Get)('tasks/stats'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "getStats", null); +__decorate([ + (0, common_1.Get)('tasks/stats/by-type'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "getStatsByType", null); +__decorate([ + (0, common_1.Get)('tasks/stats/by-class'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "getStatsByClass", null); +__decorate([ + (0, common_1.Get)('tasks/stats/monthly'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)('months')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "getMonthlyStats", null); +__decorate([ + (0, common_1.Get)('tasks/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "findOne", null); +__decorate([ + (0, common_1.Get)('tasks/:id/completions'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "getCompletions", null); +__decorate([ + (0, common_1.Post)('tasks'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, create_task_dto_1.CreateTaskDto]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "create", null); +__decorate([ + (0, common_1.Put)('tasks/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, create_task_dto_1.UpdateTaskDto]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "update", null); +__decorate([ + (0, common_1.Delete)('tasks/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "delete", null); +__decorate([ + (0, common_1.Put)('tasks/:taskId/completions/:studentId'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('taskId')), + __param(2, (0, common_1.Param)('studentId')), + __param(3, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, String, create_task_dto_1.UpdateCompletionDto]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "updateCompletion", null); +__decorate([ + (0, common_1.Post)('tasks/:id/remind'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "sendReminder", null); +__decorate([ + (0, common_1.Get)('task-templates'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "findAllTemplates", null); +__decorate([ + (0, common_1.Get)('task-templates/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "findOneTemplate", null); +__decorate([ + (0, common_1.Get)('task-templates/default/:taskType'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('taskType')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "getDefaultTemplate", null); +__decorate([ + (0, common_1.Post)('task-templates'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, create_task_dto_1.CreateTaskTemplateDto]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "createTemplate", null); +__decorate([ + (0, common_1.Put)('task-templates/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, create_task_dto_1.UpdateTaskTemplateDto]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "updateTemplate", null); +__decorate([ + (0, common_1.Delete)('task-templates/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "deleteTemplate", null); +__decorate([ + (0, common_1.Post)('tasks/from-template'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, create_task_dto_1.CreateFromTemplateDto]), + __metadata("design:returntype", void 0) +], SchoolTaskController.prototype, "createFromTemplate", null); +exports.SchoolTaskController = SchoolTaskController = __decorate([ + (0, common_1.Controller)('school'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('school'), + __metadata("design:paramtypes", [task_service_1.TaskService, + schedule_notification_service_1.ScheduleNotificationService]) +], SchoolTaskController); +let TeacherTaskController = class TeacherTaskController { + constructor(taskService, scheduleNotificationService) { + this.taskService = taskService; + this.scheduleNotificationService = scheduleNotificationService; + } + findAll(req, query) { + return this.taskService.findAll(req.user.tenantId, query); + } + getUpcoming(req, query) { + return this.taskService.getUpcoming(req.user.tenantId, query); + } + getStats(req) { + return this.taskService.getStats(req.user.tenantId); + } + getStatsByType(req) { + return this.taskService.getStatsByType(req.user.tenantId); + } + getStatsByClass(req) { + return this.taskService.getStatsByClass(req.user.tenantId); + } + getMonthlyStats(req, months) { + return this.taskService.getMonthlyStats(req.user.tenantId, months ? +months : 6); + } + findByClass(req, classId, query) { + return this.taskService.findByClass(req.user.tenantId, +classId, query); + } + findOne(req, id) { + return this.taskService.findOne(req.user.tenantId, +id); + } + getCompletions(req, id, query) { + return this.taskService.getCompletions(req.user.tenantId, +id, query); + } + create(req, dto) { + return this.taskService.create(req.user.tenantId, req.user.userId, dto); + } + update(req, id, dto) { + return this.taskService.update(req.user.tenantId, +id, dto); + } + delete(req, id) { + return this.taskService.delete(req.user.tenantId, +id); + } + sendReminder(req, id) { + return this.scheduleNotificationService.sendManualTaskReminder(req.user.tenantId, +id); + } + updateCompletion(req, taskId, studentId, dto) { + return this.taskService.updateCompletion(req.user.tenantId, +taskId, +studentId, dto); + } + findAllTemplates(req, query) { + return this.taskService.findAllTemplates(req.user.tenantId, query); + } + findOneTemplate(req, id) { + return this.taskService.findOneTemplate(req.user.tenantId, +id); + } + getDefaultTemplate(req, taskType) { + return this.taskService.getDefaultTemplate(req.user.tenantId, taskType); + } + createFromTemplate(req, dto) { + return this.taskService.createFromTemplate(req.user.tenantId, req.user.userId, dto.templateId, { targetIds: dto.targetIds, targetType: dto.targetType, startDate: dto.startDate }); + } +}; +exports.TeacherTaskController = TeacherTaskController; +__decorate([ + (0, common_1.Get)('tasks'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], TeacherTaskController.prototype, "findAll", null); +__decorate([ + (0, common_1.Get)('tasks/upcoming'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], TeacherTaskController.prototype, "getUpcoming", null); +__decorate([ + (0, common_1.Get)('tasks/stats'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], TeacherTaskController.prototype, "getStats", null); +__decorate([ + (0, common_1.Get)('tasks/stats/by-type'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], TeacherTaskController.prototype, "getStatsByType", null); +__decorate([ + (0, common_1.Get)('tasks/stats/by-class'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], TeacherTaskController.prototype, "getStatsByClass", null); +__decorate([ + (0, common_1.Get)('tasks/stats/monthly'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)('months')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], TeacherTaskController.prototype, "getMonthlyStats", null); +__decorate([ + (0, common_1.Get)('classes/:classId/tasks'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('classId')), + __param(2, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", void 0) +], TeacherTaskController.prototype, "findByClass", null); +__decorate([ + (0, common_1.Get)('tasks/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], TeacherTaskController.prototype, "findOne", null); +__decorate([ + (0, common_1.Get)('tasks/:id/completions'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", void 0) +], TeacherTaskController.prototype, "getCompletions", null); +__decorate([ + (0, common_1.Post)('tasks'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, create_task_dto_1.CreateTaskDto]), + __metadata("design:returntype", void 0) +], TeacherTaskController.prototype, "create", null); +__decorate([ + (0, common_1.Put)('tasks/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, create_task_dto_1.UpdateTaskDto]), + __metadata("design:returntype", void 0) +], TeacherTaskController.prototype, "update", null); +__decorate([ + (0, common_1.Delete)('tasks/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], TeacherTaskController.prototype, "delete", null); +__decorate([ + (0, common_1.Post)('tasks/:id/remind'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], TeacherTaskController.prototype, "sendReminder", null); +__decorate([ + (0, common_1.Put)('tasks/:taskId/completions/:studentId'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('taskId')), + __param(2, (0, common_1.Param)('studentId')), + __param(3, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, String, create_task_dto_1.UpdateCompletionDto]), + __metadata("design:returntype", void 0) +], TeacherTaskController.prototype, "updateCompletion", null); +__decorate([ + (0, common_1.Get)('task-templates'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], TeacherTaskController.prototype, "findAllTemplates", null); +__decorate([ + (0, common_1.Get)('task-templates/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], TeacherTaskController.prototype, "findOneTemplate", null); +__decorate([ + (0, common_1.Get)('task-templates/default/:taskType'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('taskType')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], TeacherTaskController.prototype, "getDefaultTemplate", null); +__decorate([ + (0, common_1.Post)('tasks/from-template'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, create_task_dto_1.CreateFromTemplateDto]), + __metadata("design:returntype", void 0) +], TeacherTaskController.prototype, "createFromTemplate", null); +exports.TeacherTaskController = TeacherTaskController = __decorate([ + (0, common_1.Controller)('teacher'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('teacher'), + __metadata("design:paramtypes", [task_service_1.TaskService, + schedule_notification_service_1.ScheduleNotificationService]) +], TeacherTaskController); +//# sourceMappingURL=task.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/task/task.controller.js.map b/reading-platform-backend/dist/src/modules/task/task.controller.js.map new file mode 100644 index 0000000..21b5372 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/task/task.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"task.controller.js","sourceRoot":"","sources":["../../../../src/modules/task/task.controller.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/task/task.module.d.ts b/reading-platform-backend/dist/src/modules/task/task.module.d.ts new file mode 100644 index 0000000..e3cde0b --- /dev/null +++ b/reading-platform-backend/dist/src/modules/task/task.module.d.ts @@ -0,0 +1,2 @@ +export declare class TaskModule { +} diff --git a/reading-platform-backend/dist/src/modules/task/task.module.js b/reading-platform-backend/dist/src/modules/task/task.module.js new file mode 100644 index 0000000..3fa0596 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/task/task.module.js @@ -0,0 +1,25 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TaskModule = void 0; +const common_1 = require("@nestjs/common"); +const task_controller_1 = require("./task.controller"); +const task_service_1 = require("./task.service"); +const notification_module_1 = require("../notification/notification.module"); +let TaskModule = class TaskModule { +}; +exports.TaskModule = TaskModule; +exports.TaskModule = TaskModule = __decorate([ + (0, common_1.Module)({ + imports: [notification_module_1.NotificationModule], + controllers: [task_controller_1.SchoolTaskController, task_controller_1.TeacherTaskController], + providers: [task_service_1.TaskService], + exports: [task_service_1.TaskService], + }) +], TaskModule); +//# sourceMappingURL=task.module.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/task/task.module.js.map b/reading-platform-backend/dist/src/modules/task/task.module.js.map new file mode 100644 index 0000000..448bb72 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/task/task.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"task.module.js","sourceRoot":"","sources":["../../../../src/modules/task/task.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,uDAAgF;AAChF,iDAA6C;AAC7C,6EAAyE;AAQlE,IAAM,UAAU,GAAhB,MAAM,UAAU;CAAG,CAAA;AAAb,gCAAU;qBAAV,UAAU;IANtB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,wCAAkB,CAAC;QAC7B,WAAW,EAAE,CAAC,sCAAoB,EAAE,uCAAqB,CAAC;QAC1D,SAAS,EAAE,CAAC,0BAAW,CAAC;QACxB,OAAO,EAAE,CAAC,0BAAW,CAAC;KACvB,CAAC;GACW,UAAU,CAAG"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/task/task.service.d.ts b/reading-platform-backend/dist/src/modules/task/task.service.d.ts new file mode 100644 index 0000000..08d0607 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/task/task.service.d.ts @@ -0,0 +1,479 @@ +import { PrismaService } from '../../database/prisma.service'; +import { CreateTaskDto, UpdateTaskDto, UpdateCompletionDto, CreateTaskTemplateDto, UpdateTaskTemplateDto } from './dto/create-task.dto'; +export declare class TaskService { + private prisma; + private readonly logger; + constructor(prisma: PrismaService); + findAll(tenantId: number, query: any): Promise<{ + items: { + targetCount: number; + completionCount: number; + _count: any; + course: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findOne(tenantId: number, id: number): Promise<{ + course: { + id: number; + name: string; + }; + targets: ({ + task: { + id: number; + title: string; + }; + } & { + id: number; + classId: number | null; + taskId: number; + studentId: number | null; + })[]; + completions: ({ + student: { + id: number; + name: string; + }; + } & { + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + })[]; + } & { + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }>; + create(tenantId: number, userId: number, dto: CreateTaskDto): Promise<{ + course: { + id: number; + name: string; + }; + targets: ({ + task: { + id: number; + title: string; + }; + } & { + id: number; + classId: number | null; + taskId: number; + studentId: number | null; + })[]; + completions: ({ + student: { + id: number; + name: string; + }; + } & { + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + })[]; + } & { + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }>; + update(tenantId: number, id: number, dto: UpdateTaskDto): Promise<{ + course: { + id: number; + name: string; + }; + targets: ({ + task: { + id: number; + title: string; + }; + } & { + id: number; + classId: number | null; + taskId: number; + studentId: number | null; + })[]; + completions: ({ + student: { + id: number; + name: string; + }; + } & { + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + })[]; + } & { + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }>; + delete(tenantId: number, id: number): Promise<{ + message: string; + }>; + getCompletions(tenantId: number, taskId: number, query: any): Promise<{ + items: ({ + student: { + id: number; + name: string; + gender: string; + class: { + id: number; + name: string; + }; + }; + } & { + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + })[]; + total: number; + page: number; + pageSize: number; + }>; + updateCompletion(tenantId: number, taskId: number, studentId: number, dto: UpdateCompletionDto): Promise<{ + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + }>; + getStudentTasks(tenantId: number, studentId: number, query: any): Promise<{ + items: ({ + task: { + id: number; + title: string; + taskType: string; + startDate: Date; + endDate: Date; + }; + } & { + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + })[]; + total: number; + page: number; + pageSize: number; + }>; + getStats(tenantId: number): Promise<{ + totalTasks: number; + publishedTasks: number; + completedTasks: number; + inProgressTasks: number; + pendingCount: number; + totalCompletions: number; + completionRate: number; + }>; + getStatsByType(tenantId: number): Promise>; + getStatsByClass(tenantId: number): Promise<{ + classId: number; + className: string; + grade: string; + total: number; + completed: number; + rate: number; + }[]>; + getMonthlyStats(tenantId: number, months?: number): Promise<{ + month: string; + tasks: number; + completions: number; + completed: number; + rate: number; + }[]>; + findByClass(tenantId: number, classId: number, query: any): Promise<{ + items: { + completionCount: number; + completedCount: number; + progress: number; + _count: any; + course: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }[]; + total: number; + page: number; + pageSize: number; + }>; + getUpcoming(tenantId: number, query: any): Promise<{ + pendingCount: number; + daysRemaining: number; + _count: any; + course: { + id: number; + name: string; + }; + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }[]>; + sendReminder(tenantId: number, taskId: number): Promise<{ + message: string; + remindedCount: number; + students: { + studentId: number; + studentName: string; + parentPhone: string; + }[]; + }>; + findAllTemplates(tenantId: number, query: any): Promise<{ + items: ({ + course: { + id: number; + name: string; + pictureBookName: string; + }; + } & { + id: number; + tenantId: number; + description: string | null; + taskType: string; + relatedCourseId: number | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + defaultDuration: number; + isDefault: boolean; + })[]; + total: number; + page: number; + pageSize: number; + }>; + findOneTemplate(tenantId: number, id: number): Promise<{ + course: { + id: number; + name: string; + pictureBookName: string; + }; + } & { + id: number; + tenantId: number; + description: string | null; + taskType: string; + relatedCourseId: number | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + defaultDuration: number; + isDefault: boolean; + }>; + createTemplate(tenantId: number, userId: number, dto: CreateTaskTemplateDto): Promise<{ + course: { + id: number; + name: string; + pictureBookName: string; + }; + } & { + id: number; + tenantId: number; + description: string | null; + taskType: string; + relatedCourseId: number | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + defaultDuration: number; + isDefault: boolean; + }>; + updateTemplate(tenantId: number, id: number, dto: UpdateTaskTemplateDto): Promise<{ + course: { + id: number; + name: string; + pictureBookName: string; + }; + } & { + id: number; + tenantId: number; + description: string | null; + taskType: string; + relatedCourseId: number | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + defaultDuration: number; + isDefault: boolean; + }>; + deleteTemplate(tenantId: number, id: number): Promise<{ + message: string; + }>; + getDefaultTemplate(tenantId: number, taskType: string): Promise<{ + course: { + id: number; + name: string; + pictureBookName: string; + }; + } & { + id: number; + tenantId: number; + description: string | null; + taskType: string; + relatedCourseId: number | null; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + defaultDuration: number; + isDefault: boolean; + }>; + createFromTemplate(tenantId: number, userId: number, templateId: number, dto: { + targetIds: number[]; + targetType: string; + startDate?: string; + }): Promise<{ + course: { + id: number; + name: string; + }; + targets: ({ + task: { + id: number; + title: string; + }; + } & { + id: number; + classId: number | null; + taskId: number; + studentId: number | null; + })[]; + completions: ({ + student: { + id: number; + name: string; + }; + } & { + id: number; + status: string; + createdAt: Date; + taskId: number; + studentId: number; + completedAt: Date | null; + feedback: string | null; + parentFeedback: string | null; + })[]; + } & { + id: number; + tenantId: number; + title: string; + description: string | null; + taskType: string; + targetType: string; + relatedCourseId: number | null; + createdBy: number; + startDate: Date; + endDate: Date; + status: string; + createdAt: Date; + updatedAt: Date; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/task/task.service.js b/reading-platform-backend/dist/src/modules/task/task.service.js new file mode 100644 index 0000000..908a938 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/task/task.service.js @@ -0,0 +1,786 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var TaskService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TaskService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../../database/prisma.service"); +const create_task_dto_1 = require("./dto/create-task.dto"); +let TaskService = TaskService_1 = class TaskService { + constructor(prisma) { + this.prisma = prisma; + this.logger = new common_1.Logger(TaskService_1.name); + } + async findAll(tenantId, query) { + const { page = 1, pageSize = 10, status, taskType, keyword } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = { + tenantId: tenantId, + }; + if (status) { + where.status = status; + } + if (taskType) { + where.taskType = taskType; + } + if (keyword) { + where.OR = [ + { title: { contains: keyword } }, + { description: { contains: keyword } }, + ]; + } + const [items, total] = await Promise.all([ + this.prisma.task.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + include: { + course: { + select: { + id: true, + name: true, + }, + }, + _count: { + select: { + targets: true, + completions: true, + }, + }, + }, + }), + this.prisma.task.count({ where }), + ]); + return { + items: items.map((task) => ({ + ...task, + targetCount: task._count.targets, + completionCount: task._count.completions, + _count: undefined, + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + async findOne(tenantId, id) { + const task = await this.prisma.task.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + include: { + course: { + select: { + id: true, + name: true, + }, + }, + targets: { + include: { + task: { + select: { id: true, title: true }, + }, + }, + }, + completions: { + include: { + student: { + select: { + id: true, + name: true, + }, + }, + }, + }, + }, + }); + if (!task) { + throw new common_1.NotFoundException('任务不存在'); + } + return task; + } + async create(tenantId, userId, dto) { + const task = await this.prisma.task.create({ + data: { + tenantId: tenantId, + title: dto.title, + description: dto.description, + taskType: dto.taskType, + targetType: dto.targetType, + relatedCourseId: dto.relatedCourseId, + createdBy: userId, + startDate: new Date(dto.startDate), + endDate: new Date(dto.endDate), + status: create_task_dto_1.TaskStatus.PUBLISHED, + }, + }); + if (dto.targetIds && dto.targetIds.length > 0) { + for (const targetId of dto.targetIds) { + await this.prisma.taskTarget.create({ + data: { + taskId: task.id, + classId: dto.targetType === 'CLASS' ? targetId : null, + studentId: dto.targetType === 'STUDENT' ? targetId : null, + }, + }); + if (dto.targetType === 'CLASS') { + const students = await this.prisma.student.findMany({ + where: { classId: targetId, tenantId }, + select: { id: true }, + }); + for (const student of students) { + await this.prisma.taskCompletion.create({ + data: { + taskId: task.id, + studentId: student.id, + status: create_task_dto_1.CompletionStatus.PENDING, + }, + }); + } + } + else { + await this.prisma.taskCompletion.create({ + data: { + taskId: task.id, + studentId: targetId, + status: create_task_dto_1.CompletionStatus.PENDING, + }, + }); + } + } + } + this.logger.log(`Task created: ${task.id}`); + return this.findOne(tenantId, task.id); + } + async update(tenantId, id, dto) { + const existing = await this.prisma.task.findFirst({ + where: { id, tenantId }, + }); + if (!existing) { + throw new common_1.NotFoundException('任务不存在'); + } + const task = await this.prisma.task.update({ + where: { id }, + data: { + title: dto.title, + description: dto.description, + startDate: dto.startDate ? new Date(dto.startDate) : undefined, + endDate: dto.endDate ? new Date(dto.endDate) : undefined, + status: dto.status, + }, + }); + if (dto.targetIds) { + await this.prisma.taskTarget.deleteMany({ + where: { taskId: id }, + }); + for (const targetId of dto.targetIds) { + await this.prisma.taskTarget.create({ + data: { + taskId: id, + classId: existing.targetType === 'CLASS' ? targetId : null, + studentId: existing.targetType === 'STUDENT' ? targetId : null, + }, + }); + } + } + this.logger.log(`Task updated: ${id}`); + return this.findOne(tenantId, id); + } + async delete(tenantId, id) { + const existing = await this.prisma.task.findFirst({ + where: { id, tenantId }, + }); + if (!existing) { + throw new common_1.NotFoundException('任务不存在'); + } + await this.prisma.task.delete({ + where: { id }, + }); + this.logger.log(`Task deleted: ${id}`); + return { message: '删除成功' }; + } + async getCompletions(tenantId, taskId, query) { + const { page = 1, pageSize = 20, status } = query; + const where = { + taskId: taskId, + task: { tenantId }, + }; + if (status) { + where.status = status; + } + const skip = (page - 1) * pageSize; + const take = +pageSize; + const [items, total] = await Promise.all([ + this.prisma.taskCompletion.findMany({ + where, + skip, + take, + include: { + student: { + select: { + id: true, + name: true, + gender: true, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }, + }, + }), + this.prisma.taskCompletion.count({ where }), + ]); + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + async updateCompletion(tenantId, taskId, studentId, dto) { + const completion = await this.prisma.taskCompletion.findFirst({ + where: { + taskId: taskId, + studentId: studentId, + task: { tenantId }, + }, + }); + if (!completion) { + throw new common_1.NotFoundException('任务完成记录不存在'); + } + const updated = await this.prisma.taskCompletion.update({ + where: { + taskId_studentId: { + taskId: taskId, + studentId: studentId, + }, + }, + data: { + status: dto.status, + completedAt: dto.status === create_task_dto_1.CompletionStatus.COMPLETED ? new Date() : undefined, + feedback: dto.feedback, + parentFeedback: dto.parentFeedback, + }, + }); + this.logger.log(`Task completion updated: task=${taskId}, student=${studentId}`); + return updated; + } + async getStudentTasks(tenantId, studentId, query) { + const { page = 1, pageSize = 10, status } = query; + const where = { + studentId: studentId, + task: { tenantId, status: create_task_dto_1.TaskStatus.PUBLISHED }, + }; + if (status) { + where.status = status; + } + const skip = (page - 1) * pageSize; + const take = +pageSize; + const [items, total] = await Promise.all([ + this.prisma.taskCompletion.findMany({ + where, + skip, + take, + orderBy: { task: { createdAt: 'desc' } }, + include: { + task: { + select: { + id: true, + title: true, + taskType: true, + startDate: true, + endDate: true, + }, + }, + }, + }), + this.prisma.taskCompletion.count({ where }), + ]); + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + async getStats(tenantId) { + const [totalTasks, publishedTasks, completedTasks, inProgressTasks, pendingCount, totalCompletions] = await Promise.all([ + this.prisma.task.count({ where: { tenantId } }), + this.prisma.task.count({ where: { tenantId, status: create_task_dto_1.TaskStatus.PUBLISHED } }), + this.prisma.taskCompletion.count({ + where: { task: { tenantId }, status: create_task_dto_1.CompletionStatus.COMPLETED }, + }), + this.prisma.taskCompletion.count({ + where: { task: { tenantId }, status: create_task_dto_1.CompletionStatus.IN_PROGRESS }, + }), + this.prisma.taskCompletion.count({ + where: { task: { tenantId }, status: create_task_dto_1.CompletionStatus.PENDING }, + }), + this.prisma.taskCompletion.count({ + where: { task: { tenantId } }, + }), + ]); + const completionRate = totalCompletions > 0 + ? Math.round((completedTasks / totalCompletions) * 100) + : 0; + return { + totalTasks, + publishedTasks, + completedTasks, + inProgressTasks, + pendingCount, + totalCompletions, + completionRate, + }; + } + async getStatsByType(tenantId) { + const tasks = await this.prisma.task.findMany({ + where: { tenantId }, + select: { + taskType: true, + completions: { + select: { status: true }, + }, + }, + }); + const typeStats = {}; + for (const task of tasks) { + if (!typeStats[task.taskType]) { + typeStats[task.taskType] = { total: 0, completed: 0, rate: 0 }; + } + typeStats[task.taskType].total += task.completions.length; + typeStats[task.taskType].completed += task.completions.filter(c => c.status === create_task_dto_1.CompletionStatus.COMPLETED).length; + } + for (const type of Object.keys(typeStats)) { + const stat = typeStats[type]; + stat.rate = stat.total > 0 ? Math.round((stat.completed / stat.total) * 100) : 0; + } + return typeStats; + } + async getStatsByClass(tenantId) { + const classes = await this.prisma.class.findMany({ + where: { tenantId }, + select: { + id: true, + name: true, + grade: true, + }, + }); + const classStats = await Promise.all(classes.map(async (cls) => { + const completions = await this.prisma.taskCompletion.findMany({ + where: { + student: { classId: cls.id }, + task: { tenantId }, + }, + select: { status: true }, + }); + const total = completions.length; + const completed = completions.filter(c => c.status === create_task_dto_1.CompletionStatus.COMPLETED).length; + const rate = total > 0 ? Math.round((completed / total) * 100) : 0; + return { + classId: cls.id, + className: cls.name, + grade: cls.grade, + total, + completed, + rate, + }; + })); + return classStats.filter(s => s.total > 0); + } + async getMonthlyStats(tenantId, months = 6) { + const now = new Date(); + const startDate = new Date(now.getFullYear(), now.getMonth() - months + 1, 1); + const tasks = await this.prisma.task.findMany({ + where: { + tenantId, + createdAt: { gte: startDate }, + }, + select: { + createdAt: true, + completions: { + select: { status: true }, + }, + }, + }); + const monthlyData = {}; + for (let i = 0; i < months; i++) { + const date = new Date(now.getFullYear(), now.getMonth() - i, 1); + const key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; + monthlyData[key] = { tasks: 0, completions: 0, completed: 0 }; + } + for (const task of tasks) { + const key = `${task.createdAt.getFullYear()}-${String(task.createdAt.getMonth() + 1).padStart(2, '0')}`; + if (monthlyData[key]) { + monthlyData[key].tasks += 1; + monthlyData[key].completions += task.completions.length; + monthlyData[key].completed += task.completions.filter(c => c.status === create_task_dto_1.CompletionStatus.COMPLETED).length; + } + } + return Object.entries(monthlyData) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([month, data]) => ({ + month, + tasks: data.tasks, + completions: data.completions, + completed: data.completed, + rate: data.completions > 0 + ? Math.round((data.completed / data.completions) * 100) + : 0, + })); + } + async findByClass(tenantId, classId, query) { + const { page = 1, pageSize = 10, status } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = { + tenantId, + targets: { + some: { + classId: classId, + }, + }, + }; + if (status) { + where.status = status; + } + const [items, total] = await Promise.all([ + this.prisma.task.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + include: { + course: { + select: { + id: true, + name: true, + }, + }, + _count: { + select: { + completions: true, + }, + }, + }, + }), + this.prisma.task.count({ where }), + ]); + const itemsWithProgress = await Promise.all(items.map(async (task) => { + const completedCount = await this.prisma.taskCompletion.count({ + where: { + taskId: task.id, + status: create_task_dto_1.CompletionStatus.COMPLETED, + }, + }); + return { + ...task, + completionCount: task._count.completions, + completedCount, + progress: task._count.completions > 0 + ? Math.round((completedCount / task._count.completions) * 100) + : 0, + _count: undefined, + }; + })); + return { + items: itemsWithProgress, + total, + page: +page, + pageSize: +pageSize, + }; + } + async getUpcoming(tenantId, query) { + const { days = 7, limit = 10 } = query; + const now = new Date(); + const endDate = new Date(); + endDate.setDate(now.getDate() + Number(days)); + const tasks = await this.prisma.task.findMany({ + where: { + tenantId, + status: create_task_dto_1.TaskStatus.PUBLISHED, + endDate: { + gte: now, + lte: endDate, + }, + }, + take: Number(limit), + orderBy: { endDate: 'asc' }, + include: { + course: { + select: { + id: true, + name: true, + }, + }, + _count: { + select: { + completions: { + where: { + status: { + not: create_task_dto_1.CompletionStatus.COMPLETED, + }, + }, + }, + }, + }, + }, + }); + return tasks.map((task) => ({ + ...task, + pendingCount: task._count.completions, + daysRemaining: Math.ceil((task.endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)), + _count: undefined, + })); + } + async sendReminder(tenantId, taskId) { + const task = await this.prisma.task.findFirst({ + where: { id: taskId, tenantId }, + include: { + completions: { + where: { + status: { + not: create_task_dto_1.CompletionStatus.COMPLETED, + }, + }, + include: { + student: { + select: { + id: true, + name: true, + parentPhone: true, + }, + }, + }, + }, + }, + }); + if (!task) { + throw new common_1.NotFoundException('任务不存在'); + } + const studentsToRemind = task.completions.map((c) => ({ + studentId: c.student.id, + studentName: c.student.name, + parentPhone: c.student.parentPhone, + })); + this.logger.log(`Reminder sent for task ${taskId} to ${studentsToRemind.length} students`); + return { + message: '提醒已发送', + remindedCount: studentsToRemind.length, + students: studentsToRemind, + }; + } + async findAllTemplates(tenantId, query) { + const { page = 1, pageSize = 10, taskType, keyword } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = { + tenantId: tenantId, + status: 'ACTIVE', + }; + if (taskType) { + where.taskType = taskType; + } + if (keyword) { + where.OR = [ + { name: { contains: keyword } }, + { description: { contains: keyword } }, + ]; + } + const [items, total] = await Promise.all([ + this.prisma.taskTemplate.findMany({ + where, + skip, + take, + orderBy: [ + { isDefault: 'desc' }, + { createdAt: 'desc' }, + ], + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + }, + }, + }, + }), + this.prisma.taskTemplate.count({ where }), + ]); + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + async findOneTemplate(tenantId, id) { + const template = await this.prisma.taskTemplate.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + }, + }, + }, + }); + if (!template) { + throw new common_1.NotFoundException('模板不存在'); + } + return template; + } + async createTemplate(tenantId, userId, dto) { + if (dto.isDefault) { + await this.prisma.taskTemplate.updateMany({ + where: { + tenantId, + taskType: dto.taskType, + isDefault: true, + }, + data: { isDefault: false }, + }); + } + const template = await this.prisma.taskTemplate.create({ + data: { + tenantId: tenantId, + name: dto.name, + description: dto.description, + taskType: dto.taskType, + relatedCourseId: dto.relatedCourseId, + defaultDuration: dto.defaultDuration || 7, + isDefault: dto.isDefault || false, + createdBy: userId, + }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + }, + }, + }, + }); + this.logger.log(`Task template created: ${template.id}`); + return template; + } + async updateTemplate(tenantId, id, dto) { + const existing = await this.prisma.taskTemplate.findFirst({ + where: { id, tenantId }, + }); + if (!existing) { + throw new common_1.NotFoundException('模板不存在'); + } + if (dto.isDefault) { + await this.prisma.taskTemplate.updateMany({ + where: { + tenantId, + taskType: existing.taskType, + isDefault: true, + id: { not: id }, + }, + data: { isDefault: false }, + }); + } + const template = await this.prisma.taskTemplate.update({ + where: { id }, + data: { + name: dto.name, + description: dto.description, + relatedCourseId: dto.relatedCourseId, + defaultDuration: dto.defaultDuration, + isDefault: dto.isDefault, + status: dto.status, + }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + }, + }, + }, + }); + this.logger.log(`Task template updated: ${id}`); + return template; + } + async deleteTemplate(tenantId, id) { + const existing = await this.prisma.taskTemplate.findFirst({ + where: { id, tenantId }, + }); + if (!existing) { + throw new common_1.NotFoundException('模板不存在'); + } + await this.prisma.taskTemplate.delete({ + where: { id }, + }); + this.logger.log(`Task template deleted: ${id}`); + return { message: '删除成功' }; + } + async getDefaultTemplate(tenantId, taskType) { + const template = await this.prisma.taskTemplate.findFirst({ + where: { + tenantId, + taskType, + isDefault: true, + status: 'ACTIVE', + }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + }, + }, + }, + }); + return template; + } + async createFromTemplate(tenantId, userId, templateId, dto) { + const template = await this.findOneTemplate(tenantId, templateId); + const start = dto.startDate ? new Date(dto.startDate) : new Date(); + const end = new Date(start); + end.setDate(end.getDate() + template.defaultDuration); + const task = await this.create(tenantId, userId, { + title: template.name, + description: template.description, + taskType: template.taskType, + targetType: dto.targetType, + targetIds: dto.targetIds, + relatedCourseId: template.relatedCourseId, + startDate: start.toISOString().split('T')[0], + endDate: end.toISOString().split('T')[0], + }); + this.logger.log(`Task created from template: template=${templateId}, task=${task.id}`); + return task; + } +}; +exports.TaskService = TaskService; +exports.TaskService = TaskService = TaskService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], TaskService); +//# sourceMappingURL=task.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/task/task.service.js.map b/reading-platform-backend/dist/src/modules/task/task.service.js.map new file mode 100644 index 0000000..b91d6f5 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/task/task.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"task.service.js","sourceRoot":"","sources":["../../../../src/modules/task/task.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.controller.d.ts b/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.controller.d.ts new file mode 100644 index 0000000..2a005ac --- /dev/null +++ b/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.controller.d.ts @@ -0,0 +1,400 @@ +import { TeacherCourseService } from './teacher-course.service'; +export declare class TeacherCourseController { + private readonly teacherCourseService; + constructor(teacherCourseService: TeacherCourseService); + getDashboard(req: any): Promise<{ + stats: { + classCount: number; + studentCount: number; + lessonCount: number; + courseCount: number; + }; + todayLessons: { + id: number; + courseId: number; + courseName: string; + pictureBookName: string; + classId: number; + className: string; + plannedDatetime: Date; + status: string; + duration: number; + }[]; + recommendedCourses: { + gradeTags: any[]; + id: number; + name: string; + pictureBookName: string; + coverImagePath: string; + duration: number; + usageCount: number; + avgRating: number; + }[]; + weeklyStats: { + lessonCount: number; + studentParticipation: number; + totalDuration: number; + avgRating: number; + }; + recentActivities: { + id: number; + type: string; + description: string; + time: Date; + }[]; + }>; + getTodayLessons(req: any): Promise<{ + id: number; + courseId: number; + courseName: string; + pictureBookName: string; + classId: number; + className: string; + plannedDatetime: Date; + status: string; + duration: number; + }[]>; + getRecommendedCourses(req: any): Promise<{ + gradeTags: any[]; + id: number; + name: string; + pictureBookName: string; + coverImagePath: string; + duration: number; + usageCount: number; + avgRating: number; + }[]>; + getWeeklyStats(req: any): Promise<{ + lessonCount: number; + studentParticipation: number; + totalDuration: number; + avgRating: number; + }>; + getLessonTrend(req: any, months?: string): Promise; + getCourseUsage(req: any): Promise<{ + name: string; + value: number; + }[]>; + findAll(req: any, query: any): Promise<{ + items: { + gradeTags: any; + domainTags: any; + id: number; + name: string; + pictureBookName: string; + coverImagePath: string; + duration: number; + usageCount: number; + avgRating: number; + publishedAt: Date; + }[]; + total: number; + page: number; + pageSize: number; + }>; + getClasses(req: any): Promise<{ + id: number; + name: string; + grade: string; + studentCount: number; + lessonCount: number; + myRole: string; + isPrimary: boolean; + }[]>; + findOne(req: any, id: string): Promise<{ + gradeTags: any; + domainTags: any; + ebookPaths: any; + audioPaths: any; + videoPaths: any; + otherResources: any; + posterPaths: any; + tenantCourses: any; + scripts: { + interactionPoints: any; + resourceIds: any; + pages: { + resourceIds: any; + id: number; + createdAt: Date; + updatedAt: Date; + pageNumber: number; + scriptId: number; + questions: string | null; + interactionComponent: string | null; + teacherNotes: string | null; + }[]; + id: number; + createdAt: Date; + updatedAt: Date; + duration: number; + sortOrder: number; + courseId: number; + stepIndex: number; + stepName: string; + stepType: string; + objective: string | null; + teacherScript: string | null; + }[]; + activities: { + onlineMaterials: any; + objectives: any; + id: number; + createdAt: Date; + name: string; + duration: number | null; + sortOrder: number; + courseId: number; + domain: string | null; + domainTagId: number | null; + activityType: string; + offlineMaterials: string | null; + activityGuide: string | null; + }[]; + resources: { + id: number; + createdAt: Date; + sortOrder: number; + courseId: number; + resourceType: string; + resourceName: string; + fileUrl: string; + fileSize: number | null; + mimeType: string | null; + metadata: string | null; + }[]; + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + pptPath: string | null; + pptName: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + getAllStudents(req: any, query: any): Promise<{ + items: { + id: number; + name: string; + gender: string; + birthDate: Date; + classId: number; + parentName: string; + parentPhone: string; + createdAt: Date; + class: { + id: number; + name: string; + grade: string; + }; + }[]; + total: number; + page: number; + pageSize: number; + }>; + getClassStudents(req: any, id: string, query: any): Promise<{ + items: { + id: number; + name: string; + gender: string; + birthDate: Date; + parentName: string; + parentPhone: string; + lessonCount: number; + readingCount: number; + createdAt: Date; + }[]; + total: number; + page: number; + pageSize: number; + class: { + id: number; + name: string; + studentCount: number; + lessonCount: number; + grade: string; + }; + }>; + getClassTeachers(req: any, id: string): Promise<{ + teacherId: number; + teacherName: string; + teacherPhone: string; + role: string; + isPrimary: boolean; + }[]>; + getTeacherSchedules(req: any, query: any): Promise<{ + items: { + className: string; + courseName: string; + hasLesson: boolean; + lessonId: number; + lessonStatus: string; + course: { + id: number; + name: string; + pictureBookName: string; + duration: number; + }; + lessons: { + id: number; + status: string; + }[]; + class: { + id: number; + name: string; + grade: string; + }; + id: number; + tenantId: number; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number | null; + courseId: number; + scheduledDate: Date | null; + scheduledTime: string | null; + weekDay: number | null; + repeatType: string; + repeatEndDate: Date | null; + source: string; + note: string | null; + reminderSent: boolean; + reminderSentAt: Date | null; + }[]; + total: number; + page: number; + pageSize: number; + }>; + getTeacherTimetable(req: any, startDate: string, endDate: string): Promise<{ + date: string; + weekDay: number; + schedules: any[]; + }[]>; + getTodaySchedules(req: any): Promise<{ + className: string; + courseName: string; + hasLesson: boolean; + lessonId: number; + lessonStatus: string; + course: { + id: number; + name: string; + pictureBookName: string; + duration: number; + }; + lessons: { + id: number; + status: string; + }[]; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number | null; + courseId: number; + scheduledDate: Date | null; + scheduledTime: string | null; + weekDay: number | null; + repeatType: string; + repeatEndDate: Date | null; + source: string; + note: string | null; + reminderSent: boolean; + reminderSentAt: Date | null; + }[]>; + createTeacherSchedule(req: any, dto: any): Promise<{ + className: string; + courseName: string; + course: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number | null; + courseId: number; + scheduledDate: Date | null; + scheduledTime: string | null; + weekDay: number | null; + repeatType: string; + repeatEndDate: Date | null; + source: string; + note: string | null; + reminderSent: boolean; + reminderSentAt: Date | null; + }>; + updateTeacherSchedule(req: any, id: string, dto: any): Promise<{ + className: string; + courseName: string; + course: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number | null; + courseId: number; + scheduledDate: Date | null; + scheduledTime: string | null; + weekDay: number | null; + repeatType: string; + repeatEndDate: Date | null; + source: string; + note: string | null; + reminderSent: boolean; + reminderSentAt: Date | null; + }>; + cancelTeacherSchedule(req: any, id: string): Promise<{ + message: string; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.controller.js b/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.controller.js new file mode 100644 index 0000000..b5ef0f9 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.controller.js @@ -0,0 +1,227 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TeacherCourseController = void 0; +const common_1 = require("@nestjs/common"); +const teacher_course_service_1 = require("./teacher-course.service"); +const jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +const roles_guard_1 = require("../common/guards/roles.guard"); +const roles_decorator_1 = require("../common/decorators/roles.decorator"); +let TeacherCourseController = class TeacherCourseController { + constructor(teacherCourseService) { + this.teacherCourseService = teacherCourseService; + } + getDashboard(req) { + return this.teacherCourseService.getDashboard(req.user.userId, req.user.tenantId); + } + getTodayLessons(req) { + return this.teacherCourseService.getTodayLessons(req.user.userId, req.user.tenantId); + } + getRecommendedCourses(req) { + return this.teacherCourseService.getRecommendedCourses(req.user.tenantId); + } + getWeeklyStats(req) { + return this.teacherCourseService.getWeeklyStats(req.user.userId); + } + getLessonTrend(req, months) { + return this.teacherCourseService.getTeacherLessonTrend(req.user.userId, months ? parseInt(months, 10) : 6); + } + getCourseUsage(req) { + return this.teacherCourseService.getTeacherCourseUsage(req.user.userId); + } + findAll(req, query) { + return this.teacherCourseService.findAll(req.user.userId, req.user.tenantId, query); + } + getClasses(req) { + return this.teacherCourseService.getTeacherClasses(req.user.userId); + } + findOne(req, id) { + return this.teacherCourseService.findOne(+id, req.user.userId, req.user.tenantId); + } + getAllStudents(req, query) { + return this.teacherCourseService.getAllTeacherStudents(req.user.userId, query); + } + getClassStudents(req, id, query) { + return this.teacherCourseService.getClassStudents(req.user.userId, +id, query); + } + getClassTeachers(req, id) { + return this.teacherCourseService.getClassTeachers(req.user.userId, +id); + } + getTeacherSchedules(req, query) { + return this.teacherCourseService.getTeacherSchedules(req.user.userId, query); + } + getTeacherTimetable(req, startDate, endDate) { + return this.teacherCourseService.getTeacherTimetable(req.user.userId, startDate, endDate); + } + getTodaySchedules(req) { + return this.teacherCourseService.getTodaySchedules(req.user.userId); + } + createTeacherSchedule(req, dto) { + return this.teacherCourseService.createTeacherSchedule(req.user.userId, req.user.tenantId, dto); + } + updateTeacherSchedule(req, id, dto) { + return this.teacherCourseService.updateTeacherSchedule(req.user.userId, +id, dto); + } + cancelTeacherSchedule(req, id) { + return this.teacherCourseService.cancelTeacherSchedule(req.user.userId, +id); + } +}; +exports.TeacherCourseController = TeacherCourseController; +__decorate([ + (0, common_1.Get)('dashboard'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], TeacherCourseController.prototype, "getDashboard", null); +__decorate([ + (0, common_1.Get)('dashboard/today'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], TeacherCourseController.prototype, "getTodayLessons", null); +__decorate([ + (0, common_1.Get)('dashboard/recommend'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], TeacherCourseController.prototype, "getRecommendedCourses", null); +__decorate([ + (0, common_1.Get)('dashboard/weekly'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], TeacherCourseController.prototype, "getWeeklyStats", null); +__decorate([ + (0, common_1.Get)('dashboard/lesson-trend'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)('months')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], TeacherCourseController.prototype, "getLessonTrend", null); +__decorate([ + (0, common_1.Get)('dashboard/course-usage'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], TeacherCourseController.prototype, "getCourseUsage", null); +__decorate([ + (0, common_1.Get)('courses'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], TeacherCourseController.prototype, "findAll", null); +__decorate([ + (0, common_1.Get)('courses/classes'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], TeacherCourseController.prototype, "getClasses", null); +__decorate([ + (0, common_1.Get)('courses/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], TeacherCourseController.prototype, "findOne", null); +__decorate([ + (0, common_1.Get)('students'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], TeacherCourseController.prototype, "getAllStudents", null); +__decorate([ + (0, common_1.Get)('classes/:id/students'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", void 0) +], TeacherCourseController.prototype, "getClassStudents", null); +__decorate([ + (0, common_1.Get)('classes/:id/teachers'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], TeacherCourseController.prototype, "getClassTeachers", null); +__decorate([ + (0, common_1.Get)('schedules'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], TeacherCourseController.prototype, "getTeacherSchedules", null); +__decorate([ + (0, common_1.Get)('schedules/timetable'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Query)('startDate')), + __param(2, (0, common_1.Query)('endDate')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, String]), + __metadata("design:returntype", void 0) +], TeacherCourseController.prototype, "getTeacherTimetable", null); +__decorate([ + (0, common_1.Get)('schedules/today'), + __param(0, (0, common_1.Request)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object]), + __metadata("design:returntype", void 0) +], TeacherCourseController.prototype, "getTodaySchedules", null); +__decorate([ + (0, common_1.Post)('schedules'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, Object]), + __metadata("design:returntype", void 0) +], TeacherCourseController.prototype, "createTeacherSchedule", null); +__decorate([ + (0, common_1.Put)('schedules/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __param(2, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String, Object]), + __metadata("design:returntype", void 0) +], TeacherCourseController.prototype, "updateTeacherSchedule", null); +__decorate([ + (0, common_1.Delete)('schedules/:id'), + __param(0, (0, common_1.Request)()), + __param(1, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [Object, String]), + __metadata("design:returntype", void 0) +], TeacherCourseController.prototype, "cancelTeacherSchedule", null); +exports.TeacherCourseController = TeacherCourseController = __decorate([ + (0, common_1.Controller)('teacher'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('teacher'), + __metadata("design:paramtypes", [teacher_course_service_1.TeacherCourseService]) +], TeacherCourseController); +//# sourceMappingURL=teacher-course.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.controller.js.map b/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.controller.js.map new file mode 100644 index 0000000..6ae197a --- /dev/null +++ b/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"teacher-course.controller.js","sourceRoot":"","sources":["../../../../src/modules/teacher-course/teacher-course.controller.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.module.d.ts b/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.module.d.ts new file mode 100644 index 0000000..239c825 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.module.d.ts @@ -0,0 +1,2 @@ +export declare class TeacherCourseModule { +} diff --git a/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.module.js b/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.module.js new file mode 100644 index 0000000..a16bb52 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.module.js @@ -0,0 +1,23 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TeacherCourseModule = void 0; +const common_1 = require("@nestjs/common"); +const teacher_course_controller_1 = require("./teacher-course.controller"); +const teacher_course_service_1 = require("./teacher-course.service"); +let TeacherCourseModule = class TeacherCourseModule { +}; +exports.TeacherCourseModule = TeacherCourseModule; +exports.TeacherCourseModule = TeacherCourseModule = __decorate([ + (0, common_1.Module)({ + controllers: [teacher_course_controller_1.TeacherCourseController], + providers: [teacher_course_service_1.TeacherCourseService], + exports: [teacher_course_service_1.TeacherCourseService], + }) +], TeacherCourseModule); +//# sourceMappingURL=teacher-course.module.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.module.js.map b/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.module.js.map new file mode 100644 index 0000000..a214da0 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"teacher-course.module.js","sourceRoot":"","sources":["../../../../src/modules/teacher-course/teacher-course.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2EAAsE;AACtE,qEAAgE;AAOzD,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;CAAG,CAAA;AAAtB,kDAAmB;8BAAnB,mBAAmB;IAL/B,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,mDAAuB,CAAC;QACtC,SAAS,EAAE,CAAC,6CAAoB,CAAC;QACjC,OAAO,EAAE,CAAC,6CAAoB,CAAC;KAChC,CAAC;GACW,mBAAmB,CAAG"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.service.d.ts b/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.service.d.ts new file mode 100644 index 0000000..c91fb06 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.service.d.ts @@ -0,0 +1,414 @@ +import { PrismaService } from '../../database/prisma.service'; +export interface TeacherLessonTrendItem { + month: string; + lessonCount: number; + avgRating: number; +} +export declare class TeacherCourseService { + private prisma; + private readonly logger; + constructor(prisma: PrismaService); + getDashboard(teacherId: number, tenantId: number): Promise<{ + stats: { + classCount: number; + studentCount: number; + lessonCount: number; + courseCount: number; + }; + todayLessons: { + id: number; + courseId: number; + courseName: string; + pictureBookName: string; + classId: number; + className: string; + plannedDatetime: Date; + status: string; + duration: number; + }[]; + recommendedCourses: { + gradeTags: any[]; + id: number; + name: string; + pictureBookName: string; + coverImagePath: string; + duration: number; + usageCount: number; + avgRating: number; + }[]; + weeklyStats: { + lessonCount: number; + studentParticipation: number; + totalDuration: number; + avgRating: number; + }; + recentActivities: { + id: number; + type: string; + description: string; + time: Date; + }[]; + }>; + private getTeacherStats; + getTodayLessons(teacherId: number, tenantId: number): Promise<{ + id: number; + courseId: number; + courseName: string; + pictureBookName: string; + classId: number; + className: string; + plannedDatetime: Date; + status: string; + duration: number; + }[]>; + getRecommendedCourses(tenantId: number): Promise<{ + gradeTags: any[]; + id: number; + name: string; + pictureBookName: string; + coverImagePath: string; + duration: number; + usageCount: number; + avgRating: number; + }[]>; + getWeeklyStats(teacherId: number): Promise<{ + lessonCount: number; + studentParticipation: number; + totalDuration: number; + avgRating: number; + }>; + private getRecentActivities; + private getActivityDescription; + getTeacherLessonTrend(teacherId: number, months?: number): Promise; + getTeacherCourseUsage(teacherId: number): Promise<{ + name: string; + value: number; + }[]>; + findAll(teacherId: number, tenantId: number, query: any): Promise<{ + items: { + gradeTags: any; + domainTags: any; + id: number; + name: string; + pictureBookName: string; + coverImagePath: string; + duration: number; + usageCount: number; + avgRating: number; + publishedAt: Date; + }[]; + total: number; + page: number; + pageSize: number; + }>; + findOne(courseId: number, teacherId: number, tenantId: number): Promise<{ + gradeTags: any; + domainTags: any; + ebookPaths: any; + audioPaths: any; + videoPaths: any; + otherResources: any; + posterPaths: any; + tenantCourses: any; + scripts: { + interactionPoints: any; + resourceIds: any; + pages: { + resourceIds: any; + id: number; + createdAt: Date; + updatedAt: Date; + pageNumber: number; + scriptId: number; + questions: string | null; + interactionComponent: string | null; + teacherNotes: string | null; + }[]; + id: number; + createdAt: Date; + updatedAt: Date; + duration: number; + sortOrder: number; + courseId: number; + stepIndex: number; + stepName: string; + stepType: string; + objective: string | null; + teacherScript: string | null; + }[]; + activities: { + onlineMaterials: any; + objectives: any; + id: number; + createdAt: Date; + name: string; + duration: number | null; + sortOrder: number; + courseId: number; + domain: string | null; + domainTagId: number | null; + activityType: string; + offlineMaterials: string | null; + activityGuide: string | null; + }[]; + resources: { + id: number; + createdAt: Date; + sortOrder: number; + courseId: number; + resourceType: string; + resourceName: string; + fileUrl: string; + fileSize: number | null; + mimeType: string | null; + metadata: string | null; + }[]; + id: number; + description: string | null; + createdBy: number | null; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + pictureBookId: number | null; + pictureBookName: string | null; + coverImagePath: string | null; + pptPath: string | null; + pptName: string | null; + tools: string | null; + studentMaterials: string | null; + lessonPlanData: string | null; + activitiesData: string | null; + assessmentData: string | null; + duration: number; + version: string; + submittedAt: Date | null; + submittedBy: number | null; + reviewedAt: Date | null; + reviewedBy: number | null; + reviewComment: string | null; + reviewChecklist: string | null; + parentId: number | null; + isLatest: boolean; + usageCount: number; + teacherCount: number; + avgRating: number; + publishedAt: Date | null; + }>; + getTeacherClasses(teacherId: number): Promise<{ + id: number; + name: string; + grade: string; + studentCount: number; + lessonCount: number; + myRole: string; + isPrimary: boolean; + }[]>; + getClassTeachers(teacherId: number, classId: number): Promise<{ + teacherId: number; + teacherName: string; + teacherPhone: string; + role: string; + isPrimary: boolean; + }[]>; + getAllTeacherStudents(teacherId: number, query: any): Promise<{ + items: { + id: number; + name: string; + gender: string; + birthDate: Date; + classId: number; + parentName: string; + parentPhone: string; + createdAt: Date; + class: { + id: number; + name: string; + grade: string; + }; + }[]; + total: number; + page: number; + pageSize: number; + }>; + getTeacherClassIds(teacherId: number): Promise; + getClassStudents(teacherId: number, classId: number, query: any): Promise<{ + items: { + id: number; + name: string; + gender: string; + birthDate: Date; + parentName: string; + parentPhone: string; + lessonCount: number; + readingCount: number; + createdAt: Date; + }[]; + total: number; + page: number; + pageSize: number; + class: { + id: number; + name: string; + studentCount: number; + lessonCount: number; + grade: string; + }; + }>; + private parseJsonArray; + getTeacherSchedules(teacherId: number, query: any): Promise<{ + items: { + className: string; + courseName: string; + hasLesson: boolean; + lessonId: number; + lessonStatus: string; + course: { + id: number; + name: string; + pictureBookName: string; + duration: number; + }; + lessons: { + id: number; + status: string; + }[]; + class: { + id: number; + name: string; + grade: string; + }; + id: number; + tenantId: number; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number | null; + courseId: number; + scheduledDate: Date | null; + scheduledTime: string | null; + weekDay: number | null; + repeatType: string; + repeatEndDate: Date | null; + source: string; + note: string | null; + reminderSent: boolean; + reminderSentAt: Date | null; + }[]; + total: number; + page: number; + pageSize: number; + }>; + getTeacherTimetable(teacherId: number, startDate: string, endDate: string): Promise<{ + date: string; + weekDay: number; + schedules: any[]; + }[]>; + getTodaySchedules(teacherId: number): Promise<{ + className: string; + courseName: string; + hasLesson: boolean; + lessonId: number; + lessonStatus: string; + course: { + id: number; + name: string; + pictureBookName: string; + duration: number; + }; + lessons: { + id: number; + status: string; + }[]; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number | null; + courseId: number; + scheduledDate: Date | null; + scheduledTime: string | null; + weekDay: number | null; + repeatType: string; + repeatEndDate: Date | null; + source: string; + note: string | null; + reminderSent: boolean; + reminderSentAt: Date | null; + }[]>; + private parseTimeToMinutes; + private isTimeOverlapping; + private checkScheduleConflict; + createTeacherSchedule(teacherId: number, tenantId: number, dto: any): Promise<{ + className: string; + courseName: string; + course: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number | null; + courseId: number; + scheduledDate: Date | null; + scheduledTime: string | null; + weekDay: number | null; + repeatType: string; + repeatEndDate: Date | null; + source: string; + note: string | null; + reminderSent: boolean; + reminderSentAt: Date | null; + }>; + updateTeacherSchedule(teacherId: number, id: number, dto: any): Promise<{ + className: string; + courseName: string; + course: { + id: number; + name: string; + }; + class: { + id: number; + name: string; + }; + id: number; + tenantId: number; + createdBy: number; + status: string; + createdAt: Date; + updatedAt: Date; + classId: number; + teacherId: number | null; + courseId: number; + scheduledDate: Date | null; + scheduledTime: string | null; + weekDay: number | null; + repeatType: string; + repeatEndDate: Date | null; + source: string; + note: string | null; + reminderSent: boolean; + reminderSentAt: Date | null; + }>; + cancelTeacherSchedule(teacherId: number, id: number): Promise<{ + message: string; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.service.js b/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.service.js new file mode 100644 index 0000000..dd4e02e --- /dev/null +++ b/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.service.js @@ -0,0 +1,960 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var TeacherCourseService_1; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TeacherCourseService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../../database/prisma.service"); +let TeacherCourseService = TeacherCourseService_1 = class TeacherCourseService { + constructor(prisma) { + this.prisma = prisma; + this.logger = new common_1.Logger(TeacherCourseService_1.name); + } + async getDashboard(teacherId, tenantId) { + const [stats, todayLessons, recommendedCourses, weeklyStats, recentActivities] = await Promise.all([ + this.getTeacherStats(teacherId, tenantId), + this.getTodayLessons(teacherId, tenantId), + this.getRecommendedCourses(tenantId), + this.getWeeklyStats(teacherId), + this.getRecentActivities(teacherId), + ]); + return { + stats, + todayLessons, + recommendedCourses, + weeklyStats, + recentActivities, + }; + } + async getTeacherStats(teacherId, tenantId) { + const classTeachers = await this.prisma.classTeacher.findMany({ + where: { + teacherId: teacherId, + }, + include: { + class: { + select: { + id: true, + studentCount: true, + tenantId: true, + }, + }, + }, + }); + const classes = classTeachers.filter((ct) => ct.class.tenantId === tenantId); + const classCount = classes.length; + const studentCount = classes.reduce((sum, ct) => sum + ct.class.studentCount, 0); + const lessonCount = await this.prisma.lesson.count({ + where: { + teacherId: teacherId, + status: 'COMPLETED', + }, + }); + const courseCount = await this.prisma.tenantCourse.count({ + where: { + tenantId: tenantId, + authorized: true, + course: { + status: 'PUBLISHED', + }, + }, + }); + return { + classCount, + studentCount, + lessonCount, + courseCount, + }; + } + async getTodayLessons(teacherId, tenantId) { + const today = new Date(); + today.setHours(0, 0, 0, 0); + const tomorrow = new Date(today); + tomorrow.setDate(tomorrow.getDate() + 1); + const lessons = await this.prisma.lesson.findMany({ + where: { + teacherId: teacherId, + tenantId: tenantId, + plannedDatetime: { + gte: today, + lt: tomorrow, + }, + }, + orderBy: { + plannedDatetime: 'asc', + }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + duration: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + return lessons.map((lesson) => ({ + id: lesson.id, + courseId: lesson.courseId, + courseName: lesson.course.name, + pictureBookName: lesson.course.pictureBookName, + classId: lesson.classId, + className: lesson.class.name, + plannedDatetime: lesson.plannedDatetime, + status: lesson.status, + duration: lesson.course.duration, + })); + } + async getRecommendedCourses(tenantId) { + const courses = await this.prisma.course.findMany({ + where: { + status: 'PUBLISHED', + tenantCourses: { + some: { + tenantId: tenantId, + authorized: true, + }, + }, + }, + orderBy: [ + { usageCount: 'desc' }, + { avgRating: 'desc' }, + ], + take: 6, + select: { + id: true, + name: true, + pictureBookName: true, + coverImagePath: true, + duration: true, + usageCount: true, + avgRating: true, + gradeTags: true, + }, + }); + return courses.map((course) => ({ + ...course, + gradeTags: this.parseJsonArray(course.gradeTags), + })); + } + async getWeeklyStats(teacherId) { + const now = new Date(); + const dayOfWeek = now.getDay(); + const monday = new Date(now); + monday.setDate(now.getDate() - (dayOfWeek === 0 ? 6 : dayOfWeek - 1)); + monday.setHours(0, 0, 0, 0); + const sunday = new Date(monday); + sunday.setDate(monday.getDate() + 7); + const lessonCount = await this.prisma.lesson.count({ + where: { + teacherId: teacherId, + status: 'COMPLETED', + endDatetime: { + gte: monday, + lt: sunday, + }, + }, + }); + const studentRecords = await this.prisma.studentRecord.findMany({ + where: { + lesson: { + teacherId: teacherId, + status: 'COMPLETED', + endDatetime: { + gte: monday, + lt: sunday, + }, + }, + }, + select: { + studentId: true, + }, + distinct: ['studentId'], + }); + const studentParticipation = studentRecords.length; + const lessons = await this.prisma.lesson.findMany({ + where: { + teacherId: teacherId, + status: 'COMPLETED', + endDatetime: { + gte: monday, + lt: sunday, + }, + }, + select: { + actualDuration: true, + course: { + select: { + duration: true, + }, + }, + }, + }); + const totalDuration = lessons.reduce((sum, lesson) => { + return sum + (lesson.actualDuration || lesson.course.duration || 0); + }, 0); + const feedbacks = await this.prisma.lessonFeedback.findMany({ + where: { + teacherId: teacherId, + lesson: { + endDatetime: { + gte: monday, + lt: sunday, + }, + }, + }, + select: { + designQuality: true, + participation: true, + goalAchievement: true, + }, + }); + let avgRating = 0; + if (feedbacks.length > 0) { + const totalRating = feedbacks.reduce((sum, f) => { + const ratings = [f.designQuality, f.participation, f.goalAchievement].filter(r => r !== null); + const avg = ratings.length > 0 ? ratings.reduce((s, r) => s + r, 0) / ratings.length : 0; + return sum + avg; + }, 0); + avgRating = Math.round((totalRating / feedbacks.length) * 10) / 10; + } + return { + lessonCount, + studentParticipation, + totalDuration, + avgRating, + }; + } + async getRecentActivities(teacherId, limit = 10) { + const lessons = await this.prisma.lesson.findMany({ + where: { + teacherId: teacherId, + }, + orderBy: { + updatedAt: 'desc', + }, + take: limit, + select: { + id: true, + status: true, + updatedAt: true, + course: { + select: { + name: true, + }, + }, + }, + }); + return lessons.map((lesson) => ({ + id: lesson.id, + type: lesson.status, + description: this.getActivityDescription(lesson.status, lesson.course?.name), + time: lesson.updatedAt, + })); + } + getActivityDescription(status, courseName) { + const course = courseName || '课程'; + switch (status) { + case 'COMPLETED': + return `完成《${course}》授课`; + case 'IN_PROGRESS': + return `正在进行《${course}》授课`; + case 'PLANNED': + return `计划《${course}》授课`; + case 'CANCELLED': + return `取消《${course}》授课`; + default: + return `操作《${course}》`; + } + } + async getTeacherLessonTrend(teacherId, months = 6) { + const result = []; + const now = new Date(); + for (let i = months - 1; i >= 0; i--) { + const startDate = new Date(now.getFullYear(), now.getMonth() - i, 1); + const endDate = new Date(now.getFullYear(), now.getMonth() - i + 1, 0, 23, 59, 59); + const lessonCount = await this.prisma.lesson.count({ + where: { + teacherId, + status: 'COMPLETED', + createdAt: { + gte: startDate, + lte: endDate, + }, + }, + }); + const feedbacks = await this.prisma.lessonFeedback.findMany({ + where: { + teacherId, + lesson: { + endDatetime: { + gte: startDate, + lte: endDate, + }, + }, + }, + select: { + designQuality: true, + participation: true, + goalAchievement: true, + }, + }); + let avgRating = 0; + if (feedbacks.length > 0) { + const totalRating = feedbacks.reduce((sum, f) => { + const ratings = [f.designQuality, f.participation, f.goalAchievement].filter((r) => r !== null); + const avg = ratings.length > 0 ? ratings.reduce((s, r) => s + r, 0) / ratings.length : 0; + return sum + avg; + }, 0); + avgRating = Math.round((totalRating / feedbacks.length) * 10) / 10; + } + const monthLabel = `${startDate.getFullYear()}-${String(startDate.getMonth() + 1).padStart(2, '0')}`; + result.push({ + month: monthLabel, + lessonCount, + avgRating, + }); + } + return result; + } + async getTeacherCourseUsage(teacherId) { + const lessons = await this.prisma.lesson.findMany({ + where: { + teacherId, + status: 'COMPLETED', + }, + select: { + courseId: true, + course: { + select: { + name: true, + }, + }, + }, + }); + const courseMap = new Map(); + lessons.forEach((lesson) => { + const courseName = lesson.course?.name || '未知课程'; + courseMap.set(courseName, (courseMap.get(courseName) || 0) + 1); + }); + const result = Array.from(courseMap.entries()) + .map(([name, value]) => ({ name, value })) + .sort((a, b) => b.value - a.value) + .slice(0, 6); + return result; + } + async findAll(teacherId, tenantId, query) { + const { page = 1, pageSize = 12, grade, domain, keyword } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = { + status: 'PUBLISHED', + tenantCourses: { + some: { + tenantId: tenantId, + authorized: true, + }, + }, + }; + if (keyword) { + where.OR = [ + { name: { contains: keyword } }, + { pictureBookName: { contains: keyword } }, + ]; + } + if (grade) { + where.gradeTags = { + contains: grade, + }; + } + if (domain) { + where.domainTags = { + contains: domain, + }; + } + const [items, total] = await Promise.all([ + this.prisma.course.findMany({ + where, + skip, + take, + orderBy: { publishedAt: 'desc' }, + select: { + id: true, + name: true, + pictureBookName: true, + coverImagePath: true, + gradeTags: true, + domainTags: true, + duration: true, + avgRating: true, + usageCount: true, + publishedAt: true, + }, + }), + this.prisma.course.count({ where }), + ]); + const parsedItems = items.map((item) => ({ + ...item, + gradeTags: JSON.parse(item.gradeTags || '[]'), + domainTags: JSON.parse(item.domainTags || '[]'), + })); + return { + items: parsedItems, + total, + page: +page, + pageSize: +pageSize, + }; + } + async findOne(courseId, teacherId, tenantId) { + const course = await this.prisma.course.findUnique({ + where: { id: courseId }, + include: { + resources: { + orderBy: { sortOrder: 'asc' }, + }, + scripts: { + orderBy: { sortOrder: 'asc' }, + include: { + pages: { + orderBy: { pageNumber: 'asc' }, + }, + }, + }, + activities: { + orderBy: { sortOrder: 'asc' }, + }, + tenantCourses: { + where: { tenantId }, + }, + }, + }); + if (!course) { + throw new common_1.NotFoundException(`Course #${courseId} not found`); + } + if (course.status !== 'PUBLISHED') { + throw new common_1.ForbiddenException('该课程未发布'); + } + const tenantCourse = course.tenantCourses.find((tc) => tc.tenantId === tenantId); + if (!tenantCourse || !tenantCourse.authorized) { + throw new common_1.ForbiddenException('您的学校未获得此课程的授权'); + } + return { + ...course, + gradeTags: JSON.parse(course.gradeTags || '[]'), + domainTags: JSON.parse(course.domainTags || '[]'), + ebookPaths: course.ebookPaths ? JSON.parse(course.ebookPaths) : null, + audioPaths: course.audioPaths ? JSON.parse(course.audioPaths) : null, + videoPaths: course.videoPaths ? JSON.parse(course.videoPaths) : null, + otherResources: course.otherResources ? JSON.parse(course.otherResources) : null, + posterPaths: course.posterPaths ? JSON.parse(course.posterPaths) : null, + tenantCourses: undefined, + scripts: course.scripts.map((script) => ({ + ...script, + interactionPoints: script.interactionPoints ? JSON.parse(script.interactionPoints) : null, + resourceIds: script.resourceIds ? JSON.parse(script.resourceIds) : null, + pages: script.pages?.map((page) => ({ + ...page, + resourceIds: page.resourceIds ? JSON.parse(page.resourceIds) : null, + })), + })), + activities: course.activities.map((activity) => ({ + ...activity, + onlineMaterials: activity.onlineMaterials ? JSON.parse(activity.onlineMaterials) : null, + objectives: activity.objectives ? JSON.parse(activity.objectives) : null, + })), + }; + } + async getTeacherClasses(teacherId) { + const classTeachers = await this.prisma.classTeacher.findMany({ + where: { teacherId }, + include: { + class: { + select: { + id: true, + name: true, + grade: true, + studentCount: true, + lessonCount: true, + }, + }, + }, + orderBy: [ + { isPrimary: 'desc' }, + { createdAt: 'asc' }, + ], + }); + return classTeachers.map((ct) => ({ + id: ct.class.id, + name: ct.class.name, + grade: ct.class.grade, + studentCount: ct.class.studentCount, + lessonCount: ct.class.lessonCount, + myRole: ct.role, + isPrimary: ct.isPrimary, + })); + } + async getClassTeachers(teacherId, classId) { + const teacherClass = await this.prisma.classTeacher.findFirst({ + where: { teacherId, classId }, + }); + if (!teacherClass) { + throw new common_1.ForbiddenException('您没有权限查看该班级'); + } + const classTeachers = await this.prisma.classTeacher.findMany({ + where: { classId }, + include: { + teacher: { + select: { + id: true, + name: true, + phone: true, + }, + }, + }, + orderBy: [ + { isPrimary: 'desc' }, + { createdAt: 'asc' }, + ], + }); + return classTeachers.map((ct) => ({ + teacherId: ct.teacher.id, + teacherName: ct.teacher.name, + teacherPhone: ct.teacher.phone, + role: ct.role, + isPrimary: ct.isPrimary, + })); + } + async getAllTeacherStudents(teacherId, query) { + const { page = 1, pageSize = 20, keyword } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const classTeachers = await this.prisma.classTeacher.findMany({ + where: { teacherId }, + select: { classId: true }, + }); + const classIds = classTeachers.map((ct) => ct.classId); + if (classIds.length === 0) { + return { + items: [], + total: 0, + page: +page, + pageSize: +pageSize, + }; + } + const where = { + classId: { in: classIds }, + }; + if (keyword) { + where.name = { contains: keyword }; + } + const [students, total] = await Promise.all([ + this.prisma.student.findMany({ + where, + skip, + take, + select: { + id: true, + name: true, + gender: true, + birthDate: true, + classId: true, + parentName: true, + parentPhone: true, + createdAt: true, + class: { + select: { + id: true, + name: true, + grade: true, + }, + }, + }, + orderBy: { createdAt: 'desc' }, + }), + this.prisma.student.count({ where }), + ]); + return { + items: students.map((student) => ({ + id: student.id, + name: student.name, + gender: student.gender, + birthDate: student.birthDate, + classId: student.classId, + parentName: student.parentName, + parentPhone: student.parentPhone, + createdAt: student.createdAt, + class: student.class, + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + async getTeacherClassIds(teacherId) { + const classTeachers = await this.prisma.classTeacher.findMany({ + where: { teacherId }, + select: { classId: true }, + }); + return classTeachers.map((ct) => ct.classId); + } + async getClassStudents(teacherId, classId, query) { + const { page = 1, pageSize = 20, keyword } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const teacherClass = await this.prisma.classTeacher.findFirst({ + where: { + teacherId, + classId, + }, + include: { + class: { + select: { + id: true, + name: true, + grade: true, + studentCount: true, + lessonCount: true, + }, + }, + }, + }); + if (!teacherClass) { + throw new common_1.ForbiddenException('您没有权限查看该班级或班级不存在'); + } + const classEntity = teacherClass.class; + const where = { + classId: classId, + }; + if (keyword) { + where.name = { contains: keyword }; + } + const [students, total] = await Promise.all([ + this.prisma.student.findMany({ + where, + skip, + take, + select: { + id: true, + name: true, + gender: true, + birthDate: true, + parentName: true, + parentPhone: true, + lessonCount: true, + readingCount: true, + createdAt: true, + }, + orderBy: { createdAt: 'desc' }, + }), + this.prisma.student.count({ where }), + ]); + return { + items: students.map(student => ({ + id: student.id, + name: student.name, + gender: student.gender, + birthDate: student.birthDate, + parentName: student.parentName, + parentPhone: student.parentPhone, + lessonCount: student.lessonCount, + readingCount: student.readingCount, + createdAt: student.createdAt, + })), + total, + page: +page, + pageSize: +pageSize, + class: classEntity, + }; + } + parseJsonArray(value) { + if (!value) + return []; + if (typeof value === 'string') { + try { + return JSON.parse(value); + } + catch { + return []; + } + } + return Array.isArray(value) ? value : []; + } + async getTeacherSchedules(teacherId, query) { + const { page = 1, pageSize = 20, startDate, endDate, status } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + const where = { + teacherId, + status: status || 'ACTIVE', + }; + if (startDate || endDate) { + where.scheduledDate = {}; + if (startDate) + where.scheduledDate.gte = new Date(startDate); + if (endDate) + where.scheduledDate.lte = new Date(endDate); + } + const [items, total] = await Promise.all([ + this.prisma.schedulePlan.findMany({ + where, + skip, + take, + orderBy: { scheduledDate: 'asc' }, + include: { + class: { select: { id: true, name: true, grade: true } }, + course: { select: { id: true, name: true, pictureBookName: true, duration: true } }, + lessons: { + where: { teacherId }, + select: { id: true, status: true }, + take: 1, + }, + }, + }), + this.prisma.schedulePlan.count({ where }), + ]); + return { + items: items.map((item) => ({ + ...item, + className: item.class.name, + courseName: item.course.name, + hasLesson: item.lessons.length > 0, + lessonId: item.lessons[0]?.id, + lessonStatus: item.lessons[0]?.status, + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + async getTeacherTimetable(teacherId, startDate, endDate) { + const schedules = await this.prisma.schedulePlan.findMany({ + where: { + teacherId, + status: 'ACTIVE', + scheduledDate: { + gte: new Date(startDate), + lte: new Date(endDate), + }, + }, + orderBy: [{ scheduledDate: 'asc' }, { scheduledTime: 'asc' }], + include: { + class: { select: { id: true, name: true, grade: true } }, + course: { select: { id: true, name: true, pictureBookName: true, duration: true } }, + lessons: { + where: { teacherId }, + select: { id: true, status: true }, + take: 1, + }, + }, + }); + const timetable = {}; + const start = new Date(startDate); + const end = new Date(endDate); + for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) { + const dateStr = d.toISOString().split('T')[0]; + timetable[dateStr] = []; + } + schedules.forEach((schedule) => { + const dateStr = schedule.scheduledDate.toISOString().split('T')[0]; + if (timetable[dateStr]) { + timetable[dateStr].push({ + ...schedule, + className: schedule.class.name, + courseName: schedule.course.name, + hasLesson: schedule.lessons.length > 0, + lessonId: schedule.lessons[0]?.id, + lessonStatus: schedule.lessons[0]?.status, + }); + } + }); + return Object.entries(timetable).map(([date, items]) => ({ + date, + weekDay: new Date(date).getDay(), + schedules: items, + })); + } + async getTodaySchedules(teacherId) { + const today = new Date(); + today.setHours(0, 0, 0, 0); + const tomorrow = new Date(today); + tomorrow.setDate(tomorrow.getDate() + 1); + const schedules = await this.prisma.schedulePlan.findMany({ + where: { + teacherId, + status: 'ACTIVE', + scheduledDate: { + gte: today, + lt: tomorrow, + }, + }, + orderBy: { scheduledTime: 'asc' }, + include: { + class: { select: { id: true, name: true } }, + course: { select: { id: true, name: true, pictureBookName: true, duration: true } }, + lessons: { + where: { teacherId }, + select: { id: true, status: true }, + take: 1, + }, + }, + }); + return schedules.map((schedule) => ({ + ...schedule, + className: schedule.class.name, + courseName: schedule.course.name, + hasLesson: schedule.lessons.length > 0, + lessonId: schedule.lessons[0]?.id, + lessonStatus: schedule.lessons[0]?.status, + })); + } + parseTimeToMinutes(timeStr) { + const [hours, minutes] = timeStr.split(':').map(Number); + return hours * 60 + minutes; + } + isTimeOverlapping(time1, time2) { + const [start1, end1] = time1.split('-').map(t => this.parseTimeToMinutes(t.trim())); + const [start2, end2] = time2.split('-').map(t => this.parseTimeToMinutes(t.trim())); + return start1 < end2 && start2 < end1; + } + async checkScheduleConflict(teacherId, scheduledDate, scheduledTime, excludeScheduleId) { + const dateStart = new Date(scheduledDate + 'T00:00:00.000Z'); + const dateEnd = new Date(scheduledDate + 'T23:59:59.999Z'); + const existingSchedules = await this.prisma.schedulePlan.findMany({ + where: { + teacherId, + scheduledDate: { + gte: dateStart, + lte: dateEnd, + }, + status: 'ACTIVE', + ...(excludeScheduleId && { id: { not: excludeScheduleId } }), + }, + include: { + class: { select: { name: true } }, + course: { select: { name: true } }, + }, + }); + for (const schedule of existingSchedules) { + if (schedule.scheduledTime && this.isTimeOverlapping(scheduledTime, schedule.scheduledTime)) { + return { + courseName: schedule.course.name, + className: schedule.class.name, + scheduledTime: schedule.scheduledTime, + }; + } + } + return null; + } + async createTeacherSchedule(teacherId, tenantId, dto) { + const classTeacher = await this.prisma.classTeacher.findFirst({ + where: { teacherId, classId: dto.classId }, + }); + if (!classTeacher) { + throw new common_1.ForbiddenException('您没有权限在此班级排课'); + } + const tenantCourse = await this.prisma.tenantCourse.findFirst({ + where: { tenantId, courseId: dto.courseId, authorized: true }, + }); + if (!tenantCourse) { + throw new common_1.ForbiddenException('该课程未授权或不存在'); + } + if (dto.scheduledDate && dto.scheduledTime) { + const conflict = await this.checkScheduleConflict(teacherId, dto.scheduledDate, dto.scheduledTime); + if (conflict) { + throw new common_1.ConflictException(`时间冲突:您在 ${conflict.scheduledTime} 已有排课「${conflict.courseName}」(${conflict.className}),请选择其他时间段`); + } + } + const schedule = await this.prisma.schedulePlan.create({ + data: { + tenantId, + classId: dto.classId, + courseId: dto.courseId, + teacherId, + scheduledDate: dto.scheduledDate ? new Date(dto.scheduledDate) : null, + scheduledTime: dto.scheduledTime, + weekDay: dto.weekDay, + repeatType: dto.repeatType || 'NONE', + repeatEndDate: dto.repeatEndDate ? new Date(dto.repeatEndDate) : null, + source: 'TEACHER', + createdBy: teacherId, + note: dto.note, + status: 'ACTIVE', + }, + include: { + class: { select: { id: true, name: true } }, + course: { select: { id: true, name: true } }, + }, + }); + this.logger.log(`Teacher schedule created: ${schedule.id} by teacher ${teacherId}`); + return { + ...schedule, + className: schedule.class.name, + courseName: schedule.course.name, + }; + } + async updateTeacherSchedule(teacherId, id, dto) { + const schedule = await this.prisma.schedulePlan.findFirst({ + where: { id, teacherId }, + }); + if (!schedule) { + throw new common_1.NotFoundException('排课计划不存在或无权限'); + } + const updated = await this.prisma.schedulePlan.update({ + where: { id }, + data: { + scheduledDate: dto.scheduledDate ? new Date(dto.scheduledDate) : undefined, + scheduledTime: dto.scheduledTime, + weekDay: dto.weekDay, + repeatType: dto.repeatType, + repeatEndDate: dto.repeatEndDate ? new Date(dto.repeatEndDate) : undefined, + note: dto.note, + status: dto.status, + }, + include: { + class: { select: { id: true, name: true } }, + course: { select: { id: true, name: true } }, + }, + }); + this.logger.log(`Teacher schedule updated: ${id}`); + return { + ...updated, + className: updated.class.name, + courseName: updated.course.name, + }; + } + async cancelTeacherSchedule(teacherId, id) { + const schedule = await this.prisma.schedulePlan.findFirst({ + where: { id, teacherId }, + }); + if (!schedule) { + throw new common_1.NotFoundException('排课计划不存在或无权限'); + } + await this.prisma.schedulePlan.update({ + where: { id }, + data: { status: 'CANCELLED' }, + }); + this.logger.log(`Teacher schedule cancelled: ${id}`); + return { message: '取消成功' }; + } +}; +exports.TeacherCourseService = TeacherCourseService; +exports.TeacherCourseService = TeacherCourseService = TeacherCourseService_1 = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], TeacherCourseService); +//# sourceMappingURL=teacher-course.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.service.js.map b/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.service.js.map new file mode 100644 index 0000000..7314dee --- /dev/null +++ b/reading-platform-backend/dist/src/modules/teacher-course/teacher-course.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"teacher-course.service.js","sourceRoot":"","sources":["../../../../src/modules/teacher-course/teacher-course.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAA8G;AAC9G,kEAA8D;AASvD,IAAM,oBAAoB,4BAA1B,MAAM,oBAAoB;IAG/B,YAAoB,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;QAFxB,WAAM,GAAG,IAAI,eAAM,CAAC,sBAAoB,CAAC,IAAI,CAAC,CAAC;IAEpB,CAAC;IAI7C,KAAK,CAAC,YAAY,CAAC,SAAiB,EAAE,QAAgB;QAEpD,MAAM,CAAC,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,WAAW,EAAE,gBAAgB,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACjG,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,QAAQ,CAAC;YACzC,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,QAAQ,CAAC;YACzC,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC;YACpC,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC;YAC9B,IAAI,CAAC,mBAAmB,CAAC,SAAS,CAAC;SACpC,CAAC,CAAC;QAEH,OAAO;YACL,KAAK;YACL,YAAY;YACZ,kBAAkB;YAClB,WAAW;YACX,gBAAgB;SACjB,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,eAAe,CAAC,SAAiB,EAAE,QAAgB;QAE/D,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC;YAC5D,KAAK,EAAE;gBACL,SAAS,EAAE,SAAS;aACrB;YACD,OAAO,EAAE;gBACP,KAAK,EAAE;oBACL,MAAM,EAAE;wBACN,EAAE,EAAE,IAAI;wBACR,YAAY,EAAE,IAAI;wBAClB,QAAQ,EAAE,IAAI;qBACf;iBACF;aACF;SACF,CAAC,CAAC;QAGH,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;QAE7E,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;QAClC,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;QAGjF,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;YACjD,KAAK,EAAE;gBACL,SAAS,EAAE,SAAS;gBACpB,MAAM,EAAE,WAAW;aACpB;SACF,CAAC,CAAC;QAGH,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC;YACvD,KAAK,EAAE;gBACL,QAAQ,EAAE,QAAQ;gBAClB,UAAU,EAAE,IAAI;gBAChB,MAAM,EAAE;oBACN,MAAM,EAAE,WAAW;iBACpB;aACF;SACF,CAAC,CAAC;QAEH,OAAO;YACL,UAAU;YACV,YAAY;YACZ,WAAW;YACX,WAAW;SACZ,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,SAAiB,EAAE,QAAgB;QACvD,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC;QACzB,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3B,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;QAEzC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;YAChD,KAAK,EAAE;gBACL,SAAS,EAAE,SAAS;gBACpB,QAAQ,EAAE,QAAQ;gBAClB,eAAe,EAAE;oBACf,GAAG,EAAE,KAAK;oBACV,EAAE,EAAE,QAAQ;iBACb;aACF;YACD,OAAO,EAAE;gBACP,eAAe,EAAE,KAAK;aACvB;YACD,OAAO,EAAE;gBACP,MAAM,EAAE;oBACN,MAAM,EAAE;wBACN,EAAE,EAAE,IAAI;wBACR,IAAI,EAAE,IAAI;wBACV,eAAe,EAAE,IAAI;wBACrB,QAAQ,EAAE,IAAI;qBACf;iBACF;gBACD,KAAK,EAAE;oBACL,MAAM,EAAE;wBACN,EAAE,EAAE,IAAI;wBACR,IAAI,EAAE,IAAI;qBACX;iBACF;aACF;SACF,CAAC,CAAC;QAEH,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAC9B,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI;YAC9B,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,eAAe;YAC9C,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI;YAC5B,eAAe,EAAE,MAAM,CAAC,eAAe;YACvC,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ;SACjC,CAAC,CAAC,CAAC;IACN,CAAC;IAED,KAAK,CAAC,qBAAqB,CAAC,QAAgB;QAE1C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;YAChD,KAAK,EAAE;gBACL,MAAM,EAAE,WAAW;gBACnB,aAAa,EAAE;oBACb,IAAI,EAAE;wBACJ,QAAQ,EAAE,QAAQ;wBAClB,UAAU,EAAE,IAAI;qBACjB;iBACF;aACF;YACD,OAAO,EAAE;gBACP,EAAE,UAAU,EAAE,MAAM,EAAE;gBACtB,EAAE,SAAS,EAAE,MAAM,EAAE;aACtB;YACD,IAAI,EAAE,CAAC;YACP,MAAM,EAAE;gBACN,EAAE,EAAE,IAAI;gBACR,IAAI,EAAE,IAAI;gBACV,eAAe,EAAE,IAAI;gBACrB,cAAc,EAAE,IAAI;gBACpB,QAAQ,EAAE,IAAI;gBACd,UAAU,EAAE,IAAI;gBAChB,SAAS,EAAE,IAAI;gBACf,SAAS,EAAE,IAAI;aAChB;SACF,CAAC,CAAC;QAEH,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAC9B,GAAG,MAAM;YACT,SAAS,EAAE,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,SAAS,CAAC;SACjD,CAAC,CAAC,CAAC;IACN,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,SAAiB;QAEpC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC;QACtE,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAE5B,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;QAGrC,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;YACjD,KAAK,EAAE;gBACL,SAAS,EAAE,SAAS;gBACpB,MAAM,EAAE,WAAW;gBACnB,WAAW,EAAE;oBACX,GAAG,EAAE,MAAM;oBACX,EAAE,EAAE,MAAM;iBACX;aACF;SACF,CAAC,CAAC;QAGH,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC;YAC9D,KAAK,EAAE;gBACL,MAAM,EAAE;oBACN,SAAS,EAAE,SAAS;oBACpB,MAAM,EAAE,WAAW;oBACnB,WAAW,EAAE;wBACX,GAAG,EAAE,MAAM;wBACX,EAAE,EAAE,MAAM;qBACX;iBACF;aACF;YACD,MAAM,EAAE;gBACN,SAAS,EAAE,IAAI;aAChB;YACD,QAAQ,EAAE,CAAC,WAAW,CAAC;SACxB,CAAC,CAAC;QACH,MAAM,oBAAoB,GAAG,cAAc,CAAC,MAAM,CAAC;QAGnD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;YAChD,KAAK,EAAE;gBACL,SAAS,EAAE,SAAS;gBACpB,MAAM,EAAE,WAAW;gBACnB,WAAW,EAAE;oBACX,GAAG,EAAE,MAAM;oBACX,EAAE,EAAE,MAAM;iBACX;aACF;YACD,MAAM,EAAE;gBACN,cAAc,EAAE,IAAI;gBACpB,MAAM,EAAE;oBACN,MAAM,EAAE;wBACN,QAAQ,EAAE,IAAI;qBACf;iBACF;aACF;SACF,CAAC,CAAC;QAEH,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;YACnD,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,cAAc,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC;QACtE,CAAC,EAAE,CAAC,CAAC,CAAC;QAGN,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC;YAC1D,KAAK,EAAE;gBACL,SAAS,EAAE,SAAS;gBACpB,MAAM,EAAE;oBACN,WAAW,EAAE;wBACX,GAAG,EAAE,MAAM;wBACX,EAAE,EAAE,MAAM;qBACX;iBACF;aACF;YACD,MAAM,EAAE;gBACN,aAAa,EAAE,IAAI;gBACnB,aAAa,EAAE,IAAI;gBACnB,eAAe,EAAE,IAAI;aACtB;SACF,CAAC,CAAC;QAEH,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;gBAC9C,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;gBAC9F,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBACzF,OAAO,GAAG,GAAG,GAAG,CAAC;YACnB,CAAC,EAAE,CAAC,CAAC,CAAC;YACN,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;QACrE,CAAC;QAED,OAAO;YACL,WAAW;YACX,oBAAoB;YACpB,aAAa;YACb,SAAS;SACV,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,mBAAmB,CAAC,SAAiB,EAAE,QAAgB,EAAE;QACrE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;YAChD,KAAK,EAAE;gBACL,SAAS,EAAE,SAAS;aACrB;YACD,OAAO,EAAE;gBACP,SAAS,EAAE,MAAM;aAClB;YACD,IAAI,EAAE,KAAK;YACX,MAAM,EAAE;gBACN,EAAE,EAAE,IAAI;gBACR,MAAM,EAAE,IAAI;gBACZ,SAAS,EAAE,IAAI;gBACf,MAAM,EAAE;oBACN,MAAM,EAAE;wBACN,IAAI,EAAE,IAAI;qBACX;iBACF;aACF;SACF,CAAC,CAAC;QAEH,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAC9B,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,IAAI,EAAE,MAAM,CAAC,MAAM;YACnB,WAAW,EAAE,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC;YAC5E,IAAI,EAAE,MAAM,CAAC,SAAS;SACvB,CAAC,CAAC,CAAC;IACN,CAAC;IAEO,sBAAsB,CAAC,MAAc,EAAE,UAAmB;QAChE,MAAM,MAAM,GAAG,UAAU,IAAI,IAAI,CAAC;QAClC,QAAQ,MAAM,EAAE,CAAC;YACf,KAAK,WAAW;gBACd,OAAO,MAAM,MAAM,KAAK,CAAC;YAC3B,KAAK,aAAa;gBAChB,OAAO,QAAQ,MAAM,KAAK,CAAC;YAC7B,KAAK,SAAS;gBACZ,OAAO,MAAM,MAAM,KAAK,CAAC;YAC3B,KAAK,WAAW;gBACd,OAAO,MAAM,MAAM,KAAK,CAAC;YAC3B;gBACE,OAAO,MAAM,MAAM,GAAG,CAAC;QAC3B,CAAC;IACH,CAAC;IAKD,KAAK,CAAC,qBAAqB,CAAC,SAAiB,EAAE,SAAiB,CAAC;QAC/D,MAAM,MAAM,GAA6B,EAAE,CAAC;QAC5C,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QAEvB,KAAK,IAAI,CAAC,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YACrE,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;YAGnF,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;gBACjD,KAAK,EAAE;oBACL,SAAS;oBACT,MAAM,EAAE,WAAW;oBACnB,SAAS,EAAE;wBACT,GAAG,EAAE,SAAS;wBACd,GAAG,EAAE,OAAO;qBACb;iBACF;aACF,CAAC,CAAC;YAGH,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC;gBAC1D,KAAK,EAAE;oBACL,SAAS;oBACT,MAAM,EAAE;wBACN,WAAW,EAAE;4BACX,GAAG,EAAE,SAAS;4BACd,GAAG,EAAE,OAAO;yBACb;qBACF;iBACF;gBACD,MAAM,EAAE;oBACN,aAAa,EAAE,IAAI;oBACnB,aAAa,EAAE,IAAI;oBACnB,eAAe,EAAE,IAAI;iBACtB;aACF,CAAC,CAAC;YAEH,IAAI,SAAS,GAAG,CAAC,CAAC;YAClB,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACzB,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;oBAC9C,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;oBAChG,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;oBACzF,OAAO,GAAG,GAAG,GAAG,CAAC;gBACnB,CAAC,EAAE,CAAC,CAAC,CAAC;gBACN,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;YACrE,CAAC;YAED,MAAM,UAAU,GAAG,GAAG,SAAS,CAAC,WAAW,EAAE,IAAI,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;YACrG,MAAM,CAAC,IAAI,CAAC;gBACV,KAAK,EAAE,UAAU;gBACjB,WAAW;gBACX,SAAS;aACV,CAAC,CAAC;QACL,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAKD,KAAK,CAAC,qBAAqB,CAAC,SAAiB;QAE3C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;YAChD,KAAK,EAAE;gBACL,SAAS;gBACT,MAAM,EAAE,WAAW;aACpB;YACD,MAAM,EAAE;gBACN,QAAQ,EAAE,IAAI;gBACd,MAAM,EAAE;oBACN,MAAM,EAAE;wBACN,IAAI,EAAE,IAAI;qBACX;iBACF;aACF;SACF,CAAC,CAAC;QAGH,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;QAE5C,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;YACzB,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,IAAI,MAAM,CAAC;YACjD,SAAS,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;QAGH,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;aAC3C,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;aACzC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;aACjC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAEf,OAAO,MAAM,CAAC;IAChB,CAAC;IAID,KAAK,CAAC,OAAO,CAAC,SAAiB,EAAE,QAAgB,EAAE,KAAU;QAC3D,MAAM,EAAE,IAAI,GAAG,CAAC,EAAE,QAAQ,GAAG,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;QAElE,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC;QACnC,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC;QAGvB,MAAM,KAAK,GAAQ;YACjB,MAAM,EAAE,WAAW;YACnB,aAAa,EAAE;gBACb,IAAI,EAAE;oBACJ,QAAQ,EAAE,QAAQ;oBAClB,UAAU,EAAE,IAAI;iBACjB;aACF;SACF,CAAC;QAGF,IAAI,OAAO,EAAE,CAAC;YACZ,KAAK,CAAC,EAAE,GAAG;gBACT,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;gBAC/B,EAAE,eAAe,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;aAC3C,CAAC;QACJ,CAAC;QAGD,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,CAAC,SAAS,GAAG;gBAChB,QAAQ,EAAE,KAAK;aAChB,CAAC;QACJ,CAAC;QAGD,IAAI,MAAM,EAAE,CAAC;YACX,KAAK,CAAC,UAAU,GAAG;gBACjB,QAAQ,EAAE,MAAM;aACjB,CAAC;QACJ,CAAC;QAED,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACvC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;gBAC1B,KAAK;gBACL,IAAI;gBACJ,IAAI;gBACJ,OAAO,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE;gBAChC,MAAM,EAAE;oBACN,EAAE,EAAE,IAAI;oBACR,IAAI,EAAE,IAAI;oBACV,eAAe,EAAE,IAAI;oBACrB,cAAc,EAAE,IAAI;oBACpB,SAAS,EAAE,IAAI;oBACf,UAAU,EAAE,IAAI;oBAChB,QAAQ,EAAE,IAAI;oBACd,SAAS,EAAE,IAAI;oBACf,UAAU,EAAE,IAAI;oBAChB,WAAW,EAAE,IAAI;iBAClB;aACF,CAAC;YACF,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC;SACpC,CAAC,CAAC;QAGH,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACvC,GAAG,IAAI;YACP,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC;YAC7C,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC;SAChD,CAAC,CAAC,CAAC;QAEJ,OAAO;YACL,KAAK,EAAE,WAAW;YAClB,KAAK;YACL,IAAI,EAAE,CAAC,IAAI;YACX,QAAQ,EAAE,CAAC,QAAQ;SACpB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,QAAgB,EAAE,SAAiB,EAAE,QAAgB;QAEjE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC;YACjD,KAAK,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE;YACvB,OAAO,EAAE;gBACP,SAAS,EAAE;oBACT,OAAO,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;iBAC9B;gBACD,OAAO,EAAE;oBACP,OAAO,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;oBAC7B,OAAO,EAAE;wBACP,KAAK,EAAE;4BACL,OAAO,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE;yBAC/B;qBACF;iBACF;gBACD,UAAU,EAAE;oBACV,OAAO,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;iBAC9B;gBACD,aAAa,EAAE;oBACb,KAAK,EAAE,EAAE,QAAQ,EAAE;iBACpB;aACF;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,0BAAiB,CAAC,WAAW,QAAQ,YAAY,CAAC,CAAC;QAC/D,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,MAAM,IAAI,2BAAkB,CAAC,QAAQ,CAAC,CAAC;QACzC,CAAC;QAGD,MAAM,YAAY,GAAG,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;QACjF,IAAI,CAAC,YAAY,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,CAAC;YAC9C,MAAM,IAAI,2BAAkB,CAAC,eAAe,CAAC,CAAC;QAChD,CAAC;QAGD,OAAO;YACL,GAAG,MAAM;YACT,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,IAAI,IAAI,CAAC;YAC/C,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,IAAI,IAAI,CAAC;YACjD,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI;YACpE,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI;YACpE,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI;YACpE,cAAc,EAAE,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI;YAChF,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI;YACvE,aAAa,EAAE,SAAS;YACxB,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;gBACvC,GAAG,MAAM;gBACT,iBAAiB,EAAE,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,IAAI;gBACzF,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI;gBACvE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;oBAClC,GAAG,IAAI;oBACP,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI;iBACpE,CAAC,CAAC;aACJ,CAAC,CAAC;YACH,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;gBAC/C,GAAG,QAAQ;gBACX,eAAe,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,IAAI;gBACvF,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI;aACzE,CAAC,CAAC;SACJ,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAC,SAAiB;QAEvC,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC;YAC5D,KAAK,EAAE,EAAE,SAAS,EAAE;YACpB,OAAO,EAAE;gBACP,KAAK,EAAE;oBACL,MAAM,EAAE;wBACN,EAAE,EAAE,IAAI;wBACR,IAAI,EAAE,IAAI;wBACV,KAAK,EAAE,IAAI;wBACX,YAAY,EAAE,IAAI;wBAClB,WAAW,EAAE,IAAI;qBAClB;iBACF;aACF;YACD,OAAO,EAAE;gBACP,EAAE,SAAS,EAAE,MAAM,EAAE;gBACrB,EAAE,SAAS,EAAE,KAAK,EAAE;aACrB;SACF,CAAC,CAAC;QAEH,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAChC,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE;YACf,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI;YACnB,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK;YACrB,YAAY,EAAE,EAAE,CAAC,KAAK,CAAC,YAAY;YACnC,WAAW,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW;YACjC,MAAM,EAAE,EAAE,CAAC,IAAI;YACf,SAAS,EAAE,EAAE,CAAC,SAAS;SACxB,CAAC,CAAC,CAAC;IACN,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,SAAiB,EAAE,OAAe;QAEvD,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC;YAC5D,KAAK,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE;SAC9B,CAAC,CAAC;QAEH,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,IAAI,2BAAkB,CAAC,YAAY,CAAC,CAAC;QAC7C,CAAC;QAED,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC;YAC5D,KAAK,EAAE,EAAE,OAAO,EAAE;YAClB,OAAO,EAAE;gBACP,OAAO,EAAE;oBACP,MAAM,EAAE;wBACN,EAAE,EAAE,IAAI;wBACR,IAAI,EAAE,IAAI;wBACV,KAAK,EAAE,IAAI;qBACZ;iBACF;aACF;YACD,OAAO,EAAE;gBACP,EAAE,SAAS,EAAE,MAAM,EAAE;gBACrB,EAAE,SAAS,EAAE,KAAK,EAAE;aACrB;SACF,CAAC,CAAC;QAEH,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAChC,SAAS,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE;YACxB,WAAW,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI;YAC5B,YAAY,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK;YAC9B,IAAI,EAAE,EAAE,CAAC,IAAI;YACb,SAAS,EAAE,EAAE,CAAC,SAAS;SACxB,CAAC,CAAC,CAAC;IACN,CAAC;IAKD,KAAK,CAAC,qBAAqB,CAAC,SAAiB,EAAE,KAAU;QACvD,MAAM,EAAE,IAAI,GAAG,CAAC,EAAE,QAAQ,GAAG,EAAE,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;QACnD,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC;QACnC,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC;QAGvB,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC;YAC5D,KAAK,EAAE,EAAE,SAAS,EAAE;YACpB,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;SAC1B,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;QAEvD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO;gBACL,KAAK,EAAE,EAAE;gBACT,KAAK,EAAE,CAAC;gBACR,IAAI,EAAE,CAAC,IAAI;gBACX,QAAQ,EAAE,CAAC,QAAQ;aACpB,CAAC;QACJ,CAAC;QAGD,MAAM,KAAK,GAAQ;YACjB,OAAO,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE;SAC1B,CAAC;QAEF,IAAI,OAAO,EAAE,CAAC;YACZ,KAAK,CAAC,IAAI,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;QACrC,CAAC;QAGD,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAC1C,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;gBAC3B,KAAK;gBACL,IAAI;gBACJ,IAAI;gBACJ,MAAM,EAAE;oBACN,EAAE,EAAE,IAAI;oBACR,IAAI,EAAE,IAAI;oBACV,MAAM,EAAE,IAAI;oBACZ,SAAS,EAAE,IAAI;oBACf,OAAO,EAAE,IAAI;oBACb,UAAU,EAAE,IAAI;oBAChB,WAAW,EAAE,IAAI;oBACjB,SAAS,EAAE,IAAI;oBACf,KAAK,EAAE;wBACL,MAAM,EAAE;4BACN,EAAE,EAAE,IAAI;4BACR,IAAI,EAAE,IAAI;4BACV,KAAK,EAAE,IAAI;yBACZ;qBACF;iBACF;gBACD,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;aAC/B,CAAC;YACF,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC;SACrC,CAAC,CAAC;QAEH,OAAO;YACL,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;gBAChC,EAAE,EAAE,OAAO,CAAC,EAAE;gBACd,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,KAAK,EAAE,OAAO,CAAC,KAAK;aACrB,CAAC,CAAC;YACH,KAAK;YACL,IAAI,EAAE,CAAC,IAAI;YACX,QAAQ,EAAE,CAAC,QAAQ;SACpB,CAAC;IACJ,CAAC;IAKD,KAAK,CAAC,kBAAkB,CAAC,SAAiB;QACxC,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC;YAC5D,KAAK,EAAE,EAAE,SAAS,EAAE;YACpB,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;SAC1B,CAAC,CAAC;QACH,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,SAAiB,EAAE,OAAe,EAAE,KAAU;QACnE,MAAM,EAAE,IAAI,GAAG,CAAC,EAAE,QAAQ,GAAG,EAAE,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;QACnD,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC;QACnC,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC;QAGvB,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC;YAC5D,KAAK,EAAE;gBACL,SAAS;gBACT,OAAO;aACR;YACD,OAAO,EAAE;gBACP,KAAK,EAAE;oBACL,MAAM,EAAE;wBACN,EAAE,EAAE,IAAI;wBACR,IAAI,EAAE,IAAI;wBACV,KAAK,EAAE,IAAI;wBACX,YAAY,EAAE,IAAI;wBAClB,WAAW,EAAE,IAAI;qBAClB;iBACF;aACF;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,IAAI,2BAAkB,CAAC,kBAAkB,CAAC,CAAC;QACnD,CAAC;QAED,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,CAAC;QAGvC,MAAM,KAAK,GAAQ;YACjB,OAAO,EAAE,OAAO;SACjB,CAAC;QAEF,IAAI,OAAO,EAAE,CAAC;YACZ,KAAK,CAAC,IAAI,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;QACrC,CAAC;QAGD,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAC1C,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;gBAC3B,KAAK;gBACL,IAAI;gBACJ,IAAI;gBACJ,MAAM,EAAE;oBACN,EAAE,EAAE,IAAI;oBACR,IAAI,EAAE,IAAI;oBACV,MAAM,EAAE,IAAI;oBACZ,SAAS,EAAE,IAAI;oBACf,UAAU,EAAE,IAAI;oBAChB,WAAW,EAAE,IAAI;oBACjB,WAAW,EAAE,IAAI;oBACjB,YAAY,EAAE,IAAI;oBAClB,SAAS,EAAE,IAAI;iBAChB;gBACD,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;aAC/B,CAAC;YACF,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC;SACrC,CAAC,CAAC;QAEH,OAAO;YACL,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBAC9B,EAAE,EAAE,OAAO,CAAC,EAAE;gBACd,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,YAAY,EAAE,OAAO,CAAC,YAAY;gBAClC,SAAS,EAAE,OAAO,CAAC,SAAS;aAC7B,CAAC,CAAC;YACH,KAAK;YACL,IAAI,EAAE,CAAC,IAAI;YACX,QAAQ,EAAE,CAAC,QAAQ;YACnB,KAAK,EAAE,WAAW;SACnB,CAAC;IACJ,CAAC;IAIO,cAAc,CAAC,KAAU;QAC/B,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,CAAC;QACtB,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,IAAI,CAAC;gBACH,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC3B,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3C,CAAC;IAID,KAAK,CAAC,mBAAmB,CAAC,SAAiB,EAAE,KAAU;QACrD,MAAM,EAAE,IAAI,GAAG,CAAC,EAAE,QAAQ,GAAG,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;QAEtE,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC;QACnC,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC;QAEvB,MAAM,KAAK,GAAQ;YACjB,SAAS;YACT,MAAM,EAAE,MAAM,IAAI,QAAQ;SAC3B,CAAC;QAEF,IAAI,SAAS,IAAI,OAAO,EAAE,CAAC;YACzB,KAAK,CAAC,aAAa,GAAG,EAAE,CAAC;YACzB,IAAI,SAAS;gBAAE,KAAK,CAAC,aAAa,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;YAC7D,IAAI,OAAO;gBAAE,KAAK,CAAC,aAAa,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3D,CAAC;QAED,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACvC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC;gBAChC,KAAK;gBACL,IAAI;gBACJ,IAAI;gBACJ,OAAO,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE;gBACjC,OAAO,EAAE;oBACP,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;oBACxD,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;oBACnF,OAAO,EAAE;wBACP,KAAK,EAAE,EAAE,SAAS,EAAE;wBACpB,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;wBAClC,IAAI,EAAE,CAAC;qBACR;iBACF;aACF,CAAC;YACF,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC;SAC1C,CAAC,CAAC;QAEH,OAAO;YACL,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAC1B,GAAG,IAAI;gBACP,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI;gBAC1B,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI;gBAC5B,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;gBAClC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;gBAC7B,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM;aACtC,CAAC,CAAC;YACH,KAAK;YACL,IAAI,EAAE,CAAC,IAAI;YACX,QAAQ,EAAE,CAAC,QAAQ;SACpB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,SAAiB,EAAE,SAAiB,EAAE,OAAe;QAC7E,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC;YACxD,KAAK,EAAE;gBACL,SAAS;gBACT,MAAM,EAAE,QAAQ;gBAChB,aAAa,EAAE;oBACb,GAAG,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC;oBACxB,GAAG,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC;iBACvB;aACF;YACD,OAAO,EAAE,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;YAC7D,OAAO,EAAE;gBACP,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;gBACxD,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;gBACnF,OAAO,EAAE;oBACP,KAAK,EAAE,EAAE,SAAS,EAAE;oBACpB,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;oBAClC,IAAI,EAAE,CAAC;iBACR;aACF;SACF,CAAC,CAAC;QAGH,MAAM,SAAS,GAA0B,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;QAClC,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;QAG9B,KAAK,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;YACnE,MAAM,OAAO,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9C,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;QAC1B,CAAC;QAGD,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;YAC7B,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAc,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YACpE,IAAI,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;gBACvB,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;oBACtB,GAAG,QAAQ;oBACX,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI;oBAC9B,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI;oBAChC,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;oBACtC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;oBACjC,YAAY,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM;iBAC1C,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,OAAO,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;YACvD,IAAI;YACJ,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE;YAChC,SAAS,EAAE,KAAK;SACjB,CAAC,CAAC,CAAC;IACN,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAC,SAAiB;QACvC,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC;QACzB,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3B,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;QAEzC,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC;YACxD,KAAK,EAAE;gBACL,SAAS;gBACT,MAAM,EAAE,QAAQ;gBAChB,aAAa,EAAE;oBACb,GAAG,EAAE,KAAK;oBACV,EAAE,EAAE,QAAQ;iBACb;aACF;YACD,OAAO,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE;YACjC,OAAO,EAAE;gBACP,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;gBAC3C,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;gBACnF,OAAO,EAAE;oBACP,KAAK,EAAE,EAAE,SAAS,EAAE;oBACpB,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;oBAClC,IAAI,EAAE,CAAC;iBACR;aACF;SACF,CAAC,CAAC;QAEH,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YAClC,GAAG,QAAQ;YACX,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI;YAC9B,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI;YAChC,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;YACtC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YACjC,YAAY,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM;SAC1C,CAAC,CAAC,CAAC;IACN,CAAC;IAOO,kBAAkB,CAAC,OAAe;QACxC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACxD,OAAO,KAAK,GAAG,EAAE,GAAG,OAAO,CAAC;IAC9B,CAAC;IAOO,iBAAiB,CAAC,KAAa,EAAE,KAAa;QACpD,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACpF,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAGpF,OAAO,MAAM,GAAG,IAAI,IAAI,MAAM,GAAG,IAAI,CAAC;IACxC,CAAC;IAKO,KAAK,CAAC,qBAAqB,CACjC,SAAiB,EACjB,aAAqB,EACrB,aAAqB,EACrB,iBAA0B;QAG1B,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,aAAa,GAAG,gBAAgB,CAAC,CAAC;QAC7D,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,aAAa,GAAG,gBAAgB,CAAC,CAAC;QAG3D,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC;YAChE,KAAK,EAAE;gBACL,SAAS;gBACT,aAAa,EAAE;oBACb,GAAG,EAAE,SAAS;oBACd,GAAG,EAAE,OAAO;iBACb;gBACD,MAAM,EAAE,QAAQ;gBAChB,GAAG,CAAC,iBAAiB,IAAI,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,iBAAiB,EAAE,EAAE,CAAC;aAC7D;YACD,OAAO,EAAE;gBACP,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;gBACjC,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;aACnC;SACF,CAAC,CAAC;QAGH,KAAK,MAAM,QAAQ,IAAI,iBAAiB,EAAE,CAAC;YACzC,IAAI,QAAQ,CAAC,aAAa,IAAI,IAAI,CAAC,iBAAiB,CAAC,aAAa,EAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;gBAC5F,OAAO;oBACL,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI;oBAChC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI;oBAC9B,aAAa,EAAE,QAAQ,CAAC,aAAa;iBACtC,CAAC;YACJ,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,qBAAqB,CAAC,SAAiB,EAAE,QAAgB,EAAE,GAAQ;QAEvE,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC;YAC5D,KAAK,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE;SAC3C,CAAC,CAAC;QAEH,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,IAAI,2BAAkB,CAAC,aAAa,CAAC,CAAC;QAC9C,CAAC;QAGD,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC;YAC5D,KAAK,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE;SAC9D,CAAC,CAAC;QAEH,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,IAAI,2BAAkB,CAAC,YAAY,CAAC,CAAC;QAC7C,CAAC;QAGD,IAAI,GAAG,CAAC,aAAa,IAAI,GAAG,CAAC,aAAa,EAAE,CAAC;YAC3C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC,SAAS,EAAE,GAAG,CAAC,aAAa,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;YACnG,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,IAAI,0BAAiB,CACzB,WAAW,QAAQ,CAAC,aAAa,SAAS,QAAQ,CAAC,UAAU,KAAK,QAAQ,CAAC,SAAS,YAAY,CACjG,CAAC;YACJ,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC;YACrD,IAAI,EAAE;gBACJ,QAAQ;gBACR,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,QAAQ,EAAE,GAAG,CAAC,QAAQ;gBACtB,SAAS;gBACT,aAAa,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI;gBACrE,aAAa,EAAE,GAAG,CAAC,aAAa;gBAChC,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,UAAU,EAAE,GAAG,CAAC,UAAU,IAAI,MAAM;gBACpC,aAAa,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI;gBACrE,MAAM,EAAE,SAAS;gBACjB,SAAS,EAAE,SAAS;gBACpB,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,MAAM,EAAE,QAAQ;aACjB;YACD,OAAO,EAAE;gBACP,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;gBAC3C,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;aAC7C;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,6BAA6B,QAAQ,CAAC,EAAE,eAAe,SAAS,EAAE,CAAC,CAAC;QAEpF,OAAO;YACL,GAAG,QAAQ;YACX,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI;YAC9B,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI;SACjC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,qBAAqB,CAAC,SAAiB,EAAE,EAAU,EAAE,GAAQ;QACjE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC;YACxD,KAAK,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE;SACzB,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,0BAAiB,CAAC,aAAa,CAAC,CAAC;QAC7C,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC;YACpD,KAAK,EAAE,EAAE,EAAE,EAAE;YACb,IAAI,EAAE;gBACJ,aAAa,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS;gBAC1E,aAAa,EAAE,GAAG,CAAC,aAAa;gBAChC,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,aAAa,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS;gBAC1E,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,MAAM,EAAE,GAAG,CAAC,MAAM;aACnB;YACD,OAAO,EAAE;gBACP,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;gBAC3C,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;aAC7C;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,6BAA6B,EAAE,EAAE,CAAC,CAAC;QAEnD,OAAO;YACL,GAAG,OAAO;YACV,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI;YAC7B,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI;SAChC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,qBAAqB,CAAC,SAAiB,EAAE,EAAU;QACvD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC;YACxD,KAAK,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE;SACzB,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,0BAAiB,CAAC,aAAa,CAAC,CAAC;QAC7C,CAAC;QAED,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC;YACpC,KAAK,EAAE,EAAE,EAAE,EAAE;YACb,IAAI,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE;SAC9B,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,+BAA+B,EAAE,EAAE,CAAC,CAAC;QAErD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAC7B,CAAC;CACF,CAAA;AAzmCY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,mBAAU,GAAE;qCAIiB,8BAAa;GAH9B,oBAAoB,CAymChC"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/tenant/dto/tenant.dto.d.ts b/reading-platform-backend/dist/src/modules/tenant/dto/tenant.dto.d.ts new file mode 100644 index 0000000..5b9e960 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/tenant/dto/tenant.dto.d.ts @@ -0,0 +1,40 @@ +export declare class TenantQueryDto { + page?: number; + pageSize?: number; + keyword?: string; + status?: string; + packageType?: string; +} +export declare class CreateTenantDto { + name: string; + loginAccount: string; + password?: string; + address?: string; + contactPerson?: string; + contactPhone?: string; + packageType?: string; + teacherQuota?: number; + studentQuota?: number; + startDate?: string; + expireDate?: string; +} +export declare class UpdateTenantDto { + name?: string; + address?: string; + contactPerson?: string; + contactPhone?: string; + packageType?: string; + teacherQuota?: number; + studentQuota?: number; + startDate?: string; + expireDate?: string; + status?: string; +} +export declare class UpdateTenantQuotaDto { + packageType?: string; + teacherQuota?: number; + studentQuota?: number; +} +export declare class UpdateTenantStatusDto { + status: string; +} diff --git a/reading-platform-backend/dist/src/modules/tenant/dto/tenant.dto.js b/reading-platform-backend/dist/src/modules/tenant/dto/tenant.dto.js new file mode 100644 index 0000000..0edc9c6 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/tenant/dto/tenant.dto.js @@ -0,0 +1,206 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.UpdateTenantStatusDto = exports.UpdateTenantQuotaDto = exports.UpdateTenantDto = exports.CreateTenantDto = exports.TenantQueryDto = void 0; +const class_validator_1 = require("class-validator"); +class TenantQueryDto { + constructor() { + this.page = 1; + this.pageSize = 10; + } +} +exports.TenantQueryDto = TenantQueryDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + (0, class_validator_1.Min)(1), + __metadata("design:type", Number) +], TenantQueryDto.prototype, "page", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + (0, class_validator_1.Min)(1), + (0, class_validator_1.Max)(100), + __metadata("design:type", Number) +], TenantQueryDto.prototype, "pageSize", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], TenantQueryDto.prototype, "keyword", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], TenantQueryDto.prototype, "status", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], TenantQueryDto.prototype, "packageType", void 0); +class CreateTenantDto { + constructor() { + this.packageType = 'STANDARD'; + this.teacherQuota = 20; + this.studentQuota = 200; + } +} +exports.CreateTenantDto = CreateTenantDto; +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '学校名称不能为空' }), + __metadata("design:type", String) +], CreateTenantDto.prototype, "name", void 0); +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '登录账号不能为空' }), + (0, class_validator_1.Matches)(/^[a-zA-Z][a-zA-Z0-9_]{3,19}$/, { + message: '登录账号必须以字母开头,4-20位字母、数字或下划线', + }), + __metadata("design:type", String) +], CreateTenantDto.prototype, "loginAccount", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.Matches)(/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{6,20}$/, { + message: '密码至少6位,需包含字母和数字', + }), + __metadata("design:type", String) +], CreateTenantDto.prototype, "password", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateTenantDto.prototype, "address", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateTenantDto.prototype, "contactPerson", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.Matches)(/^1[3-9]\d{9}$/, { message: '请输入正确的手机号' }), + __metadata("design:type", String) +], CreateTenantDto.prototype, "contactPhone", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], CreateTenantDto.prototype, "packageType", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + (0, class_validator_1.Min)(1), + __metadata("design:type", Number) +], CreateTenantDto.prototype, "teacherQuota", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + (0, class_validator_1.Min)(1), + __metadata("design:type", Number) +], CreateTenantDto.prototype, "studentQuota", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], CreateTenantDto.prototype, "startDate", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], CreateTenantDto.prototype, "expireDate", void 0); +class UpdateTenantDto { +} +exports.UpdateTenantDto = UpdateTenantDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '学校名称不能为空' }), + __metadata("design:type", String) +], UpdateTenantDto.prototype, "name", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateTenantDto.prototype, "address", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateTenantDto.prototype, "contactPerson", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + (0, class_validator_1.Matches)(/^1[3-9]\d{9}$/, { message: '请输入正确的手机号' }), + __metadata("design:type", String) +], UpdateTenantDto.prototype, "contactPhone", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateTenantDto.prototype, "packageType", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + (0, class_validator_1.Min)(1), + __metadata("design:type", Number) +], UpdateTenantDto.prototype, "teacherQuota", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + (0, class_validator_1.Min)(1), + __metadata("design:type", Number) +], UpdateTenantDto.prototype, "studentQuota", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], UpdateTenantDto.prototype, "startDate", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsDateString)(), + __metadata("design:type", String) +], UpdateTenantDto.prototype, "expireDate", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateTenantDto.prototype, "status", void 0); +class UpdateTenantQuotaDto { +} +exports.UpdateTenantQuotaDto = UpdateTenantQuotaDto; +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsString)(), + __metadata("design:type", String) +], UpdateTenantQuotaDto.prototype, "packageType", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + (0, class_validator_1.Min)(1), + __metadata("design:type", Number) +], UpdateTenantQuotaDto.prototype, "teacherQuota", void 0); +__decorate([ + (0, class_validator_1.IsOptional)(), + (0, class_validator_1.IsInt)(), + (0, class_validator_1.Min)(1), + __metadata("design:type", Number) +], UpdateTenantQuotaDto.prototype, "studentQuota", void 0); +class UpdateTenantStatusDto { +} +exports.UpdateTenantStatusDto = UpdateTenantStatusDto; +__decorate([ + (0, class_validator_1.IsString)(), + (0, class_validator_1.IsNotEmpty)({ message: '状态不能为空' }), + __metadata("design:type", String) +], UpdateTenantStatusDto.prototype, "status", void 0); +//# sourceMappingURL=tenant.dto.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/tenant/dto/tenant.dto.js.map b/reading-platform-backend/dist/src/modules/tenant/dto/tenant.dto.js.map new file mode 100644 index 0000000..2bdf8fe --- /dev/null +++ b/reading-platform-backend/dist/src/modules/tenant/dto/tenant.dto.js.map @@ -0,0 +1 @@ +{"version":3,"file":"tenant.dto.js","sourceRoot":"","sources":["../../../../../src/modules/tenant/dto/tenant.dto.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/tenant/tenant.controller.d.ts b/reading-platform-backend/dist/src/modules/tenant/tenant.controller.d.ts new file mode 100644 index 0000000..5a7c43d --- /dev/null +++ b/reading-platform-backend/dist/src/modules/tenant/tenant.controller.d.ts @@ -0,0 +1,138 @@ +import { TenantService } from './tenant.service'; +import { TenantQueryDto, CreateTenantDto, UpdateTenantDto, UpdateTenantQuotaDto, UpdateTenantStatusDto } from './dto/tenant.dto'; +export declare class TenantController { + private readonly tenantService; + constructor(tenantService: TenantService); + findAll(query: TenantQueryDto): Promise<{ + items: { + id: number; + startDate: string; + status: string; + createdAt: Date; + name: string; + teacherCount: number; + loginAccount: string; + address: string; + contactPerson: string; + contactPhone: string; + packageType: string; + teacherQuota: number; + studentQuota: number; + expireDate: string; + studentCount: number; + }[]; + total: number; + page: number; + pageSize: number; + totalPages: number; + }>; + getStats(): Promise<{ + totalCount: number; + activeCount: number; + expiredCount: number; + packageDistribution: { + packageType: string; + count: number; + }[]; + }>; + findOne(id: string): Promise<{ + storageQuota: string; + storageUsed: string; + id: number; + startDate: string; + status: string; + createdAt: Date; + updatedAt: Date; + _count: { + lessons: number; + teachers: number; + students: number; + classes: number; + }; + name: string; + teacherCount: number; + loginAccount: string; + address: string; + contactPerson: string; + contactPhone: string; + logoUrl: string; + packageType: string; + teacherQuota: number; + studentQuota: number; + expireDate: string; + studentCount: number; + teachers: { + id: number; + status: string; + name: string; + lessonCount: number; + phone: string; + email: string; + }[]; + students: { + id: number; + name: string; + classId: number; + gender: string; + readingCount: number; + }[]; + classes: { + id: number; + name: string; + studentCount: number; + grade: string; + }[]; + }>; + create(createTenantDto: CreateTenantDto): Promise<{ + tempPassword: string; + id: number; + startDate: string; + status: string; + createdAt: Date; + name: string; + loginAccount: string; + address: string; + contactPerson: string; + contactPhone: string; + packageType: string; + teacherQuota: number; + studentQuota: number; + expireDate: string; + }>; + update(id: string, updateTenantDto: UpdateTenantDto): Promise<{ + id: number; + startDate: string; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + loginAccount: string; + address: string; + contactPerson: string; + contactPhone: string; + packageType: string; + teacherQuota: number; + studentQuota: number; + expireDate: string; + }>; + updateQuota(id: string, dto: UpdateTenantQuotaDto): Promise<{ + id: number; + name: string; + teacherCount: number; + packageType: string; + teacherQuota: number; + studentQuota: number; + studentCount: number; + }>; + updateStatus(id: string, dto: UpdateTenantStatusDto): Promise<{ + id: number; + status: string; + name: string; + }>; + resetPassword(id: string): Promise<{ + tempPassword: string; + }>; + remove(id: string): Promise<{ + success: boolean; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/tenant/tenant.controller.js b/reading-platform-backend/dist/src/modules/tenant/tenant.controller.js new file mode 100644 index 0000000..b578707 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/tenant/tenant.controller.js @@ -0,0 +1,126 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TenantController = void 0; +const common_1 = require("@nestjs/common"); +const tenant_service_1 = require("./tenant.service"); +const jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +const roles_guard_1 = require("../common/guards/roles.guard"); +const roles_decorator_1 = require("../common/decorators/roles.decorator"); +const tenant_dto_1 = require("./dto/tenant.dto"); +let TenantController = class TenantController { + constructor(tenantService) { + this.tenantService = tenantService; + } + findAll(query) { + return this.tenantService.findAllPaginated(query); + } + getStats() { + return this.tenantService.getStats(); + } + findOne(id) { + return this.tenantService.findOne(+id); + } + create(createTenantDto) { + return this.tenantService.create(createTenantDto); + } + update(id, updateTenantDto) { + return this.tenantService.update(+id, updateTenantDto); + } + updateQuota(id, dto) { + return this.tenantService.updateQuota(+id, dto); + } + updateStatus(id, dto) { + return this.tenantService.updateStatus(+id, dto); + } + resetPassword(id) { + return this.tenantService.resetPassword(+id); + } + remove(id) { + return this.tenantService.remove(+id); + } +}; +exports.TenantController = TenantController; +__decorate([ + (0, common_1.Get)(), + __param(0, (0, common_1.Query)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [tenant_dto_1.TenantQueryDto]), + __metadata("design:returntype", void 0) +], TenantController.prototype, "findAll", null); +__decorate([ + (0, common_1.Get)('stats'), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", void 0) +], TenantController.prototype, "getStats", null); +__decorate([ + (0, common_1.Get)(':id'), + __param(0, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", void 0) +], TenantController.prototype, "findOne", null); +__decorate([ + (0, common_1.Post)(), + __param(0, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [tenant_dto_1.CreateTenantDto]), + __metadata("design:returntype", void 0) +], TenantController.prototype, "create", null); +__decorate([ + (0, common_1.Put)(':id'), + __param(0, (0, common_1.Param)('id')), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, tenant_dto_1.UpdateTenantDto]), + __metadata("design:returntype", void 0) +], TenantController.prototype, "update", null); +__decorate([ + (0, common_1.Put)(':id/quota'), + __param(0, (0, common_1.Param)('id')), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, tenant_dto_1.UpdateTenantQuotaDto]), + __metadata("design:returntype", void 0) +], TenantController.prototype, "updateQuota", null); +__decorate([ + (0, common_1.Put)(':id/status'), + __param(0, (0, common_1.Param)('id')), + __param(1, (0, common_1.Body)()), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String, tenant_dto_1.UpdateTenantStatusDto]), + __metadata("design:returntype", void 0) +], TenantController.prototype, "updateStatus", null); +__decorate([ + (0, common_1.Post)(':id/reset-password'), + __param(0, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", void 0) +], TenantController.prototype, "resetPassword", null); +__decorate([ + (0, common_1.Delete)(':id'), + __param(0, (0, common_1.Param)('id')), + __metadata("design:type", Function), + __metadata("design:paramtypes", [String]), + __metadata("design:returntype", void 0) +], TenantController.prototype, "remove", null); +exports.TenantController = TenantController = __decorate([ + (0, common_1.Controller)('admin/tenants'), + (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, roles_guard_1.RolesGuard), + (0, roles_decorator_1.Roles)('admin'), + __metadata("design:paramtypes", [tenant_service_1.TenantService]) +], TenantController); +//# sourceMappingURL=tenant.controller.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/tenant/tenant.controller.js.map b/reading-platform-backend/dist/src/modules/tenant/tenant.controller.js.map new file mode 100644 index 0000000..dee78df --- /dev/null +++ b/reading-platform-backend/dist/src/modules/tenant/tenant.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"tenant.controller.js","sourceRoot":"","sources":["../../../../src/modules/tenant/tenant.controller.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/tenant/tenant.module.d.ts b/reading-platform-backend/dist/src/modules/tenant/tenant.module.d.ts new file mode 100644 index 0000000..4e6342f --- /dev/null +++ b/reading-platform-backend/dist/src/modules/tenant/tenant.module.d.ts @@ -0,0 +1,2 @@ +export declare class TenantModule { +} diff --git a/reading-platform-backend/dist/src/modules/tenant/tenant.module.js b/reading-platform-backend/dist/src/modules/tenant/tenant.module.js new file mode 100644 index 0000000..b2c6181 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/tenant/tenant.module.js @@ -0,0 +1,25 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TenantModule = void 0; +const common_1 = require("@nestjs/common"); +const tenant_service_1 = require("./tenant.service"); +const tenant_controller_1 = require("./tenant.controller"); +const prisma_module_1 = require("../../database/prisma.module"); +let TenantModule = class TenantModule { +}; +exports.TenantModule = TenantModule; +exports.TenantModule = TenantModule = __decorate([ + (0, common_1.Module)({ + imports: [prisma_module_1.PrismaModule], + controllers: [tenant_controller_1.TenantController], + providers: [tenant_service_1.TenantService], + exports: [tenant_service_1.TenantService], + }) +], TenantModule); +//# sourceMappingURL=tenant.module.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/tenant/tenant.module.js.map b/reading-platform-backend/dist/src/modules/tenant/tenant.module.js.map new file mode 100644 index 0000000..260b2f0 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/tenant/tenant.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"tenant.module.js","sourceRoot":"","sources":["../../../../src/modules/tenant/tenant.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,qDAAiD;AACjD,2DAAuD;AACvD,gEAA4D;AAQrD,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,oCAAY;uBAAZ,YAAY;IANxB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,4BAAY,CAAC;QACvB,WAAW,EAAE,CAAC,oCAAgB,CAAC;QAC/B,SAAS,EAAE,CAAC,8BAAa,CAAC;QAC1B,OAAO,EAAE,CAAC,8BAAa,CAAC;KACzB,CAAC;GACW,YAAY,CAAG"} \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/tenant/tenant.service.d.ts b/reading-platform-backend/dist/src/modules/tenant/tenant.service.d.ts new file mode 100644 index 0000000..89ff311 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/tenant/tenant.service.d.ts @@ -0,0 +1,154 @@ +import { PrismaService } from '../../database/prisma.service'; +import { TenantQueryDto, CreateTenantDto, UpdateTenantDto, UpdateTenantQuotaDto, UpdateTenantStatusDto } from './dto/tenant.dto'; +export declare class TenantService { + private prisma; + constructor(prisma: PrismaService); + findAllPaginated(query: TenantQueryDto): Promise<{ + items: { + id: number; + startDate: string; + status: string; + createdAt: Date; + name: string; + teacherCount: number; + loginAccount: string; + address: string; + contactPerson: string; + contactPhone: string; + packageType: string; + teacherQuota: number; + studentQuota: number; + expireDate: string; + studentCount: number; + }[]; + total: number; + page: number; + pageSize: number; + totalPages: number; + }>; + findAll(): Promise<{ + id: number; + startDate: string; + status: string; + createdAt: Date; + name: string; + teacherCount: number; + address: string; + contactPerson: string; + contactPhone: string; + packageType: string; + teacherQuota: number; + studentQuota: number; + expireDate: string; + studentCount: number; + }[]>; + findOne(id: number): Promise<{ + storageQuota: string; + storageUsed: string; + id: number; + startDate: string; + status: string; + createdAt: Date; + updatedAt: Date; + _count: { + lessons: number; + teachers: number; + students: number; + classes: number; + }; + name: string; + teacherCount: number; + loginAccount: string; + address: string; + contactPerson: string; + contactPhone: string; + logoUrl: string; + packageType: string; + teacherQuota: number; + studentQuota: number; + expireDate: string; + studentCount: number; + teachers: { + id: number; + status: string; + name: string; + lessonCount: number; + phone: string; + email: string; + }[]; + students: { + id: number; + name: string; + classId: number; + gender: string; + readingCount: number; + }[]; + classes: { + id: number; + name: string; + studentCount: number; + grade: string; + }[]; + }>; + create(dto: CreateTenantDto): Promise<{ + tempPassword: string; + id: number; + startDate: string; + status: string; + createdAt: Date; + name: string; + loginAccount: string; + address: string; + contactPerson: string; + contactPhone: string; + packageType: string; + teacherQuota: number; + studentQuota: number; + expireDate: string; + }>; + update(id: number, dto: UpdateTenantDto): Promise<{ + id: number; + startDate: string; + status: string; + createdAt: Date; + updatedAt: Date; + name: string; + loginAccount: string; + address: string; + contactPerson: string; + contactPhone: string; + packageType: string; + teacherQuota: number; + studentQuota: number; + expireDate: string; + }>; + updateQuota(id: number, dto: UpdateTenantQuotaDto): Promise<{ + id: number; + name: string; + teacherCount: number; + packageType: string; + teacherQuota: number; + studentQuota: number; + studentCount: number; + }>; + updateStatus(id: number, dto: UpdateTenantStatusDto): Promise<{ + id: number; + status: string; + name: string; + }>; + resetPassword(id: number): Promise<{ + tempPassword: string; + }>; + remove(id: number): Promise<{ + success: boolean; + }>; + getStats(): Promise<{ + totalCount: number; + activeCount: number; + expiredCount: number; + packageDistribution: { + packageType: string; + count: number; + }[]; + }>; +} diff --git a/reading-platform-backend/dist/src/modules/tenant/tenant.service.js b/reading-platform-backend/dist/src/modules/tenant/tenant.service.js new file mode 100644 index 0000000..630a0bb --- /dev/null +++ b/reading-platform-backend/dist/src/modules/tenant/tenant.service.js @@ -0,0 +1,392 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TenantService = void 0; +const common_1 = require("@nestjs/common"); +const prisma_service_1 = require("../../database/prisma.service"); +const bcrypt = __importStar(require("bcrypt")); +let TenantService = class TenantService { + constructor(prisma) { + this.prisma = prisma; + } + async findAllPaginated(query) { + const { page = 1, pageSize = 10, keyword, status, packageType } = query; + const skip = (page - 1) * pageSize; + const where = {}; + if (keyword) { + where.OR = [ + { name: { contains: keyword } }, + { loginAccount: { contains: keyword } }, + { contactPerson: { contains: keyword } }, + { contactPhone: { contains: keyword } }, + ]; + } + if (status) { + where.status = status; + } + if (packageType) { + where.packageType = packageType; + } + const [items, total] = await Promise.all([ + this.prisma.tenant.findMany({ + where, + select: { + id: true, + name: true, + loginAccount: true, + address: true, + contactPerson: true, + contactPhone: true, + packageType: true, + teacherQuota: true, + studentQuota: true, + teacherCount: true, + studentCount: true, + startDate: true, + expireDate: true, + status: true, + createdAt: true, + }, + orderBy: { createdAt: 'desc' }, + skip, + take: pageSize, + }), + this.prisma.tenant.count({ where }), + ]); + return { + items, + total, + page, + pageSize, + totalPages: Math.ceil(total / pageSize), + }; + } + async findAll() { + return this.prisma.tenant.findMany({ + select: { + id: true, + name: true, + address: true, + contactPerson: true, + contactPhone: true, + packageType: true, + teacherQuota: true, + studentQuota: true, + teacherCount: true, + studentCount: true, + startDate: true, + expireDate: true, + status: true, + createdAt: true, + }, + orderBy: { createdAt: 'desc' }, + }); + } + async findOne(id) { + const tenant = await this.prisma.tenant.findUnique({ + where: { id }, + select: { + id: true, + name: true, + loginAccount: true, + address: true, + contactPerson: true, + contactPhone: true, + logoUrl: true, + packageType: true, + teacherQuota: true, + studentQuota: true, + storageQuota: true, + teacherCount: true, + studentCount: true, + storageUsed: true, + startDate: true, + expireDate: true, + status: true, + createdAt: true, + updatedAt: true, + teachers: { + select: { + id: true, + name: true, + phone: true, + email: true, + status: true, + lessonCount: true, + }, + take: 10, + orderBy: { createdAt: 'desc' }, + }, + students: { + select: { + id: true, + name: true, + classId: true, + gender: true, + readingCount: true, + }, + take: 10, + orderBy: { createdAt: 'desc' }, + }, + classes: { + select: { + id: true, + name: true, + grade: true, + studentCount: true, + }, + take: 10, + }, + _count: { + select: { + teachers: true, + students: true, + classes: true, + lessons: true, + }, + }, + }, + }); + if (!tenant) { + throw new common_1.NotFoundException(`租户 #${id} 不存在`); + } + return { + ...tenant, + storageQuota: tenant.storageQuota?.toString() || '0', + storageUsed: tenant.storageUsed?.toString() || '0', + }; + } + async create(dto) { + const existing = await this.prisma.tenant.findUnique({ + where: { loginAccount: dto.loginAccount }, + }); + if (existing) { + throw new common_1.ConflictException('登录账号已存在'); + } + const existingTeacher = await this.prisma.teacher.findUnique({ + where: { loginAccount: dto.loginAccount }, + }); + if (existingTeacher) { + throw new common_1.ConflictException('该账号已被教师使用'); + } + const defaultPassword = dto.password || '123456'; + const passwordHash = await bcrypt.hash(defaultPassword, 10); + const startDate = dto.startDate || new Date().toISOString().split('T')[0]; + const expireDate = dto.expireDate || + new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; + const tenant = await this.prisma.tenant.create({ + data: { + name: dto.name, + loginAccount: dto.loginAccount, + passwordHash, + address: dto.address, + contactPerson: dto.contactPerson, + contactPhone: dto.contactPhone, + packageType: dto.packageType || 'STANDARD', + teacherQuota: dto.teacherQuota || 20, + studentQuota: dto.studentQuota || 200, + startDate, + expireDate, + status: 'ACTIVE', + }, + select: { + id: true, + name: true, + loginAccount: true, + address: true, + contactPerson: true, + contactPhone: true, + packageType: true, + teacherQuota: true, + studentQuota: true, + startDate: true, + expireDate: true, + status: true, + createdAt: true, + }, + }); + return { + ...tenant, + tempPassword: dto.password || '123456', + }; + } + async update(id, dto) { + const existing = await this.prisma.tenant.findUnique({ + where: { id }, + }); + if (!existing) { + throw new common_1.NotFoundException(`租户 #${id} 不存在`); + } + return this.prisma.tenant.update({ + where: { id }, + data: { + name: dto.name, + address: dto.address, + contactPerson: dto.contactPerson, + contactPhone: dto.contactPhone, + packageType: dto.packageType, + teacherQuota: dto.teacherQuota, + studentQuota: dto.studentQuota, + startDate: dto.startDate, + expireDate: dto.expireDate, + status: dto.status, + }, + select: { + id: true, + name: true, + loginAccount: true, + address: true, + contactPerson: true, + contactPhone: true, + packageType: true, + teacherQuota: true, + studentQuota: true, + startDate: true, + expireDate: true, + status: true, + createdAt: true, + updatedAt: true, + }, + }); + } + async updateQuota(id, dto) { + const existing = await this.prisma.tenant.findUnique({ + where: { id }, + }); + if (!existing) { + throw new common_1.NotFoundException(`租户 #${id} 不存在`); + } + if (dto.teacherQuota !== undefined && dto.teacherQuota < existing.teacherCount) { + throw new common_1.BadRequestException(`教师配额不能小于当前已用数量 (${existing.teacherCount})`); + } + if (dto.studentQuota !== undefined && dto.studentQuota < existing.studentCount) { + throw new common_1.BadRequestException(`学生配额不能小于当前已用数量 (${existing.studentCount})`); + } + return this.prisma.tenant.update({ + where: { id }, + data: { + packageType: dto.packageType, + teacherQuota: dto.teacherQuota, + studentQuota: dto.studentQuota, + }, + select: { + id: true, + name: true, + packageType: true, + teacherQuota: true, + studentQuota: true, + teacherCount: true, + studentCount: true, + }, + }); + } + async updateStatus(id, dto) { + const existing = await this.prisma.tenant.findUnique({ + where: { id }, + }); + if (!existing) { + throw new common_1.NotFoundException(`租户 #${id} 不存在`); + } + return this.prisma.tenant.update({ + where: { id }, + data: { + status: dto.status, + }, + select: { + id: true, + name: true, + status: true, + }, + }); + } + async resetPassword(id) { + const existing = await this.prisma.tenant.findUnique({ + where: { id }, + }); + if (!existing) { + throw new common_1.NotFoundException(`租户 #${id} 不存在`); + } + const tempPassword = Math.random().toString(36).slice(-8); + const passwordHash = await bcrypt.hash(tempPassword, 10); + await this.prisma.tenant.update({ + where: { id }, + data: { + passwordHash, + }, + }); + return { + tempPassword, + }; + } + async remove(id) { + const existing = await this.prisma.tenant.findUnique({ + where: { id }, + }); + if (!existing) { + throw new common_1.NotFoundException(`租户 #${id} 不存在`); + } + await this.prisma.tenant.delete({ + where: { id }, + }); + return { success: true }; + } + async getStats() { + const [totalCount, activeCount, expiredCount] = await Promise.all([ + this.prisma.tenant.count(), + this.prisma.tenant.count({ where: { status: 'ACTIVE' } }), + this.prisma.tenant.count({ where: { status: { not: 'ACTIVE' } } }), + ]); + const packageDistribution = await this.prisma.tenant.groupBy({ + by: ['packageType'], + _count: { + id: true, + }, + }); + return { + totalCount, + activeCount, + expiredCount, + packageDistribution: packageDistribution.map((item) => ({ + packageType: item.packageType, + count: item._count.id, + })), + }; + } +}; +exports.TenantService = TenantService; +exports.TenantService = TenantService = __decorate([ + (0, common_1.Injectable)(), + __metadata("design:paramtypes", [prisma_service_1.PrismaService]) +], TenantService); +//# sourceMappingURL=tenant.service.js.map \ No newline at end of file diff --git a/reading-platform-backend/dist/src/modules/tenant/tenant.service.js.map b/reading-platform-backend/dist/src/modules/tenant/tenant.service.js.map new file mode 100644 index 0000000..b8b7b75 --- /dev/null +++ b/reading-platform-backend/dist/src/modules/tenant/tenant.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"tenant.service.js","sourceRoot":"","sources":["../../../../src/modules/tenant/tenant.service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/reading-platform-backend/dist/tsconfig.tsbuildinfo b/reading-platform-backend/dist/tsconfig.tsbuildinfo new file mode 100644 index 0000000..7603367 --- /dev/null +++ b/reading-platform-backend/dist/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"fileNames":["../node_modules/typescript/lib/lib.es5.d.ts","../node_modules/typescript/lib/lib.es2015.d.ts","../node_modules/typescript/lib/lib.es2016.d.ts","../node_modules/typescript/lib/lib.es2017.d.ts","../node_modules/typescript/lib/lib.es2018.d.ts","../node_modules/typescript/lib/lib.es2019.d.ts","../node_modules/typescript/lib/lib.es2020.d.ts","../node_modules/typescript/lib/lib.es2021.d.ts","../node_modules/typescript/lib/lib.dom.d.ts","../node_modules/typescript/lib/lib.dom.iterable.d.ts","../node_modules/typescript/lib/lib.dom.asynciterable.d.ts","../node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../node_modules/typescript/lib/lib.scripthost.d.ts","../node_modules/typescript/lib/lib.es2015.core.d.ts","../node_modules/typescript/lib/lib.es2015.collection.d.ts","../node_modules/typescript/lib/lib.es2015.generator.d.ts","../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../node_modules/typescript/lib/lib.es2015.promise.d.ts","../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../node_modules/typescript/lib/lib.es2016.intl.d.ts","../node_modules/typescript/lib/lib.es2017.date.d.ts","../node_modules/typescript/lib/lib.es2017.object.d.ts","../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../node_modules/typescript/lib/lib.es2017.string.d.ts","../node_modules/typescript/lib/lib.es2017.intl.d.ts","../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../node_modules/typescript/lib/lib.es2018.intl.d.ts","../node_modules/typescript/lib/lib.es2018.promise.d.ts","../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../node_modules/typescript/lib/lib.es2019.array.d.ts","../node_modules/typescript/lib/lib.es2019.object.d.ts","../node_modules/typescript/lib/lib.es2019.string.d.ts","../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../node_modules/typescript/lib/lib.es2019.intl.d.ts","../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../node_modules/typescript/lib/lib.es2020.date.d.ts","../node_modules/typescript/lib/lib.es2020.promise.d.ts","../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../node_modules/typescript/lib/lib.es2020.string.d.ts","../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../node_modules/typescript/lib/lib.es2020.intl.d.ts","../node_modules/typescript/lib/lib.es2020.number.d.ts","../node_modules/typescript/lib/lib.es2021.promise.d.ts","../node_modules/typescript/lib/lib.es2021.string.d.ts","../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../node_modules/typescript/lib/lib.es2021.intl.d.ts","../node_modules/typescript/lib/lib.decorators.d.ts","../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../node_modules/typescript/lib/lib.es2021.full.d.ts","../node_modules/reflect-metadata/index.d.ts","../node_modules/@nestjs/common/decorators/core/bind.decorator.d.ts","../node_modules/@nestjs/common/interfaces/abstract.interface.d.ts","../node_modules/@nestjs/common/interfaces/controllers/controller-metadata.interface.d.ts","../node_modules/@nestjs/common/interfaces/controllers/controller.interface.d.ts","../node_modules/@nestjs/common/interfaces/features/arguments-host.interface.d.ts","../node_modules/@nestjs/common/interfaces/exceptions/exception-filter.interface.d.ts","../node_modules/rxjs/dist/types/internal/subscription.d.ts","../node_modules/rxjs/dist/types/internal/subscriber.d.ts","../node_modules/rxjs/dist/types/internal/operator.d.ts","../node_modules/rxjs/dist/types/internal/observable.d.ts","../node_modules/rxjs/dist/types/internal/types.d.ts","../node_modules/rxjs/dist/types/internal/operators/audit.d.ts","../node_modules/rxjs/dist/types/internal/operators/audittime.d.ts","../node_modules/rxjs/dist/types/internal/operators/buffer.d.ts","../node_modules/rxjs/dist/types/internal/operators/buffercount.d.ts","../node_modules/rxjs/dist/types/internal/operators/buffertime.d.ts","../node_modules/rxjs/dist/types/internal/operators/buffertoggle.d.ts","../node_modules/rxjs/dist/types/internal/operators/bufferwhen.d.ts","../node_modules/rxjs/dist/types/internal/operators/catcherror.d.ts","../node_modules/rxjs/dist/types/internal/operators/combinelatestall.d.ts","../node_modules/rxjs/dist/types/internal/operators/combineall.d.ts","../node_modules/rxjs/dist/types/internal/operators/combinelatest.d.ts","../node_modules/rxjs/dist/types/internal/operators/combinelatestwith.d.ts","../node_modules/rxjs/dist/types/internal/operators/concat.d.ts","../node_modules/rxjs/dist/types/internal/operators/concatall.d.ts","../node_modules/rxjs/dist/types/internal/operators/concatmap.d.ts","../node_modules/rxjs/dist/types/internal/operators/concatmapto.d.ts","../node_modules/rxjs/dist/types/internal/operators/concatwith.d.ts","../node_modules/rxjs/dist/types/internal/operators/connect.d.ts","../node_modules/rxjs/dist/types/internal/operators/count.d.ts","../node_modules/rxjs/dist/types/internal/operators/debounce.d.ts","../node_modules/rxjs/dist/types/internal/operators/debouncetime.d.ts","../node_modules/rxjs/dist/types/internal/operators/defaultifempty.d.ts","../node_modules/rxjs/dist/types/internal/operators/delay.d.ts","../node_modules/rxjs/dist/types/internal/operators/delaywhen.d.ts","../node_modules/rxjs/dist/types/internal/operators/dematerialize.d.ts","../node_modules/rxjs/dist/types/internal/operators/distinct.d.ts","../node_modules/rxjs/dist/types/internal/operators/distinctuntilchanged.d.ts","../node_modules/rxjs/dist/types/internal/operators/distinctuntilkeychanged.d.ts","../node_modules/rxjs/dist/types/internal/operators/elementat.d.ts","../node_modules/rxjs/dist/types/internal/operators/endwith.d.ts","../node_modules/rxjs/dist/types/internal/operators/every.d.ts","../node_modules/rxjs/dist/types/internal/operators/exhaustall.d.ts","../node_modules/rxjs/dist/types/internal/operators/exhaust.d.ts","../node_modules/rxjs/dist/types/internal/operators/exhaustmap.d.ts","../node_modules/rxjs/dist/types/internal/operators/expand.d.ts","../node_modules/rxjs/dist/types/internal/operators/filter.d.ts","../node_modules/rxjs/dist/types/internal/operators/finalize.d.ts","../node_modules/rxjs/dist/types/internal/operators/find.d.ts","../node_modules/rxjs/dist/types/internal/operators/findindex.d.ts","../node_modules/rxjs/dist/types/internal/operators/first.d.ts","../node_modules/rxjs/dist/types/internal/subject.d.ts","../node_modules/rxjs/dist/types/internal/operators/groupby.d.ts","../node_modules/rxjs/dist/types/internal/operators/ignoreelements.d.ts","../node_modules/rxjs/dist/types/internal/operators/isempty.d.ts","../node_modules/rxjs/dist/types/internal/operators/last.d.ts","../node_modules/rxjs/dist/types/internal/operators/map.d.ts","../node_modules/rxjs/dist/types/internal/operators/mapto.d.ts","../node_modules/rxjs/dist/types/internal/notification.d.ts","../node_modules/rxjs/dist/types/internal/operators/materialize.d.ts","../node_modules/rxjs/dist/types/internal/operators/max.d.ts","../node_modules/rxjs/dist/types/internal/operators/merge.d.ts","../node_modules/rxjs/dist/types/internal/operators/mergeall.d.ts","../node_modules/rxjs/dist/types/internal/operators/mergemap.d.ts","../node_modules/rxjs/dist/types/internal/operators/flatmap.d.ts","../node_modules/rxjs/dist/types/internal/operators/mergemapto.d.ts","../node_modules/rxjs/dist/types/internal/operators/mergescan.d.ts","../node_modules/rxjs/dist/types/internal/operators/mergewith.d.ts","../node_modules/rxjs/dist/types/internal/operators/min.d.ts","../node_modules/rxjs/dist/types/internal/observable/connectableobservable.d.ts","../node_modules/rxjs/dist/types/internal/operators/multicast.d.ts","../node_modules/rxjs/dist/types/internal/operators/observeon.d.ts","../node_modules/rxjs/dist/types/internal/operators/onerrorresumenextwith.d.ts","../node_modules/rxjs/dist/types/internal/operators/pairwise.d.ts","../node_modules/rxjs/dist/types/internal/operators/partition.d.ts","../node_modules/rxjs/dist/types/internal/operators/pluck.d.ts","../node_modules/rxjs/dist/types/internal/operators/publish.d.ts","../node_modules/rxjs/dist/types/internal/operators/publishbehavior.d.ts","../node_modules/rxjs/dist/types/internal/operators/publishlast.d.ts","../node_modules/rxjs/dist/types/internal/operators/publishreplay.d.ts","../node_modules/rxjs/dist/types/internal/operators/race.d.ts","../node_modules/rxjs/dist/types/internal/operators/racewith.d.ts","../node_modules/rxjs/dist/types/internal/operators/reduce.d.ts","../node_modules/rxjs/dist/types/internal/operators/repeat.d.ts","../node_modules/rxjs/dist/types/internal/operators/repeatwhen.d.ts","../node_modules/rxjs/dist/types/internal/operators/retry.d.ts","../node_modules/rxjs/dist/types/internal/operators/retrywhen.d.ts","../node_modules/rxjs/dist/types/internal/operators/refcount.d.ts","../node_modules/rxjs/dist/types/internal/operators/sample.d.ts","../node_modules/rxjs/dist/types/internal/operators/sampletime.d.ts","../node_modules/rxjs/dist/types/internal/operators/scan.d.ts","../node_modules/rxjs/dist/types/internal/operators/sequenceequal.d.ts","../node_modules/rxjs/dist/types/internal/operators/share.d.ts","../node_modules/rxjs/dist/types/internal/operators/sharereplay.d.ts","../node_modules/rxjs/dist/types/internal/operators/single.d.ts","../node_modules/rxjs/dist/types/internal/operators/skip.d.ts","../node_modules/rxjs/dist/types/internal/operators/skiplast.d.ts","../node_modules/rxjs/dist/types/internal/operators/skipuntil.d.ts","../node_modules/rxjs/dist/types/internal/operators/skipwhile.d.ts","../node_modules/rxjs/dist/types/internal/operators/startwith.d.ts","../node_modules/rxjs/dist/types/internal/operators/subscribeon.d.ts","../node_modules/rxjs/dist/types/internal/operators/switchall.d.ts","../node_modules/rxjs/dist/types/internal/operators/switchmap.d.ts","../node_modules/rxjs/dist/types/internal/operators/switchmapto.d.ts","../node_modules/rxjs/dist/types/internal/operators/switchscan.d.ts","../node_modules/rxjs/dist/types/internal/operators/take.d.ts","../node_modules/rxjs/dist/types/internal/operators/takelast.d.ts","../node_modules/rxjs/dist/types/internal/operators/takeuntil.d.ts","../node_modules/rxjs/dist/types/internal/operators/takewhile.d.ts","../node_modules/rxjs/dist/types/internal/operators/tap.d.ts","../node_modules/rxjs/dist/types/internal/operators/throttle.d.ts","../node_modules/rxjs/dist/types/internal/operators/throttletime.d.ts","../node_modules/rxjs/dist/types/internal/operators/throwifempty.d.ts","../node_modules/rxjs/dist/types/internal/operators/timeinterval.d.ts","../node_modules/rxjs/dist/types/internal/operators/timeout.d.ts","../node_modules/rxjs/dist/types/internal/operators/timeoutwith.d.ts","../node_modules/rxjs/dist/types/internal/operators/timestamp.d.ts","../node_modules/rxjs/dist/types/internal/operators/toarray.d.ts","../node_modules/rxjs/dist/types/internal/operators/window.d.ts","../node_modules/rxjs/dist/types/internal/operators/windowcount.d.ts","../node_modules/rxjs/dist/types/internal/operators/windowtime.d.ts","../node_modules/rxjs/dist/types/internal/operators/windowtoggle.d.ts","../node_modules/rxjs/dist/types/internal/operators/windowwhen.d.ts","../node_modules/rxjs/dist/types/internal/operators/withlatestfrom.d.ts","../node_modules/rxjs/dist/types/internal/operators/zip.d.ts","../node_modules/rxjs/dist/types/internal/operators/zipall.d.ts","../node_modules/rxjs/dist/types/internal/operators/zipwith.d.ts","../node_modules/rxjs/dist/types/operators/index.d.ts","../node_modules/rxjs/dist/types/internal/scheduler/action.d.ts","../node_modules/rxjs/dist/types/internal/scheduler.d.ts","../node_modules/rxjs/dist/types/internal/testing/testmessage.d.ts","../node_modules/rxjs/dist/types/internal/testing/subscriptionlog.d.ts","../node_modules/rxjs/dist/types/internal/testing/subscriptionloggable.d.ts","../node_modules/rxjs/dist/types/internal/testing/coldobservable.d.ts","../node_modules/rxjs/dist/types/internal/testing/hotobservable.d.ts","../node_modules/rxjs/dist/types/internal/scheduler/asyncscheduler.d.ts","../node_modules/rxjs/dist/types/internal/scheduler/timerhandle.d.ts","../node_modules/rxjs/dist/types/internal/scheduler/asyncaction.d.ts","../node_modules/rxjs/dist/types/internal/scheduler/virtualtimescheduler.d.ts","../node_modules/rxjs/dist/types/internal/testing/testscheduler.d.ts","../node_modules/rxjs/dist/types/testing/index.d.ts","../node_modules/rxjs/dist/types/internal/symbol/observable.d.ts","../node_modules/rxjs/dist/types/internal/observable/dom/animationframes.d.ts","../node_modules/rxjs/dist/types/internal/behaviorsubject.d.ts","../node_modules/rxjs/dist/types/internal/replaysubject.d.ts","../node_modules/rxjs/dist/types/internal/asyncsubject.d.ts","../node_modules/rxjs/dist/types/internal/scheduler/asapscheduler.d.ts","../node_modules/rxjs/dist/types/internal/scheduler/asap.d.ts","../node_modules/rxjs/dist/types/internal/scheduler/async.d.ts","../node_modules/rxjs/dist/types/internal/scheduler/queuescheduler.d.ts","../node_modules/rxjs/dist/types/internal/scheduler/queue.d.ts","../node_modules/rxjs/dist/types/internal/scheduler/animationframescheduler.d.ts","../node_modules/rxjs/dist/types/internal/scheduler/animationframe.d.ts","../node_modules/rxjs/dist/types/internal/util/identity.d.ts","../node_modules/rxjs/dist/types/internal/util/pipe.d.ts","../node_modules/rxjs/dist/types/internal/util/noop.d.ts","../node_modules/rxjs/dist/types/internal/util/isobservable.d.ts","../node_modules/rxjs/dist/types/internal/lastvaluefrom.d.ts","../node_modules/rxjs/dist/types/internal/firstvaluefrom.d.ts","../node_modules/rxjs/dist/types/internal/util/argumentoutofrangeerror.d.ts","../node_modules/rxjs/dist/types/internal/util/emptyerror.d.ts","../node_modules/rxjs/dist/types/internal/util/notfounderror.d.ts","../node_modules/rxjs/dist/types/internal/util/objectunsubscribederror.d.ts","../node_modules/rxjs/dist/types/internal/util/sequenceerror.d.ts","../node_modules/rxjs/dist/types/internal/util/unsubscriptionerror.d.ts","../node_modules/rxjs/dist/types/internal/observable/bindcallback.d.ts","../node_modules/rxjs/dist/types/internal/observable/bindnodecallback.d.ts","../node_modules/rxjs/dist/types/internal/anycatcher.d.ts","../node_modules/rxjs/dist/types/internal/observable/combinelatest.d.ts","../node_modules/rxjs/dist/types/internal/observable/concat.d.ts","../node_modules/rxjs/dist/types/internal/observable/connectable.d.ts","../node_modules/rxjs/dist/types/internal/observable/defer.d.ts","../node_modules/rxjs/dist/types/internal/observable/empty.d.ts","../node_modules/rxjs/dist/types/internal/observable/forkjoin.d.ts","../node_modules/rxjs/dist/types/internal/observable/from.d.ts","../node_modules/rxjs/dist/types/internal/observable/fromevent.d.ts","../node_modules/rxjs/dist/types/internal/observable/fromeventpattern.d.ts","../node_modules/rxjs/dist/types/internal/observable/generate.d.ts","../node_modules/rxjs/dist/types/internal/observable/iif.d.ts","../node_modules/rxjs/dist/types/internal/observable/interval.d.ts","../node_modules/rxjs/dist/types/internal/observable/merge.d.ts","../node_modules/rxjs/dist/types/internal/observable/never.d.ts","../node_modules/rxjs/dist/types/internal/observable/of.d.ts","../node_modules/rxjs/dist/types/internal/observable/onerrorresumenext.d.ts","../node_modules/rxjs/dist/types/internal/observable/pairs.d.ts","../node_modules/rxjs/dist/types/internal/observable/partition.d.ts","../node_modules/rxjs/dist/types/internal/observable/race.d.ts","../node_modules/rxjs/dist/types/internal/observable/range.d.ts","../node_modules/rxjs/dist/types/internal/observable/throwerror.d.ts","../node_modules/rxjs/dist/types/internal/observable/timer.d.ts","../node_modules/rxjs/dist/types/internal/observable/using.d.ts","../node_modules/rxjs/dist/types/internal/observable/zip.d.ts","../node_modules/rxjs/dist/types/internal/scheduled/scheduled.d.ts","../node_modules/rxjs/dist/types/internal/config.d.ts","../node_modules/rxjs/dist/types/index.d.ts","../node_modules/@nestjs/common/interfaces/exceptions/rpc-exception-filter.interface.d.ts","../node_modules/@nestjs/common/interfaces/exceptions/ws-exception-filter.interface.d.ts","../node_modules/@nestjs/common/interfaces/external/validation-error.interface.d.ts","../node_modules/@nestjs/common/interfaces/features/execution-context.interface.d.ts","../node_modules/@nestjs/common/interfaces/features/can-activate.interface.d.ts","../node_modules/@nestjs/common/interfaces/features/custom-route-param-factory.interface.d.ts","../node_modules/@nestjs/common/interfaces/features/nest-interceptor.interface.d.ts","../node_modules/@nestjs/common/interfaces/features/paramtype.interface.d.ts","../node_modules/@nestjs/common/interfaces/type.interface.d.ts","../node_modules/@nestjs/common/interfaces/features/pipe-transform.interface.d.ts","../node_modules/@nestjs/common/enums/request-method.enum.d.ts","../node_modules/@nestjs/common/enums/http-status.enum.d.ts","../node_modules/@nestjs/common/enums/shutdown-signal.enum.d.ts","../node_modules/@nestjs/common/enums/version-type.enum.d.ts","../node_modules/@nestjs/common/enums/index.d.ts","../node_modules/@nestjs/common/interfaces/version-options.interface.d.ts","../node_modules/@nestjs/common/interfaces/middleware/middleware-configuration.interface.d.ts","../node_modules/@nestjs/common/interfaces/middleware/middleware-consumer.interface.d.ts","../node_modules/@nestjs/common/interfaces/middleware/middleware-config-proxy.interface.d.ts","../node_modules/@nestjs/common/interfaces/middleware/nest-middleware.interface.d.ts","../node_modules/@nestjs/common/interfaces/middleware/index.d.ts","../node_modules/@nestjs/common/interfaces/global-prefix-options.interface.d.ts","../node_modules/@nestjs/common/interfaces/hooks/before-application-shutdown.interface.d.ts","../node_modules/@nestjs/common/interfaces/hooks/on-application-bootstrap.interface.d.ts","../node_modules/@nestjs/common/interfaces/hooks/on-application-shutdown.interface.d.ts","../node_modules/@nestjs/common/interfaces/hooks/on-destroy.interface.d.ts","../node_modules/@nestjs/common/interfaces/hooks/on-init.interface.d.ts","../node_modules/@nestjs/common/interfaces/hooks/index.d.ts","../node_modules/@nestjs/common/interfaces/http/http-exception-body.interface.d.ts","../node_modules/@nestjs/common/interfaces/http/http-redirect-response.interface.d.ts","../node_modules/@nestjs/common/interfaces/external/cors-options.interface.d.ts","../node_modules/@nestjs/common/interfaces/external/https-options.interface.d.ts","../node_modules/@nestjs/common/services/logger.service.d.ts","../node_modules/@nestjs/common/interfaces/nest-application-context-options.interface.d.ts","../node_modules/@nestjs/common/interfaces/nest-application-options.interface.d.ts","../node_modules/@nestjs/common/interfaces/http/http-server.interface.d.ts","../node_modules/@nestjs/common/interfaces/http/message-event.interface.d.ts","../node_modules/@nestjs/common/interfaces/http/raw-body-request.interface.d.ts","../node_modules/@nestjs/common/interfaces/http/index.d.ts","../node_modules/@nestjs/common/interfaces/injectable.interface.d.ts","../node_modules/@nestjs/common/interfaces/microservices/nest-hybrid-application-options.interface.d.ts","../node_modules/@nestjs/common/interfaces/modules/forward-reference.interface.d.ts","../node_modules/@nestjs/common/interfaces/scope-options.interface.d.ts","../node_modules/@nestjs/common/interfaces/modules/injection-token.interface.d.ts","../node_modules/@nestjs/common/interfaces/modules/optional-factory-dependency.interface.d.ts","../node_modules/@nestjs/common/interfaces/modules/provider.interface.d.ts","../node_modules/@nestjs/common/interfaces/modules/module-metadata.interface.d.ts","../node_modules/@nestjs/common/interfaces/modules/dynamic-module.interface.d.ts","../node_modules/@nestjs/common/interfaces/modules/introspection-result.interface.d.ts","../node_modules/@nestjs/common/interfaces/modules/nest-module.interface.d.ts","../node_modules/@nestjs/common/interfaces/modules/index.d.ts","../node_modules/@nestjs/common/interfaces/nest-application-context.interface.d.ts","../node_modules/@nestjs/common/interfaces/websockets/web-socket-adapter.interface.d.ts","../node_modules/@nestjs/common/interfaces/nest-application.interface.d.ts","../node_modules/@nestjs/common/interfaces/nest-microservice.interface.d.ts","../node_modules/@nestjs/common/interfaces/index.d.ts","../node_modules/@nestjs/common/decorators/core/catch.decorator.d.ts","../node_modules/@nestjs/common/decorators/core/controller.decorator.d.ts","../node_modules/@nestjs/common/decorators/core/dependencies.decorator.d.ts","../node_modules/@nestjs/common/decorators/core/exception-filters.decorator.d.ts","../node_modules/@nestjs/common/decorators/core/inject.decorator.d.ts","../node_modules/@nestjs/common/decorators/core/injectable.decorator.d.ts","../node_modules/@nestjs/common/decorators/core/optional.decorator.d.ts","../node_modules/@nestjs/common/decorators/core/set-metadata.decorator.d.ts","../node_modules/@nestjs/common/decorators/core/use-guards.decorator.d.ts","../node_modules/@nestjs/common/decorators/core/use-interceptors.decorator.d.ts","../node_modules/@nestjs/common/decorators/core/use-pipes.decorator.d.ts","../node_modules/@nestjs/common/decorators/core/apply-decorators.d.ts","../node_modules/@nestjs/common/decorators/core/version.decorator.d.ts","../node_modules/@nestjs/common/decorators/core/index.d.ts","../node_modules/@nestjs/common/decorators/modules/global.decorator.d.ts","../node_modules/@nestjs/common/decorators/modules/module.decorator.d.ts","../node_modules/@nestjs/common/decorators/modules/index.d.ts","../node_modules/@nestjs/common/decorators/http/request-mapping.decorator.d.ts","../node_modules/@nestjs/common/decorators/http/route-params.decorator.d.ts","../node_modules/@nestjs/common/decorators/http/http-code.decorator.d.ts","../node_modules/@nestjs/common/decorators/http/create-route-param-metadata.decorator.d.ts","../node_modules/@nestjs/common/decorators/http/render.decorator.d.ts","../node_modules/@nestjs/common/decorators/http/header.decorator.d.ts","../node_modules/@nestjs/common/decorators/http/redirect.decorator.d.ts","../node_modules/@nestjs/common/decorators/http/sse.decorator.d.ts","../node_modules/@nestjs/common/decorators/http/index.d.ts","../node_modules/@nestjs/common/decorators/index.d.ts","../node_modules/@nestjs/common/exceptions/http.exception.d.ts","../node_modules/@nestjs/common/exceptions/bad-request.exception.d.ts","../node_modules/@nestjs/common/exceptions/unauthorized.exception.d.ts","../node_modules/@nestjs/common/exceptions/method-not-allowed.exception.d.ts","../node_modules/@nestjs/common/exceptions/not-found.exception.d.ts","../node_modules/@nestjs/common/exceptions/forbidden.exception.d.ts","../node_modules/@nestjs/common/exceptions/not-acceptable.exception.d.ts","../node_modules/@nestjs/common/exceptions/request-timeout.exception.d.ts","../node_modules/@nestjs/common/exceptions/conflict.exception.d.ts","../node_modules/@nestjs/common/exceptions/gone.exception.d.ts","../node_modules/@nestjs/common/exceptions/payload-too-large.exception.d.ts","../node_modules/@nestjs/common/exceptions/unsupported-media-type.exception.d.ts","../node_modules/@nestjs/common/exceptions/unprocessable-entity.exception.d.ts","../node_modules/@nestjs/common/exceptions/internal-server-error.exception.d.ts","../node_modules/@nestjs/common/exceptions/not-implemented.exception.d.ts","../node_modules/@nestjs/common/exceptions/http-version-not-supported.exception.d.ts","../node_modules/@nestjs/common/exceptions/bad-gateway.exception.d.ts","../node_modules/@nestjs/common/exceptions/service-unavailable.exception.d.ts","../node_modules/@nestjs/common/exceptions/gateway-timeout.exception.d.ts","../node_modules/@nestjs/common/exceptions/im-a-teapot.exception.d.ts","../node_modules/@nestjs/common/exceptions/precondition-failed.exception.d.ts","../node_modules/@nestjs/common/exceptions/misdirected.exception.d.ts","../node_modules/@nestjs/common/exceptions/index.d.ts","../node_modules/@nestjs/common/file-stream/interfaces/streamable-options.interface.d.ts","../node_modules/@nestjs/common/file-stream/interfaces/streamable-handler-response.interface.d.ts","../node_modules/@nestjs/common/file-stream/interfaces/index.d.ts","../node_modules/@nestjs/common/services/console-logger.service.d.ts","../node_modules/@nestjs/common/services/index.d.ts","../node_modules/@nestjs/common/file-stream/streamable-file.d.ts","../node_modules/@nestjs/common/file-stream/index.d.ts","../node_modules/@nestjs/common/module-utils/constants.d.ts","../node_modules/@nestjs/common/module-utils/interfaces/configurable-module-async-options.interface.d.ts","../node_modules/@nestjs/common/module-utils/interfaces/configurable-module-cls.interface.d.ts","../node_modules/@nestjs/common/module-utils/interfaces/configurable-module-host.interface.d.ts","../node_modules/@nestjs/common/module-utils/interfaces/index.d.ts","../node_modules/@nestjs/common/module-utils/configurable-module.builder.d.ts","../node_modules/@nestjs/common/module-utils/index.d.ts","../node_modules/@nestjs/common/pipes/default-value.pipe.d.ts","../node_modules/@nestjs/common/interfaces/external/class-transform-options.interface.d.ts","../node_modules/@nestjs/common/interfaces/external/transformer-package.interface.d.ts","../node_modules/@nestjs/common/interfaces/external/validator-options.interface.d.ts","../node_modules/@nestjs/common/interfaces/external/validator-package.interface.d.ts","../node_modules/@nestjs/common/utils/http-error-by-code.util.d.ts","../node_modules/@nestjs/common/pipes/validation.pipe.d.ts","../node_modules/@nestjs/common/pipes/parse-array.pipe.d.ts","../node_modules/@nestjs/common/pipes/parse-bool.pipe.d.ts","../node_modules/@nestjs/common/pipes/parse-int.pipe.d.ts","../node_modules/@nestjs/common/pipes/parse-float.pipe.d.ts","../node_modules/@nestjs/common/pipes/parse-enum.pipe.d.ts","../node_modules/@nestjs/common/pipes/parse-uuid.pipe.d.ts","../node_modules/@nestjs/common/pipes/file/interfaces/file.interface.d.ts","../node_modules/@nestjs/common/pipes/file/interfaces/index.d.ts","../node_modules/@nestjs/common/pipes/file/file-validator.interface.d.ts","../node_modules/@nestjs/common/pipes/file/file-type.validator.d.ts","../node_modules/@nestjs/common/pipes/file/max-file-size.validator.d.ts","../node_modules/@nestjs/common/pipes/file/parse-file-options.interface.d.ts","../node_modules/@nestjs/common/pipes/file/parse-file.pipe.d.ts","../node_modules/@nestjs/common/pipes/file/parse-file-pipe.builder.d.ts","../node_modules/@nestjs/common/pipes/file/index.d.ts","../node_modules/@nestjs/common/pipes/index.d.ts","../node_modules/@nestjs/common/serializer/class-serializer.interfaces.d.ts","../node_modules/@nestjs/common/serializer/class-serializer.interceptor.d.ts","../node_modules/@nestjs/common/serializer/decorators/serialize-options.decorator.d.ts","../node_modules/@nestjs/common/serializer/decorators/index.d.ts","../node_modules/@nestjs/common/serializer/index.d.ts","../node_modules/@nestjs/common/utils/forward-ref.util.d.ts","../node_modules/@nestjs/common/utils/index.d.ts","../node_modules/@nestjs/common/index.d.ts","../node_modules/@nestjs/config/dist/conditional.module.d.ts","../node_modules/@nestjs/config/dist/interfaces/config-change-event.interface.d.ts","../node_modules/@nestjs/config/dist/types/config-object.type.d.ts","../node_modules/@nestjs/config/dist/types/config.type.d.ts","../node_modules/@nestjs/config/dist/types/no-infer.type.d.ts","../node_modules/@nestjs/config/dist/types/path-value.type.d.ts","../node_modules/@nestjs/config/dist/types/index.d.ts","../node_modules/@nestjs/config/dist/interfaces/config-factory.interface.d.ts","../node_modules/@types/node/compatibility/disposable.d.ts","../node_modules/@types/node/compatibility/indexable.d.ts","../node_modules/@types/node/compatibility/iterators.d.ts","../node_modules/@types/node/compatibility/index.d.ts","../node_modules/@types/node/ts5.6/globals.typedarray.d.ts","../node_modules/@types/node/ts5.6/buffer.buffer.d.ts","../node_modules/@types/node/globals.d.ts","../node_modules/@types/node/web-globals/abortcontroller.d.ts","../node_modules/@types/node/web-globals/domexception.d.ts","../node_modules/@types/node/web-globals/events.d.ts","../node_modules/buffer/index.d.ts","../node_modules/undici-types/header.d.ts","../node_modules/undici-types/readable.d.ts","../node_modules/undici-types/file.d.ts","../node_modules/undici-types/fetch.d.ts","../node_modules/undici-types/formdata.d.ts","../node_modules/undici-types/connector.d.ts","../node_modules/undici-types/client.d.ts","../node_modules/undici-types/errors.d.ts","../node_modules/undici-types/dispatcher.d.ts","../node_modules/undici-types/global-dispatcher.d.ts","../node_modules/undici-types/global-origin.d.ts","../node_modules/undici-types/pool-stats.d.ts","../node_modules/undici-types/pool.d.ts","../node_modules/undici-types/handlers.d.ts","../node_modules/undici-types/balanced-pool.d.ts","../node_modules/undici-types/agent.d.ts","../node_modules/undici-types/mock-interceptor.d.ts","../node_modules/undici-types/mock-agent.d.ts","../node_modules/undici-types/mock-client.d.ts","../node_modules/undici-types/mock-pool.d.ts","../node_modules/undici-types/mock-errors.d.ts","../node_modules/undici-types/proxy-agent.d.ts","../node_modules/undici-types/env-http-proxy-agent.d.ts","../node_modules/undici-types/retry-handler.d.ts","../node_modules/undici-types/retry-agent.d.ts","../node_modules/undici-types/api.d.ts","../node_modules/undici-types/interceptors.d.ts","../node_modules/undici-types/util.d.ts","../node_modules/undici-types/cookies.d.ts","../node_modules/undici-types/patch.d.ts","../node_modules/undici-types/websocket.d.ts","../node_modules/undici-types/eventsource.d.ts","../node_modules/undici-types/filereader.d.ts","../node_modules/undici-types/diagnostics-channel.d.ts","../node_modules/undici-types/content-type.d.ts","../node_modules/undici-types/cache.d.ts","../node_modules/undici-types/index.d.ts","../node_modules/@types/node/web-globals/fetch.d.ts","../node_modules/@types/node/assert.d.ts","../node_modules/@types/node/assert/strict.d.ts","../node_modules/@types/node/async_hooks.d.ts","../node_modules/@types/node/buffer.d.ts","../node_modules/@types/node/child_process.d.ts","../node_modules/@types/node/cluster.d.ts","../node_modules/@types/node/console.d.ts","../node_modules/@types/node/constants.d.ts","../node_modules/@types/node/crypto.d.ts","../node_modules/@types/node/dgram.d.ts","../node_modules/@types/node/diagnostics_channel.d.ts","../node_modules/@types/node/dns.d.ts","../node_modules/@types/node/dns/promises.d.ts","../node_modules/@types/node/domain.d.ts","../node_modules/@types/node/events.d.ts","../node_modules/@types/node/fs.d.ts","../node_modules/@types/node/fs/promises.d.ts","../node_modules/@types/node/http.d.ts","../node_modules/@types/node/http2.d.ts","../node_modules/@types/node/https.d.ts","../node_modules/@types/node/inspector.generated.d.ts","../node_modules/@types/node/module.d.ts","../node_modules/@types/node/net.d.ts","../node_modules/@types/node/os.d.ts","../node_modules/@types/node/path.d.ts","../node_modules/@types/node/perf_hooks.d.ts","../node_modules/@types/node/process.d.ts","../node_modules/@types/node/punycode.d.ts","../node_modules/@types/node/querystring.d.ts","../node_modules/@types/node/readline.d.ts","../node_modules/@types/node/readline/promises.d.ts","../node_modules/@types/node/repl.d.ts","../node_modules/@types/node/sea.d.ts","../node_modules/@types/node/stream.d.ts","../node_modules/@types/node/stream/promises.d.ts","../node_modules/@types/node/stream/consumers.d.ts","../node_modules/@types/node/stream/web.d.ts","../node_modules/@types/node/string_decoder.d.ts","../node_modules/@types/node/test.d.ts","../node_modules/@types/node/timers.d.ts","../node_modules/@types/node/timers/promises.d.ts","../node_modules/@types/node/tls.d.ts","../node_modules/@types/node/trace_events.d.ts","../node_modules/@types/node/tty.d.ts","../node_modules/@types/node/url.d.ts","../node_modules/@types/node/util.d.ts","../node_modules/@types/node/v8.d.ts","../node_modules/@types/node/vm.d.ts","../node_modules/@types/node/wasi.d.ts","../node_modules/@types/node/worker_threads.d.ts","../node_modules/@types/node/zlib.d.ts","../node_modules/@types/node/ts5.6/index.d.ts","../node_modules/dotenv-expand/lib/main.d.ts","../node_modules/@nestjs/config/dist/interfaces/config-module-options.interface.d.ts","../node_modules/@nestjs/config/dist/interfaces/index.d.ts","../node_modules/@nestjs/config/dist/config.module.d.ts","../node_modules/@nestjs/config/dist/config.service.d.ts","../node_modules/@nestjs/config/dist/utils/register-as.util.d.ts","../node_modules/@nestjs/config/dist/utils/get-config-token.util.d.ts","../node_modules/@nestjs/config/dist/utils/index.d.ts","../node_modules/@nestjs/config/dist/index.d.ts","../node_modules/@nestjs/config/index.d.ts","../node_modules/@nestjs/throttler/dist/throttler-storage-record.interface.d.ts","../node_modules/@nestjs/throttler/dist/throttler-storage.interface.d.ts","../node_modules/@nestjs/throttler/dist/throttler.guard.interface.d.ts","../node_modules/@nestjs/throttler/dist/throttler-module-options.interface.d.ts","../node_modules/@nestjs/throttler/dist/throttler.decorator.d.ts","../node_modules/@nestjs/throttler/dist/throttler.exception.d.ts","../node_modules/@nestjs/core/adapters/http-adapter.d.ts","../node_modules/@nestjs/core/adapters/index.d.ts","../node_modules/@nestjs/common/constants.d.ts","../node_modules/@nestjs/core/inspector/interfaces/edge.interface.d.ts","../node_modules/@nestjs/core/inspector/interfaces/entrypoint.interface.d.ts","../node_modules/@nestjs/core/inspector/interfaces/extras.interface.d.ts","../node_modules/@nestjs/core/inspector/interfaces/node.interface.d.ts","../node_modules/@nestjs/core/injector/settlement-signal.d.ts","../node_modules/@nestjs/core/injector/injector.d.ts","../node_modules/@nestjs/core/inspector/interfaces/serialized-graph-metadata.interface.d.ts","../node_modules/@nestjs/core/inspector/interfaces/serialized-graph-json.interface.d.ts","../node_modules/@nestjs/core/inspector/serialized-graph.d.ts","../node_modules/@nestjs/core/injector/module-token-factory.d.ts","../node_modules/@nestjs/core/injector/compiler.d.ts","../node_modules/@nestjs/core/injector/modules-container.d.ts","../node_modules/@nestjs/core/injector/container.d.ts","../node_modules/@nestjs/core/injector/instance-links-host.d.ts","../node_modules/@nestjs/core/injector/abstract-instance-resolver.d.ts","../node_modules/@nestjs/core/injector/module-ref.d.ts","../node_modules/@nestjs/core/injector/module.d.ts","../node_modules/@nestjs/core/injector/instance-wrapper.d.ts","../node_modules/@nestjs/core/router/interfaces/exclude-route-metadata.interface.d.ts","../node_modules/@nestjs/core/application-config.d.ts","../node_modules/@nestjs/core/constants.d.ts","../node_modules/@nestjs/core/discovery/discovery-module.d.ts","../node_modules/@nestjs/core/discovery/discovery-service.d.ts","../node_modules/@nestjs/core/discovery/index.d.ts","../node_modules/@nestjs/core/helpers/http-adapter-host.d.ts","../node_modules/@nestjs/core/exceptions/base-exception-filter.d.ts","../node_modules/@nestjs/core/exceptions/index.d.ts","../node_modules/@nestjs/core/helpers/context-id-factory.d.ts","../node_modules/@nestjs/common/interfaces/exceptions/exception-filter-metadata.interface.d.ts","../node_modules/@nestjs/core/exceptions/exceptions-handler.d.ts","../node_modules/@nestjs/core/router/router-proxy.d.ts","../node_modules/@nestjs/core/helpers/context-creator.d.ts","../node_modules/@nestjs/core/exceptions/base-exception-filter-context.d.ts","../node_modules/@nestjs/common/interfaces/exceptions/rpc-exception-filter-metadata.interface.d.ts","../node_modules/@nestjs/common/interfaces/exceptions/index.d.ts","../node_modules/@nestjs/core/exceptions/external-exception-filter.d.ts","../node_modules/@nestjs/core/exceptions/external-exceptions-handler.d.ts","../node_modules/@nestjs/core/exceptions/external-exception-filter-context.d.ts","../node_modules/@nestjs/core/guards/constants.d.ts","../node_modules/@nestjs/core/helpers/execution-context-host.d.ts","../node_modules/@nestjs/core/guards/guards-consumer.d.ts","../node_modules/@nestjs/core/guards/guards-context-creator.d.ts","../node_modules/@nestjs/core/guards/index.d.ts","../node_modules/@nestjs/core/interceptors/interceptors-consumer.d.ts","../node_modules/@nestjs/core/interceptors/interceptors-context-creator.d.ts","../node_modules/@nestjs/core/interceptors/index.d.ts","../node_modules/@nestjs/common/enums/route-paramtypes.enum.d.ts","../node_modules/@nestjs/core/pipes/params-token-factory.d.ts","../node_modules/@nestjs/core/pipes/pipes-consumer.d.ts","../node_modules/@nestjs/core/pipes/pipes-context-creator.d.ts","../node_modules/@nestjs/core/pipes/index.d.ts","../node_modules/@nestjs/core/helpers/context-utils.d.ts","../node_modules/@nestjs/core/injector/inquirer/inquirer-constants.d.ts","../node_modules/@nestjs/core/injector/inquirer/index.d.ts","../node_modules/@nestjs/core/interfaces/module-definition.interface.d.ts","../node_modules/@nestjs/core/interfaces/module-override.interface.d.ts","../node_modules/@nestjs/core/inspector/interfaces/enhancer-metadata-cache-entry.interface.d.ts","../node_modules/@nestjs/core/inspector/graph-inspector.d.ts","../node_modules/@nestjs/core/metadata-scanner.d.ts","../node_modules/@nestjs/core/scanner.d.ts","../node_modules/@nestjs/core/injector/instance-loader.d.ts","../node_modules/@nestjs/core/injector/lazy-module-loader/lazy-module-loader-options.interface.d.ts","../node_modules/@nestjs/core/injector/lazy-module-loader/lazy-module-loader.d.ts","../node_modules/@nestjs/core/injector/index.d.ts","../node_modules/@nestjs/core/helpers/interfaces/external-handler-metadata.interface.d.ts","../node_modules/@nestjs/core/helpers/interfaces/params-metadata.interface.d.ts","../node_modules/@nestjs/core/helpers/external-context-creator.d.ts","../node_modules/@nestjs/core/helpers/index.d.ts","../node_modules/@nestjs/core/inspector/initialize-on-preview.allowlist.d.ts","../node_modules/@nestjs/core/inspector/partial-graph.host.d.ts","../node_modules/@nestjs/core/inspector/index.d.ts","../node_modules/@nestjs/core/middleware/route-info-path-extractor.d.ts","../node_modules/@nestjs/core/middleware/routes-mapper.d.ts","../node_modules/@nestjs/core/middleware/builder.d.ts","../node_modules/@nestjs/core/middleware/index.d.ts","../node_modules/@nestjs/core/nest-application-context.d.ts","../node_modules/@nestjs/core/nest-application.d.ts","../node_modules/@nestjs/common/interfaces/microservices/nest-microservice-options.interface.d.ts","../node_modules/@nestjs/core/nest-factory.d.ts","../node_modules/@nestjs/core/repl/repl.d.ts","../node_modules/@nestjs/core/repl/index.d.ts","../node_modules/@nestjs/core/router/interfaces/routes.interface.d.ts","../node_modules/@nestjs/core/router/interfaces/index.d.ts","../node_modules/@nestjs/core/router/request/request-constants.d.ts","../node_modules/@nestjs/core/router/request/index.d.ts","../node_modules/@nestjs/core/router/router-module.d.ts","../node_modules/@nestjs/core/router/index.d.ts","../node_modules/@nestjs/core/services/reflector.service.d.ts","../node_modules/@nestjs/core/services/index.d.ts","../node_modules/@nestjs/core/index.d.ts","../node_modules/@nestjs/throttler/dist/throttler.guard.d.ts","../node_modules/@nestjs/throttler/dist/throttler.module.d.ts","../node_modules/@nestjs/throttler/dist/throttler.providers.d.ts","../node_modules/@nestjs/throttler/dist/throttler-storage-options.interface.d.ts","../node_modules/@nestjs/throttler/dist/throttler.service.d.ts","../node_modules/@nestjs/throttler/dist/utilities.d.ts","../node_modules/@nestjs/throttler/dist/index.d.ts","../node_modules/@prisma/client/runtime/library.d.ts","../node_modules/.prisma/client/index.d.ts","../node_modules/.prisma/client/default.d.ts","../node_modules/@prisma/client/default.d.ts","../src/database/prisma.service.ts","../src/database/prisma.module.ts","../node_modules/@types/jsonwebtoken/index.d.ts","../node_modules/@nestjs/jwt/dist/interfaces/jwt-module-options.interface.d.ts","../node_modules/@nestjs/jwt/dist/interfaces/index.d.ts","../node_modules/@nestjs/jwt/dist/jwt.errors.d.ts","../node_modules/@nestjs/jwt/dist/jwt.module.d.ts","../node_modules/@nestjs/jwt/dist/jwt.service.d.ts","../node_modules/@nestjs/jwt/dist/index.d.ts","../node_modules/@nestjs/jwt/index.d.ts","../node_modules/@nestjs/passport/dist/abstract.strategy.d.ts","../node_modules/@nestjs/passport/dist/interfaces/auth-module.options.d.ts","../node_modules/@nestjs/passport/dist/interfaces/type.interface.d.ts","../node_modules/@nestjs/passport/dist/interfaces/index.d.ts","../node_modules/@nestjs/passport/dist/auth.guard.d.ts","../node_modules/@nestjs/passport/dist/passport.module.d.ts","../node_modules/@types/send/index.d.ts","../node_modules/@types/qs/index.d.ts","../node_modules/@types/range-parser/index.d.ts","../node_modules/@types/express-serve-static-core/index.d.ts","../node_modules/@types/http-errors/index.d.ts","../node_modules/@types/mime/index.d.ts","../node_modules/@types/serve-static/node_modules/@types/send/index.d.ts","../node_modules/@types/serve-static/index.d.ts","../node_modules/@types/connect/index.d.ts","../node_modules/@types/body-parser/index.d.ts","../node_modules/@types/express/index.d.ts","../node_modules/@types/passport/index.d.ts","../node_modules/@nestjs/passport/dist/passport/passport.serializer.d.ts","../node_modules/@nestjs/passport/dist/passport/passport.strategy.d.ts","../node_modules/@nestjs/passport/dist/index.d.ts","../node_modules/@nestjs/passport/index.d.ts","../node_modules/@types/bcrypt/index.d.ts","../src/modules/auth/auth.service.ts","../src/modules/common/guards/jwt-auth.guard.ts","../node_modules/class-validator/types/validation/validationerror.d.ts","../node_modules/class-validator/types/validation/validatoroptions.d.ts","../node_modules/class-validator/types/validation-schema/validationschema.d.ts","../node_modules/class-validator/types/container.d.ts","../node_modules/class-validator/types/validation/validationarguments.d.ts","../node_modules/class-validator/types/decorator/validationoptions.d.ts","../node_modules/class-validator/types/decorator/common/allow.d.ts","../node_modules/class-validator/types/decorator/common/isdefined.d.ts","../node_modules/class-validator/types/decorator/common/isoptional.d.ts","../node_modules/class-validator/types/decorator/common/validate.d.ts","../node_modules/class-validator/types/validation/validatorconstraintinterface.d.ts","../node_modules/class-validator/types/decorator/common/validateby.d.ts","../node_modules/class-validator/types/decorator/common/validateif.d.ts","../node_modules/class-validator/types/decorator/common/validatenested.d.ts","../node_modules/class-validator/types/decorator/common/validatepromise.d.ts","../node_modules/class-validator/types/decorator/common/islatlong.d.ts","../node_modules/class-validator/types/decorator/common/islatitude.d.ts","../node_modules/class-validator/types/decorator/common/islongitude.d.ts","../node_modules/class-validator/types/decorator/common/equals.d.ts","../node_modules/class-validator/types/decorator/common/notequals.d.ts","../node_modules/class-validator/types/decorator/common/isempty.d.ts","../node_modules/class-validator/types/decorator/common/isnotempty.d.ts","../node_modules/class-validator/types/decorator/common/isin.d.ts","../node_modules/class-validator/types/decorator/common/isnotin.d.ts","../node_modules/class-validator/types/decorator/number/isdivisibleby.d.ts","../node_modules/class-validator/types/decorator/number/ispositive.d.ts","../node_modules/class-validator/types/decorator/number/isnegative.d.ts","../node_modules/class-validator/types/decorator/number/max.d.ts","../node_modules/class-validator/types/decorator/number/min.d.ts","../node_modules/class-validator/types/decorator/date/mindate.d.ts","../node_modules/class-validator/types/decorator/date/maxdate.d.ts","../node_modules/class-validator/types/decorator/string/contains.d.ts","../node_modules/class-validator/types/decorator/string/notcontains.d.ts","../node_modules/@types/validator/lib/isboolean.d.ts","../node_modules/@types/validator/lib/isemail.d.ts","../node_modules/@types/validator/lib/isfqdn.d.ts","../node_modules/@types/validator/lib/isiban.d.ts","../node_modules/@types/validator/lib/isiso31661alpha2.d.ts","../node_modules/@types/validator/lib/isiso4217.d.ts","../node_modules/@types/validator/lib/isiso6391.d.ts","../node_modules/@types/validator/lib/istaxid.d.ts","../node_modules/@types/validator/lib/isurl.d.ts","../node_modules/@types/validator/index.d.ts","../node_modules/class-validator/types/decorator/string/isalpha.d.ts","../node_modules/class-validator/types/decorator/string/isalphanumeric.d.ts","../node_modules/class-validator/types/decorator/string/isdecimal.d.ts","../node_modules/class-validator/types/decorator/string/isascii.d.ts","../node_modules/class-validator/types/decorator/string/isbase64.d.ts","../node_modules/class-validator/types/decorator/string/isbytelength.d.ts","../node_modules/class-validator/types/decorator/string/iscreditcard.d.ts","../node_modules/class-validator/types/decorator/string/iscurrency.d.ts","../node_modules/class-validator/types/decorator/string/isemail.d.ts","../node_modules/class-validator/types/decorator/string/isfqdn.d.ts","../node_modules/class-validator/types/decorator/string/isfullwidth.d.ts","../node_modules/class-validator/types/decorator/string/ishalfwidth.d.ts","../node_modules/class-validator/types/decorator/string/isvariablewidth.d.ts","../node_modules/class-validator/types/decorator/string/ishexcolor.d.ts","../node_modules/class-validator/types/decorator/string/ishexadecimal.d.ts","../node_modules/class-validator/types/decorator/string/ismacaddress.d.ts","../node_modules/class-validator/types/decorator/string/isip.d.ts","../node_modules/class-validator/types/decorator/string/isport.d.ts","../node_modules/class-validator/types/decorator/string/isisbn.d.ts","../node_modules/class-validator/types/decorator/string/isisin.d.ts","../node_modules/class-validator/types/decorator/string/isiso8601.d.ts","../node_modules/class-validator/types/decorator/string/isjson.d.ts","../node_modules/class-validator/types/decorator/string/isjwt.d.ts","../node_modules/class-validator/types/decorator/string/islowercase.d.ts","../node_modules/class-validator/types/decorator/string/ismobilephone.d.ts","../node_modules/class-validator/types/decorator/string/isiso31661alpha2.d.ts","../node_modules/class-validator/types/decorator/string/isiso31661alpha3.d.ts","../node_modules/class-validator/types/decorator/string/ismongoid.d.ts","../node_modules/class-validator/types/decorator/string/ismultibyte.d.ts","../node_modules/class-validator/types/decorator/string/issurrogatepair.d.ts","../node_modules/class-validator/types/decorator/string/isurl.d.ts","../node_modules/class-validator/types/decorator/string/isuuid.d.ts","../node_modules/class-validator/types/decorator/string/isfirebasepushid.d.ts","../node_modules/class-validator/types/decorator/string/isuppercase.d.ts","../node_modules/class-validator/types/decorator/string/length.d.ts","../node_modules/class-validator/types/decorator/string/maxlength.d.ts","../node_modules/class-validator/types/decorator/string/minlength.d.ts","../node_modules/class-validator/types/decorator/string/matches.d.ts","../node_modules/libphonenumber-js/types.d.cts","../node_modules/libphonenumber-js/max/index.d.cts","../node_modules/class-validator/types/decorator/string/isphonenumber.d.ts","../node_modules/class-validator/types/decorator/string/ismilitarytime.d.ts","../node_modules/class-validator/types/decorator/string/ishash.d.ts","../node_modules/class-validator/types/decorator/string/isissn.d.ts","../node_modules/class-validator/types/decorator/string/isdatestring.d.ts","../node_modules/class-validator/types/decorator/string/isbooleanstring.d.ts","../node_modules/class-validator/types/decorator/string/isnumberstring.d.ts","../node_modules/class-validator/types/decorator/string/isbase32.d.ts","../node_modules/class-validator/types/decorator/string/isbic.d.ts","../node_modules/class-validator/types/decorator/string/isbtcaddress.d.ts","../node_modules/class-validator/types/decorator/string/isdatauri.d.ts","../node_modules/class-validator/types/decorator/string/isean.d.ts","../node_modules/class-validator/types/decorator/string/isethereumaddress.d.ts","../node_modules/class-validator/types/decorator/string/ishsl.d.ts","../node_modules/class-validator/types/decorator/string/isiban.d.ts","../node_modules/class-validator/types/decorator/string/isidentitycard.d.ts","../node_modules/class-validator/types/decorator/string/isisrc.d.ts","../node_modules/class-validator/types/decorator/string/islocale.d.ts","../node_modules/class-validator/types/decorator/string/ismagneturi.d.ts","../node_modules/class-validator/types/decorator/string/ismimetype.d.ts","../node_modules/class-validator/types/decorator/string/isoctal.d.ts","../node_modules/class-validator/types/decorator/string/ispassportnumber.d.ts","../node_modules/class-validator/types/decorator/string/ispostalcode.d.ts","../node_modules/class-validator/types/decorator/string/isrfc3339.d.ts","../node_modules/class-validator/types/decorator/string/isrgbcolor.d.ts","../node_modules/class-validator/types/decorator/string/issemver.d.ts","../node_modules/class-validator/types/decorator/string/isstrongpassword.d.ts","../node_modules/class-validator/types/decorator/string/istimezone.d.ts","../node_modules/class-validator/types/decorator/string/isbase58.d.ts","../node_modules/class-validator/types/decorator/string/is-tax-id.d.ts","../node_modules/class-validator/types/decorator/string/is-iso4217-currency-code.d.ts","../node_modules/class-validator/types/decorator/typechecker/isboolean.d.ts","../node_modules/class-validator/types/decorator/typechecker/isdate.d.ts","../node_modules/class-validator/types/decorator/typechecker/isnumber.d.ts","../node_modules/class-validator/types/decorator/typechecker/isenum.d.ts","../node_modules/class-validator/types/decorator/typechecker/isint.d.ts","../node_modules/class-validator/types/decorator/typechecker/isstring.d.ts","../node_modules/class-validator/types/decorator/typechecker/isarray.d.ts","../node_modules/class-validator/types/decorator/typechecker/isobject.d.ts","../node_modules/class-validator/types/decorator/array/arraycontains.d.ts","../node_modules/class-validator/types/decorator/array/arraynotcontains.d.ts","../node_modules/class-validator/types/decorator/array/arraynotempty.d.ts","../node_modules/class-validator/types/decorator/array/arrayminsize.d.ts","../node_modules/class-validator/types/decorator/array/arraymaxsize.d.ts","../node_modules/class-validator/types/decorator/array/arrayunique.d.ts","../node_modules/class-validator/types/decorator/object/isnotemptyobject.d.ts","../node_modules/class-validator/types/decorator/object/isinstance.d.ts","../node_modules/class-validator/types/decorator/decorators.d.ts","../node_modules/class-validator/types/validation/validationtypes.d.ts","../node_modules/class-validator/types/validation/validator.d.ts","../node_modules/class-validator/types/register-decorator.d.ts","../node_modules/class-validator/types/metadata/validationmetadataargs.d.ts","../node_modules/class-validator/types/metadata/validationmetadata.d.ts","../node_modules/class-validator/types/metadata/constraintmetadata.d.ts","../node_modules/class-validator/types/metadata/metadatastorage.d.ts","../node_modules/class-validator/types/index.d.ts","../src/modules/auth/dto/login.dto.ts","../src/modules/auth/auth.controller.ts","../node_modules/@types/passport-strategy/index.d.ts","../node_modules/@types/passport-jwt/index.d.ts","../src/modules/auth/strategies/jwt.strategy.ts","../src/modules/auth/auth.module.ts","../src/modules/course/course-validation.service.ts","../src/modules/course/course.service.ts","../src/modules/common/decorators/roles.decorator.ts","../src/modules/common/guards/roles.guard.ts","../src/modules/course/course.controller.ts","../src/modules/course/course.module.ts","../src/modules/tenant/dto/tenant.dto.ts","../src/modules/tenant/tenant.service.ts","../src/modules/tenant/tenant.controller.ts","../src/modules/tenant/tenant.module.ts","../src/modules/common/decorators/log-operation.decorator.ts","../src/modules/common/interceptors/log.interceptor.ts","../src/modules/common/operation-log.service.ts","../src/modules/common/operation-log.controller.ts","../src/modules/common/common.module.ts","../node_modules/@nestjs/platform-express/interfaces/nest-express-body-parser-options.interface.d.ts","../node_modules/@nestjs/platform-express/interfaces/nest-express-body-parser.interface.d.ts","../node_modules/@nestjs/platform-express/interfaces/serve-static-options.interface.d.ts","../node_modules/@nestjs/platform-express/adapters/express-adapter.d.ts","../node_modules/@nestjs/platform-express/adapters/index.d.ts","../node_modules/@nestjs/platform-express/interfaces/nest-express-application.interface.d.ts","../node_modules/@nestjs/platform-express/interfaces/index.d.ts","../node_modules/@nestjs/platform-express/multer/interfaces/multer-options.interface.d.ts","../node_modules/@nestjs/platform-express/multer/interceptors/any-files.interceptor.d.ts","../node_modules/@nestjs/platform-express/multer/interceptors/file-fields.interceptor.d.ts","../node_modules/@nestjs/platform-express/multer/interceptors/file.interceptor.d.ts","../node_modules/@nestjs/platform-express/multer/interceptors/files.interceptor.d.ts","../node_modules/@nestjs/platform-express/multer/interceptors/no-files.interceptor.d.ts","../node_modules/@nestjs/platform-express/multer/interceptors/index.d.ts","../node_modules/@nestjs/platform-express/multer/interfaces/files-upload-module.interface.d.ts","../node_modules/@nestjs/platform-express/multer/interfaces/index.d.ts","../node_modules/@nestjs/platform-express/multer/multer.module.d.ts","../node_modules/@nestjs/platform-express/multer/index.d.ts","../node_modules/@nestjs/platform-express/index.d.ts","../node_modules/@types/multer/index.d.ts","../src/modules/file-upload/file-upload.service.ts","../src/modules/file-upload/file-upload.controller.ts","../src/modules/file-upload/file-upload.module.ts","../src/modules/teacher-course/teacher-course.service.ts","../src/modules/teacher-course/teacher-course.controller.ts","../src/modules/teacher-course/teacher-course.module.ts","../src/modules/lesson/dto/create-lesson.dto.ts","../src/modules/lesson/dto/finish-lesson.dto.ts","../src/modules/lesson/lesson.service.ts","../src/modules/lesson/lesson.controller.ts","../src/modules/lesson/lesson.module.ts","../src/modules/school/dto/create-teacher.dto.ts","../src/modules/school/dto/create-student.dto.ts","../src/modules/school/dto/create-class.dto.ts","../src/modules/school/dto/class-teacher.dto.ts","../src/modules/school/dto/schedule.dto.ts","../node_modules/xlsx/types/index.d.ts","../src/modules/school/school.service.ts","../src/modules/school/school.controller.ts","../src/modules/school/stats.service.ts","../src/modules/school/stats.controller.ts","../src/modules/school/package.controller.ts","../src/modules/school/settings.service.ts","../src/modules/school/settings.controller.ts","../src/modules/school/export.service.ts","../src/modules/school/export.controller.ts","../src/modules/school/school.module.ts","../src/modules/resource/dto/create-resource.dto.ts","../src/modules/resource/resource.service.ts","../src/modules/resource/resource.controller.ts","../src/modules/resource/resource.module.ts","../src/modules/growth/dto/create-growth.dto.ts","../src/modules/growth/growth.service.ts","../src/modules/growth/growth.controller.ts","../src/modules/growth/growth.module.ts","../src/modules/task/dto/create-task.dto.ts","../src/modules/task/task.service.ts","../node_modules/@nestjs/schedule/dist/enums/cron-expression.enum.d.ts","../node_modules/@nestjs/schedule/dist/enums/index.d.ts","../node_modules/@types/luxon/src/zone.d.ts","../node_modules/@types/luxon/src/settings.d.ts","../node_modules/@types/luxon/src/_util.d.ts","../node_modules/@types/luxon/src/misc.d.ts","../node_modules/@types/luxon/src/duration.d.ts","../node_modules/@types/luxon/src/interval.d.ts","../node_modules/@types/luxon/src/datetime.d.ts","../node_modules/@types/luxon/src/info.d.ts","../node_modules/@types/luxon/src/luxon.d.ts","../node_modules/@types/luxon/index.d.ts","../node_modules/cron/dist/errors.d.ts","../node_modules/cron/dist/constants.d.ts","../node_modules/cron/dist/job.d.ts","../node_modules/cron/dist/types/utils.d.ts","../node_modules/cron/dist/types/cron.types.d.ts","../node_modules/cron/dist/time.d.ts","../node_modules/cron/dist/index.d.ts","../node_modules/@nestjs/schedule/dist/decorators/cron.decorator.d.ts","../node_modules/@nestjs/schedule/dist/decorators/interval.decorator.d.ts","../node_modules/@nestjs/schedule/dist/decorators/timeout.decorator.d.ts","../node_modules/@nestjs/schedule/dist/decorators/index.d.ts","../node_modules/@nestjs/schedule/dist/interfaces/schedule-module-options.interface.d.ts","../node_modules/@nestjs/schedule/dist/schedule.module.d.ts","../node_modules/@nestjs/schedule/dist/scheduler.registry.d.ts","../node_modules/@nestjs/schedule/dist/index.d.ts","../node_modules/@nestjs/schedule/index.d.ts","../src/modules/notification/notification.service.ts","../src/modules/notification/schedule-notification.service.ts","../src/modules/task/task.controller.ts","../src/modules/notification/notification.controller.ts","../src/modules/notification/notification.module.ts","../src/modules/task/task.module.ts","../src/modules/parent/parent.service.ts","../src/modules/parent/parent.controller.ts","../src/modules/parent/parent.module.ts","../node_modules/exceljs/index.d.ts","../src/modules/export/export.service.ts","../src/modules/export/export.controller.ts","../src/modules/export/export.module.ts","../src/modules/admin/admin-settings.service.ts","../src/modules/admin/admin-settings.controller.ts","../src/modules/admin/admin-stats.service.ts","../src/modules/admin/admin-stats.controller.ts","../src/modules/admin/admin.module.ts","../src/app.module.ts","../src/common/filters/http-exception.filter.ts","../src/main.ts","../src/modules/school/dto/import-students.dto.ts","../prisma/seed.ts","../node_modules/@babel/types/lib/index.d.ts","../node_modules/@types/babel__generator/index.d.ts","../node_modules/@babel/parser/typings/babel-parser.d.ts","../node_modules/@types/babel__template/index.d.ts","../node_modules/@types/babel__traverse/index.d.ts","../node_modules/@types/babel__core/index.d.ts","../node_modules/@types/estree/index.d.ts","../node_modules/@types/json-schema/index.d.ts","../node_modules/@types/eslint/use-at-your-own-risk.d.ts","../node_modules/@types/eslint/index.d.ts","../node_modules/@types/eslint-scope/index.d.ts","../node_modules/@types/graceful-fs/index.d.ts","../node_modules/@types/istanbul-lib-coverage/index.d.ts","../node_modules/@types/istanbul-lib-report/index.d.ts","../node_modules/@types/istanbul-reports/index.d.ts","../node_modules/@jest/expect-utils/build/index.d.ts","../node_modules/chalk/index.d.ts","../node_modules/@sinclair/typebox/typebox.d.ts","../node_modules/@jest/schemas/build/index.d.ts","../node_modules/pretty-format/build/index.d.ts","../node_modules/jest-diff/build/index.d.ts","../node_modules/jest-matcher-utils/build/index.d.ts","../node_modules/expect/build/index.d.ts","../node_modules/@types/jest/index.d.ts","../node_modules/@types/passport-local/index.d.ts","../node_modules/@types/semver/functions/inc.d.ts","../node_modules/@types/semver/classes/semver.d.ts","../node_modules/@types/semver/functions/parse.d.ts","../node_modules/@types/semver/functions/valid.d.ts","../node_modules/@types/semver/functions/clean.d.ts","../node_modules/@types/semver/functions/diff.d.ts","../node_modules/@types/semver/functions/major.d.ts","../node_modules/@types/semver/functions/minor.d.ts","../node_modules/@types/semver/functions/patch.d.ts","../node_modules/@types/semver/functions/prerelease.d.ts","../node_modules/@types/semver/functions/compare.d.ts","../node_modules/@types/semver/functions/rcompare.d.ts","../node_modules/@types/semver/functions/compare-loose.d.ts","../node_modules/@types/semver/functions/compare-build.d.ts","../node_modules/@types/semver/functions/sort.d.ts","../node_modules/@types/semver/functions/rsort.d.ts","../node_modules/@types/semver/functions/gt.d.ts","../node_modules/@types/semver/functions/lt.d.ts","../node_modules/@types/semver/functions/eq.d.ts","../node_modules/@types/semver/functions/neq.d.ts","../node_modules/@types/semver/functions/gte.d.ts","../node_modules/@types/semver/functions/lte.d.ts","../node_modules/@types/semver/functions/cmp.d.ts","../node_modules/@types/semver/functions/coerce.d.ts","../node_modules/@types/semver/classes/comparator.d.ts","../node_modules/@types/semver/classes/range.d.ts","../node_modules/@types/semver/functions/satisfies.d.ts","../node_modules/@types/semver/ranges/max-satisfying.d.ts","../node_modules/@types/semver/ranges/min-satisfying.d.ts","../node_modules/@types/semver/ranges/to-comparators.d.ts","../node_modules/@types/semver/ranges/min-version.d.ts","../node_modules/@types/semver/ranges/valid.d.ts","../node_modules/@types/semver/ranges/outside.d.ts","../node_modules/@types/semver/ranges/gtr.d.ts","../node_modules/@types/semver/ranges/ltr.d.ts","../node_modules/@types/semver/ranges/intersects.d.ts","../node_modules/@types/semver/ranges/simplify.d.ts","../node_modules/@types/semver/ranges/subset.d.ts","../node_modules/@types/semver/internals/identifiers.d.ts","../node_modules/@types/semver/index.d.ts","../node_modules/@types/stack-utils/index.d.ts","../node_modules/@types/strip-bom/index.d.ts","../node_modules/@types/strip-json-comments/index.d.ts","../node_modules/@types/yargs-parser/index.d.ts","../node_modules/@types/yargs/index.d.ts"],"fileIdsList":[[416,463,629],[416,463,628],[416,463,935],[416,463],[416,463,952],[307,416,463],[402,416,463],[57,308,309,310,311,312,313,314,315,316,317,318,319,320,416,463],[260,294,416,463],[267,416,463],[257,307,402,416,463],[325,326,327,328,329,330,331,332,416,463],[262,416,463],[307,402,416,463],[321,324,333,416,463],[322,323,416,463],[298,416,463],[262,263,264,265,416,463],[335,416,463],[280,416,463],[335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,416,463],[363,416,463],[358,359,416,463],[360,362,416,463,493],[56,266,307,334,357,362,364,371,394,399,401,416,463],[62,260,416,463],[61,416,463],[62,252,253,416,463,559,564],[252,260,416,463],[61,251,416,463],[260,373,416,463],[254,375,416,463],[251,255,416,463],[61,307,416,463],[259,260,416,463],[272,416,463],[274,275,276,277,278,416,463],[266,416,463],[266,267,282,286,416,463],[280,281,287,288,289,416,463],[58,59,60,61,62,252,253,254,255,256,257,258,259,260,261,267,272,273,279,286,290,291,292,294,302,303,304,305,306,416,463],[285,416,463],[268,269,270,271,416,463],[260,268,269,416,463],[260,266,267,416,463],[260,270,416,463],[260,298,416,463],[293,295,296,297,298,299,300,301,416,463],[58,260,416,463],[294,416,463],[58,260,293,297,299,416,463],[269,416,463],[295,416,463],[260,294,295,296,416,463],[284,416,463],[260,264,284,302,416,463],[282,283,285,416,463],[256,258,267,273,282,287,303,304,307,416,463],[62,256,258,261,303,304,416,463],[265,416,463],[251,416,463],[284,307,365,369,416,463],[369,370,416,463],[307,365,416,463],[307,365,366,416,463],[366,367,416,463],[366,367,368,416,463],[261,416,463],[386,387,416,463],[386,416,463],[387,388,389,390,391,392,416,463],[385,416,463],[377,387,416,463],[387,388,389,390,391,416,463],[261,386,387,390,416,463],[372,378,379,380,381,382,383,384,393,416,463],[261,307,378,416,463],[261,377,416,463],[261,377,402,416,463],[254,260,261,373,374,375,376,377,416,463],[251,307,373,374,395,416,463],[307,373,416,463],[397,416,463],[334,395,416,463],[395,396,398,416,463],[284,361,416,463],[293,416,463],[266,307,416,463],[400,416,463],[402,416,463,514],[251,404,409,416,463],[403,409,416,463,514,515,516,519],[409,416,463],[410,416,463,512],[404,410,416,463,513],[405,406,407,408,416,463],[416,463,517,518],[409,416,463,514,520],[416,463,520],[282,286,307,402,416,463],[416,463,528],[307,402,416,463,548,549],[416,463,530],[402,416,463,542,547,548],[416,463,552,553],[62,307,416,463,543,548,562],[402,416,463,529,555],[61,402,416,463,556,559],[307,416,463,543,548,550,561,563,567],[61,416,463,565,566],[416,463,556],[251,307,402,416,463,570],[307,402,416,463,543,548,550,562],[416,463,569,571,572],[307,416,463,548],[416,463,548],[307,402,416,463,570],[61,307,402,416,463],[307,402,416,463,542,543,548,568,570,573,576,581,582,595,596],[251,416,463,528],[416,463,555,558,597],[416,463,582,594],[56,416,463,529,550,551,554,557,589,594,598,601,605,606,607,609,611,617,619],[307,402,416,463,536,544,547,548],[307,416,463,540],[307,402,416,463,530,539,540,541,542,547,548,550,620],[416,463,542,543,546,548,584,593],[307,402,416,463,535,547,548],[416,463,583],[402,416,463,543,548],[402,416,463,536,543,547,588],[307,402,416,463,530,535,547],[402,416,463,541,542,546,586,590,591,592],[402,416,463,536,543,544,545,547,548],[260,402,416,463],[307,416,463,530,543,546,548],[416,463,547],[416,463,532,533,534,543,547,548,587],[416,463,539,588,599,600],[402,416,463,530,548],[402,416,463,530],[416,463,531,532,533,534,537,539],[416,463,536],[416,463,538,539],[402,416,463,531,532,533,534,537,538],[416,463,574,575],[307,416,463,543,548,550,562],[416,463,585],[291,416,463],[272,307,416,463,602,603],[416,463,604],[307,416,463,550],[307,416,463,543,550],[285,307,402,416,463,536,543,544,545,547,548],[282,284,307,402,416,463,529,543,550,588,606],[285,286,402,416,463,528,608],[416,463,578,579,580],[402,416,463,577],[416,463,610],[402,416,463,491],[416,463,613,615,616],[416,463,612],[416,463,614],[402,416,463,542,547,613],[416,463,560],[307,402,416,463,530,543,547,548,550,585,586,588,589],[416,463,618],[416,463,634,636,637,638,639],[416,463,635],[402,416,463,511,634],[402,416,463,635],[416,463,511,634,636],[416,463,640],[402,416,463,643,645],[416,463,642,645,646,647,660,661],[416,463,643,644],[402,416,463,643],[416,463,659],[416,463,645],[416,463,662],[282,286,307,402,416,463,477,479,528,827,828,829],[416,463,830],[416,463,831,833,844],[416,463,827,828,832],[402,416,463,477,479,658,827,828,829],[416,463,477],[416,463,840,842,843],[402,416,463,834],[416,463,835,836,837,838,839],[307,416,463,834],[416,463,841],[402,416,463,841],[416,463,902],[416,463,903,904,905],[416,463,884],[416,463,885,906,907,908,909],[402,416,463,907],[416,463,910],[416,463,523,525,526,527,621,622,623,625,626],[307,416,463,523,524],[416,463,522],[416,463,525],[402,416,463,523,524,525,620],[402,416,463,525],[402,416,463,523,525],[402,416,463,522,523,624],[416,463,630],[416,463,935,936,937,938,939],[416,463,935,937],[416,463,511],[416,463,477,511,656],[416,463,477,511],[416,463,941,944],[416,463,941,942,943],[416,463,944],[416,463,474,477,511,648,649,650],[416,463,649,651,655,657],[416,463,475,511],[416,463,947],[416,463,948],[416,463,954,957],[416,463,468,511],[416,463,894],[416,463,887],[416,463,886,888,890,891,895],[416,463,888,889,892],[416,463,886,889,892],[416,463,888,890,892],[416,463,886,887,889,890,891,892,893],[416,463,886,892],[416,463,888],[416,463,493,658],[416,460,463],[416,462,463],[416,463,468,496],[416,463,464,469,474,482,493,504],[416,463,464,465,474,482],[411,412,413,416,463],[416,463,466,505],[416,463,467,468,475,483],[416,463,468,493,501],[416,463,469,471,474,482],[416,462,463,470],[416,463,471,472],[416,463,473,474],[416,462,463,474],[416,463,474,475,476,493,504],[416,463,474,475,476,489,493,496],[416,463,471,474,477,482,493,504],[416,463,474,475,477,478,482,493,501,504],[416,463,477,479,493,501,504],[416,463,474,480],[416,463,481,504,509],[416,463,471,474,482,493],[416,463,483],[416,463,484],[416,462,463,485],[416,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510],[416,463,487],[416,463,488],[416,463,474,489,490],[416,463,489,491,505,507],[416,463,474,493,494,496],[416,463,495,496],[416,463,493,494],[416,463,496],[416,463,497],[416,460,463,493,498],[416,463,474,499,500],[416,463,499,500],[416,463,468,482,493,501],[416,463,502],[463],[414,415,416,417,418,419,420,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510],[416,463,482,503],[416,463,477,488,504],[416,463,468,505],[416,463,493,506],[416,463,481,507],[416,463,508],[416,458,463],[416,458,463,474,476,485,493,496,504,507,509],[416,463,493,510],[416,463,634,808],[416,463,658,659,808],[416,463,658,659],[416,463,477,658],[416,463,961,999],[416,463,961,984,999],[416,463,960,999],[416,463,999],[416,463,961],[416,463,961,985,999],[416,463,960,961,962,963,964,965,966,967,968,969,970,971,972,973,974,975,976,977,978,979,980,981,982,983,984,985,986,987,988,989,990,991,992,993,994,995,996,997,998],[416,463,985,999],[416,463,475,493,511],[416,463,477,511,652,654],[416,463,475,493,511,653],[416,463,700,701,702,703,704,705,706,707,708],[416,463,1003],[416,463,672],[416,463,671,672,677],[416,463,673,674,675,676,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796],[416,463,672,709],[416,463,672,749],[416,463,671],[416,463,667,668,669,670,671,672,677,797,798,799,800,804],[416,463,677],[416,463,669,802,803],[416,463,671,801],[416,463,672,677],[416,463,667,668],[416,463,895,898,900,901],[416,463,895,900,901],[416,463,895,896,900],[416,463,464,895,897,898,899],[416,463,474,493],[416,463,950,956],[416,463,954],[416,463,951,955],[416,463,748],[416,463,953],[63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,79,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,119,120,121,122,123,124,125,126,127,128,129,130,132,133,134,135,136,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,182,183,184,186,195,197,198,199,200,201,202,204,205,207,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,416,463],[108,416,463],[64,67,416,463],[66,416,463],[66,67,416,463],[63,64,65,67,416,463],[64,66,67,224,416,463],[67,416,463],[63,66,108,416,463],[66,67,224,416,463],[66,232,416,463],[64,66,67,416,463],[76,416,463],[99,416,463],[120,416,463],[66,67,108,416,463],[67,115,416,463],[66,67,108,126,416,463],[66,67,126,416,463],[67,167,416,463],[67,108,416,463],[63,67,185,416,463],[63,67,186,416,463],[208,416,463],[192,194,416,463],[203,416,463],[192,416,463],[63,67,185,192,193,416,463],[185,186,194,416,463],[206,416,463],[63,67,192,193,194,416,463],[65,66,67,416,463],[63,67,416,463],[64,66,186,187,188,189,416,463],[108,186,187,188,189,416,463],[186,188,416,463],[66,187,188,190,191,195,416,463],[63,66,416,463],[67,210,416,463],[68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,109,110,111,112,113,114,116,117,118,119,120,121,122,123,124,125,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,416,463],[196,416,463],[416,430,434,463,504],[416,430,463,493,504],[416,425,463],[416,427,430,463,501,504],[416,463,482,501],[416,425,463,511],[416,427,430,463,482,504],[416,422,423,426,429,463,474,493,504],[416,430,437,463],[416,422,428,463],[416,430,451,452,463],[416,426,430,463,496,504,511],[416,451,463,511],[416,424,425,463,511],[416,430,463],[416,424,425,426,427,428,429,430,431,432,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,452,453,454,455,456,457,463],[416,430,445,463],[416,430,437,438,463],[416,428,430,438,439,463],[416,429,463],[416,422,425,430,463],[416,430,434,438,439,463],[416,434,463],[416,428,430,433,463,504],[416,422,427,430,437,463],[416,463,493],[416,425,430,451,463,509,511],[416,463,631,664],[402,416,463,521,627,633,811,817,821,826,849,852,857,873,877,881,916,917,920,924,929],[402,416,463,475,484,658],[402,416,463,632],[402,416,463,631],[402,416,463,484,521,620,845,930,931],[402,416,463,666,814,815,925],[402,416,463,666,814,815,927],[402,416,463,633,925,926,927,928],[402,416,463,665,666,806],[402,416,463,521,633,641,663,665,807,810],[402,416,463,632,641,664],[416,463,805],[402,416,463,521,663,809],[402,416,463,666,815,823,824,825],[402,416,463,620,663],[402,416,463,620,814],[251,402,416,463,620,632,822],[402,416,463,666,814,815,824],[402,416,463,666,813,814,815],[402,416,463,633,812,813,816],[402,416,463,632,812],[402,416,463,658,666,814,815,922],[402,416,463,922,923],[402,416,463,632,921],[402,416,463,845,846,847],[402,416,463,847,848],[402,416,463,475,484],[402,416,463,666,814,815,878,879],[402,416,463,879,880],[402,416,463,632,878],[402,416,463,666,814,815,853,854,855],[402,416,463,855,856],[402,416,463,632,853,854],[402,416,463,666,814,815,912],[402,416,463,911,912,913,915],[402,416,463,632,911,912],[402,416,463,666,814,815,918],[402,416,463,918,919],[402,416,463,666,814,815,874,875],[402,416,463,875,876],[402,416,463,632,874],[402,416,463,658,666,814,815,871],[402,416,463,632,863],[402,416,463,632,666,814,815],[402,416,463,666,814,815,822,823,845,858,859,860,861,862,864],[402,416,463,864,865,866,867,868,869,870,871,872],[402,416,463,632,664,858,859,860,861,862,863],[402,416,463,666,814,815,869],[402,416,463,666,814,815,866],[402,416,463,666,814,815,882,883,913],[402,416,463,883,914,916],[402,416,463,632,882],[402,416,463,666,814,815,850],[402,416,463,850,851],[402,416,463,666,814,815,818,819],[402,416,463,633,819,820],[402,416,463,632,664,818]],"fileInfos":[{"version":"44e584d4f6444f58791784f1d530875970993129442a847597db702a073ca68c","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"9a68c0c07ae2fa71b44384a839b7b8d81662a236d4b9ac30916718f7510b1b2d","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"9e8ca8ed051c2697578c023d9c29d6df689a083561feba5c14aedee895853999","affectsGlobalScope":true,"impliedFormat":1},{"version":"69e65d976bf166ce4a9e6f6c18f94d2424bf116e90837ace179610dbccad9b42","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7a3c8b952931daebdfc7a2897c53c0a1c73624593fa070e46bd537e64dcd20a","affectsGlobalScope":true,"impliedFormat":1},{"version":"80e18897e5884b6723488d4f5652167e7bb5024f946743134ecc4aa4ee731f89","affectsGlobalScope":true,"impliedFormat":1},{"version":"cd034f499c6cdca722b60c04b5b1b78e058487a7085a8e0d6fb50809947ee573","affectsGlobalScope":true,"impliedFormat":1},{"version":"6920e1448680767498a0b77c6a00a8e77d14d62c3da8967b171f1ddffa3c18e4","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"45d8ccb3dfd57355eb29749919142d4321a0aa4df6acdfc54e30433d7176600a","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"93495ff27b8746f55d19fcbcdbaccc99fd95f19d057aed1bd2c0cafe1335fbf0","affectsGlobalScope":true,"impliedFormat":1},{"version":"6fc23bb8c3965964be8c597310a2878b53a0306edb71d4b5a4dfe760186bcc01","affectsGlobalScope":true,"impliedFormat":1},{"version":"ea011c76963fb15ef1cdd7ce6a6808b46322c527de2077b6cfdf23ae6f5f9ec7","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"4738f2420687fd85629c9efb470793bb753709c2379e5f85bc1815d875ceadcd","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"9fc46429fbe091ac5ad2608c657201eb68b6f1b8341bd6d670047d32ed0a88fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"1a94697425a99354df73d9c8291e2ecd4dddd370aed4023c2d6dee6cccb32666","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"bf14a426dbbf1022d11bd08d6b8e709a2e9d246f0c6c1032f3b2edb9a902adbe","affectsGlobalScope":true,"impliedFormat":1},{"version":"e3f9fc0ec0b96a9e642f11eda09c0be83a61c7b336977f8b9fdb1e9788e925fe","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"479553e3779be7d4f68e9f40cdb82d038e5ef7592010100410723ceced22a0f7","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"d3d7b04b45033f57351c8434f60b6be1ea71a2dfec2d0a0c3c83badbb0e3e693","affectsGlobalScope":true,"impliedFormat":1},{"version":"956d27abdea9652e8368ce029bb1e0b9174e9678a273529f426df4b3d90abd60","affectsGlobalScope":true,"impliedFormat":1},{"version":"33358442698bb565130f52ba79bfd3d4d484ac85fe33f3cb1759c54d18201393","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"4a66df3ab5de5cfcda11538cffddd67ff6a174e003788e270914c1e0248483cf","impliedFormat":1},{"version":"8d6d51a5118d000ed3bfe6e1dd1335bebfff3fef23cd2af2f84a24d30f90cc90","affectsGlobalScope":true,"impliedFormat":1},{"version":"6d8dedbec739bc79642c1e96e9bfc0b83b25b104a0486aebf016fc7b85b39f48","impliedFormat":1},{"version":"e89535c3ec439608bcd0f68af555d0e5ddf121c54abe69343549718bd7506b9c","impliedFormat":1},{"version":"622a984b60c294ffb2f9152cf1d4d12e91d2b733d820eec949cf54d63a3c1025","impliedFormat":1},{"version":"81aae92abdeaccd9c1723cef39232c90c1aed9d9cf199e6e2a523b7d8e058a11","impliedFormat":1},{"version":"a63a6c6806a1e519688ef7bd8ca57be912fc0764485119dbd923021eb4e79665","impliedFormat":1},{"version":"75b57b109d774acca1e151df21cf5cb54c7a1df33a273f0457b9aee4ebd36fb9","impliedFormat":1},{"version":"073ca26c96184db9941b5ec0ddea6981c9b816156d9095747809e524fdd90e35","impliedFormat":1},{"version":"e41d17a2ec23306d953cda34e573ed62954ca6ea9b8c8b74e013d07a6886ce47","impliedFormat":1},{"version":"241bd4add06f06f0699dcd58f3b334718d85e3045d9e9d4fa556f11f4d1569c1","impliedFormat":1},{"version":"2ae3787e1498b20aad1b9c2ee9ea517ec30e89b70d242d8e3e52d1e091039695","impliedFormat":1},{"version":"c7c72c4cffb1bc83617eefed71ed68cc89df73cab9e19507ccdecb3e72b4967e","affectsGlobalScope":true,"impliedFormat":1},{"version":"b8bff8a60af0173430b18d9c3e5c443eaa3c515617210c0c7b3d2e1743c19ecb","impliedFormat":1},{"version":"38b38db08e7121828294dec10957a7a9ff263e33e2a904b346516d4a4acca482","impliedFormat":1},{"version":"a76ebdf2579e68e4cfe618269c47e5a12a4e045c2805ed7f7ab37af8daa6b091","impliedFormat":1},{"version":"8a2aaea564939c22be05d665cc955996721bad6d43148f8fa21ae8f64afecd37","impliedFormat":1},{"version":"e59d36b7b6e8ba2dd36d032a5f5c279d2460968c8b4e691ca384f118fb09b52a","impliedFormat":1},{"version":"e96885c0684c9042ec72a9a43ef977f6b4b4a2728f4b9e737edcbaa0c74e5bf6","impliedFormat":1},{"version":"95950a187596e206d32d5d9c7b932901088c65ed8f9040e614aa8e321e0225ef","impliedFormat":1},{"version":"89e061244da3fc21b7330f4bd32f47c1813dd4d7f1dc3d0883d88943f035b993","impliedFormat":1},{"version":"e46558c2e04d06207b080138678020448e7fc201f3d69c2601b0d1456105f29a","impliedFormat":1},{"version":"71549375db52b1163411dba383b5f4618bdf35dc57fa327a1c7d135cf9bf67d1","impliedFormat":1},{"version":"7e6b2d61d6215a4e82ea75bc31a80ebb8ad0c2b37a60c10c70dd671e8d9d6d5d","impliedFormat":1},{"version":"78bea05df2896083cca28ed75784dde46d4b194984e8fc559123b56873580a23","impliedFormat":1},{"version":"5dd04ced37b7ea09f29d277db11f160df7fd73ba8b9dba86cb25552e0653a637","impliedFormat":1},{"version":"f74b81712e06605677ae1f061600201c425430151f95b5ef4d04387ad7617e6a","impliedFormat":1},{"version":"9a72847fcf4ac937e352d40810f7b7aec7422d9178451148296cf1aa19467620","impliedFormat":1},{"version":"3ae18f60e0b96fa1e025059b7d25b3247ba4dcb5f4372f6d6e67ce2adac74eac","impliedFormat":1},{"version":"2b9260f44a2e071450ae82c110f5dc8f330c9e5c3e85567ed97248330f2bf639","impliedFormat":1},{"version":"4f196e13684186bda6f5115fc4677a87cf84a0c9c4fc17b8f51e0984f3697b6d","impliedFormat":1},{"version":"61419f2c5822b28c1ea483258437c1faab87d00c6f84481aa22afb3380d8e9a4","impliedFormat":1},{"version":"64479aee03812264e421c0bf5104a953ca7b02740ba80090aead1330d0effe91","impliedFormat":1},{"version":"0521108c9f8ddb17654a0a54dae6ba9667c99eddccfd6af5748113e022d1c37a","impliedFormat":1},{"version":"c5570e504be103e255d80c60b56c367bf45d502ca52ee35c55dec882f6563b5c","impliedFormat":1},{"version":"ee764e6e9a7f2b987cc1a2c0a9afd7a8f4d5ebc4fdb66ad557a7f14a8c2bd320","impliedFormat":1},{"version":"0520b5093712c10c6ef23b5fea2f833bf5481771977112500045e5ea7e8e2b69","impliedFormat":1},{"version":"5c3cf26654cf762ac4d7fd7b83f09acfe08eef88d2d6983b9a5a423cb4004ca3","impliedFormat":1},{"version":"e60fa19cf7911c1623b891155d7eb6b7e844e9afdf5738e3b46f3b687730a2bd","impliedFormat":1},{"version":"b1fd72ff2bb0ba91bb588f3e5329f8fc884eb859794f1c4657a2bfa122ae54d0","impliedFormat":1},{"version":"6cf42a4f3cfec648545925d43afaa8bb364ac10a839ffed88249da109361b275","impliedFormat":1},{"version":"d7058e75920120b142a9d57be25562a3cd9a936269fd52908505f530105f2ec4","impliedFormat":1},{"version":"6df52b70d7f7702202f672541a5f4a424d478ee5be51a9d37b8ccbe1dbf3c0f2","impliedFormat":1},{"version":"0ca7f997e9a4d8985e842b7c882e521b6f63233c4086e9fe79dd7a9dc4742b5e","impliedFormat":1},{"version":"91046b5c6b55d3b194c81fd4df52f687736fad3095e9d103ead92bb64dc160ee","impliedFormat":1},{"version":"db5704fdad56c74dfc5941283c1182ed471bd17598209d3ac4a49faa72e43cfc","impliedFormat":1},{"version":"758e8e89559b02b81bc0f8fd395b17ad5aff75490c862cbe369bb1a3d1577c40","impliedFormat":1},{"version":"2ee64342c077b1868f1834c063f575063051edd6e2964257d34aad032d6b657c","impliedFormat":1},{"version":"6f6b4b3d670b6a5f0e24ea001c1b3d36453c539195e875687950a178f1730fa7","impliedFormat":1},{"version":"a472a1d3f25ce13a1d44911cd3983956ac040ce2018e155435ea34afb25f864c","impliedFormat":1},{"version":"b48b83a86dd9cfe36f8776b3ff52fcd45b0e043c0538dc4a4b149ba45fe367b9","impliedFormat":1},{"version":"792de5c062444bd2ee0413fb766e57e03cce7cdaebbfc52fc0c7c8e95069c96b","impliedFormat":1},{"version":"a79e3e81094c7a04a885bad9b049c519aace53300fb8a0fe4f26727cb5a746ce","impliedFormat":1},{"version":"93181bac0d90db185bb730c95214f6118ae997fe836a98a49664147fbcaf1988","impliedFormat":1},{"version":"8a4e89564d8ea66ad87ee3762e07540f9f0656a62043c910d819b4746fc429c5","impliedFormat":1},{"version":"b9011d99942889a0f95e120d06b698c628b0b6fdc3e6b7ecb459b97ed7d5bcc6","impliedFormat":1},{"version":"4d639cbbcc2f8f9ce6d55d5d503830d6c2556251df332dc5255d75af53c8a0e7","impliedFormat":1},{"version":"cdb48277f600ab5f429ecf1c5ea046683bc6b9f73f3deab9a100adac4b34969c","impliedFormat":1},{"version":"75be84956a29040a1afbe864c0a7a369dfdb739380072484eff153905ef867ee","impliedFormat":1},{"version":"b06b4adc2ae03331a92abd1b19af8eb91ec2bf8541747ee355887a167d53145e","impliedFormat":1},{"version":"c54166a85bd60f86d1ebb90ce0117c0ecb850b8a33b366691629fdf26f1bbbd8","impliedFormat":1},{"version":"0d417c15c5c635384d5f1819cc253a540fe786cc3fda32f6a2ae266671506a21","impliedFormat":1},{"version":"80f23f1d60fbed356f726b3b26f9d348dddbb34027926d10d59fad961e70a730","impliedFormat":1},{"version":"cb59317243a11379a101eb2f27b9df1022674c3df1df0727360a0a3f963f523b","impliedFormat":1},{"version":"cc20bb2227dd5de0aab0c8d697d1572f8000550e62c7bf5c92f212f657dd88c5","impliedFormat":1},{"version":"06b8a7d46195b6b3980e523ef59746702fd210b71681a83a5cf73799623621f9","impliedFormat":1},{"version":"860e4405959f646c101b8005a191298b2381af8f33716dc5f42097e4620608f8","impliedFormat":1},{"version":"f7e32adf714b8f25d3c1783473abec3f2e82d5724538d8dcf6f51baaaff1ca7a","impliedFormat":1},{"version":"d0da80c845999a16c24d0783033fb5366ada98df17867c98ad433ede05cd87fd","impliedFormat":1},{"version":"bfbf80f9cd4558af2d7b2006065340aaaced15947d590045253ded50aabb9bc5","impliedFormat":1},{"version":"fd9a991b51870325e46ebb0e6e18722d313f60cd8e596e645ec5ac15b96dbf4e","impliedFormat":1},{"version":"c3bd2b94e4298f81743d92945b80e9b56c1cdfb2bef43c149b7106a2491b1fc9","impliedFormat":1},{"version":"a246cce57f558f9ebaffd55c1e5673da44ea603b4da3b2b47eb88915d30a9181","impliedFormat":1},{"version":"d993eacc103c5a065227153c9aae8acea3a4322fe1a169ee7c70b77015bf0bb2","impliedFormat":1},{"version":"fc2b03d0c042aa1627406e753a26a1eaad01b3c496510a78016822ef8d456bb6","impliedFormat":1},{"version":"063c7ebbe756f0155a8b453f410ca6b76ffa1bbc1048735bcaf9c7c81a1ce35f","impliedFormat":1},{"version":"314e402cd481370d08f63051ae8b8c8e6370db5ee3b8820eeeaaf8d722a6dac6","impliedFormat":1},{"version":"9669075ac38ce36b638b290ba468233980d9f38bdc62f0519213b2fd3e2552ec","impliedFormat":1},{"version":"4d123de012c24e2f373925100be73d50517ac490f9ed3578ac82d0168bfbd303","impliedFormat":1},{"version":"656c9af789629aa36b39092bee3757034009620439d9a39912f587538033ce28","impliedFormat":1},{"version":"3ac3f4bdb8c0905d4c3035d6f7fb20118c21e8a17bee46d3735195b0c2a9f39f","impliedFormat":1},{"version":"1f453e6798ed29c86f703e9b41662640d4f2e61337007f27ac1c616f20093f69","impliedFormat":1},{"version":"af43b7871ff21c62bf1a54ec5c488e31a8d3408d5b51ff2e9f8581b6c55f2fc7","impliedFormat":1},{"version":"70550511d25cbb0b6a64dcac7fffc3c1397fd4cbeb6b23ccc7f9b794ab8a6954","impliedFormat":1},{"version":"af0fbf08386603a62f2a78c42d998c90353b1f1d22e05a384545f7accf881e0a","impliedFormat":1},{"version":"cefc20054d20b85b534206dbcedd509bb74f87f3d8bc45c58c7be3a76caa45e1","impliedFormat":1},{"version":"ad6eee4877d0f7e5244d34bc5026fd6e9cf8e66c5c79416b73f9f6ebf132f924","impliedFormat":1},{"version":"4888fd2bcfee9a0ce89d0df860d233e0cee8ee9c479b6bd5a5d5f9aae98342fe","impliedFormat":1},{"version":"f4749c102ced952aa6f40f0b579865429c4869f6d83df91000e98005476bee87","impliedFormat":1},{"version":"56654d2c5923598384e71cb808fac2818ca3f07dd23bb018988a39d5e64f268b","impliedFormat":1},{"version":"8b6719d3b9e65863da5390cb26994602c10a315aa16e7d70778a63fee6c4c079","impliedFormat":1},{"version":"05f56cd4b929977d18df8f3d08a4c929a2592ef5af083e79974b20a063f30940","impliedFormat":1},{"version":"547d3c406a21b30e2b78629ecc0b2ddaf652d9e0bdb2d59ceebce5612906df33","impliedFormat":1},{"version":"b3a4f9385279443c3a5568ec914a9492b59a723386161fd5ef0619d9f8982f97","impliedFormat":1},{"version":"3fe66aba4fbe0c3ba196a4f9ed2a776fe99dc4d1567a558fb11693e9fcc4e6ed","impliedFormat":1},{"version":"140eef237c7db06fc5adcb5df434ee21e81ee3a6fd57e1a75b8b3750aa2df2d8","impliedFormat":1},{"version":"0944ec553e4744efae790c68807a461720cff9f3977d4911ac0d918a17c9dd99","impliedFormat":1},{"version":"cb46b38d5e791acaa243bf342b8b5f8491639847463ac965b93896d4fb0af0d9","impliedFormat":1},{"version":"7c7d9e116fe51100ff766703e6b5e4424f51ad8977fe474ddd8d0959aa6de257","impliedFormat":1},{"version":"af70a2567e586be0083df3938b6a6792e6821363d8ef559ad8d721a33a5bcdaf","impliedFormat":1},{"version":"006cff3a8bcb92d77953f49a94cd7d5272fef4ab488b9052ef82b6a1260d870b","impliedFormat":1},{"version":"7d44bfdc8ee5e9af70738ff652c622ae3ad81815e63ab49bdc593d34cb3a68e5","impliedFormat":1},{"version":"339814517abd4dbc7b5f013dfd3b5e37ef0ea914a8bbe65413ecffd668792bc6","impliedFormat":1},{"version":"34d5bc0a6958967ec237c99f980155b5145b76e6eb927c9ffc57d8680326b5d8","impliedFormat":1},{"version":"9eae79b70c9d8288032cbe1b21d0941f6bd4f315e14786b2c1d10bccc634e897","impliedFormat":1},{"version":"18ce015ed308ea469b13b17f99ce53bbb97975855b2a09b86c052eefa4aa013a","impliedFormat":1},{"version":"5a931bc4106194e474be141e0bc1046629510dc95b9a0e4b02a3783847222965","impliedFormat":1},{"version":"5e5f371bf23d5ced2212a5ff56675aefbd0c9b3f4d4fdda1b6123ac6e28f058c","impliedFormat":1},{"version":"907c17ad5a05eecb29b42b36cc8fec6437be27cc4986bb3a218e4f74f606911c","impliedFormat":1},{"version":"ce60a562cd2a92f37a88f2ddd99a3abfbc5848d7baf38c48fb8d3243701fcb75","impliedFormat":1},{"version":"a726ad2d0a98bfffbe8bc1cd2d90b6d831638c0adc750ce73103a471eb9a891c","impliedFormat":1},{"version":"f44c0c8ce58d3dacac016607a1a90e5342d830ea84c48d2e571408087ae55894","impliedFormat":1},{"version":"75a315a098e630e734d9bc932d9841b64b30f7a349a20cf4717bf93044eff113","impliedFormat":1},{"version":"9131d95e32b3d4611d4046a613e022637348f6cebfe68230d4e81b691e4761a1","impliedFormat":1},{"version":"b03aa292cfdcd4edc3af00a7dbd71136dd067ec70a7536b655b82f4dd444e857","impliedFormat":1},{"version":"b6e2b0448ced813b8c207810d96551a26e7d7bb73255eea4b9701698f78846d6","impliedFormat":1},{"version":"8ae10cd85c1bd94d2f2d17c4cbd25c068a4b2471c70c2d96434239f97040747a","impliedFormat":1},{"version":"9ed5b799c50467b0c9f81ddf544b6bcda3e34d92076d6cab183c84511e45c39f","impliedFormat":1},{"version":"b4fa87cc1833839e51c49f20de71230e259c15b2c9c3e89e4814acc1d1ef10de","impliedFormat":1},{"version":"e90ac9e4ac0326faa1bc39f37af38ace0f9d4a655cd6d147713c653139cf4928","impliedFormat":1},{"version":"ea27110249d12e072956473a86fd1965df8e1be985f3b686b4e277afefdde584","impliedFormat":1},{"version":"8776a368617ce51129b74db7d55c3373dadcce5d0701e61d106e99998922a239","impliedFormat":1},{"version":"5666075052877fe2fdddd5b16de03168076cf0f03fbca5c1d4a3b8f43cba570c","impliedFormat":1},{"version":"9108ab5af05418f599ab48186193b1b07034c79a4a212a7f73535903ba4ca249","impliedFormat":1},{"version":"bb4e2cdcadf9c9e6ee2820af23cee6582d47c9c9c13b0dca1baaffe01fbbcb5f","impliedFormat":1},{"version":"6e30d0b5a1441d831d19fe02300ab3d83726abd5141cbcc0e2993fa0efd33db4","impliedFormat":1},{"version":"423f28126b2fc8d8d6fa558035309000a1297ed24473c595b7dec52e5c7ebae5","impliedFormat":1},{"version":"fb30734f82083d4790775dae393cd004924ebcbfde49849d9430bf0f0229dd16","impliedFormat":1},{"version":"2c92b04a7a4a1cd9501e1be338bf435738964130fb2ad5bd6c339ee41224ac4c","impliedFormat":1},{"version":"c5c5f0157b41833180419dacfbd2bcce78fb1a51c136bd4bcba5249864d8b9b5","impliedFormat":1},{"version":"02ae43d5bae42efcd5a00d3923e764895ce056bca005a9f4e623aa6b4797c8af","impliedFormat":1},{"version":"db6e01f17012a9d7b610ae764f94a1af850f5d98c9c826ad61747dca0fb800bd","impliedFormat":1},{"version":"8a44b424edee7bb17dc35a558cc15f92555f14a0441205613e0e50452ab3a602","impliedFormat":1},{"version":"24a00d0f98b799e6f628373249ece352b328089c3383b5606214357e9107e7d5","impliedFormat":1},{"version":"33637e3bc64edd2075d4071c55d60b32bdb0d243652977c66c964021b6fc8066","impliedFormat":1},{"version":"0f0ad9f14dedfdca37260931fac1edf0f6b951c629e84027255512f06a6ebc4c","impliedFormat":1},{"version":"16ad86c48bf950f5a480dc812b64225ca4a071827d3d18ffc5ec1ae176399e36","impliedFormat":1},{"version":"8cbf55a11ff59fd2b8e39a4aa08e25c5ddce46e3af0ed71fb51610607a13c505","impliedFormat":1},{"version":"d5bc4544938741f5daf8f3a339bfbf0d880da9e89e79f44a6383aaf056fe0159","impliedFormat":1},{"version":"97f9169882d393e6f303f570168ca86b5fe9aab556e9a43672dae7e6bb8e6495","impliedFormat":1},{"version":"7c9adb3fcd7851497818120b7e151465406e711d6a596a71b807f3a17853cb58","impliedFormat":1},{"version":"6752d402f9282dd6f6317c8c048aaaac27295739a166eed27e00391b358fed9a","impliedFormat":1},{"version":"9fd7466b77020847dbc9d2165829796bf7ea00895b2520ff3752ffdcff53564b","impliedFormat":1},{"version":"fbfc12d54a4488c2eb166ed63bab0fb34413e97069af273210cf39da5280c8d6","impliedFormat":1},{"version":"85a84240002b7cf577cec637167f0383409d086e3c4443852ca248fc6e16711e","impliedFormat":1},{"version":"84794e3abd045880e0fadcf062b648faf982aa80cfc56d28d80120e298178626","impliedFormat":1},{"version":"053d8b827286a16a669a36ffc8ccc8acdf8cc154c096610aa12348b8c493c7b8","impliedFormat":1},{"version":"3cce4ce031710970fe12d4f7834375f5fd455aa129af4c11eb787935923ff551","impliedFormat":1},{"version":"8f62cbd3afbd6a07bb8c934294b6bfbe437021b89e53a4da7de2648ecfc7af25","impliedFormat":1},{"version":"62c3621d34fb2567c17a2c4b89914ebefbfbd1b1b875b070391a7d4f722e55dc","impliedFormat":1},{"version":"c05ac811542e0b59cb9c2e8f60e983461f0b0e39cea93e320fad447ff8e474f3","impliedFormat":1},{"version":"8e7a5b8f867b99cc8763c0b024068fb58e09f7da2c4810c12833e1ca6eb11c4f","impliedFormat":1},{"version":"132351cbd8437a463757d3510258d0fa98fd3ebef336f56d6f359cf3e177a3ce","impliedFormat":1},{"version":"df877050b04c29b9f8409aa10278d586825f511f0841d1ec41b6554f8362092b","impliedFormat":1},{"version":"33d1888c3c27d3180b7fd20bac84e97ecad94b49830d5dd306f9e770213027d1","impliedFormat":1},{"version":"ee942c58036a0de88505ffd7c129f86125b783888288c2389330168677d6347f","impliedFormat":1},{"version":"a3f317d500c30ea56d41501632cdcc376dae6d24770563a5e59c039e1c2a08ec","impliedFormat":1},{"version":"eb21ddc3a8136a12e69176531197def71dc28ffaf357b74d4bf83407bd845991","impliedFormat":1},{"version":"0c1651a159995dfa784c57b4ea9944f16bdf8d924ed2d8b3db5c25d25749a343","impliedFormat":1},{"version":"aaa13958e03409d72e179b5d7f6ec5c6cc666b7be14773ae7b6b5ee4921e52db","impliedFormat":1},{"version":"0a86e049843ad02977a94bb9cdfec287a6c5a0a4b6b5391a6648b1a122072c5a","impliedFormat":1},{"version":"40f06693e2e3e58526b713c937895c02e113552dc8ba81ecd49cdd9596567ddb","impliedFormat":1},{"version":"4ed5e1992aedb174fb8f5aa8796aa6d4dcb8bd819b4af1b162a222b680a37fa0","impliedFormat":1},{"version":"d7f4bd46a8b97232ea6f8c28012b8d2b995e55e729d11405f159d3e00c51420a","impliedFormat":1},{"version":"d604d413aff031f4bfbdae1560e54ebf503d374464d76d50a2c6ded4df525712","impliedFormat":1},{"version":"e4f4f9cf1e3ac9fd91ada072e4d428ecbf0aa6dc57138fb797b8a0ca3a1d521c","impliedFormat":1},{"version":"12bfd290936824373edda13f48a4094adee93239b9a73432db603127881a300d","impliedFormat":1},{"version":"340ceb3ea308f8e98264988a663640e567c553b8d6dc7d5e43a8f3b64f780374","impliedFormat":1},{"version":"c5a769564e530fba3ec696d0a5cff1709b9095a0bdf5b0826d940d2fc9786413","impliedFormat":1},{"version":"7124ef724c3fc833a17896f2d994c368230a8d4b235baed39aa8037db31de54f","impliedFormat":1},{"version":"5de1c0759a76e7710f76899dcae601386424eab11fb2efaf190f2b0f09c3d3d3","impliedFormat":1},{"version":"9c5ee8f7e581f045b6be979f062a61bf076d362bf89c7f966b993a23424e8b0d","impliedFormat":1},{"version":"1a11df987948a86aa1ec4867907c59bdf431f13ed2270444bf47f788a5c7f92d","impliedFormat":1},{"version":"8018dd2e95e7ce6e613ddd81672a54532614dc745520a2f9e3860ff7fb1be0ca","impliedFormat":1},{"version":"b756781cd40d465da57d1fc6a442c34ae61fe8c802d752aace24f6a43fedacee","impliedFormat":1},{"version":"0fe76167c87289ea094e01616dcbab795c11b56bad23e1ef8aba9aa37e93432a","impliedFormat":1},{"version":"3a45029dba46b1f091e8dc4d784e7be970e209cd7d4ff02bd15270a98a9ba24b","impliedFormat":1},{"version":"032c1581f921f8874cf42966f27fd04afcabbb7878fa708a8251cac5415a2a06","impliedFormat":1},{"version":"69c68ed9652842ce4b8e495d63d2cd425862104c9fb7661f72e7aa8a9ef836f8","impliedFormat":1},{"version":"0e704ee6e9fd8b6a5a7167886f4d8915f4bc22ed79f19cb7b32bd28458f50643","impliedFormat":1},{"version":"06f62a14599a68bcde148d1efd60c2e52e8fa540cc7dcfa4477af132bb3de271","impliedFormat":1},{"version":"904a96f84b1bcee9a7f0f258d17f8692e6652a0390566515fe6741a5c6db8c1c","impliedFormat":1},{"version":"11f19ce32d21222419cecab448fa335017ebebf4f9e5457c4fa9df42fa2dcca7","impliedFormat":1},{"version":"2e8ee2cbb5e9159764e2189cf5547aebd0e6b0d9a64d479397bb051cd1991744","impliedFormat":1},{"version":"1b0471d75f5adb7f545c1a97c02a0f825851b95fe6e069ac6ecaa461b8bb321d","impliedFormat":1},{"version":"1d157c31a02b1e5cca9bc495b3d8d39f4b42b409da79f863fb953fbe3c7d4884","impliedFormat":1},{"version":"07baaceaec03d88a4b78cb0651b25f1ae0322ac1aa0b555ae3749a79a41cba86","impliedFormat":1},{"version":"619a132f634b4ebe5b4b4179ea5870f62f2cb09916a25957bff17b408de8b56d","impliedFormat":1},{"version":"f60fa446a397eb1aead9c4e568faf2df8068b4d0306ebc075fb4be16ed26b741","impliedFormat":1},{"version":"f3cb784be4d9e91f966a0b5052a098d9b53b0af0d341f690585b0cc05c6ca412","impliedFormat":1},{"version":"350f63439f8fe2e06c97368ddc7fb6d6c676d54f59520966f7dbbe6a4586014e","impliedFormat":1},{"version":"eba613b9b357ac8c50a925fa31dc7e65ff3b95a07efbaa684b624f143d8d34ba","impliedFormat":1},{"version":"45b74185005ed45bec3f07cac6e4d68eaf02ead9ff5a66721679fb28020e5e7c","impliedFormat":1},{"version":"0f6199602df09bdb12b95b5434f5d7474b1490d2cd8cc036364ab3ba6fd24263","impliedFormat":1},{"version":"c8ca7fd9ec7a3ec82185bfc8213e4a7f63ae748fd6fced931741d23ef4ea3c0f","impliedFormat":1},{"version":"5c6a8a3c2a8d059f0592d4eab59b062210a1c871117968b10797dee36d991ef7","impliedFormat":1},{"version":"ad77fd25ece8e09247040826a777dc181f974d28257c9cd5acb4921b51967bd8","impliedFormat":1},{"version":"795a08ae4e193f345073b49f68826ab6a9b280400b440906e4ec5c237ae777e6","impliedFormat":1},{"version":"8153df63cf65122809db17128e5918f59d6bb43a371b5218f4430c4585f64085","impliedFormat":1},{"version":"a8150bc382dd12ce58e00764d2366e1d59a590288ee3123af8a4a2cb4ef7f9df","impliedFormat":1},{"version":"5adfaf2f9f33957264ad199a186456a4676b2724ed700fc313ff945d03372169","impliedFormat":1},{"version":"d5c41a741cd408c34cb91f84468f70e9bda3dfeabf33251a61039b3cdb8b22d8","impliedFormat":1},{"version":"c91d3f9753a311284e76cdcb348cbb50bca98733336ec726b54d77b7361b34de","impliedFormat":1},{"version":"cbaf4a4aa8a8c02aa681c5870d5c69127974de29b7e01df570edec391a417959","impliedFormat":1},{"version":"c7135e329a18b0e712378d5c7bc2faec6f5ab0e955ea0002250f9e232af8b3e4","impliedFormat":1},{"version":"340a45cd77b41d8a6deda248167fa23d3dc67ec798d411bd282f7b3d555b1695","impliedFormat":1},{"version":"fae330f86bc10db6841b310f32367aaa6f553036a3afc426e0389ddc5566cd74","impliedFormat":1},{"version":"cf25d45c02d5fd5d7adb16230a0e1d6715441eef5c0a79a21bfeaa9bbc058939","impliedFormat":1},{"version":"54c3822eaf6436f2eddc92dd6e410750465aba218adbf8ce5d488d773919ec01","impliedFormat":1},{"version":"99d99a765426accf8133737843fb024a154dc6545fc0ffbba968a7c0b848959d","impliedFormat":1},{"version":"c782c5fd5fa5491c827ecade05c3af3351201dd1c7e77e06711c8029b7a9ee4d","impliedFormat":1},{"version":"883d2104e448bb351c49dd9689a7e8117b480b614b2622732655cef03021bf6d","impliedFormat":1},{"version":"d9b00ee2eca9b149663fdba1c1956331841ae296ee03eaaff6c5becbc0ff1ea8","impliedFormat":1},{"version":"09a7e04beb0547c43270b327c067c85a4e2154372417390731dfe092c4350998","impliedFormat":1},{"version":"eee530aaa93e9ec362e3941ee8355e2d073c7b21d88c2af4713e3d701dab8fef","impliedFormat":1},{"version":"28d47319b97dbeee9130b78eae03b2061d46dedbf92b0d9de13ed7ab8399ccd0","impliedFormat":1},{"version":"8b8b92781a6bf150f9ee83f3d8ee278b6cdb98b8308c7ab3413684fc5d9078ef","impliedFormat":1},{"version":"7a0e4cd92545ad03910fd019ae9838718643bd4dde39881c745f236914901dfa","impliedFormat":1},{"version":"c99ebd20316217e349004ee1a0bc74d32d041fb6864093f10f31984c737b8cad","impliedFormat":1},{"version":"6f622e7f054f5ab86258362ac0a64a2d6a27f1e88732d6f5f052f422e08a70e7","impliedFormat":1},{"version":"d62d2ef93ceeb41cf9dfab25989a1e5f9ca5160741aac7f1453c69a6c14c69be","impliedFormat":1},{"version":"1491e80d72873fc586605283f2d9056ee59b166333a769e64378240df130d1c9","impliedFormat":1},{"version":"c32c073d389cfaa3b3e562423e16c2e6d26b8edebbb7d73ccffff4aa66f2171d","impliedFormat":1},{"version":"eca72bf229eecadb63e758613c62fab13815879053539a22477d83a48a21cd73","impliedFormat":1},{"version":"633db46fd1765736409a4767bfc670861468dde60dbb9a501fba4c1b72f8644d","impliedFormat":1},{"version":"689390db63cb282e6d0e5ce9b8f1ec2ec0912d0e2e6dac7235699a15ad17d339","impliedFormat":1},{"version":"f2ee748883723aa9325e5d7f30fce424f6a786706e1b91a5a55237c78ee89c4a","impliedFormat":1},{"version":"d928324d17146fce30b99a28d1d6b48648feac72bbd23641d3ce5ac34aefdfee","impliedFormat":1},{"version":"142f5190d730259339be1433931c0eb31ae7c7806f4e325f8a470bd9221b6533","impliedFormat":1},{"version":"c33a88f2578e8df2fdf36c6a0482bbee615eb3234c8f084ba31a9a96bd306b7f","impliedFormat":1},{"version":"22cca068109eb0e6b4f8acc3fe638d1e6ac277e2044246438763319792b546a1","impliedFormat":1},{"version":"8776e64e6165838ac152fa949456732755b0976d1867ae5534ce248f0ccd7f41","impliedFormat":1},{"version":"66cd33c4151ea27f6e17c6071652eadde9da1b3637dae65fd060212211c695ce","impliedFormat":1},{"version":"5c4c5b49bbb01828402bb04af1d71673b18852c11b7e95bfd5cf4c3d80d352c8","impliedFormat":1},{"version":"7030df3d920343df00324df59dc93a959a33e0f4940af3fefef8c07b7ee329bf","impliedFormat":1},{"version":"a96bc00e0c356e29e620eaec24a56d6dd7f4e304feefcc99066a1141c6fe05a7","impliedFormat":1},{"version":"d12cc0e5b09943c4cd0848f787eb9d07bf78b60798e4588c50582db9d4decc70","impliedFormat":1},{"version":"53b094f1afe442490555eeeb0384fc1ceb487560c83e31f9c64fb934c2dccd94","impliedFormat":1},{"version":"19c3760af3cbc9da99d5b7763b9e33aaf8d018bc2ed843287b7ff4343adf4634","impliedFormat":1},{"version":"9d1e38aeb76084848d2fcd39b458ec88246de028c0f3f448b304b15d764b23d2","impliedFormat":1},{"version":"d406da1eccf18cec56fd29730c24af69758fe3ff49c4f94335e797119cbc0554","impliedFormat":1},{"version":"4898c93890a136da9156c75acd1a80a941a961b3032a0cf14e1fa09a764448b7","impliedFormat":1},{"version":"f5d7a845e3e1c6c27351ea5f358073d0b0681537a2da6201fab254aa434121d3","impliedFormat":1},{"version":"9ddf8e9069327faa75d20135cab675779844f66590249769c3d35dd2a38c2ba9","impliedFormat":1},{"version":"d7c30f0abfe9e197e376b016086cf66b2ffb84015139963f37301ed0da9d3d0d","impliedFormat":1},{"version":"ff75bba0148f07775bcb54bf4823421ed4ebdb751b3bf79cc003bd22e49d7d73","impliedFormat":1},{"version":"d40d20ac633703a7333770bfd60360126fc3302d5392d237bbb76e8c529a4f95","impliedFormat":1},{"version":"35a9867207c488061fb4f6fe4715802fbc164b4400018d2fa0149ad02db9a61c","impliedFormat":1},{"version":"91bf47a209ad0eae090023c3ebc1165a491cf9758799368ffcbee8dbe7448f33","impliedFormat":1},{"version":"0abe2cd72812bbfc509975860277c7cd6f6e0be95d765a9da77fee98264a7e32","impliedFormat":1},{"version":"13286c0c8524606b17a8d68650970bab896fb505f348f71601abf0f2296e8913","impliedFormat":1},{"version":"fc2a131847515b3dff2f0e835633d9a00a9d03ed59e690e27eec85b7b0522f92","impliedFormat":1},{"version":"90433c678bc26751eb7a5d54a2bb0a14be6f5717f69abb5f7a04afc75dce15a4","impliedFormat":1},{"version":"cd0565ace87a2d7802bf4c20ea23a997c54e598b9eb89f9c75e69478c1f7a0b4","impliedFormat":1},{"version":"738020d2c8fc9df92d5dee4b682d35a776eaedfe2166d12bc8f186e1ea57cc52","impliedFormat":1},{"version":"86dd7c5657a0b0bc6bee8002edcfd544458d3d3c60974555746eb9b2583dc35e","impliedFormat":1},{"version":"d97b96b6ecd4ee03f9f1170722c825ef778430a6a0d7aab03b8929012bf773cd","impliedFormat":1},{"version":"f61963dc02ef27c48fb0e0016a413b1e00bcb8b97a3f5d4473cedc7b44c8dc77","impliedFormat":1},{"version":"272dbfe04cfa965d6fff63fdaba415c1b5a515b1881ae265148f8a84ddeb318f","impliedFormat":1},{"version":"2035fb009b5fafa9a4f4e3b3fdb06d9225b89f2cbbf17a5b62413bf72cea721a","impliedFormat":1},{"version":"eefafec7c059f07b885b79b327d381c9a560e82b439793de597441a4e68d774a","impliedFormat":1},{"version":"72636f59b635c378dc9ea5246b9b3517b1214e340e468e54cb80126353053b2e","impliedFormat":1},{"version":"ebb79f267a3bf2de5f8edc1995c5d31777b539935fab8b7d863e8efb06c8e9ea","impliedFormat":1},{"version":"ada033e6a4c7f4e147e6d76bb881069dc66750619f8cc2472d65beeec1100145","impliedFormat":1},{"version":"0c04cc14a807a5dc0e3752d18a3b2655a135fefbf76ddcdabd0c5df037530d41","impliedFormat":1},{"version":"605d29d619180fbec287d1701e8b1f51f2d16747ec308d20aba3e9a0dac43a0f","impliedFormat":1},{"version":"67c19848b442d77c767414084fc571ce118b08301c4ddff904889d318f3a3363","impliedFormat":1},{"version":"c704ff0e0cb86d1b791767a88af21dadfee259180720a14c12baee668d0eb8fb","impliedFormat":1},{"version":"195c50e15d5b3ea034e01fbdca6f8ad4b35ad47463805bb0360bdffd6fce3009","impliedFormat":1},{"version":"da665f00b6877ae4adb39cd548257f487a76e3d99e006a702a4f38b4b39431cb","impliedFormat":1},{"version":"2b82adc9eead34b824a3f4dad315203fbfa56bee0061ccf9b485820606564f70","impliedFormat":1},{"version":"eb47aaa5e1b0a69388bb48422a991b9364a9c206a97983e0227289a9e1fca178","impliedFormat":1},{"version":"d7a4309673b06223537bc9544b1a5fe9425628e1c8ab5605f3c5ebc27ecb8074","impliedFormat":1},{"version":"db2108aea36e7faa83c38f6fe8225b9ad40835c0cba7fa38e969768299b83173","impliedFormat":1},{"version":"3eadfd083d40777b403f4f4eecfa40f93876f2a01779157cc114b2565a7afb51","impliedFormat":1},{"version":"cb6789ce3eba018d5a7996ccbf50e27541d850e9b4ee97fdcb3cbd8c5093691f","impliedFormat":1},{"version":"a3684ea9719122f9477902acd08cd363a6f3cff6d493df89d4dc12fa58204e27","impliedFormat":1},{"version":"2828dabf17a6507d39ebcc58fef847e111dcf2d51b8e4ff0d32732c72be032b3","impliedFormat":1},{"version":"c0c46113b4cd5ec9e7cf56e6dbfb3930ef6cbba914c0883eeced396988ae8320","impliedFormat":1},{"version":"118ea3f4e7b9c12e92551be0766706f57a411b4f18a1b4762cfde3cd6d4f0a96","impliedFormat":1},{"version":"2ad163aaddfa29231a021de6838f59378a210501634f125ed04cfa7d066ffc53","impliedFormat":1},{"version":"6305acbe492b9882ec940f8f0c8e5d1e1395258852f99328efcb1cf1683ca817","impliedFormat":1},{"version":"7619b1f6087a4e9336b2c42bd784b05aa4a2204a364b60171e5a628f817a381e","impliedFormat":1},{"version":"15be9120572c9fbcd3c267bd93b4140354514c9e70734e6fcca65ff4a246f83a","impliedFormat":1},{"version":"412482ab85893cec1d6f26231359474d1f59f6339e2743c08da1b05fc1d12767","impliedFormat":1},{"version":"858e2315e58af0d28fcd7f141a2505aba6a76fd10378ba0ad169b0336fee33fc","impliedFormat":1},{"version":"02da6c1b34f4ae2120d70cf5f9268bf1aedf62e55529d34f5974f5a93655ce38","impliedFormat":1},{"version":"3ecf179ef1cc28f7f9b46c8d2e496d50b542c176e94ed0147bab147b4a961cd6","impliedFormat":1},{"version":"b145da03ce7e174af5ced2cbbd16e96d3d5c2212f9a90d3657b63a5650a73b7f","impliedFormat":1},{"version":"c7aadab66a2bc90eeb0ab145ca4daebcbc038e24359263de3b40e7b1c7affba6","impliedFormat":1},{"version":"99518dc06286877a7b716e0f22c1a72d3c62be42701324b49f27bcc03573efff","impliedFormat":1},{"version":"f4575fd196a7e33c7be9773a71bcc5fbe7182a2152be909f6b8e8e7ba2438f06","impliedFormat":1},{"version":"05cba5acd77a4384389b9c62739104b5a1693efd66e6abac6c5ffc53280ae777","impliedFormat":1},{"version":"acacda82ebd929fe2fe9e31a37f193fc8498a7393a1c31dc5ceb656e2b45b708","impliedFormat":1},{"version":"1b13e7c5c58ab894fe65b099b6d19bb8afae6d04252db1bf55fe6ba95a0af954","impliedFormat":1},{"version":"4355d326c3129e5853b56267903f294ad03e34cc28b75f96b80734882dedac80","impliedFormat":1},{"version":"37139a8d45342c05b6a5aa1698a2e8e882d6dca5fb9a77aa91f05ac04e92e70b","impliedFormat":1},{"version":"e37191297f1234d3ae54edbf174489f9a3091a05fe959724db36f8e58d21fb17","impliedFormat":1},{"version":"3fca8fb3aab1bc7abb9b1420f517e9012fdddcbe18803bea2dd48fad6c45e92e","impliedFormat":1},{"version":"d0b0779e0cac4809a9a3c764ba3bd68314de758765a8e3b9291fe1671bfeb8a1","impliedFormat":1},{"version":"d2116b5f989aa68e585ae261b9d6d836be6ed1be0b55b47336d9f3db34674e86","impliedFormat":1},{"version":"d79a227dd654be16d8006eac8b67212679d1df494dfe6da22ea0bd34a13e010c","impliedFormat":1},{"version":"b9c89b4a2435c171e0a9a56668f510a376cb7991eaecef08b619e6d484841735","impliedFormat":1},{"version":"44a298a6c52a7dab8e970e95a6dabe20972a7c31c340842e0dc57f2c822826eb","impliedFormat":1},{"version":"6a79b61f57699de0a381c8a13f4c4bcd120556bfab0b4576994b6917cb62948b","impliedFormat":1},{"version":"c5133d7bdec65f465df12f0b507fbc0d96c78bfa5a012b0eb322cf1ff654e733","impliedFormat":1},{"version":"00b9ff040025f6b00e0f4ac8305fea1809975b325af31541bd9d69fa3b5e57b1","impliedFormat":1},{"version":"9f96b9fd0362a7bfe6a3aa70baa883c47ae167469c904782c99ccc942f62f0dc","impliedFormat":1},{"version":"54d91053dc6a2936bfd01a130cc3b524e11aa0349da082e8ac03a8bf44250338","impliedFormat":1},{"version":"89049878a456b5e0870bb50289ea8ece28a2abd0255301a261fa8ab6a3e9a07d","impliedFormat":1},{"version":"55ae9554811525f24818e19bdc8779fa99df434be7c03e5fc47fa441315f0226","impliedFormat":1},{"version":"24abac81e9c60089a126704e936192b2309413b40a53d9da68dadd1dd107684e","impliedFormat":1},{"version":"f13310c360ecffddb3858dcb33a7619665369d465f55e7386c31d45dfc3847bf","impliedFormat":1},{"version":"e7bde95a05a0564ee1450bc9a53797b0ac7944bf24d87d6f645baca3aa60df48","impliedFormat":1},{"version":"62e68ce120914431a7d34232d3eca643a7ddd67584387936a5202ae1c4dd9a1b","impliedFormat":1},{"version":"91d695bba902cc2eda7edc076cd17c5c9340f7bb254597deb6679e343effadbb","impliedFormat":1},{"version":"e1cb8168c7e0bd4857a66558fe7fe6c66d08432a0a943c51bacdac83773d5745","impliedFormat":1},{"version":"a464510505f31a356e9833963d89ce39f37a098715fc2863e533255af4410525","impliedFormat":1},{"version":"ebbe6765a836bfa7f03181bc433c8984ca29626270ca1e240c009851222cb8a7","impliedFormat":1},{"version":"ac10457b51ee4a3173b7165c87c795eadd094e024f1d9f0b6f0c131126e3d903","impliedFormat":1},{"version":"468df9d24a6e2bc6b4351417e3b5b4c2ca08264d6d5045fe18eb42e7996e58b4","impliedFormat":1},{"version":"954523d1f4856180cbf79b35bd754e14d3b2aea06c7efd71b254c745976086e9","impliedFormat":1},{"version":"a8af4739274959d70f7da4bfdd64f71cfc08d825c2d5d3561bc7baed760b33ef","impliedFormat":1},{"version":"090fda1107e7d4f8f30a2b341834ed949f01737b5ec6021bb6981f8907330bdb","impliedFormat":1},{"version":"cc32874a27100c32e3706d347eb4f435d6dd5c0d83e547c157352f977bbc6385","impliedFormat":1},{"version":"e45b069d58c9ac341d371b8bc3db4fa7351b9eee1731bffd651cfc1eb622f844","impliedFormat":1},{"version":"7f3c74caad25bfb6dfbf78c6fe194efcf8f79d1703d785fc05cd606fe0270525","impliedFormat":1},{"version":"54f3f7ff36384ca5c9e1627118b43df3014b7e0f62c9722619d19cdb7e43d608","impliedFormat":1},{"version":"2f346f1233bae487f1f9a11025fc73a1bf9093ee47980a9f4a75b84ea0bb7021","impliedFormat":1},{"version":"013444d0b8c1f7b5115462c31573a699fee7458381b0611062a0069d3ef810e8","impliedFormat":1},{"version":"0612b149cabbc136cb25de9daf062659f306b67793edc5e39755c51c724e2949","impliedFormat":1},{"version":"2579b150b86b5f644d86a6d58f17e3b801772c78866c34d41f86f3fc9eb523fe","impliedFormat":1},{"version":"0353e05b0d8475c10ddd88056e0483b191aa5cdea00a25e0505b96e023f1a2d9","impliedFormat":1},{"version":"8c4df93dafcf06adc42a63477cc38b352565a3ed0a19dd8ef7dfacc253749327","impliedFormat":1},{"version":"22a35275abc67f8aba44efc52b2f4b1abc2c94e183d36647fdab5a5e7c1bdf23","impliedFormat":1},{"version":"99193bafaa9ce112889698de25c4b8c80b1209bb7402189aea1c7ada708a8a54","impliedFormat":1},{"version":"70473538c6eb9494d53bf1539fe69df68d87c348743d8f7244dcb02ca3619484","impliedFormat":1},{"version":"c48932ab06a4e7531bdca7b0f739ace5fa273f9a1b9009bcd26902f8c0b851f0","impliedFormat":1},{"version":"df6c83e574308f6540c19e3409370482a7d8f448d56c65790b4ac0ab6f6fedd8","impliedFormat":1},{"version":"32f19b665839b1382b21afc41917cda47a56e744cd3df9986b13a72746d1c522","impliedFormat":1},{"version":"8db1ed144dd2304b9bd6e41211e22bad5f4ab1d8006e6ac127b29599f4b36083","impliedFormat":1},{"version":"843a5e3737f2abbbbd43bf2014b70f1c69a80530814a27ae1f8be213ae9ec222","impliedFormat":1},{"version":"6fc1be224ad6b3f3ec11535820def2d21636a47205c2c9de32238ba1ac8d82e6","impliedFormat":1},{"version":"5a44788293f9165116c9c183be66cefef0dc5d718782a04847de53bf664f3cc1","impliedFormat":1},{"version":"afd653ae63ce07075b018ba5ce8f4e977b6055c81cc65998410b904b94003c0a","impliedFormat":1},{"version":"9172155acfeb17b9d75f65b84f36cb3eb0ff3cd763db3f0d1ad5f6d10d55662f","impliedFormat":1},{"version":"71807b208e5f15feffb3ff530bec5b46b1217af0d8cc96dde00d549353bcb864","impliedFormat":1},{"version":"1a6eca5c2bc446481046c01a54553c3ffb856f81607a074f9f0256c59dd0ab13","impliedFormat":1},{"version":"dff93e0997c4e64ff29e9f70cad172c0b438c4f58c119f17a51c94d48164475a","impliedFormat":1},{"version":"fd1ddf926b323dfa439be49c1d41bbe233fe5656975a11183aeb3bf2addfa3bb","impliedFormat":1},{"version":"6dda11db28da6bcc7ff09242cd1866bdddd0ae91e2db3bea03ba66112399641a","impliedFormat":1},{"version":"ea4cd1e72af1aa49cf208b9cb4caf542437beb7a7a5b522f50a5f1b7480362ed","impliedFormat":1},{"version":"903a7d68a222d94da11a5a89449fdd5dd75d83cd95af34c0242e10b85ec33a93","impliedFormat":1},{"version":"e7fe2e7ed5c3a7beff60361632be19a8943e53466b7dd69c34f89faf473206d7","impliedFormat":1},{"version":"b4896cee83379e159f83021e262223354db79e439092e485611163e2082224ff","impliedFormat":1},{"version":"5243e79a643e41d9653011d6c66e95048fc0478eb8593dc079b70877a2e3990e","impliedFormat":1},{"version":"70521b6ab0dcba37539e5303104f29b721bfb2940b2776da4cc818c07e1fefc1","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"1456e80bd8a3870034d89f91bd7df12ac29acfb083e31c0bb1fb38ca7bf5fbc2","affectsGlobalScope":true,"impliedFormat":1},{"version":"a98aedd64ad81793f146d36d1611ed9ba61b8b49ff040f0d13a103ed626595d9","affectsGlobalScope":true,"impliedFormat":1},{"version":"6d9ef24f9a22a88e3e9b3b3d8c40ab1ddb0853f1bfbd5c843c37800138437b61","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e9c23ba78aabc2e0a27033f18737a6df754067731e69dc5f52823957d60a4b6","impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"b52476feb4a0cbcb25e5931b930fc73cb6643fb1a5060bf8a3dda0eeae5b4b68","affectsGlobalScope":true,"impliedFormat":1},{"version":"e2677634fe27e87348825bb041651e22d50a613e2fdf6a4a3ade971d71bac37e","impliedFormat":1},{"version":"7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419","impliedFormat":1},{"version":"8c0bcd6c6b67b4b503c11e91a1fb91522ed585900eab2ab1f61bba7d7caa9d6f","impliedFormat":1},{"version":"8cd19276b6590b3ebbeeb030ac271871b9ed0afc3074ac88a94ed2449174b776","affectsGlobalScope":true,"impliedFormat":1},{"version":"696eb8d28f5949b87d894b26dc97318ef944c794a9a4e4f62360cd1d1958014b","impliedFormat":1},{"version":"3f8fa3061bd7402970b399300880d55257953ee6d3cd408722cb9ac20126460c","impliedFormat":1},{"version":"35ec8b6760fd7138bbf5809b84551e31028fb2ba7b6dc91d95d098bf212ca8b4","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"68bd56c92c2bd7d2339457eb84d63e7de3bd56a69b25f3576e1568d21a162398","affectsGlobalScope":true,"impliedFormat":1},{"version":"3e93b123f7c2944969d291b35fed2af79a6e9e27fdd5faa99748a51c07c02d28","impliedFormat":1},{"version":"9d19808c8c291a9010a6c788e8532a2da70f811adb431c97520803e0ec649991","impliedFormat":1},{"version":"87aad3dd9752067dc875cfaa466fc44246451c0c560b820796bdd528e29bef40","impliedFormat":1},{"version":"4aacb0dd020eeaef65426153686cc639a78ec2885dc72ad220be1d25f1a439df","impliedFormat":1},{"version":"f0bd7e6d931657b59605c44112eaf8b980ba7f957a5051ed21cb93d978cf2f45","impliedFormat":1},{"version":"8db0ae9cb14d9955b14c214f34dae1b9ef2baee2fe4ce794a4cd3ac2531e3255","affectsGlobalScope":true,"impliedFormat":1},{"version":"15fc6f7512c86810273af28f224251a5a879e4261b4d4c7e532abfbfc3983134","impliedFormat":1},{"version":"58adba1a8ab2d10b54dc1dced4e41f4e7c9772cbbac40939c0dc8ce2cdb1d442","impliedFormat":1},{"version":"4b34bdb6f29a4347b7db9c0f8622686035fe25adb1c9e927acd8d22a2cbb6ccb","impliedFormat":1},{"version":"714435130b9015fae551788df2a88038471a5a11eb471f27c4ede86552842bc9","impliedFormat":1},{"version":"855cd5f7eb396f5f1ab1bc0f8580339bff77b68a770f84c6b254e319bbfd1ac7","impliedFormat":1},{"version":"5650cf3dace09e7c25d384e3e6b818b938f68f4e8de96f52d9c5a1b3db068e86","impliedFormat":1},{"version":"1354ca5c38bd3fd3836a68e0f7c9f91f172582ba30ab15bb8c075891b91502b7","affectsGlobalScope":true,"impliedFormat":1},{"version":"27fdb0da0daf3b337c5530c5f266efe046a6ceb606e395b346974e4360c36419","impliedFormat":1},{"version":"2d2fcaab481b31a5882065c7951255703ddbe1c0e507af56ea42d79ac3911201","impliedFormat":1},{"version":"a192fe8ec33f75edbc8d8f3ed79f768dfae11ff5735e7fe52bfa69956e46d78d","impliedFormat":1},{"version":"ca867399f7db82df981d6915bcbb2d81131d7d1ef683bc782b59f71dda59bc85","affectsGlobalScope":true,"impliedFormat":1},{"version":"0e456fd5b101271183d99a9087875a282323e3a3ff0d7bcf1881537eaa8b8e63","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e043a1bc8fbf2a255bccf9bf27e0f1caf916c3b0518ea34aa72357c0afd42ec","impliedFormat":1},{"version":"b4f70ec656a11d570e1a9edce07d118cd58d9760239e2ece99306ee9dfe61d02","impliedFormat":1},{"version":"3bc2f1e2c95c04048212c569ed38e338873f6a8593930cf5a7ef24ffb38fc3b6","impliedFormat":1},{"version":"6e70e9570e98aae2b825b533aa6292b6abd542e8d9f6e9475e88e1d7ba17c866","impliedFormat":1},{"version":"f9d9d753d430ed050dc1bf2667a1bab711ccbb1c1507183d794cc195a5b085cc","impliedFormat":1},{"version":"9eece5e586312581ccd106d4853e861aaaa1a39f8e3ea672b8c3847eedd12f6e","impliedFormat":1},{"version":"47ab634529c5955b6ad793474ae188fce3e6163e3a3fb5edd7e0e48f14435333","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"fad4e3c207fe23922d0b2d06b01acbfb9714c4f2685cf80fd384c8a100c82fd0","affectsGlobalScope":true,"impliedFormat":1},{"version":"74cf591a0f63db318651e0e04cb55f8791385f86e987a67fd4d2eaab8191f730","impliedFormat":1},{"version":"5eab9b3dc9b34f185417342436ec3f106898da5f4801992d8ff38ab3aff346b5","impliedFormat":1},{"version":"12ed4559eba17cd977aa0db658d25c4047067444b51acfdcbf38470630642b23","affectsGlobalScope":true,"impliedFormat":1},{"version":"f3ffabc95802521e1e4bcba4c88d8615176dc6e09111d920c7a213bdda6e1d65","impliedFormat":1},{"version":"ddc734b4fae82a01d247e9e342d020976640b5e93b4e9b3a1e30e5518883a060","impliedFormat":1},{"version":"ae56f65caf3be91108707bd8dfbccc2a57a91feb5daabf7165a06a945545ed26","impliedFormat":1},{"version":"a136d5de521da20f31631a0a96bf712370779d1c05b7015d7019a9b2a0446ca9","impliedFormat":1},{"version":"c3b41e74b9a84b88b1dca61ec39eee25c0dbc8e7d519ba11bb070918cfacf656","affectsGlobalScope":true,"impliedFormat":1},{"version":"4737a9dc24d0e68b734e6cfbcea0c15a2cfafeb493485e27905f7856988c6b29","affectsGlobalScope":true,"impliedFormat":1},{"version":"36d8d3e7506b631c9582c251a2c0b8a28855af3f76719b12b534c6edf952748d","impliedFormat":1},{"version":"1ca69210cc42729e7ca97d3a9ad48f2e9cb0042bada4075b588ae5387debd318","impliedFormat":1},{"version":"f5ebe66baaf7c552cfa59d75f2bfba679f329204847db3cec385acda245e574e","impliedFormat":1},{"version":"ed59add13139f84da271cafd32e2171876b0a0af2f798d0c663e8eeb867732cf","affectsGlobalScope":true,"impliedFormat":1},{"version":"05db535df8bdc30d9116fe754a3473d1b6479afbc14ae8eb18b605c62677d518","impliedFormat":1},{"version":"0ea329e5eab6719ff83bcb97e8bd03f1faab4feb74704010783b881fc9d80f92","impliedFormat":1},{"version":"08bb8fb1430620b088894ecbb0a6cb972f963d63911bb3704febfa0d3a2f6ea5","impliedFormat":1},{"version":"5e4631f04c72971410015548c8137d6b007256c071ec504de385372033fec177","impliedFormat":1},{"version":"eb234b3e285e8bc071bdddc1ec0460095e13ead6222d44b02c4e0869522f9ba3","impliedFormat":1},{"version":"ce4e58f029088cc5f0e6e7c7863f6ace0bc04c2c4be7bc6730471c2432bd5895","impliedFormat":1},{"version":"018421260380d05df31b567b90368e1eacf22655b2b8dc2c11e0e76e5fd8978f","impliedFormat":1},{"version":"ef803dca265d6ba37f97b46e21c66d055a3007f71c1995d9ef15d4a07b0d2ad0","impliedFormat":1},{"version":"3d4adf825b7ac087cfbf3d54a7dc16a3959877bb4f5080e14d5e9d8d6159eba8","impliedFormat":1},{"version":"f9e034b1ae29825c00532e08ea852b0c72885c343ee48d2975db0a6481218ab3","impliedFormat":1},{"version":"1193f49cbb883f40326461fe379e58ffa4c18d15bf6d6a1974ad2894e4fb20f3","impliedFormat":1},{"version":"8f1241f5d9f0d3d72117768b3c974e462840fbd85026fb66685078945404cf2f","impliedFormat":1},{"version":"4f0424b7c6857793498a6e60041af2a38658e8193a403a701574e80df50a360a","impliedFormat":1},{"version":"034856eb35ba68a5b7190db2d8e743cc640996545b7200e6766e86b27a2fd527","impliedFormat":1},{"version":"adb4283721e37317d30627d9c31404e46a6bb0174e5806c43c59d08d8d91ea67","impliedFormat":1},{"version":"ec379d84f25c38ceaaa81715fd1b6a0b3a000386ea41155969dc49f88eab33ef","impliedFormat":1},{"version":"d02329b04183e4f319fd78e5726375b2154d4eab6ec90ee3706b4090f94d3d99","impliedFormat":1},{"version":"81477bb2c9b97a9dd5ce7750ab4ae655e74172f0d536d637be345ba76b41cd92","impliedFormat":1},{"version":"b8ad793dc17938bc462812e3522bbd3d62519d91d9b4a6422bed1383c2d3eb42","impliedFormat":1},{"version":"8b0b6a4c032a56d5651f7dd02ba3f05fbfe4131c4095093633cda3cae0991972","impliedFormat":1},{"version":"ff3c48a17bf10dfbb62448152042e4a48a56c9972059997ab9e7ed03b191809b","impliedFormat":1},{"version":"192a0c215bffe5e4ac7b9ff1e90e94bf4dfdad4f0f69a5ae07fccc36435ebb87","impliedFormat":1},{"version":"3ef8565e3d254583cced37534f161c31e3a8f341ff005c98b582c6d8c9274538","impliedFormat":1},{"version":"d7e42a3800e287d2a1af8479c7dd58c8663e80a01686cb89e0068be6c777d687","impliedFormat":1},{"version":"1098034333d3eb3c1d974435cacba9bd5a625711453412b3a514774fec7ca748","impliedFormat":1},{"version":"f2388b97b898a93d5a864e85627e3af8638695ebfa6d732ecd39d382824f0e63","impliedFormat":1},{"version":"6c6bd91368169cfa94b4f8cc64ebca2b050685ec76bc4082c44ce125b5530cca","impliedFormat":1},{"version":"f477375e6f0bf2a638a71d4e7a3da8885e3a03f3e5350688541d136b10b762a6","impliedFormat":1},{"version":"a44d6ea4dc70c3d789e9cef3cc42b79c78d17d3ce07f5fd278a7e1cbe824da56","impliedFormat":1},{"version":"272af80940fcc0c8325e4a04322c50d11f8b8842f96ac66cbd440835e958dd14","impliedFormat":1},{"version":"1803e48a3ec919ccafbcafeef5e410776ca0644ae8c6c87beca4c92d8a964434","impliedFormat":1},{"version":"875c43c5409e197e72ee517cb1f8fd358406b4adf058dbdc1e50c8db93d68f26","impliedFormat":1},{"version":"8854713984b9588eac1cab69c9e2a6e1a33760d9a2d182169059991914dd8577","impliedFormat":1},{"version":"e333d487ca89f26eafb95ea4b59bea8ba26b357e9f2fd3728be81d999f9e8cf6","impliedFormat":1},{"version":"2f554c6798b731fc39ff4e3d86aadc932fdeaa063e3cbab025623ff5653c0031","impliedFormat":1},{"version":"fe4613c6c0d23edc04cd8585bdd86bc7337dc6265fb52037d11ca19eeb5e5aaf","impliedFormat":1},{"version":"53b26fbee1a21a6403cf4625d0e501a966b9ccf735754b854366cee8984b711c","impliedFormat":1},{"version":"c503be3ddb3990ab27ca20c6559d29b547d9f9413e05d2987dd7c4bcf52f3736","impliedFormat":1},{"version":"598b15f0ae9a73082631d14cb8297a1285150ca325dbce98fc29c4f0b7079443","impliedFormat":1},{"version":"8c59d8256086ed17676139ee43c1155673e357ab956fb9d00711a7cac73e059d","impliedFormat":1},{"version":"cfe88132f67aa055a3f49d59b01585fa8d890f5a66a0a13bb71973d57573eee7","impliedFormat":1},{"version":"53ce488a97f0b50686ade64252f60a1e491591dd7324f017b86d78239bd232ca","impliedFormat":1},{"version":"50fd11b764194f06977c162c37e5a70bcf0d3579bf82dd4de4eee3ac68d0f82f","impliedFormat":1},{"version":"e0ceb647dcdf6b27fd37e8b0406c7eafb8adfc99414837f3c9bfd28ffed6150a","impliedFormat":1},{"version":"99579aa074ed298e7a3d6a47e68f0cd099e92411212d5081ce88344a5b1b528d","impliedFormat":1},{"version":"c94c1aa80687a277396307b80774ca540d0559c2f7ba340168c2637c82b1f766","impliedFormat":1},{"version":"ce7dbf31739cc7bca35ca50e4f0cbd75cd31fd6c05c66841f8748e225dc73aaf","impliedFormat":1},{"version":"942ab34f62ac3f3d20014615b6442b6dc51815e30a878ebc390dd70e0dec63bf","impliedFormat":1},{"version":"7a671bf8b4ad81b8b8aea76213ca31b8a5de4ba39490fbdee249fc5ba974a622","impliedFormat":1},{"version":"8e07f13fb0f67e12863b096734f004e14c5ebfd34a524ed4c863c80354c25a44","impliedFormat":1},{"version":"6f6bdb523e5162216efc36ebba4f1ef8e845f1a9e55f15387df8e85206448aee","impliedFormat":1},{"version":"aa2d6531a04d6379318d29891de396f61ccc171bfd2f8448cc1649c184becdf2","impliedFormat":1},{"version":"d422f0c340060a53cb56d0db24dd170e31e236a808130ab106f7ab2c846f1cdb","impliedFormat":1},{"version":"424403ef35c4c97a7f00ea85f4a5e2f088659c731e75dbe0c546137cb64ef8d8","impliedFormat":1},{"version":"16900e9a60518461d7889be8efeca3fe2cbcd3f6ce6dee70fea81dfbf8990a76","impliedFormat":1},{"version":"6daf17b3bd9499bd0cc1733ab227267d48cd0145ed9967c983ccb8f52eb72d6e","impliedFormat":1},{"version":"e4177e6220d0fef2500432c723dbd2eb9a27dcb491344e6b342be58cc1379ec0","impliedFormat":1},{"version":"ab710f1ee2866e473454a348cffd8d5486e3c07c255f214e19e59a4f17eece4d","impliedFormat":1},{"version":"db7ff3459e80382c61441ea9171f183252b6acc82957ecb6285fff4dca55c585","impliedFormat":1},{"version":"4a168e11fe0f46918721d2f6fcdb676333395736371db1c113ae30b6fde9ccd2","impliedFormat":1},{"version":"2a899aef0c6c94cc3537fe93ec8047647e77a3f52ee7cacda95a8c956d3623fb","impliedFormat":1},{"version":"ef2c1585cad462bdf65f2640e7bcd75cd0dbc45bae297e75072e11fe3db017fa","impliedFormat":1},{"version":"6a52170a5e4600bbb47a94a1dd9522dca7348ce591d8cdbb7d4fe3e23bbea461","impliedFormat":1},{"version":"6f6eadb32844b0ec7b322293b011316486894f110443197c4c9fbcba01b3b2fa","impliedFormat":1},{"version":"a51e08f41e3e948c287268a275bfe652856a10f68ddd2bf3e3aaf5b8cdb9ef85","impliedFormat":1},{"version":"16c144a21cd99926eeba1605aec9984439e91aa864d1c210e176ca668f5f586a","impliedFormat":1},{"version":"af48a76b75041e2b3e7bd8eed786c07f39ea896bb2ff165e27e18208d09b8bee","impliedFormat":1},{"version":"fd4107bd5c899165a21ab93768904d5cfb3e98b952f91fbf5a12789a4c0744e6","impliedFormat":1},{"version":"deb092bc337b2cb0a1b14f3d43f56bc663e1447694e6d479d6df8296bdd452d6","impliedFormat":1},{"version":"041bc1c3620322cb6152183857601707ef6626e9d99f736e8780533689fb1bf9","impliedFormat":1},{"version":"77165b117f552be305d3bc2ef83424ff1e67afb22bfabd14ebebb3468c21fcaa","impliedFormat":1},{"version":"128e7c2ffd37aa29e05367400d718b0e4770cefb1e658d8783ec80a16bc0643a","impliedFormat":1},{"version":"076ac4f2d642c473fa7f01c8c1b7b4ef58f921130174d9cf78430651f44c43ec","impliedFormat":1},{"version":"396c1e5a39706999ec8cc582916e05fcb4f901631d2c192c1292e95089a494d9","impliedFormat":1},{"version":"89df75d28f34fc698fe261f9489125b4e5828fbd62d863bbe93373d3ed995056","impliedFormat":1},{"version":"8ccf5843249a042f4553a308816fe8a03aa423e55544637757d0cfa338bb5186","impliedFormat":1},{"version":"93b44aa4a7b27ba57d9e2bad6fb7943956de85c5cc330d2c3e30cd25b4583d44","impliedFormat":1},{"version":"a0c6216075f54cafdfa90412596b165ff85e2cadd319c49557cc8410f487b77c","impliedFormat":1},{"version":"3c359d811ec0097cba00fb2afd844b125a2ddf4cad88afaf864e88c8d3d358bd","impliedFormat":1},{"version":"d8ec19be7d6d3950992c3418f3a4aa2bcad144252bd7c0891462b5879f436e4e","impliedFormat":1},{"version":"db37aa3208b48bdcbc27c0c1ae3d1b86c0d5159e65543e8ab79cbfb37b1f2f34","impliedFormat":1},{"version":"d62f09256941e92a95b78ae2267e4cf5ff2ca8915d62b9561b1bc85af1baf428","impliedFormat":1},{"version":"e6223b7263dd7a49f4691bf8df2b1e69f764fb46972937e6f9b28538d050b1ba","impliedFormat":1},{"version":"2daf06d8e15cbca27baa6c106253b92dad96afd87af9996cf49a47103b97dc95","impliedFormat":1},{"version":"1db014db736a09668e0c0576585174dbcfd6471bb5e2d79f151a241e0d18d66b","impliedFormat":1},{"version":"8a153d30edde9cefd102e5523b5a9673c298fc7cf7af5173ae946cbb8dd48f11","impliedFormat":1},{"version":"abaaf8d606990f505ee5f76d0b45a44df60886a7d470820fcfb2c06eafa99659","impliedFormat":1},{"version":"8109e0580fc71dbefd6091b8825acf83209b6c07d3f54c33afeafab5e1f88844","impliedFormat":1},{"version":"d92a80c2c05cf974704088f9da904fe5eadc0b3ad49ddd1ef70ca8028b5adda1","impliedFormat":1},{"version":"fbd7450f20b4486c54f8a90486c395b14f76da66ba30a7d83590e199848f0660","impliedFormat":1},{"version":"ece5b0e45c865645ab65880854899a5422a0b76ada7baa49300c76d38a530ee1","impliedFormat":1},{"version":"62d89ac385aeab821e2d55b4f9a23a277d44f33c67fefe4859c17b80fdb397ea","impliedFormat":1},{"version":"f4dee11887c5564886026263c6ee65c0babc971b2b8848d85c35927af25da827","impliedFormat":1},{"version":"fb8dd49a4cd6d802be4554fbab193bb06e2035905779777f32326cb57cf6a2c2","impliedFormat":1},{"version":"df29ade4994de2d9327a5f44a706bbe6103022a8f40316839afa38d3e078ee06","impliedFormat":1},{"version":"82d3e00d56a71fc169f3cf9ec5f5ffcc92f6c0e67d4dfc130dafe9f1886d5515","impliedFormat":1},{"version":"d38f45cb868a830d130ac8b87d3f7e8caff4961a3a1feae055de5e538e20879a","impliedFormat":1},{"version":"4c30a5cb3097befb9704d16aa4670e64e39ea69c5964a1433b9ffd32e1a5a3a1","impliedFormat":1},{"version":"1b33478647aa1b771314745807397002a410c746480e9447db959110999873ce","impliedFormat":1},{"version":"7b3a5e25bf3c51af55cb2986b89949317aa0f6cbfb5317edd7d4037fa52219a9","impliedFormat":1},{"version":"3cd50f6a83629c0ec330fc482e587bfa96532d4c9ce85e6c3ddf9f52f63eee11","impliedFormat":1},{"version":"9fac6ebf3c60ced53dd21def30a679ec225fc3ff4b8d66b86326c285a4eebb5a","impliedFormat":1},{"version":"8cb83cb98c460cd716d2a98b64eb1a07a3a65c7362436550e02f5c2d212871d1","impliedFormat":1},{"version":"07bc8a3551e39e70c38e7293b1a09916867d728043e352b119f951742cb91624","impliedFormat":1},{"version":"e47adc2176f43c617c0ab47f2d9b2bb1706d9e0669bf349a30c3fe09ddd63261","impliedFormat":1},{"version":"7fec79dfd7319fec7456b1b53134edb54c411ba493a0aef350eee75a4f223eeb","impliedFormat":1},{"version":"189c489705bb96a308dcde9b3336011d08bfbca568bcaf5d5d55c05468e9de7a","impliedFormat":1},{"version":"98f4b1074567341764b580bf14c5aabe82a4390d11553780814f7e932970a6f7","impliedFormat":1},{"version":"dadfa5fd3d5c511ca6bfe240243b5cf2e0f87e44ea63e23c4b2fce253c0d4601","impliedFormat":1},{"version":"2e252235037a2cd8feebfbf74aa460f783e5d423895d13f29a934d7655a1f8be","impliedFormat":1},{"version":"763f4ac187891a6d71ae8821f45eef7ff915b5d687233349e2c8a76c22b3bf2a","impliedFormat":1},{"version":"cb5b0d51a7c42a3916d839e1ee149bcc18ffb9037f29636510fa433ff65684ca","impliedFormat":1},{"version":"b7d85dc2de8db4ca983d848c8cfad6cf4d743f8cb35afe1957bedf997c858052","impliedFormat":1},{"version":"83daad5d7ae60a0aede88ea6b9e40853abcbe279c10187342b25e96e35bc9f78","impliedFormat":1},{"version":"c39ddfb764058d817d0e8c4044363950edb075fa52ab0054d09dec01c5ec7267","impliedFormat":1},{"version":"3dffa83b578e67fcbfd7965c5ecb72476a293f9224608e17e0bca0eef53eb6b4","impliedFormat":1},{"version":"f7a5ab7b54bdc6a13cf1015e1b5d6eeb31d765d54045281bfeefcdfcc982a37c","impliedFormat":1},{"version":"39eaec2510829bd8503fd25defd6477575b08abd1e73bd12a73a4b1fa2ceb213","impliedFormat":1},{"version":"21247c958d397091ec30e63b27294baa1d1434c333da4fda697743190311dc62","impliedFormat":1},{"version":"4d40689c11256f894912c50f44b4d943e135393d45b34f161434b90491235c38","impliedFormat":1},{"version":"d5eb5865d4cbaa9985cc3cfb920b230cdcf3363f1e70903a08dc4baab80b0ce1","impliedFormat":1},{"version":"51ebca098538b252953b1ef83c165f25b52271bfb6049cd09d197dddd4cd43c5","impliedFormat":1},{"version":"ef5a57b347e022aa7049f34466d447c077ac2ec98b0d6fb112fd7a3294963639","signature":"07d984efaeb2286cfd7ab0ccc34e1e6deb3961c1c94129122aa63fe2ab7351eb"},{"version":"9223a0889abb0669020e94a9b8c1e68274cdc05533c1f79d84fe516450e94ebd","signature":"bbc394ad2a2ec9c5dbcd9f60d4b365bf809989c90dd3c202b20fddc20e699bdf"},{"version":"0bf811dcbddc95e2551f704cfd2afc267bf619f8b8f2b7bdbb94df96ec3cbfe3","impliedFormat":1},{"version":"243e3c271aff347e8461255546750cf7d413585016c510e33907e42a754d6937","impliedFormat":1},{"version":"7c14e702387296711c1a829bc95052ff02f533d4aa27d53cc0186c795094a3a9","impliedFormat":1},{"version":"4c72d080623b3dcd8ebd41f38f7ac7804475510449d074ca9044a1cbe95517ae","impliedFormat":1},{"version":"579f8828da42ae02db6915a0223d23b0da07157ff484fecdbf8a96fffa0fa4df","impliedFormat":1},{"version":"3f17ea1a2d703cfe38e9fecf8d8606717128454d2889cef4458a175788ad1b60","impliedFormat":1},{"version":"3ae3b86c48ae3b092e5d5548acbf4416b427fed498730c227180b5b1a8aa86e3","impliedFormat":1},{"version":"8f1241f5d9f0d3d72117768b3c974e462840fbd85026fb66685078945404cf2f","impliedFormat":1},{"version":"ba63131c5e91f797736444933af16ffa42f9f8c150d859ec65f568f037a416ea","impliedFormat":1},{"version":"aa99b580bd92dcb2802c9067534ebc32381f0e1f681a65366bcf3adae208a3a4","impliedFormat":1},{"version":"340a45cd77b41d8a6deda248167fa23d3dc67ec798d411bd282f7b3d555b1695","impliedFormat":1},{"version":"0e9aa853b5eb2ca09e0e3e3eb94cbd1d5fb3d682ab69817d4d11fe225953fc57","impliedFormat":1},{"version":"179683df1e78572988152d598f44297da79ac302545770710bba87563ce53e06","impliedFormat":1},{"version":"793c353144f16601da994fa4e62c09b7525836ce999c44f69c28929072ca206a","impliedFormat":1},{"version":"d34aa8df2d0b18fb56b1d772ff9b3c7aea7256cf0d692f969be6e1d27b74d660","impliedFormat":1},{"version":"baac9896d29bcc55391d769e408ff400d61273d832dd500f21de766205255acb","impliedFormat":1},{"version":"2f5747b1508ccf83fad0c251ba1e5da2f5a30b78b09ffa1cfaf633045160afed","impliedFormat":1},{"version":"90407bbaa24977b8a6a90861148ac98d8652afe69992a90d823f29e9807fe2d7","affectsGlobalScope":true,"impliedFormat":1},{"version":"b71c603a539078a5e3a039b20f2b0a0d1708967530cf97dec8850a9ca45baa2b","impliedFormat":1},{"version":"d3f2d715f57df3f04bf7b16dde01dec10366f64fce44503c92b8f78f614c1769","impliedFormat":1},{"version":"cb90077223cc1365fa21ef0911a1f9b8f2f878943523d97350dc557973ca3823","impliedFormat":1},{"version":"18f1541b81b80d806120a3489af683edfb811deb91aeca19735d9bb2613e6311","impliedFormat":1},{"version":"104c67f0da1bdf0d94865419247e20eded83ce7f9911a1aa75fc675c077ca66e","impliedFormat":1},{"version":"cc0d0b339f31ce0ab3b7a5b714d8e578ce698f1e13d7f8c60bfb766baeb1d35c","impliedFormat":1},{"version":"232f118ae64ab84dcd26ddb60eaed5a6e44302d36249abf05e9e3fc2cbb701a2","impliedFormat":1},{"version":"ff155930718467b27e379e4a195e4607ce277f805cad9d2fa5f4fd5dec224df6","affectsGlobalScope":true,"impliedFormat":1},{"version":"599ac4a84b7aa6a298731179ec1663a623ff8ac324cdc1dabb9c73c1259dc854","impliedFormat":1},{"version":"3d348edaf4ef0169b476e42e1489ddc800ae03bd5dd3acb12354225718170774","impliedFormat":1},{"version":"585bc61f439c027640754dd26e480afa202f33e51db41ee283311a59c12c62e7","impliedFormat":1},{"version":"8f1241f5d9f0d3d72117768b3c974e462840fbd85026fb66685078945404cf2f","impliedFormat":1},{"version":"160b24efb5a868df9c54f337656b4ef55fcbe0548fe15408e1c0630ec559c559","impliedFormat":1},{"version":"400ae7f601851117b675224e21262b8673dacdba1286a7817bc13db89a359241","signature":"e9e19d99ec3b9ddd69e313c682ad9f6e9d37ffa440a8c5328eb7f589b7e8b373"},"c1edbbc6df7fe0682a90cff315a25a90e4fe9ea4eb6a3df7a86b7e1aa4c8e876",{"version":"cb5eaaa2a079305b1c5344af739b29c479746f7a7aefffc7175d23d8b7c8dbb0","impliedFormat":1},{"version":"bd324dccada40f2c94aaa1ebc82b11ce3927b7a2fe74a5ab92b431d495a86e6f","impliedFormat":1},{"version":"56749bf8b557c4c76181b2fd87e41bde2b67843303ae2eabb299623897d704d6","impliedFormat":1},{"version":"5a6fbec8c8e62c37e9685a91a6ef0f6ecaddb1ee90f7b2c2b71b454b40a0d9a6","impliedFormat":1},{"version":"e7435f2f56c50688250f3b6ef99d8f3a1443f4e3d65b4526dfb31dfd4ba532f8","impliedFormat":1},{"version":"6fc56a681a637069675b2e11b4aa105efe146f7a88876f23537e9ea139297cf9","impliedFormat":1},{"version":"33b7f4106cf45ae7ccbb95acd551e9a5cd3c27f598d48216bda84213b8ae0c7e","impliedFormat":1},{"version":"176d6f604b228f727afb8e96fd6ff78c7ca38102e07acfb86a0034d8f8a2064a","impliedFormat":1},{"version":"1b1a02c54361b8c222392054648a2137fc5983ad5680134a653b1d9f655fe43d","impliedFormat":1},{"version":"8bcb884d06860a129dbffa3500d51116d9d1040bb3bf1c9762eb2f1e7fd5c85c","impliedFormat":1},{"version":"e55c0f31407e1e4eee10994001a4f570e1817897a707655f0bbe4d4a66920e9e","impliedFormat":1},{"version":"a37c2194c586faa8979f50a5c5ca165b0903d31ee62a9fe65e4494aa099712c0","impliedFormat":1},{"version":"6602339ddc9cd7e54261bda0e70fb356d9cdc10e3ec7feb5fa28982f8a4d9e34","impliedFormat":1},{"version":"7ffaa736b8a04b0b8af66092da536f71ef13a5ef0428c7711f32b94b68f7c8c8","impliedFormat":1},{"version":"7b4930d666bbe5d10a19fcc8f60cfa392d3ad3383b7f61e979881d2c251bc895","impliedFormat":1},{"version":"46342f04405a2be3fbfb5e38fe3411325769f14482b8cd48077f2d14b64abcfb","impliedFormat":1},{"version":"8fa675c4f44e6020328cf85fdf25419300f35d591b4f56f56e00f9d52b6fbb3b","impliedFormat":1},{"version":"ba98f23160cfa6b47ee8072b8f54201f21a1ee9addc2ef461ebadf559fe5c43a","impliedFormat":1},{"version":"45a4591b53459e21217dc9803367a651e5a1c30358a015f27de0b3e719db816b","impliedFormat":1},{"version":"9ef22bee37885193b9fae7f4cad9502542c12c7fe16afe61e826cdd822643d84","impliedFormat":1},{"version":"b0451895b894c102eed19d50bd5fcb3afd116097f77a7d83625624fafcca8939","impliedFormat":1},{"version":"bce17120b679ff4f1be70f5fe5c56044e07ed45f1e555db6486c6ded8e1da1c8","impliedFormat":1},{"version":"7590477bfa2e309e677ff7f31cb466f377fcd0e10a72950439c3203175309958","impliedFormat":1},{"version":"3f9ebd554335d2c4c4e7dc67af342d37dc8f2938afa64605d8a93236022cc8a5","impliedFormat":1},{"version":"1c077c9f6c0bc02a36207994a6e92a8fbf72d017c4567f640b52bf32984d2392","impliedFormat":1},{"version":"600b42323925b32902b17563654405968aa12ee39e665f83987b7759224cc317","impliedFormat":1},{"version":"32c8f85f6b4e145537dfe61b94ddd98b47dbdd1d37dc4b7042a8d969cd63a1aa","impliedFormat":1},{"version":"2426ed0e9982c3d734a6896b697adf5ae93d634b73eb15b48da8106634f6d911","impliedFormat":1},{"version":"057431f69d565fb44c246f9f64eac09cf309a9af7afb97e588ebef19cc33c779","impliedFormat":1},{"version":"960d026ca8bf27a8f7a3920ee50438b50ec913d635aa92542ca07558f9c59eca","impliedFormat":1},{"version":"71f5d895cc1a8a935c40c070d3d0fade53ae7e303fd76f443b8b541dee19a90c","impliedFormat":1},{"version":"252eb4750d0439d1674ad0dc30d2a2a3e4655e08ad9e58a7e236b21e78d1d540","impliedFormat":1},{"version":"e344b4a389bb2dfa98f144f3f195387a02b6bdb69deed4a96d16cc283c567778","impliedFormat":1},{"version":"c6cdcd12d577032b84eed1de4d2de2ae343463701a25961b202cff93989439fb","impliedFormat":1},{"version":"3dc633586d48fcd04a4f8acdbf7631b8e4a334632f252d5707e04b299069721e","impliedFormat":1},{"version":"3322858f01c0349ee7968a5ce93a1ca0c154c4692aa8f1721dc5192a9191a168","impliedFormat":1},{"version":"6dde0a77adad4173a49e6de4edd6ef70f5598cbebb5c80d76c111943854636ca","impliedFormat":1},{"version":"09acacae732e3cc67a6415026cfae979ebe900905500147a629837b790a366b3","impliedFormat":1},{"version":"f7b622759e094a3c2e19640e0cb233b21810d2762b3e894ef7f415334125eb22","impliedFormat":1},{"version":"99236ea5c4c583082975823fd19bcce6a44963c5c894e20384bc72e7eccf9b03","impliedFormat":1},{"version":"f6688a02946a3f7490aa9e26d76d1c97a388e42e77388cbab010b69982c86e9e","impliedFormat":1},{"version":"9f642953aba68babd23de41de85d4e97f0c39ef074cb8ab8aa7d55237f62aff6","impliedFormat":1},{"version":"159d95163a0ed369175ae7838fa21a9e9e703de5fdb0f978721293dd403d9f4a","impliedFormat":1},{"version":"2d2ec3235e01474f45a68f28cf826c2f5228b79f7d474d12ca3604cdcfdac80c","impliedFormat":1},{"version":"6dd249868034c0434e170ba6e0451d67a0c98e5a74fd57a7999174ee22a0fa7b","impliedFormat":1},{"version":"9716553c72caf4ff992be810e650707924ec6962f6812bd3fbdb9ac3544fd38f","impliedFormat":1},{"version":"506bc8f4d2d639bebb120e18d3752ddeee11321fd1070ad2ce05612753c628d6","impliedFormat":1},{"version":"053c51bbc32db54be396654ab5ecd03a66118d64102ac9e22e950059bc862a5e","impliedFormat":1},{"version":"1977f62a560f3b0fc824281fd027a97ce06c4b2d47b408f3a439c29f1e9f7e10","impliedFormat":1},{"version":"627570f2487bd8d899dd4f36ecb20fe0eb2f8c379eff297e24caba0c985a6c43","impliedFormat":1},{"version":"0f6e0b1a1deb1ab297103955c8cd3797d18f0f7f7d30048ae73ba7c9fb5a1d89","impliedFormat":1},{"version":"0a051f254f9a16cdde942571baab358018386830fed9bdfff42478e38ba641ce","impliedFormat":1},{"version":"17269f8dfc30c4846ab7d8b5d3c97ac76f50f33de96f996b9bf974d817ed025b","impliedFormat":1},{"version":"9e82194af3a7d314ccbc64bb94bfb62f4bfea047db3422a7f6c5caf2d06540a9","impliedFormat":1},{"version":"083d6f3547ccbf25dfa37b950c50bee6691ed5c42107f038cc324dbca1e173ae","impliedFormat":1},{"version":"952a9eab21103b79b7a6cca8ad970c3872883aa71273f540285cad360c35da40","impliedFormat":1},{"version":"8ba48776335db39e0329018c04486907069f3d7ee06ce8b1a6134b7d745271cc","impliedFormat":1},{"version":"e6d5809e52ed7ef1860d1c483e005d1f71bab36772ef0fd80d5df6db1da0e815","impliedFormat":1},{"version":"893e5cfbae9ed690b75b8b2118b140665e08d182ed8531e1363ec050905e6cb2","impliedFormat":1},{"version":"6ae7c7ada66314a0c3acfbf6f6edf379a12106d8d6a1a15bd35bd803908f2c31","impliedFormat":1},{"version":"e4b1e912737472765e6d2264b8721995f86a463a1225f5e2a27f783ecc013a7b","impliedFormat":1},{"version":"97146bbe9e6b1aab070510a45976faaf37724c747a42d08563aeae7ba0334b4f","impliedFormat":1},{"version":"c40d552bd2a4644b0617ec2f0f1c58618a25d098d2d4aa7c65fb446f3c305b54","impliedFormat":1},{"version":"09e64dea2925f3a0ef972d7c11e7fa75fec4c0824e9383db23eacf17b368532f","impliedFormat":1},{"version":"424ddba00938bb9ae68138f1d03c669f43556fc3e9448ed676866c864ca3f1d6","impliedFormat":1},{"version":"a0fe12181346c8404aab9d9a938360133b770a0c08b75a2fce967d77ca4b543f","impliedFormat":1},{"version":"3cc6eb7935ff45d7628b93bb6aaf1a32e8cb3b24287f9e75694b607484b377b3","impliedFormat":1},{"version":"ced02e78a2e10f89f4d70440d0a8de952a5946623519c54747bc84214d644bac","impliedFormat":1},{"version":"efd463021ccc91579ed8ae62584176baab2cd407c555c69214152480531a2072","impliedFormat":1},{"version":"29647c3b79320cfeecb5862e1f79220e059b26db2be52ea256df9cf9203fb401","impliedFormat":1},{"version":"e8cdefd2dc293cb4866ee8f04368e7001884650bb0f43357c4fe044cc2e1674f","impliedFormat":1},{"version":"582a3578ebba9238eb0c5d30b4d231356d3e8116fea497119920208fb48ccf85","impliedFormat":1},{"version":"185eae4a1e8a54e38f36cd6681cfa54c975a2fc3bc2ba6a39bf8163fac85188d","impliedFormat":1},{"version":"0c0a02625cf59a0c7be595ccc270904042bea523518299b754c705f76d2a6919","impliedFormat":1},{"version":"c44fc1bbdb5d1c8025073cb7c5eab553aa02c069235a1fc4613cd096d578ab80","impliedFormat":1},{"version":"cee72255e129896f0240ceb58c22e207b83d2cc81d8446190d1b4ef9b507ccd6","impliedFormat":1},{"version":"3b54670e11a8d3512f87e46645aa9c83ae93afead4a302299a192ac5458aa586","impliedFormat":1},{"version":"c2fc4d3a130e9dc0e40f7e7d192ef2494a39c37da88b5454c8adf143623e5979","impliedFormat":1},{"version":"2e693158fc1eedba3a5766e032d3620c0e9c8ad0418e4769be8a0f103fdb52cd","impliedFormat":1},{"version":"516275ccf3e66dc391533afd4d326c44dd750345b68bb573fc592e4e4b74545f","impliedFormat":1},{"version":"07c342622568693847f6cb898679402dd19740f815fd43bec996daf24a1e2b85","impliedFormat":1},{"version":"fa40d705f9813843d47f19321591499f14d1a18fa5e8ca9beaee5aac633c3d0d","impliedFormat":1},{"version":"a7a6330fb015f72d821e23004e63a3827e0c632b614ef3a310b3c81b66de61fd","impliedFormat":1},{"version":"89968316b7069339433bd42d53fe56df98b6990783dfe00c9513fb4bd01c2a1c","impliedFormat":1},{"version":"a4096686f982f6977433ee9759ecbef49da29d7e6a5d8278f0fbc7b9f70fce12","impliedFormat":1},{"version":"62e62a477c56cda719013606616dd856cfdc37c60448d0feb53654860d3113bb","impliedFormat":1},{"version":"207c107dd2bd23fa9febac2fe05c7c72cdac02c3f57003ab2e1c6794a6db0c05","impliedFormat":1},{"version":"55133e906c4ddabecdfcbc6a2efd4536a3ac47a8fa0a3fe6d0b918cac882e0d4","impliedFormat":1},{"version":"2147f8d114cf58c05106c3dccea9924d069c69508b5980ed4011d2b648af2ffe","impliedFormat":1},{"version":"2eb4012a758b9a7ba9121951d7c4b9f103fe2fc626f13bec3e29037bb9420dc6","impliedFormat":1},{"version":"fe61f001bd4bd0a374daa75a2ba6d1bb12c849060a607593a3d9a44e6b1df590","impliedFormat":1},{"version":"cfe8221c909ad721b3da6080570553dea2f0e729afbdbcf2c141252cf22f39b5","impliedFormat":1},{"version":"34e89249b6d840032b9acdec61d136877f84f2cd3e3980355b8a18f119809956","impliedFormat":1},{"version":"6f36ff8f8a898184277e7c6e3bf6126f91c7a8b6a841f5b5e6cb415cfc34820e","impliedFormat":1},{"version":"4b6378c9b1b3a2521316c96f5c777e32a1b14d05b034ccd223499e26de8a379c","impliedFormat":1},{"version":"07be5ae9bf5a51f3d98ffcfacf7de2fe4842a7e5016f741e9fad165bb929be93","impliedFormat":1},{"version":"cb1b37eda1afc730d2909a0f62cac4a256276d5e62fea36db1473981a5a65ab1","impliedFormat":1},{"version":"195f855b39c8a6e50eb1f37d8f794fbd98e41199dffbc98bf629506b6def73d7","impliedFormat":1},{"version":"471386a0a7e4eb88c260bdde4c627e634a772bf22f830c4ec1dad823154fd6f5","impliedFormat":1},{"version":"108314a60f3cb2454f2d889c1fb8b3826795399e5d92e87b2918f14d70c01e69","impliedFormat":1},{"version":"d75cc838286d6b1260f0968557cd5f28495d7341c02ac93989fb5096deddfb47","impliedFormat":1},{"version":"d531dc11bb3a8a577bd9ff83e12638098bfc9e0856b25852b91aac70b0887f2a","impliedFormat":1},{"version":"19968b998a2ab7dfd39de0c942fc738b2b610895843fec25477bc393687babd8","impliedFormat":1},{"version":"c0e6319f0839d76beed6e37b45ec4bb80b394d836db308ae9db4dea0fe8a9297","impliedFormat":1},{"version":"1a7b11be5c442dab3f4af9faf20402798fddf1d3c904f7b310f05d91423ba870","impliedFormat":1},{"version":"079d3f1ddcaf6c0ff28cfc7851b0ce79fcd694b3590afa6b8efa6d1656216924","impliedFormat":1},{"version":"2c817fa37b3d2aa72f01ce4d3f93413a7fbdecafe1b9fb7bd7baaa1bbd46eb08","impliedFormat":1},{"version":"682203aed293a0986cc2fccc6321d862742b48d7359118ac8f36b290d28920d2","impliedFormat":1},{"version":"7406d75a4761b34ce126f099eafe6643b929522e9696e5db5043f4e5c74a9e40","impliedFormat":1},{"version":"7e9c4e62351e3af1e5e49e88ebb1384467c9cd7a03c132a3b96842ccdc8045c4","impliedFormat":1},{"version":"ea1f9c60a912065c08e0876bd9500e8fa194738855effb4c7962f1bfb9b1da86","impliedFormat":1},{"version":"903f34c920e699dacbc483780b45d1f1edcb1ebf4b585a999ece78e403bb2db3","impliedFormat":1},{"version":"100ebfd0470433805c43be5ae377b7a15f56b5d7181c314c21789c4fe9789595","impliedFormat":1},{"version":"12533f60d36d03d3cf48d91dc0b1d585f530e4c9818a4d695f672f2901a74a86","impliedFormat":1},{"version":"21d9968dad7a7f021080167d874b718197a60535418e240389d0b651dd8110e7","impliedFormat":1},{"version":"2ef7349b243bce723d67901991d5ad0dfc534da994af61c7c172a99ff599e135","impliedFormat":1},{"version":"fa103f65225a4b42576ae02d17604b02330aea35b8aaf889a8423d38c18fa253","impliedFormat":1},{"version":"1b9173f64a1eaee88fa0c66ab4af8474e3c9741e0b0bd1d83bfca6f0574b6025","impliedFormat":1},{"version":"1b212f0159d984162b3e567678e377f522d7bee4d02ada1cc770549c51087170","impliedFormat":1},{"version":"46bd71615bdf9bfa8499b9cfce52da03507f7140c93866805d04155fa19caa1b","impliedFormat":1},{"version":"86cb49eb242fe19c5572f58624354ffb8743ff0f4522428ebcabc9d54a837c73","impliedFormat":1},{"version":"fc2fb9f11e930479d03430ee5b6588c3788695372b0ab42599f3ec7e78c0f6d5","impliedFormat":1},{"version":"bb1e5cf70d99c277c9f1fe7a216b527dd6bd2f26b307a8ab65d24248fb3319f5","impliedFormat":1},{"version":"817547eacf93922e22570ba411f23e9164544dead83e379c7ae9c1cfc700c2cf","impliedFormat":1},{"version":"a728478cb11ab09a46e664c0782610d7dd5c9db3f9a249f002c92918ca0308f7","impliedFormat":1},{"version":"9e91ef9c3e057d6d9df8bcbfbba0207e83ef9ab98aa302cf9223e81e32fdfe8d","impliedFormat":1},{"version":"66d30ef7f307f95b3f9c4f97e6c1a5e4c462703de03f2f81aca8a1a2f8739dbd","impliedFormat":1},{"version":"293ca178fd6c23ed33050052c6544c9d630f9d3b11d42c36aa86218472129243","impliedFormat":1},{"version":"90a4be0e17ba5824558c38c93894e7f480b3adf5edd1fe04877ab56c56111595","impliedFormat":1},{"version":"fadd55cddab059940934df39ce2689d37110cfe37cc6775f06b0e8decf3092d7","impliedFormat":1},{"version":"91324fe0902334523537221b6c0bef83901761cfd3bd1f140c9036fa6710fa2b","impliedFormat":1},{"version":"b4f3b4e20e2193179481ab325b8bd0871b986e1e8a8ed2961ce020c2dba7c02d","impliedFormat":1},{"version":"41744c67366a0482db029a21f0df4b52cd6f1c85cbc426b981b83b378ccb6e65","impliedFormat":1},{"version":"c3f3cf7561dd31867635c22f3c47c8491af4cfa3758c53e822a136828fc24e5d","impliedFormat":1},{"version":"a88ddea30fae38aa071a43b43205312dc5ff86f9e21d85ba26b14690dc19d95e","impliedFormat":1},{"version":"b5b2d0510e5455234016bbbaba3839ca21adbc715d1b9c3d6dede7d411a28545","impliedFormat":1},{"version":"5515f17f45c6aafe6459afa3318bba040cb466a8d91617041566808a5fd77a44","impliedFormat":1},{"version":"4df1f0c17953b0450aa988c9930061f8861b114e1649e1a16cfd70c5cbdf8d83","impliedFormat":1},{"version":"441104b363d80fe57eb79a50d495e0b7e3ebeb45a5f0d1a4067d71ef75e8fbfa","impliedFormat":1},{"version":"9e48a0f685b26dc6ec290a84ee297390f1f43b16417e9c8099cb855b7ff113e3","signature":"0cf792151b307dea6804a38395b1fe7b4075687e38d13c234f2f485c315334d7"},{"version":"8f9880ff820d696eae8bfe379382ca6329827f3150a218bb96da4580ffa0d414","signature":"6d36c3b7ee878a43b16ed5e0d17bcd02fa6d72a27f4152366693cd1c085d7e5d"},{"version":"03c92769f389dbd9e45232f7eb01c3e0f482b62555aaf2029dcbf380d5cee9e4","impliedFormat":1},{"version":"32d7f70fd3498bc76a46dab8b03af4215f445f490f8e213c80cf06b636a4e413","impliedFormat":1},"b52a26461891c221a327525438fd3d48bf85de81bad9405876d777248ba56451",{"version":"d653424b032310532b686244e24ee88021102037a68be96cda2638016f8a49f9","signature":"a28b5c0c372fb375910b3fe3c3ce4331509bc18ccef7cc39c9ee9d8daf8225d1"},{"version":"1ad92dbc0673bd8c8cc4759c83ae0d76ea93df1f21fa467f0badd4d084a05aa4","signature":"289848ea7c82e6e560387447624ba321ad88764af8e1515044544d756cf9e590"},{"version":"7f432427f973d9585104f9a08189f0e9804bf35e8712f0678f674ffbc86cd211","signature":"4ca3229a0b7ab6f1951c621e07c500bbe7518f86e991a971b8063d27051c8809"},"8080391fc73ab433dcea7bfa51c3ab08e5bb1cab10572fcbc16481ed755d9fac","2a00b0fd7ecdebfe355032f424217c786fab24e10e1331d8895a77fb8795ac01",{"version":"1844f90cd5705c7962e7c9b4cf74aded930cec18f93ad8ac565981cdc160730c","signature":"f092be2d5edd3af4d2fbef0dee84ced262c0a65f9a1fb43d7a1f8a4f0b79cd15"},{"version":"86aa295b0bbbd43a40edf3f2b69fcbcb5e471e550e9c9caf596c5a482390cd68","signature":"89ca1fd1c04411c4031a09748b91cd80315f78b180dc089f29d7d4ae7ec7773a"},{"version":"0ccb4e37fd34c26883d9d011bd45112fc90c7fe6fb06a5025c3724da077f4a83","signature":"2954ad1bcc55ceb11ad6f46f51baf174d5af01dc78a1dc43696eae92852c2114"},{"version":"55dafcdac85183c45d8e20a9a5d5afe795b4f97ca96d0deb510b9c1990368fe4","signature":"89c4171f7148b8204d315052cc4e882de8a88f40a39d73042bf5bfa72077eeed"},{"version":"45ead1cd1e3b62955e4e403753704c49d13e9c1814f8ad463d57ee75dff59080","signature":"03f20dbbf4f996a7e1f7d6b8c75db32e2d39e09bc8497309718e8eb8a05e9baf"},{"version":"3c1690755a85170ada6d8299619191b0a67c41bf0bce8ad3fd8f78941a479626","signature":"5f7092ca69cd41b85746205cbd0a0506a11707c69db783d300dbbe00a452d627"},{"version":"b74ab927cdf427b9df76fc72f0fcc1d20f63ce6477fe72b55901fdad78d2c8fa","signature":"f3a9acde05f22c6a3e754437e693c1dda933a2e4c6614ad745d6633eff3bf0e7"},{"version":"2e342dd056db3b3b7662d7cda3f22c72351e622fe98996197e10be39089702b9","signature":"f0929a3b516c7f04c4b39502fcfacf2e5bbde2aaaf28544f0986dd5c1bfca61c"},{"version":"a82f4df4e59c05a4a43eacce801519080fa98db631b6c3d019fa7375129c5034","signature":"76efd0b1245c96aaa393c5265906b5b77466d5137cac031fcbc52e31c8b0fd4f"},{"version":"f69979dab109363bf1e92a702b5cfada4ba50ff540fc8153bbb44934eb0f6b88","signature":"5f67c50a5091418ae78028af3c4a53415b71c8e1d083320d9208950c7c3d967e"},{"version":"6bb392b98265954a6c72a3125c62c78261f2d4db5f39767b1571742ae0eb50a7","signature":"7214164cfa10573d2bf79da5174719e21aa58d3745730ba5464fd4491e9d5c6c"},{"version":"25e5c8b73c6ad21f39e8e72f954090f30b431a993252bccea5bdad4a3d93c760","impliedFormat":1},{"version":"5bf595f68b7c1d46ae8385e3363c6e0d4695b6da58a84c6340489fc07ffc73f8","impliedFormat":1},{"version":"b87682ddc9e2c3714ca66991cdd86ff7e18cae6fd010742a93bd612a07d19697","impliedFormat":1},{"version":"87d3ab3f2edb68849714195c008bf9be6067b081ef5a199c9c32f743c6871522","impliedFormat":1},{"version":"86bf2bfe29d0bc3fbc68e64c25ea6eab9bcb3c518ae941012ed75b1e87d391ae","impliedFormat":1},{"version":"8d9c4957c4feed3de73c44eb472f5e44dfb0f0cb75db6ea00f38939bd77f6e84","impliedFormat":1},{"version":"00b4f8b82e78f658b7e269c95d07e55d391235ce34d432764687441177ae7f64","impliedFormat":1},{"version":"57880096566780d72e02a5b34d8577e78cdf072bfd624452a95d65bd8f07cbe0","impliedFormat":1},{"version":"10ac50eaf9eb62c048efe576592b14830a757f7ea7ed28ee8deafc19c9845297","impliedFormat":1},{"version":"e75af112e5487476f7c427945fbd76ca46b28285586ad349a25731d196222d56","impliedFormat":1},{"version":"e91adad3da69c366d57067fcf234030b8a05bcf98c25a759a7a5cd22398ac201","impliedFormat":1},{"version":"d7d6e1974124a2dad1a1b816ba2436a95f44feeda0573d6c9fb355f590cf9086","impliedFormat":1},{"version":"464413fcd7e7a3e1d3f2676dc5ef4ebe211c10e3107e126d4516d79439e4e808","impliedFormat":1},{"version":"18f912e4672327b3dd17d70e91da6fcd79d497ba01dde9053a23e7691f56908c","impliedFormat":1},{"version":"2974e2f06de97e1d6e61d1462b54d7da2c03b3e8458ee4b3dc36273bc6dda990","impliedFormat":1},{"version":"d8c1697db4bb3234ff3f8481545284992f1516bc712421b81ee3ef3f226ae112","impliedFormat":1},{"version":"59b6cce93747f7eb2c0405d9f32b77874e059d9881ec8f1b65ff6c068fcce6f2","impliedFormat":1},{"version":"e2c3c3ca3818d610599392a9431e60ec021c5d59262ecd616538484990f6e331","impliedFormat":1},{"version":"e3cd60be3c4f95c43420be67eaa21637585b7c1a8129f9b39983bbd294f9513c","impliedFormat":1},{"version":"58564964bef3ffbd810241a8bd1c3a54347dd8adf04e1077ba49051009d3007d","affectsGlobalScope":true,"impliedFormat":1},"a0fd8814c82fb4096819a76c3b27e850c68d82fb4804e494e71213976b77b2eb","1af19032d045e05774bfba12057fd0fc753c7179fc476b7dcc884b33ea64c984","361fc6bc75790c61067bd897e1f4d5270db9b77405a6db186e5a5554d75f5472",{"version":"6afe7198e533196a050436bb5563e051314c3086e5b7453d31419b2e0eb8e209","signature":"ab733417a559cb00c9f0a68c547b00f58d11072c93156fab5ce61b7e7c499742"},{"version":"fa98dbde558043e9c9783dcf87be161f1740f8f1fa0d24d0ca7d25042ab2c3cd","signature":"54bd3bba489151adf372f94237cc6686c31189e361fa2bad11563f79eebe2e95"},{"version":"64ccee34285f80c9c992673033d53e08c35060dc98fb6852c6ee2a3aae40921e","signature":"ea5e4ebce73e576a6519eb2b96b8a3d229b6d3203566cc3b171973a665f00ac8"},{"version":"d073dc5181fec0351bdd2238a5ae0593eb36a29f5b10046a0184a1403f53abdb","signature":"f159a19c257f5f04efe47d8a0be66ddd81dba8aeb5b5f19384c4e1d8ffac3611"},{"version":"71d9030d18c220533761ff42670427461d77ac8271c20e5e16233bb7d05e0716","signature":"2a9c83e69b5e4540cb27403761673d1e680b89cd7e6b24d481bdb522c89bbc72"},{"version":"c1e988ce93bd94fc78c924e29b2135e504520ef2dd26f37178876d7787d2a674","signature":"aba1c160c3e33b9a8aa20c041f95411639623072c5703c9e091795d86ed17cce"},{"version":"c108746405cee9a1a577f6665fef8de82546f394a2a43478fd7acdd871fca319","signature":"5a4bc61db45f14789dc30f314d48350c7a3568a96b56ade650f32baeb90d3203"},{"version":"9b2db45ef6eac4814e892d12d4a17e52f5ea85432156561eca3e4969cca4ee07","signature":"248c3ace8b02af932506305729e31039ea318c65f892b349915bdb0af77d5d12"},{"version":"73aec672a832034ac697e37fdf1c8dccb38a89e64482daf8550a16ec4c89b8f2","signature":"d0155d8caad48502a93b23100da5539b39e0d3f33381e8e564b5d5060b8e488e"},{"version":"ee28ad3f10aba8c9165b1e8d670162b8a12f5ca3053894dbe7047bc11c5a65a7","signature":"442c18ea4feac01c872e38a7929812805baff27d202b56a5c9ed4dd931e12d86"},{"version":"2e7844f864245e2c2a001b751fa6674cbf9f466a92e87d7f7fddfbe3c698dfcf","signature":"9a4a5abc75b83581c2edca47a6598982855f895a7d90aef8a90953f2b9d217c5"},{"version":"b1a344a044a446623787fd9f9a88207550095a0d03c97f2077548a11e669c05a","signature":"2475b8bd761aa5c731fed548f6b03b3fdf85c461730bedaddffce54fdb98b35f"},{"version":"04e7debf207a5946a41311078939f88a7202dbf9d25ebc8aaacaca78cc7a86f9","signature":"71260753215e2bd66730264484a09052c251b81a16345b503fe85db47482a292"},{"version":"593654eebe902db28ca173f021f74ea9f77e8b344aebb0a80fa4d10f29bb3a9d","impliedFormat":1},{"version":"b0dcd18b79fa367830399ac2ae400058f83ea0d78238fee00d6bb42c6d29848a","signature":"6e4af890733e445e3918e88c0ccc46e6228ff1da98bc0ed651ce44dad94dd8e3"},{"version":"60af42b5919c4722eb06796d28d42d3d408a5aa5383654ddb25b892d0678f06a","signature":"504e48ced6908184624c4621259b93971608914e5ce8f1fa15d83ccc0e722101"},{"version":"c37d4dc39f3837d4246299b07f9266e21c93f9f85c88a883acc4d7a5f3482ae9","signature":"5a46641bf5e6b0573c322c061e5b10b775998611b691e54eb883b9b99d274519"},{"version":"cab1685580f9d3582a99704da92611cb7eeeb0316d00fd89667d19c6700c903e","signature":"b32476b42b1194d544d327a0d7d9ca2953cb1c93afc6d1e13218f054d7d12124"},{"version":"f05fc1e20083da5b77b0677ffb235607a11b51082f559c12e03a373c8316c586","signature":"accb6ee2cbdcd4d378165eeb6e887385565bcae3ece67ce57e4cf98f205cb28f"},{"version":"4a8956aa605ddb9348a9c79e34228d4acef80e4b37b2758bdae8a0153fe4cda4","signature":"ba880f8d021863d48a90e993830dae44c4b6f3e38656771035554566b0f1cb26"},{"version":"05e8b986b8a36bcfc86e45cb1279957b581614e787e25b65800b4c363ca9141e","signature":"fac5c5fea95bfbfff38408533a5e6e425f75c5c34733165ff0eb32b47af81966"},{"version":"c875eb597d43e07376dfdf52645fcb9514354829848a07cb9f2f57eae56d5d90","signature":"7fc9679b110f7cee4f036d243aae2a1ce0a0e4b01d2f77a59d4c85d97fb5a6c0"},{"version":"6157ea484ad157bae38cee49d495f96cb4f01a538b3e859f0059d4c7bc87e0e1","signature":"70ee758322961202383ecf5587d569f6795cb4c7a86e81ec92dbc0f81326aa06"},{"version":"2d975a3d9aca4b5475b5980444fa685c99a70a299cd698652c79153b28b2a784","signature":"7086ee60abfa239d0004effbaf95bc34c1b0bc086b187c7cb2f0e31fb92ed43a"},{"version":"d6ded480da3503d4e9be3669483e3b944106f4e81e4381610f86acda9f9c7275","signature":"b0079a176f9bdf828b121b557cbff9098e56eb1e195a3aeb0e70f449e3c2da33"},{"version":"f736944ed9470f842948f8347f89488f3a1fc14937e9f94fdd8c597e20916b07","signature":"2a342530dadf3fab9bc56bf6466702a36968e13d07e5298423ee8486199a4d82"},{"version":"4ef688f92ac4de773226cee4f975760d1b646b6cc5664d8fe1494b0ae4713b0b","signature":"a4db781c15bb55ab566d86f64eda02ced229894fa6fa5a685e0bb2865a321444"},{"version":"a6439d7c6737cd9a8477d2fc00876e4bacd0d950bde86150b710ac204f1e7474","signature":"0ebdb3234816edf412c8effdd07ea2f3a4be0a61f1e0b3b2617d53dcb1a8ec4c"},{"version":"ff74755fb3bd5df464d2037afe3914ff1f7f52d40c9a07f8138bc5995a301c8e","signature":"cd1ecb4eeb4b6bd2ed19483ea59aa42b7c48d5fb48e598296029c301f57d4ec5"},{"version":"850aa3d806326c88464f1765e34681019b5d686ec973bf12ef6311b85469e88a","signature":"aaaf1f03c9efcb14463a781ce855c0d954109c34587a0072c737f939602109b4"},{"version":"f5fb1dd57af67699093f9b6ffb598d0b8e6b1718b15e7f01fdeca782e290043d","signature":"cdbcbf3e0166201d9b0828c4fe038f761f6115a8475d1bc491e741d76c2c64c0"},{"version":"0ccc0059b15b5901fa7e0e8b53e5a5ddfed077e7b643d3cbb539131da1a9e203","signature":"ff6d1e239eded8ed48fe9d34189b850d6114b07989c0f67835b6316d81574558"},{"version":"5921b74e7f861e9d1322c0c4af3415e1b166da2f10f8db8d687af4a3d7a07aac","signature":"2793157341dba12ecc66141b2a610fe789941ea89c6c92ebe4eac1b601dabfbd"},{"version":"387b825a30dd66cddc4c0f5e1f821c06689c80450618c1c89b8c7f2bd818c723","signature":"0f8c482431d45c0b2a1ae6254cfdee24a8c115d946565a6278e2e8cadcad6942"},{"version":"953cbf62815703fa9970c9cfec3c8d033da04a90c2409af6070dcc6858cf6b98","impliedFormat":1},{"version":"68065ce3af3ef8599af8338068cf336be35249eff281ee393186a0ef40db3abf","impliedFormat":1},{"version":"5339f84dfcb7b04aa1c2b4d7713d6128039381447f07abc2e48d36685e2eef44","impliedFormat":1},{"version":"fb35a61a39c933d31b5b2549d906b2c932a1486622958586f662dbd4b2fe72e6","impliedFormat":1},{"version":"24e2728268be1ad2407bab004549d2753a49b2acb0f117a04c4e28ffb3ecdd4f","impliedFormat":1},{"version":"aff159b14eba59afe98a88fe6f57881ba02895fb9763512dda9083497bdcd0e6","impliedFormat":1},{"version":"1f2bddea07543ccda708134cca0600b4d9ac9bd774ec1ede0a69935b04df1496","impliedFormat":1},{"version":"6e8997d08f6798d0a9416df24312cafd084e6184a205d9283eba95ef56f8ef8b","impliedFormat":1},{"version":"ac6968717607889d24d6e407effb48dd5af82005925b4725b1d9eb52a8a047e2","impliedFormat":1},{"version":"26080058b725ac0b480241751255b4391f722263778e84e66a62068705aafd3c","impliedFormat":1},{"version":"46afbf46c3d62eac2afead3a2011d506637bf4f2c05e1fd64bbf7e2bb2947b7c","impliedFormat":1},{"version":"84d02daa32c7a8bff4946bbc7d878ffb7114c19879f7bfceeeb39bef48e93c42","impliedFormat":1},{"version":"29723e0bc48036a127c3b8874f3abe9b695c56103f685f2b817fc532b8995e33","impliedFormat":1},{"version":"991cf4ed946cdf4c140ccaad45c61fc36a25b238a8fa95af51e93cb20c4b0503","impliedFormat":1},{"version":"81ef252ff5df76bccf7863bb355ccbb8af69f7d1064b3ef87b2b01c30fb2c1f4","impliedFormat":1},{"version":"0f17f5f14a5f53e5709404b5b59fe816eaad15a469412b73330e6f69834234e0","impliedFormat":1},{"version":"01edea77be9c2bef3a5f3fc46324c5e420e5bd72b499c5dec217c91866be5a99","impliedFormat":1},{"version":"39209d2b85d238810ef19ab3905c9498918343bc8f72a1dcae7fc0b08270d9a0","impliedFormat":1},{"version":"92a130d875262e78c581f98faa07c62f4510885df6d98213c72f3b83a1be93c1","impliedFormat":1},{"version":"367b818a25afccdbddf932a62a02012869f59fe66d359ff4aca78a7c2bb680aa","impliedFormat":1},{"version":"0aa14ffe353b8bab88046e64a92efa5cd039f095759fe884d188702956e2cba2","impliedFormat":1},{"version":"68d3eee1d509f45625e39ba325a72c6ce1d2116e3d5c3a40f513472e66622e02","impliedFormat":1},{"version":"4e5f1234308de112f09920e0a0b99f35a9780b3abbc13a84445f32a490d0bb87","impliedFormat":1},{"version":"9ac0e5aea87c4a1d37b4677145e9a75bc8e13bf887bd1148a4acb21ab7398d00","impliedFormat":1},{"version":"625b802ecd18feb6a9d69ef8ef58d6c08c9c9022b8105cdeaa3fc77acaab5667","impliedFormat":1},{"version":"2ac33d7f6999e0fb363d1e483d80f087d3e7d712ff6fcc2b4f7b18b5dab92f37","impliedFormat":1},{"version":"195749d135be639001a554e4b4025b66b3c5c627d90b68266c14399bde120cec","impliedFormat":1},{"version":"8f1241f5d9f0d3d72117768b3c974e462840fbd85026fb66685078945404cf2f","impliedFormat":1},{"version":"e070102beb7e7ddc99f18f4a692a3bb5f3deaf6369347da8654de90b2845c2fd","signature":"3de7cab5e82823e5bb33fe904f8d17c17b21ebdf4d0255a20566644c54626117"},{"version":"b86edc85512b744a2e07c78035d8dda27bb7827af04c2c41756e94760f101bdf","signature":"0175f8bf07a59b69d80511dea1af06b9b510eb96b4aea5b38b41026cbf2e0690"},{"version":"4420d128dde4f2a3edde7ca2359978aac28889aa5f9d74dd90223e682a46bb78","signature":"7bff6f26cdc43e4a9b86cafdb471abc8cbffb9d1892561a52ab5d9d919a29034"},{"version":"6d21472eed61c5bd01b8d21bbdb5527ce7c146c23abc4f5f6585fcb56705291e","signature":"73057eb512792de1e125190cc4dd2dc54f8b431c4eca05324f854f079e409b65"},{"version":"d54a97df09469e477b23485a22fc4efacb0ebd911ba52d5b5830d8d60124bd91","signature":"0a9137fd1f6cc95a03dc750636d3c795b2a8f1c060aec6c4bd1eb1a55dce802e"},{"version":"001bafcaf7b89f75dd777cd273f9e8472623f44cc22f67a79fb3d01fa38706ce","signature":"e8f1bca3cc46ab477ded80ab60d1f1a79e912f0d6338b1af6b2635b5a0b4bff9"},{"version":"46bb64b34efb5ad48ccfc48ba46afbd439df11ac9bde903e92701ca41741e40d","signature":"94b4120adf8b0d746748c8e56a91b838f54696a0e707695a8222744278e7af5e"},{"version":"ebb14e7a0bc25e43bde017ef255d50edf38a70a01772df2b25544da129d78b2c","signature":"a5024050368af5ae1a93b0771e6d967e868779ec7720835f3ecb65e53545299e"},{"version":"948b13d67afc45b57f75f32be3650a43557cf8c46b7f28908da80f1309c800dd","signature":"9854225e5bb7e958d065b612330fcd9fa13fbce221f67546a653936c0540e4c3"},{"version":"d10d70b4fe21847c61cb0ab72b60162d2cc23ef64e5606822d110cce2dbc9dd8","impliedFormat":1},{"version":"c7500485ac44104d8e23d5849329d90ef93753faa41532cebf3073684e2404ba","signature":"e8eb71a6e2d4d2ee40e0e5a148932f666a8f431d392bffca7a4a44cac4ad2665"},{"version":"a56c2939526b2a9e29bf1cae321e5f84d1854d731cd264d310ddc4996c5ee51f","signature":"50da018f1984a1f044f21efbd648e567a87b84728f385c3e6036889fe2905d41"},{"version":"5fa4a13fa09465ba26ea3851755a05d91733548aa065a86eb9ed7f4827ca05a9","signature":"539ca8eb26ee7514a013b1a5e907a2b28d2dd745b484d0bec0f6a01d7d40a600"},{"version":"c7d7a47394dad98061274892fb0743ebce2cb0f0583eda6143ee8311cb5907ac","signature":"4b355fd9f17011adda2ae8e36e0cfac5ed7cc865adf7501ab5ac142bd449d8d8"},{"version":"92bb8a9aaccdfc7066947a04aff9eb6d830892391ca081663253140412ea6b3c","signature":"08d54e286cfe0b0ef741be1b7c4e638807a9b3f75ffc09a9fb3d500e980e1ea5"},{"version":"676e4e277b3e26d0e6b6f3aea74d22ccd0a84191ad177927718d4ae9a38a4576","signature":"8244896f24acb6519dc3bb4bd1000f0bdb47425af3a5212db1eb4cd2d6a82f45"},{"version":"2fa5b81d1b6028bfd6c02b1f2b1b1346f16b87cfd49d1417806c47ecaa0881af","signature":"a080f2a01cd5c6a3b6ed05daf3cb710f685513815f7cf3127813178acd33e1b7"},{"version":"354298d5865ed2e6698034c79b6171fcd270119da63416d6be2c460db1619c43","signature":"4cb17d629e7bb0df751ae3e10d78a23d59ea51fc7c02044f9a003ba6f36fedf7"},{"version":"df274a05b6c76765a4c2a0d489855d56b3885120bf108917580a420af8f11adb","signature":"b82491e2990291580288c5602d4c017238977749d52b17391f0e45d9a29be644"},"bc12722746bca513d15d6091ee505d8da6e159f22ff0b7210702cdb70ea65168",{"version":"4e471852c50148b8468b18813c8ca875b1feaa3eae8f4086163b3858516a3ebd","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"73a3b93d2b1c85f544d3b603f32ba4c4fa39bc3f65af411e302a184a196988b1","signature":"33bbc1924c4cfcae22f6220d9f9a2f5a8ebef1e4c85143a263501886b8dd7689"},{"version":"a0df771a66d5dc23fdac670c841b1dd1a7911cb09f6458887ff7c346f5aa2b4c","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"556ccd493ec36c7d7cb130d51be66e147b91cc1415be383d71da0f1e49f742a9","impliedFormat":1},{"version":"b6d03c9cfe2cf0ba4c673c209fcd7c46c815b2619fd2aad59fc4229aaef2ed43","impliedFormat":1},{"version":"95aba78013d782537cc5e23868e736bec5d377b918990e28ed56110e3ae8b958","impliedFormat":1},{"version":"670a76db379b27c8ff42f1ba927828a22862e2ab0b0908e38b671f0e912cc5ed","impliedFormat":1},{"version":"13b77ab19ef7aadd86a1e54f2f08ea23a6d74e102909e3c00d31f231ed040f62","impliedFormat":1},{"version":"069bebfee29864e3955378107e243508b163e77ab10de6a5ee03ae06939f0bb9","impliedFormat":1},{"version":"151ff381ef9ff8da2da9b9663ebf657eac35c4c9a19183420c05728f31a6761d","impliedFormat":1},{"version":"f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","impliedFormat":1},{"version":"a4a39b5714adfcadd3bbea6698ca2e942606d833bde62ad5fb6ec55f5e438ff8","impliedFormat":1},{"version":"bbc1d029093135d7d9bfa4b38cbf8761db505026cc458b5e9c8b74f4000e5e75","impliedFormat":1},{"version":"1f68ab0e055994eb337b67aa87d2a15e0200951e9664959b3866ee6f6b11a0fe","impliedFormat":1},{"version":"afe73051ff6a03a9565cbd8ebb0e956ee3df5e913ad5c1ded64218aabfa3dcb5","impliedFormat":1},{"version":"035a5df183489c2e22f3cf59fc1ed2b043d27f357eecc0eb8d8e840059d44245","impliedFormat":1},{"version":"a4809f4d92317535e6b22b01019437030077a76fec1d93b9881c9ed4738fcc54","impliedFormat":1},{"version":"5f53fa0bd22096d2a78533f94e02c899143b8f0f9891a46965294ee8b91a9434","impliedFormat":1},{"version":"cdcc132f207d097d7d3aa75615ab9a2e71d6a478162dde8b67f88ea19f3e54de","impliedFormat":1},{"version":"0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","impliedFormat":1},{"version":"e1028394c1cf96d5d057ecc647e31e457b919092f882ed0c7092152b077fed9d","impliedFormat":1},{"version":"f315e1e65a1f80992f0509e84e4ae2df15ecd9ef73df975f7c98813b71e4c8da","impliedFormat":1},{"version":"5b9586e9b0b6322e5bfbd2c29bd3b8e21ab9d871f82346cb71020e3d84bae73e","impliedFormat":1},{"version":"3e70a7e67c2cb16f8cd49097360c0309fe9d1e3210ff9222e9dac1f8df9d4fb6","impliedFormat":1},{"version":"ab68d2a3e3e8767c3fba8f80de099a1cfc18c0de79e42cb02ae66e22dfe14a66","impliedFormat":1},{"version":"d96cc6598148bf1a98fb2e8dcf01c63a4b3558bdaec6ef35e087fd0562eb40ec","impliedFormat":1},{"version":"f8db4fea512ab759b2223b90ecbbe7dae919c02f8ce95ec03f7fb1cf757cfbeb","affectsGlobalScope":true,"impliedFormat":1},{"version":"17668c1aab598920796050ee5a00d961ede5e92595f6ac8908a975ed75a537e5","impliedFormat":1},{"version":"ce6a3f09b8db73a7e9701aca91a04b4fabaf77436dd35b24482f9ee816016b17","impliedFormat":1},{"version":"20e086e5b64fdd52396de67761cc0e94693494deadb731264aac122adf08de3f","impliedFormat":1},{"version":"6e78f75403b3ec65efb41c70d392aeda94360f11cedc9fb2c039c9ea23b30962","impliedFormat":1},{"version":"c863198dae89420f3c552b5a03da6ed6d0acfa3807a64772b895db624b0de707","impliedFormat":1},{"version":"8b03a5e327d7db67112ebbc93b4f744133eda2c1743dbb0a990c61a8007823ef","impliedFormat":1},{"version":"42fad1f540271e35ca37cecda12c4ce2eef27f0f5cf0f8dd761d723c744d3159","impliedFormat":1},{"version":"ff3743a5de32bee10906aff63d1de726f6a7fd6ee2da4b8229054dfa69de2c34","impliedFormat":1},{"version":"83acd370f7f84f203e71ebba33ba61b7f1291ca027d7f9a662c6307d74e4ac22","impliedFormat":1},{"version":"1445cec898f90bdd18b2949b9590b3c012f5b7e1804e6e329fb0fe053946d5ec","impliedFormat":1},{"version":"0e5318ec2275d8da858b541920d9306650ae6ac8012f0e872fe66eb50321a669","impliedFormat":1},{"version":"cf530297c3fb3a92ec9591dd4fa229d58b5981e45fe6702a0bd2bea53a5e59be","impliedFormat":1},{"version":"c1f6f7d08d42148ddfe164d36d7aba91f467dbcb3caa715966ff95f55048b3a4","impliedFormat":1},{"version":"eefd2bbc8edb14c3bd1246794e5c070a80f9b8f3730bd42efb80df3cc50b9039","impliedFormat":1},{"version":"0c1ee27b8f6a00097c2d6d91a21ee4d096ab52c1e28350f6362542b55380059a","impliedFormat":1},{"version":"7677d5b0db9e020d3017720f853ba18f415219fb3a9597343b1b1012cfd699f7","impliedFormat":1},{"version":"bc1c6bc119c1784b1a2be6d9c47addec0d83ef0d52c8fbe1f14a51b4dfffc675","impliedFormat":1},{"version":"52cf2ce99c2a23de70225e252e9822a22b4e0adb82643ab0b710858810e00bf1","impliedFormat":1},{"version":"770625067bb27a20b9826255a8d47b6b5b0a2d3dfcbd21f89904c731f671ba77","impliedFormat":1},{"version":"d1ed6765f4d7906a05968fb5cd6d1db8afa14dbe512a4884e8ea5c0f5e142c80","impliedFormat":1},{"version":"799c0f1b07c092626cf1efd71d459997635911bb5f7fc1196efe449bba87e965","impliedFormat":1},{"version":"2a184e4462b9914a30b1b5c41cf80c6d3428f17b20d3afb711fff3f0644001fd","impliedFormat":1},{"version":"9eabde32a3aa5d80de34af2c2206cdc3ee094c6504a8d0c2d6d20c7c179503cc","impliedFormat":1},{"version":"397c8051b6cfcb48aa22656f0faca2553c5f56187262135162ee79d2b2f6c966","impliedFormat":1},{"version":"a8ead142e0c87dcd5dc130eba1f8eeed506b08952d905c47621dc2f583b1bff9","impliedFormat":1},{"version":"a02f10ea5f73130efca046429254a4e3c06b5475baecc8f7b99a0014731be8b3","impliedFormat":1},{"version":"c2576a4083232b0e2d9bd06875dd43d371dee2e090325a9eac0133fd5650c1cb","impliedFormat":1},{"version":"4c9a0564bb317349de6a24eb4efea8bb79898fa72ad63a1809165f5bd42970dd","impliedFormat":1},{"version":"f40ac11d8859092d20f953aae14ba967282c3bb056431a37fced1866ec7a2681","impliedFormat":1},{"version":"cc11e9e79d4746cc59e0e17473a59d6f104692fd0eeea1bdb2e206eabed83b03","impliedFormat":1},{"version":"b444a410d34fb5e98aa5ee2b381362044f4884652e8bc8a11c8fe14bbd85518e","impliedFormat":1},{"version":"c35808c1f5e16d2c571aa65067e3cb95afeff843b259ecfa2fc107a9519b5392","impliedFormat":1},{"version":"14d5dc055143e941c8743c6a21fa459f961cbc3deedf1bfe47b11587ca4b3ef5","impliedFormat":1},{"version":"a3ad4e1fc542751005267d50a6298e6765928c0c3a8dce1572f2ba6ca518661c","impliedFormat":1},{"version":"f237e7c97a3a89f4591afd49ecb3bd8d14f51a1c4adc8fcae3430febedff5eb6","impliedFormat":1},{"version":"3ffdfbec93b7aed71082af62b8c3e0cc71261cc68d796665faa1e91604fbae8f","impliedFormat":1},{"version":"662201f943ed45b1ad600d03a90dffe20841e725203ced8b708c91fcd7f9379a","impliedFormat":1},{"version":"c9ef74c64ed051ea5b958621e7fb853fe3b56e8787c1587aefc6ea988b3c7e79","impliedFormat":1},{"version":"2462ccfac5f3375794b861abaa81da380f1bbd9401de59ffa43119a0b644253d","impliedFormat":1},{"version":"34baf65cfee92f110d6653322e2120c2d368ee64b3c7981dff08ed105c4f19b0","impliedFormat":1},{"version":"a56fe175741cc8841835eb72e61fa5a34adcbc249ede0e3494c229f0750f6b85","impliedFormat":1},{"version":"ab82804a14454734010dcdcd43f564ff7b0389bee4c5692eec76ff5b30d4cf66","impliedFormat":1},{"version":"4006c872e38a2c4e09c593bc0cdd32b7b4f5c4843910bea0def631c483fff6c5","impliedFormat":1},{"version":"ab6aa3a65d473871ee093e3b7b71ed0f9c69e07d1d4295f45c9efd91a771241d","impliedFormat":1},{"version":"bae8d023ef6b23df7da26f51cea44321f95817c190342a36882e93b80d07a960","impliedFormat":1},{"version":"26a770cec4bd2e7dbba95c6e536390fffe83c6268b78974a93727903b515c4e7","impliedFormat":1}],"root":[632,633,665,666,806,807,[810,826],[847,862],[864,883],[912,920],[922,934]],"options":{"allowSyntheticDefaultImports":true,"declaration":true,"emitDecoratorMetadata":true,"esModuleInterop":true,"experimentalDecorators":true,"module":1,"noFallthroughCasesInSwitch":false,"noImplicitAny":false,"outDir":"./","removeComments":true,"skipLibCheck":true,"sourceMap":true,"strictBindCallApply":false,"strictNullChecks":false,"target":8,"useDefineForClassFields":false},"referencedMap":[[630,1],[629,2],[937,3],[935,4],[950,4],[953,5],[530,4],[319,4],[57,4],[308,6],[309,6],[310,4],[311,7],[321,8],[312,4],[313,9],[314,4],[315,4],[316,6],[317,6],[318,6],[320,10],[328,11],[330,4],[327,4],[333,12],[331,4],[329,4],[325,13],[326,14],[332,4],[334,15],[322,4],[324,16],[323,17],[263,4],[266,18],[262,4],[577,4],[264,4],[265,4],[351,19],[336,19],[343,19],[340,19],[353,19],[344,19],[350,19],[335,20],[354,19],[357,21],[348,19],[338,19],[356,19],[341,19],[339,19],[349,19],[345,19],[355,19],[342,19],[352,19],[337,19],[347,19],[346,19],[364,22],[360,23],[359,4],[358,4],[363,24],[402,25],[58,4],[59,4],[60,4],[559,26],[62,27],[565,28],[564,29],[252,30],[253,27],[373,4],[282,4],[283,4],[374,31],[254,4],[375,4],[376,32],[61,4],[256,33],[257,4],[255,34],[258,33],[259,4],[261,35],[273,36],[274,4],[279,37],[275,4],[276,4],[277,4],[278,4],[280,4],[281,38],[287,39],[290,40],[288,4],[289,4],[307,41],[291,4],[292,4],[608,42],[272,43],[270,44],[268,45],[269,46],[271,4],[299,47],[293,4],[302,48],[295,49],[300,50],[298,51],[301,52],[296,53],[297,54],[285,55],[303,56],[286,57],[305,58],[306,59],[294,4],[260,4],[267,60],[304,61],[370,62],[365,4],[371,63],[366,64],[367,65],[368,66],[369,67],[372,68],[388,69],[387,70],[393,71],[385,4],[386,72],[389,69],[390,73],[392,74],[391,75],[394,76],[379,77],[380,78],[383,79],[382,79],[381,78],[384,78],[378,80],[396,81],[395,82],[398,83],[397,84],[399,85],[361,55],[362,86],[284,4],[400,87],[377,88],[401,89],[403,7],[515,90],[516,91],[520,92],[404,4],[410,93],[513,94],[514,95],[405,4],[406,4],[409,96],[407,4],[408,4],[518,4],[519,97],[517,98],[521,99],[528,100],[529,101],[550,102],[551,103],[552,4],[553,104],[554,105],[563,106],[556,107],[560,108],[568,109],[566,7],[567,110],[557,111],[569,4],[571,112],[572,113],[573,114],[562,115],[558,116],[582,117],[570,118],[597,119],[555,120],[598,121],[595,122],[596,7],[620,123],[545,124],[541,125],[543,126],[594,127],[536,128],[584,129],[583,4],[544,130],[591,131],[548,132],[592,4],[593,133],[546,134],[540,135],[547,136],[542,137],[535,4],[588,138],[601,139],[599,7],[531,7],[587,140],[532,14],[533,103],[534,141],[538,142],[537,143],[600,144],[539,145],[576,146],[574,112],[575,147],[585,14],[586,148],[589,149],[604,150],[605,151],[602,152],[603,153],[606,154],[607,155],[609,156],[581,157],[578,158],[579,6],[580,147],[611,159],[610,160],[617,161],[549,7],[613,162],[612,7],[615,163],[614,4],[616,164],[561,165],[590,166],[619,167],[618,7],[640,168],[636,169],[635,170],[637,4],[638,171],[639,172],[641,173],[642,4],[646,174],[662,175],[643,7],[645,176],[644,4],[647,177],[660,178],[661,179],[663,180],[830,181],[831,182],[845,183],[833,184],[832,185],[827,186],[828,4],[829,4],[844,187],[835,188],[836,188],[837,188],[838,188],[840,189],[839,188],[841,190],[842,191],[834,4],[843,192],[903,193],[906,194],[904,4],[905,4],[884,4],[885,195],[910,196],[907,7],[908,197],[909,193],[911,198],[627,199],[525,200],[624,4],[522,4],[523,201],[526,202],[527,7],[621,203],[524,201],[622,204],[623,205],[625,206],[626,4],[631,207],[628,4],[952,4],[940,208],[936,3],[938,209],[939,3],[664,210],[657,211],[656,212],[945,213],[944,214],[943,215],[941,4],[651,216],[658,217],[946,218],[652,4],[947,4],[948,219],[949,220],[958,221],[942,4],[634,222],[895,223],[888,224],[892,225],[890,226],[893,227],[891,228],[894,229],[889,4],[887,230],[886,231],[653,4],[846,232],[460,233],[461,233],[462,234],[463,235],[464,236],[465,237],[411,4],[414,238],[412,4],[413,4],[466,239],[467,240],[468,241],[469,242],[470,243],[471,244],[472,244],[473,245],[474,246],[475,247],[476,248],[417,4],[477,249],[478,250],[479,251],[480,252],[481,253],[482,254],[483,255],[484,256],[485,257],[486,258],[487,259],[488,260],[489,261],[490,261],[491,262],[492,4],[493,263],[495,264],[494,265],[496,266],[497,267],[498,268],[499,269],[500,270],[501,271],[502,272],[416,273],[415,4],[511,274],[503,275],[504,276],[505,277],[506,278],[507,279],[508,280],[418,4],[419,4],[420,4],[459,281],[509,282],[510,283],[809,284],[959,285],[808,286],[659,287],[649,4],[650,4],[984,288],[985,289],[961,290],[964,291],[982,288],[983,288],[973,288],[972,292],[970,288],[965,288],[978,288],[976,288],[980,288],[960,288],[977,288],[981,288],[966,288],[967,288],[979,288],[962,288],[968,288],[969,288],[971,288],[975,288],[986,293],[974,288],[963,288],[999,294],[998,4],[993,293],[995,295],[994,293],[987,293],[988,293],[990,293],[992,293],[996,295],[997,295],[989,295],[991,295],[648,296],[655,297],[654,298],[1000,4],[1001,4],[1002,4],[709,299],[700,4],[701,4],[702,4],[703,4],[704,4],[705,4],[706,4],[707,4],[708,4],[1003,4],[1004,300],[421,4],[951,4],[670,4],[789,301],[793,301],[792,301],[790,301],[791,301],[794,301],[673,301],[685,301],[674,301],[687,301],[689,301],[683,301],[682,301],[684,301],[688,301],[690,301],[675,301],[686,301],[676,301],[678,302],[679,301],[680,301],[681,301],[697,301],[696,301],[797,303],[691,301],[693,301],[692,301],[694,301],[695,301],[796,301],[795,301],[698,301],[780,301],[779,301],[710,304],[711,304],[713,301],[757,301],[778,301],[714,304],[758,301],[755,301],[759,301],[715,301],[716,301],[717,304],[760,301],[754,304],[712,304],[761,301],[718,304],[762,301],[742,301],[719,304],[720,301],[721,301],[752,304],[724,301],[723,301],[763,301],[764,301],[765,304],[726,301],[728,301],[729,301],[735,301],[736,301],[730,304],[766,301],[753,304],[731,301],[732,301],[767,301],[733,301],[725,304],[768,301],[751,301],[769,301],[734,304],[737,301],[738,301],[756,304],[770,301],[771,301],[750,305],[727,301],[772,304],[773,301],[774,301],[775,301],[776,304],[739,301],[777,301],[743,301],[740,304],[741,304],[722,301],[744,301],[747,301],[745,301],[746,301],[699,301],[787,301],[781,301],[782,301],[784,301],[785,301],[783,301],[788,301],[786,301],[672,306],[805,307],[803,308],[804,309],[802,310],[801,301],[800,311],[669,4],[671,4],[667,4],[798,4],[799,312],[677,306],[668,4],[897,4],[896,4],[902,313],[898,314],[901,315],[900,316],[899,4],[512,210],[921,317],[957,318],[955,319],[956,320],[749,321],[748,4],[954,322],[56,4],[251,323],[224,4],[202,324],[200,324],[250,325],[215,326],[214,326],[115,327],[66,328],[222,327],[223,327],[225,329],[226,327],[227,330],[126,331],[228,327],[199,327],[229,327],[230,332],[231,327],[232,326],[233,333],[234,327],[235,327],[236,327],[237,327],[238,326],[239,327],[240,327],[241,327],[242,327],[243,334],[244,327],[245,327],[246,327],[247,327],[248,327],[65,325],[68,330],[69,330],[70,330],[71,330],[72,330],[73,330],[74,330],[75,327],[77,335],[78,330],[76,330],[79,330],[80,330],[81,330],[82,330],[83,330],[84,330],[85,327],[86,330],[87,330],[88,330],[89,330],[90,330],[91,327],[92,330],[93,330],[94,330],[95,330],[96,330],[97,330],[98,327],[100,336],[99,330],[101,330],[102,330],[103,330],[104,330],[105,334],[106,327],[107,327],[121,337],[109,338],[110,330],[111,330],[112,327],[113,330],[114,330],[116,339],[117,330],[118,330],[119,330],[120,330],[122,330],[123,330],[124,330],[125,330],[127,340],[128,330],[129,330],[130,330],[131,327],[132,330],[133,341],[134,341],[135,341],[136,327],[137,330],[138,330],[139,330],[144,330],[140,330],[141,327],[142,330],[143,327],[145,330],[146,330],[147,330],[148,330],[149,330],[150,330],[151,327],[152,330],[153,330],[154,330],[155,330],[156,330],[157,330],[158,330],[159,330],[160,330],[161,330],[162,330],[163,330],[164,330],[165,330],[166,330],[167,330],[168,342],[169,330],[170,330],[171,330],[172,330],[173,330],[174,330],[175,327],[176,327],[177,327],[178,327],[179,327],[180,330],[181,330],[182,330],[183,330],[201,343],[249,327],[186,344],[185,345],[209,346],[208,347],[204,348],[203,347],[205,349],[194,350],[192,351],[207,352],[206,349],[193,4],[195,353],[108,354],[64,355],[63,330],[198,4],[190,356],[191,357],[188,4],[189,358],[187,330],[196,359],[67,360],[216,4],[217,4],[210,4],[213,326],[212,4],[218,4],[219,4],[211,361],[220,4],[221,4],[184,362],[197,363],[53,4],[54,4],[11,4],[9,4],[10,4],[15,4],[14,4],[2,4],[16,4],[17,4],[18,4],[19,4],[20,4],[21,4],[22,4],[23,4],[3,4],[24,4],[4,4],[25,4],[29,4],[26,4],[27,4],[28,4],[30,4],[31,4],[32,4],[5,4],[33,4],[34,4],[35,4],[36,4],[6,4],[40,4],[37,4],[38,4],[39,4],[41,4],[7,4],[42,4],[47,4],[48,4],[43,4],[44,4],[45,4],[46,4],[8,4],[55,4],[52,4],[49,4],[50,4],[51,4],[1,4],[13,4],[12,4],[437,364],[447,365],[436,364],[457,366],[428,367],[427,368],[456,210],[450,369],[455,370],[430,371],[444,372],[429,373],[453,374],[425,375],[424,210],[454,376],[426,377],[431,378],[432,4],[435,378],[422,4],[458,379],[448,380],[439,381],[440,382],[442,383],[438,384],[441,385],[451,210],[433,386],[434,387],[443,388],[423,389],[446,380],[445,378],[449,4],[452,390],[863,4],[934,391],[930,392],[931,393],[633,394],[632,395],[932,396],[926,397],[925,394],[928,398],[927,394],[929,399],[807,400],[811,401],[665,402],[806,403],[810,404],[826,405],[822,7],[814,7],[666,406],[815,407],[823,408],[825,409],[824,394],[812,7],[816,410],[817,411],[813,412],[923,413],[924,414],[922,415],[848,416],[849,417],[847,418],[878,403],[880,419],[881,420],[879,421],[853,403],[854,403],[856,422],[857,423],[855,424],[915,425],[916,426],[912,394],[913,427],[919,428],[920,429],[918,394],[874,403],[876,430],[877,431],[875,432],[861,403],[860,403],[859,403],[858,403],[933,403],[862,403],[872,433],[871,434],[868,435],[865,436],[873,437],[864,438],[870,439],[869,394],[867,440],[866,394],[882,403],[914,441],[917,442],[883,443],[851,444],[852,445],[850,394],[818,403],[820,446],[821,447],[819,448]],"version":"5.6.3"} \ No newline at end of file diff --git a/reading-platform-backend/nest-cli.json b/reading-platform-backend/nest-cli.json new file mode 100644 index 0000000..cbabe9f --- /dev/null +++ b/reading-platform-backend/nest-cli.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true, + "webpack": false, + "tsConfigPath": "tsconfig.json" + } +} diff --git a/reading-platform-backend/package.json b/reading-platform-backend/package.json new file mode 100644 index 0000000..e7767b4 --- /dev/null +++ b/reading-platform-backend/package.json @@ -0,0 +1,66 @@ +{ + "name": "reading-platform-backend", + "version": "1.0.0", + "description": "幼儿阅读教学服务平台后端", + "main": "dist/src/main.js", + "scripts": { + "prestart:dev": "npx tsc", + "start:dev": "node dist/src/main.js", + "build": "npx tsc", + "start": "node dist/src/main.js", + "start:prod": "node dist/src/main.js", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "prisma:generate": "prisma generate", + "prisma:migrate": "prisma migrate dev", + "prisma:studio": "prisma studio", + "seed": "ts-node prisma/seed.ts" + }, + "dependencies": { + "@nestjs/common": "^10.3.0", + "@nestjs/config": "^3.1.1", + "@nestjs/core": "^10.3.0", + "@nestjs/jwt": "^10.2.0", + "@nestjs/passport": "^10.0.3", + "@nestjs/platform-express": "^10.4.22", + "@nestjs/schedule": "^6.1.1", + "@nestjs/throttler": "^5.2.0", + "@prisma/client": "^5.8.0", + "@types/multer": "^2.0.0", + "ali-oss": "^6.18.1", + "bcrypt": "^5.1.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", + "compression": "^1.8.1", + "dayjs": "^1.11.10", + "exceljs": "^4.4.0", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", + "reflect-metadata": "^0.1.14", + "rxjs": "^7.8.1", + "xlsx": "^0.18.5" + }, + "devDependencies": { + "@nestjs/cli": "^10.3.0", + "@nestjs/schematics": "^10.1.0", + "@nestjs/testing": "^10.3.0", + "@types/bcrypt": "^5.0.2", + "@types/express": "^4.17.21", + "@types/jest": "^29.5.11", + "@types/node": "^20.10.6", + "@types/passport-jwt": "^4.0.0", + "@types/passport-local": "^1.0.38", + "@typescript-eslint/eslint-plugin": "^6.18.0", + "@typescript-eslint/parser": "^6.18.0", + "eslint": "^8.56.0", + "jest": "^29.7.0", + "prisma": "^5.8.0", + "source-map-support": "^0.5.21", + "ts-jest": "^29.1.1", + "ts-loader": "^9.5.1", + "ts-node": "^10.9.2", + "ts-node-dev": "^2.0.0", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.6.3" + } +} diff --git a/reading-platform-backend/prisma/dev.db b/reading-platform-backend/prisma/dev.db new file mode 100644 index 0000000..e69de29 diff --git a/reading-platform-backend/prisma/migrations/20260210055321_init/migration.sql b/reading-platform-backend/prisma/migrations/20260210055321_init/migration.sql new file mode 100644 index 0000000..3362422 --- /dev/null +++ b/reading-platform-backend/prisma/migrations/20260210055321_init/migration.sql @@ -0,0 +1,262 @@ +-- CreateTable +CREATE TABLE "tenants" ( + "id" BIGINT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "address" TEXT, + "contact_person" TEXT, + "contact_phone" TEXT, + "logo_url" TEXT, + "package_type" TEXT NOT NULL DEFAULT 'STANDARD', + "teacher_quota" INTEGER NOT NULL DEFAULT 20, + "student_quota" INTEGER NOT NULL DEFAULT 200, + "storage_quota" BIGINT NOT NULL DEFAULT 5368709120, + "start_date" TEXT NOT NULL, + "expire_date" TEXT NOT NULL, + "teacher_count" INTEGER NOT NULL DEFAULT 0, + "student_count" INTEGER NOT NULL DEFAULT 0, + "storage_used" BIGINT NOT NULL DEFAULT 0, + "status" TEXT NOT NULL DEFAULT 'ACTIVE', + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL +); + +-- CreateTable +CREATE TABLE "teachers" ( + "id" BIGINT NOT NULL PRIMARY KEY, + "tenant_id" BIGINT NOT NULL, + "name" TEXT NOT NULL, + "phone" TEXT NOT NULL, + "email" TEXT, + "login_account" TEXT NOT NULL, + "password_hash" TEXT NOT NULL, + "class_ids" TEXT DEFAULT '[]', + "status" TEXT NOT NULL DEFAULT 'ACTIVE', + "lesson_count" INTEGER NOT NULL DEFAULT 0, + "feedback_count" INTEGER NOT NULL DEFAULT 0, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + "last_login_at" DATETIME, + CONSTRAINT "teachers_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "classes" ( + "id" BIGINT NOT NULL PRIMARY KEY, + "tenant_id" BIGINT NOT NULL, + "name" TEXT NOT NULL, + "grade" TEXT NOT NULL, + "teacher_id" BIGINT, + "student_count" INTEGER NOT NULL DEFAULT 0, + "lesson_count" INTEGER NOT NULL DEFAULT 0, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + CONSTRAINT "classes_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "classes_teacher_id_fkey" FOREIGN KEY ("teacher_id") REFERENCES "teachers" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "students" ( + "id" BIGINT NOT NULL PRIMARY KEY, + "tenant_id" BIGINT NOT NULL, + "class_id" BIGINT NOT NULL, + "name" TEXT NOT NULL, + "gender" TEXT, + "birth_date" DATETIME, + "parent_phone" TEXT, + "parent_name" TEXT, + "reading_count" INTEGER NOT NULL DEFAULT 0, + "lesson_count" INTEGER NOT NULL DEFAULT 0, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + CONSTRAINT "students_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "students_class_id_fkey" FOREIGN KEY ("class_id") REFERENCES "classes" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "courses" ( + "id" BIGINT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "description" TEXT, + "picture_book_id" INTEGER, + "picture_book_name" TEXT, + "grade_tags" TEXT NOT NULL DEFAULT '[]', + "domain_tags" TEXT NOT NULL DEFAULT '[]', + "duration" INTEGER NOT NULL DEFAULT 25, + "status" TEXT NOT NULL DEFAULT 'DRAFT', + "version" TEXT NOT NULL DEFAULT '1.0', + "usage_count" INTEGER NOT NULL DEFAULT 0, + "teacher_count" INTEGER NOT NULL DEFAULT 0, + "avg_rating" REAL NOT NULL DEFAULT 0, + "created_by" INTEGER, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + "published_at" DATETIME +); + +-- CreateTable +CREATE TABLE "course_resources" ( + "id" BIGINT NOT NULL PRIMARY KEY, + "course_id" BIGINT NOT NULL, + "resource_type" TEXT NOT NULL, + "resource_name" TEXT NOT NULL, + "file_url" TEXT NOT NULL, + "file_size" BIGINT, + "mime_type" TEXT, + "metadata" TEXT, + "sort_order" INTEGER NOT NULL DEFAULT 0, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "course_resources_course_id_fkey" FOREIGN KEY ("course_id") REFERENCES "courses" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "course_scripts" ( + "id" BIGINT NOT NULL PRIMARY KEY, + "course_id" BIGINT NOT NULL, + "step_index" INTEGER NOT NULL, + "step_name" TEXT NOT NULL, + "step_type" TEXT NOT NULL, + "duration" INTEGER NOT NULL, + "objective" TEXT, + "teacher_script" TEXT, + "interaction_points" TEXT, + "resource_ids" TEXT, + "sort_order" INTEGER NOT NULL DEFAULT 0, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + CONSTRAINT "course_scripts_course_id_fkey" FOREIGN KEY ("course_id") REFERENCES "courses" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "course_script_pages" ( + "id" BIGINT NOT NULL PRIMARY KEY, + "script_id" BIGINT NOT NULL, + "page_number" INTEGER NOT NULL, + "questions" TEXT, + "interaction_component" TEXT, + "teacher_notes" TEXT, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + CONSTRAINT "course_script_pages_script_id_fkey" FOREIGN KEY ("script_id") REFERENCES "course_scripts" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "course_activities" ( + "id" BIGINT NOT NULL PRIMARY KEY, + "course_id" BIGINT NOT NULL, + "name" TEXT NOT NULL, + "domain" TEXT, + "domain_tag_id" INTEGER, + "activity_type" TEXT NOT NULL, + "duration" INTEGER, + "online_materials" TEXT, + "offlineMaterials" TEXT, + "activityGuide" TEXT, + "objectives" TEXT, + "sort_order" INTEGER NOT NULL DEFAULT 0, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "course_activities_course_id_fkey" FOREIGN KEY ("course_id") REFERENCES "courses" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "lessons" ( + "id" BIGINT NOT NULL PRIMARY KEY, + "tenant_id" BIGINT NOT NULL, + "teacher_id" BIGINT NOT NULL, + "class_id" BIGINT NOT NULL, + "course_id" BIGINT NOT NULL, + "planned_datetime" DATETIME, + "start_datetime" DATETIME, + "end_datetime" DATETIME, + "actual_duration" INTEGER, + "status" TEXT NOT NULL DEFAULT 'PLANNED', + "overall_rating" TEXT, + "participation_rating" TEXT, + "completion_note" TEXT, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + CONSTRAINT "lessons_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "lessons_teacher_id_fkey" FOREIGN KEY ("teacher_id") REFERENCES "teachers" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "lessons_class_id_fkey" FOREIGN KEY ("class_id") REFERENCES "classes" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "lessons_course_id_fkey" FOREIGN KEY ("course_id") REFERENCES "courses" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "lesson_feedbacks" ( + "id" BIGINT NOT NULL PRIMARY KEY, + "lesson_id" BIGINT NOT NULL, + "teacher_id" BIGINT NOT NULL, + "design_quality" INTEGER, + "participation" INTEGER, + "goal_achievement" INTEGER, + "step_feedbacks" TEXT, + "pros" TEXT, + "suggestions" TEXT, + "activities_done" TEXT, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + CONSTRAINT "lesson_feedbacks_lesson_id_fkey" FOREIGN KEY ("lesson_id") REFERENCES "lessons" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "lesson_feedbacks_teacher_id_fkey" FOREIGN KEY ("teacher_id") REFERENCES "teachers" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "student_records" ( + "id" BIGINT NOT NULL PRIMARY KEY, + "lesson_id" BIGINT NOT NULL, + "student_id" BIGINT NOT NULL, + "focus" INTEGER, + "participation" INTEGER, + "interest" INTEGER, + "understanding" INTEGER, + "domainAchievements" TEXT, + "notes" TEXT, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + CONSTRAINT "student_records_lesson_id_fkey" FOREIGN KEY ("lesson_id") REFERENCES "lessons" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "student_records_student_id_fkey" FOREIGN KEY ("student_id") REFERENCES "students" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "tags" ( + "id" BIGINT NOT NULL PRIMARY KEY, + "level" INTEGER NOT NULL, + "code" TEXT NOT NULL, + "name" TEXT NOT NULL, + "parent_id" BIGINT, + "description" TEXT, + "metadata" TEXT, + "sort_order" INTEGER NOT NULL DEFAULT 0, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- CreateTable +CREATE TABLE "tenant_courses" ( + "id" BIGINT NOT NULL PRIMARY KEY, + "tenant_id" BIGINT NOT NULL, + "course_id" BIGINT NOT NULL, + "authorized" BOOLEAN NOT NULL DEFAULT true, + "authorized_at" DATETIME, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "tenant_courses_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "tenant_courses_course_id_fkey" FOREIGN KEY ("course_id") REFERENCES "courses" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateIndex +CREATE UNIQUE INDEX "teachers_login_account_key" ON "teachers"("login_account"); + +-- CreateIndex +CREATE UNIQUE INDEX "course_scripts_course_id_step_index_key" ON "course_scripts"("course_id", "step_index"); + +-- CreateIndex +CREATE UNIQUE INDEX "course_script_pages_script_id_page_number_key" ON "course_script_pages"("script_id", "page_number"); + +-- CreateIndex +CREATE UNIQUE INDEX "lesson_feedbacks_lesson_id_teacher_id_key" ON "lesson_feedbacks"("lesson_id", "teacher_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "student_records_lesson_id_student_id_key" ON "student_records"("lesson_id", "student_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "tags_code_key" ON "tags"("code"); + +-- CreateIndex +CREATE UNIQUE INDEX "tenant_courses_tenant_id_course_id_key" ON "tenant_courses"("tenant_id", "course_id"); diff --git a/reading-platform-backend/prisma/migrations/20260210092744_init/migration.sql b/reading-platform-backend/prisma/migrations/20260210092744_init/migration.sql new file mode 100644 index 0000000..3a738ad --- /dev/null +++ b/reading-platform-backend/prisma/migrations/20260210092744_init/migration.sql @@ -0,0 +1,323 @@ +/* + Warnings: + + - The primary key for the `classes` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `classes` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `teacher_id` on the `classes` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `tenant_id` on the `classes` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `course_activities` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `course_id` on the `course_activities` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `id` on the `course_activities` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `course_resources` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `course_id` on the `course_resources` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `file_size` on the `course_resources` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `id` on the `course_resources` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `course_script_pages` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `course_script_pages` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `script_id` on the `course_script_pages` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `course_scripts` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `course_id` on the `course_scripts` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `id` on the `course_scripts` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `courses` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `courses` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `lesson_feedbacks` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `lesson_feedbacks` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `lesson_id` on the `lesson_feedbacks` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `teacher_id` on the `lesson_feedbacks` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `lessons` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `class_id` on the `lessons` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `course_id` on the `lessons` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `id` on the `lessons` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `teacher_id` on the `lessons` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `tenant_id` on the `lessons` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `student_records` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `student_records` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `lesson_id` on the `student_records` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `student_id` on the `student_records` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `students` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `class_id` on the `students` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `id` on the `students` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `tenant_id` on the `students` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `tags` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `tags` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `parent_id` on the `tags` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `teachers` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `teachers` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `tenant_id` on the `teachers` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `tenant_courses` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `course_id` on the `tenant_courses` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `id` on the `tenant_courses` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - You are about to alter the column `tenant_id` on the `tenant_courses` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - The primary key for the `tenants` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to alter the column `id` on the `tenants` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `Int`. + - Made the column `picture_book_id` on table `courses` required. This step will fail if there are existing NULL values in that column. + +*/ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_classes" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "tenant_id" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "grade" TEXT NOT NULL, + "teacher_id" INTEGER, + "student_count" INTEGER NOT NULL DEFAULT 0, + "lesson_count" INTEGER NOT NULL DEFAULT 0, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + CONSTRAINT "classes_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "classes_teacher_id_fkey" FOREIGN KEY ("teacher_id") REFERENCES "teachers" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); +INSERT INTO "new_classes" ("created_at", "grade", "id", "lesson_count", "name", "student_count", "teacher_id", "tenant_id", "updated_at") SELECT "created_at", "grade", "id", "lesson_count", "name", "student_count", "teacher_id", "tenant_id", "updated_at" FROM "classes"; +DROP TABLE "classes"; +ALTER TABLE "new_classes" RENAME TO "classes"; +CREATE TABLE "new_course_activities" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "course_id" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "domain" TEXT, + "domain_tag_id" INTEGER, + "activity_type" TEXT NOT NULL, + "duration" INTEGER, + "online_materials" TEXT, + "offlineMaterials" TEXT, + "activityGuide" TEXT, + "objectives" TEXT, + "sort_order" INTEGER NOT NULL DEFAULT 0, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "course_activities_course_id_fkey" FOREIGN KEY ("course_id") REFERENCES "courses" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); +INSERT INTO "new_course_activities" ("activityGuide", "activity_type", "course_id", "created_at", "domain", "domain_tag_id", "duration", "id", "name", "objectives", "offlineMaterials", "online_materials", "sort_order") SELECT "activityGuide", "activity_type", "course_id", "created_at", "domain", "domain_tag_id", "duration", "id", "name", "objectives", "offlineMaterials", "online_materials", "sort_order" FROM "course_activities"; +DROP TABLE "course_activities"; +ALTER TABLE "new_course_activities" RENAME TO "course_activities"; +CREATE TABLE "new_course_resources" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "course_id" INTEGER NOT NULL, + "resource_type" TEXT NOT NULL, + "resource_name" TEXT NOT NULL, + "file_url" TEXT NOT NULL, + "file_size" INTEGER, + "mime_type" TEXT, + "metadata" TEXT, + "sort_order" INTEGER NOT NULL DEFAULT 0, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "course_resources_course_id_fkey" FOREIGN KEY ("course_id") REFERENCES "courses" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); +INSERT INTO "new_course_resources" ("course_id", "created_at", "file_size", "file_url", "id", "metadata", "mime_type", "resource_name", "resource_type", "sort_order") SELECT "course_id", "created_at", "file_size", "file_url", "id", "metadata", "mime_type", "resource_name", "resource_type", "sort_order" FROM "course_resources"; +DROP TABLE "course_resources"; +ALTER TABLE "new_course_resources" RENAME TO "course_resources"; +CREATE TABLE "new_course_script_pages" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "script_id" INTEGER NOT NULL, + "page_number" INTEGER NOT NULL, + "questions" TEXT, + "interaction_component" TEXT, + "teacher_notes" TEXT, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + CONSTRAINT "course_script_pages_script_id_fkey" FOREIGN KEY ("script_id") REFERENCES "course_scripts" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); +INSERT INTO "new_course_script_pages" ("created_at", "id", "interaction_component", "page_number", "questions", "script_id", "teacher_notes", "updated_at") SELECT "created_at", "id", "interaction_component", "page_number", "questions", "script_id", "teacher_notes", "updated_at" FROM "course_script_pages"; +DROP TABLE "course_script_pages"; +ALTER TABLE "new_course_script_pages" RENAME TO "course_script_pages"; +CREATE UNIQUE INDEX "course_script_pages_script_id_page_number_key" ON "course_script_pages"("script_id", "page_number"); +CREATE TABLE "new_course_scripts" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "course_id" INTEGER NOT NULL, + "step_index" INTEGER NOT NULL, + "step_name" TEXT NOT NULL, + "step_type" TEXT NOT NULL, + "duration" INTEGER NOT NULL, + "objective" TEXT, + "teacher_script" TEXT, + "interaction_points" TEXT, + "resource_ids" TEXT, + "sort_order" INTEGER NOT NULL DEFAULT 0, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + CONSTRAINT "course_scripts_course_id_fkey" FOREIGN KEY ("course_id") REFERENCES "courses" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); +INSERT INTO "new_course_scripts" ("course_id", "created_at", "duration", "id", "interaction_points", "objective", "resource_ids", "sort_order", "step_index", "step_name", "step_type", "teacher_script", "updated_at") SELECT "course_id", "created_at", "duration", "id", "interaction_points", "objective", "resource_ids", "sort_order", "step_index", "step_name", "step_type", "teacher_script", "updated_at" FROM "course_scripts"; +DROP TABLE "course_scripts"; +ALTER TABLE "new_course_scripts" RENAME TO "course_scripts"; +CREATE UNIQUE INDEX "course_scripts_course_id_step_index_key" ON "course_scripts"("course_id", "step_index"); +CREATE TABLE "new_courses" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "name" TEXT NOT NULL, + "description" TEXT, + "picture_book_id" INTEGER NOT NULL, + "picture_book_name" TEXT, + "grade_tags" TEXT NOT NULL DEFAULT '[]', + "domain_tags" TEXT NOT NULL DEFAULT '[]', + "duration" INTEGER NOT NULL DEFAULT 25, + "status" TEXT NOT NULL DEFAULT 'DRAFT', + "version" TEXT NOT NULL DEFAULT '1.0', + "usage_count" INTEGER NOT NULL DEFAULT 0, + "teacher_count" INTEGER NOT NULL DEFAULT 0, + "avg_rating" REAL NOT NULL DEFAULT 0, + "created_by" INTEGER, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + "published_at" DATETIME +); +INSERT INTO "new_courses" ("avg_rating", "created_at", "created_by", "description", "domain_tags", "duration", "grade_tags", "id", "name", "picture_book_id", "picture_book_name", "published_at", "status", "teacher_count", "updated_at", "usage_count", "version") SELECT "avg_rating", "created_at", "created_by", "description", "domain_tags", "duration", "grade_tags", "id", "name", "picture_book_id", "picture_book_name", "published_at", "status", "teacher_count", "updated_at", "usage_count", "version" FROM "courses"; +DROP TABLE "courses"; +ALTER TABLE "new_courses" RENAME TO "courses"; +CREATE TABLE "new_lesson_feedbacks" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "lesson_id" INTEGER NOT NULL, + "teacher_id" INTEGER NOT NULL, + "design_quality" INTEGER, + "participation" INTEGER, + "goal_achievement" INTEGER, + "step_feedbacks" TEXT, + "pros" TEXT, + "suggestions" TEXT, + "activities_done" TEXT, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + CONSTRAINT "lesson_feedbacks_lesson_id_fkey" FOREIGN KEY ("lesson_id") REFERENCES "lessons" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "lesson_feedbacks_teacher_id_fkey" FOREIGN KEY ("teacher_id") REFERENCES "teachers" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_lesson_feedbacks" ("activities_done", "created_at", "design_quality", "goal_achievement", "id", "lesson_id", "participation", "pros", "step_feedbacks", "suggestions", "teacher_id", "updated_at") SELECT "activities_done", "created_at", "design_quality", "goal_achievement", "id", "lesson_id", "participation", "pros", "step_feedbacks", "suggestions", "teacher_id", "updated_at" FROM "lesson_feedbacks"; +DROP TABLE "lesson_feedbacks"; +ALTER TABLE "new_lesson_feedbacks" RENAME TO "lesson_feedbacks"; +CREATE UNIQUE INDEX "lesson_feedbacks_lesson_id_teacher_id_key" ON "lesson_feedbacks"("lesson_id", "teacher_id"); +CREATE TABLE "new_lessons" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "tenant_id" INTEGER NOT NULL, + "teacher_id" INTEGER NOT NULL, + "class_id" INTEGER NOT NULL, + "course_id" INTEGER NOT NULL, + "planned_datetime" DATETIME, + "start_datetime" DATETIME, + "end_datetime" DATETIME, + "actual_duration" INTEGER, + "status" TEXT NOT NULL DEFAULT 'PLANNED', + "overall_rating" TEXT, + "participation_rating" TEXT, + "completion_note" TEXT, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + CONSTRAINT "lessons_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "lessons_teacher_id_fkey" FOREIGN KEY ("teacher_id") REFERENCES "teachers" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "lessons_class_id_fkey" FOREIGN KEY ("class_id") REFERENCES "classes" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "lessons_course_id_fkey" FOREIGN KEY ("course_id") REFERENCES "courses" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_lessons" ("actual_duration", "class_id", "completion_note", "course_id", "created_at", "end_datetime", "id", "overall_rating", "participation_rating", "planned_datetime", "start_datetime", "status", "teacher_id", "tenant_id", "updated_at") SELECT "actual_duration", "class_id", "completion_note", "course_id", "created_at", "end_datetime", "id", "overall_rating", "participation_rating", "planned_datetime", "start_datetime", "status", "teacher_id", "tenant_id", "updated_at" FROM "lessons"; +DROP TABLE "lessons"; +ALTER TABLE "new_lessons" RENAME TO "lessons"; +CREATE TABLE "new_student_records" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "lesson_id" INTEGER NOT NULL, + "student_id" INTEGER NOT NULL, + "focus" INTEGER, + "participation" INTEGER, + "interest" INTEGER, + "understanding" INTEGER, + "domainAchievements" TEXT, + "notes" TEXT, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + CONSTRAINT "student_records_lesson_id_fkey" FOREIGN KEY ("lesson_id") REFERENCES "lessons" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "student_records_student_id_fkey" FOREIGN KEY ("student_id") REFERENCES "students" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_student_records" ("created_at", "domainAchievements", "focus", "id", "interest", "lesson_id", "notes", "participation", "student_id", "understanding", "updated_at") SELECT "created_at", "domainAchievements", "focus", "id", "interest", "lesson_id", "notes", "participation", "student_id", "understanding", "updated_at" FROM "student_records"; +DROP TABLE "student_records"; +ALTER TABLE "new_student_records" RENAME TO "student_records"; +CREATE UNIQUE INDEX "student_records_lesson_id_student_id_key" ON "student_records"("lesson_id", "student_id"); +CREATE TABLE "new_students" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "tenant_id" INTEGER NOT NULL, + "class_id" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "gender" TEXT, + "birth_date" DATETIME, + "parent_phone" TEXT, + "parent_name" TEXT, + "reading_count" INTEGER NOT NULL DEFAULT 0, + "lesson_count" INTEGER NOT NULL DEFAULT 0, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + CONSTRAINT "students_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "students_class_id_fkey" FOREIGN KEY ("class_id") REFERENCES "classes" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); +INSERT INTO "new_students" ("birth_date", "class_id", "created_at", "gender", "id", "lesson_count", "name", "parent_name", "parent_phone", "reading_count", "tenant_id", "updated_at") SELECT "birth_date", "class_id", "created_at", "gender", "id", "lesson_count", "name", "parent_name", "parent_phone", "reading_count", "tenant_id", "updated_at" FROM "students"; +DROP TABLE "students"; +ALTER TABLE "new_students" RENAME TO "students"; +CREATE TABLE "new_tags" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "level" INTEGER NOT NULL, + "code" TEXT NOT NULL, + "name" TEXT NOT NULL, + "parent_id" INTEGER, + "description" TEXT, + "metadata" TEXT, + "sort_order" INTEGER NOT NULL DEFAULT 0, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); +INSERT INTO "new_tags" ("code", "created_at", "description", "id", "level", "metadata", "name", "parent_id", "sort_order") SELECT "code", "created_at", "description", "id", "level", "metadata", "name", "parent_id", "sort_order" FROM "tags"; +DROP TABLE "tags"; +ALTER TABLE "new_tags" RENAME TO "tags"; +CREATE UNIQUE INDEX "tags_code_key" ON "tags"("code"); +CREATE TABLE "new_teachers" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "tenant_id" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "phone" TEXT NOT NULL, + "email" TEXT, + "login_account" TEXT NOT NULL, + "password_hash" TEXT NOT NULL, + "class_ids" TEXT DEFAULT '[]', + "status" TEXT NOT NULL DEFAULT 'ACTIVE', + "lesson_count" INTEGER NOT NULL DEFAULT 0, + "feedback_count" INTEGER NOT NULL DEFAULT 0, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + "last_login_at" DATETIME, + CONSTRAINT "teachers_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); +INSERT INTO "new_teachers" ("class_ids", "created_at", "email", "feedback_count", "id", "last_login_at", "lesson_count", "login_account", "name", "password_hash", "phone", "status", "tenant_id", "updated_at") SELECT "class_ids", "created_at", "email", "feedback_count", "id", "last_login_at", "lesson_count", "login_account", "name", "password_hash", "phone", "status", "tenant_id", "updated_at" FROM "teachers"; +DROP TABLE "teachers"; +ALTER TABLE "new_teachers" RENAME TO "teachers"; +CREATE UNIQUE INDEX "teachers_login_account_key" ON "teachers"("login_account"); +CREATE TABLE "new_tenant_courses" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "tenant_id" INTEGER NOT NULL, + "course_id" INTEGER NOT NULL, + "authorized" BOOLEAN NOT NULL DEFAULT true, + "authorized_at" DATETIME, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "tenant_courses_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "tenant_courses_course_id_fkey" FOREIGN KEY ("course_id") REFERENCES "courses" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); +INSERT INTO "new_tenant_courses" ("authorized", "authorized_at", "course_id", "created_at", "id", "tenant_id") SELECT "authorized", "authorized_at", "course_id", "created_at", "id", "tenant_id" FROM "tenant_courses"; +DROP TABLE "tenant_courses"; +ALTER TABLE "new_tenant_courses" RENAME TO "tenant_courses"; +CREATE UNIQUE INDEX "tenant_courses_tenant_id_course_id_key" ON "tenant_courses"("tenant_id", "course_id"); +CREATE TABLE "new_tenants" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "name" TEXT NOT NULL, + "address" TEXT, + "contact_person" TEXT, + "contact_phone" TEXT, + "logo_url" TEXT, + "package_type" TEXT NOT NULL DEFAULT 'STANDARD', + "teacher_quota" INTEGER NOT NULL DEFAULT 20, + "student_quota" INTEGER NOT NULL DEFAULT 200, + "storage_quota" BIGINT NOT NULL DEFAULT 5368709120, + "start_date" TEXT NOT NULL, + "expire_date" TEXT NOT NULL, + "teacher_count" INTEGER NOT NULL DEFAULT 0, + "student_count" INTEGER NOT NULL DEFAULT 0, + "storage_used" BIGINT NOT NULL DEFAULT 0, + "status" TEXT NOT NULL DEFAULT 'ACTIVE', + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL +); +INSERT INTO "new_tenants" ("address", "contact_person", "contact_phone", "created_at", "expire_date", "id", "logo_url", "name", "package_type", "start_date", "status", "storage_quota", "storage_used", "student_count", "student_quota", "teacher_count", "teacher_quota", "updated_at") SELECT "address", "contact_person", "contact_phone", "created_at", "expire_date", "id", "logo_url", "name", "package_type", "start_date", "status", "storage_quota", "storage_used", "student_count", "student_quota", "teacher_count", "teacher_quota", "updated_at" FROM "tenants"; +DROP TABLE "tenants"; +ALTER TABLE "new_tenants" RENAME TO "tenants"; +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/reading-platform-backend/prisma/migrations/20260210093209_make_picture_book_nullable/migration.sql b/reading-platform-backend/prisma/migrations/20260210093209_make_picture_book_nullable/migration.sql new file mode 100644 index 0000000..7797270 --- /dev/null +++ b/reading-platform-backend/prisma/migrations/20260210093209_make_picture_book_nullable/migration.sql @@ -0,0 +1,27 @@ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_courses" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "name" TEXT NOT NULL, + "description" TEXT, + "picture_book_id" INTEGER, + "picture_book_name" TEXT, + "grade_tags" TEXT NOT NULL DEFAULT '[]', + "domain_tags" TEXT NOT NULL DEFAULT '[]', + "duration" INTEGER NOT NULL DEFAULT 25, + "status" TEXT NOT NULL DEFAULT 'DRAFT', + "version" TEXT NOT NULL DEFAULT '1.0', + "usage_count" INTEGER NOT NULL DEFAULT 0, + "teacher_count" INTEGER NOT NULL DEFAULT 0, + "avg_rating" REAL NOT NULL DEFAULT 0, + "created_by" INTEGER, + "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" DATETIME NOT NULL, + "published_at" DATETIME +); +INSERT INTO "new_courses" ("avg_rating", "created_at", "created_by", "description", "domain_tags", "duration", "grade_tags", "id", "name", "picture_book_id", "picture_book_name", "published_at", "status", "teacher_count", "updated_at", "usage_count", "version") SELECT "avg_rating", "created_at", "created_by", "description", "domain_tags", "duration", "grade_tags", "id", "name", "picture_book_id", "picture_book_name", "published_at", "status", "teacher_count", "updated_at", "usage_count", "version" FROM "courses"; +DROP TABLE "courses"; +ALTER TABLE "new_courses" RENAME TO "courses"; +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/reading-platform-backend/prisma/migrations/migration_lock.toml b/reading-platform-backend/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..e5e5c47 --- /dev/null +++ b/reading-platform-backend/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "sqlite" \ No newline at end of file diff --git a/reading-platform-backend/prisma/schema.prisma b/reading-platform-backend/prisma/schema.prisma new file mode 100644 index 0000000..a93fdc9 --- /dev/null +++ b/reading-platform-backend/prisma/schema.prisma @@ -0,0 +1,840 @@ +// prisma/schema.prisma - SQLite版本(快速启动) + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "sqlite" + url = env("DATABASE_URL") +} + +// ==================== 租户 ==================== + +model Tenant { + id Int @id @default(autoincrement()) + name String + loginAccount String? @unique @map("login_account") + passwordHash String? @map("password_hash") + address String? + contactPerson String? @map("contact_person") + contactPhone String? @map("contact_phone") + logoUrl String? @map("logo_url") + + packageType String @default("STANDARD") @map("package_type") + teacherQuota Int @default(20) @map("teacher_quota") + studentQuota Int @default(200) @map("student_quota") + storageQuota BigInt @default(5368709120) @map("storage_quota") + + startDate String @map("start_date") + expireDate String @map("expire_date") + + teacherCount Int @default(0) @map("teacher_count") + studentCount Int @default(0) @map("student_count") + storageUsed BigInt @default(0) @map("storage_used") + + status String @default("ACTIVE") + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + teachers Teacher[] + students Student[] + classes Class[] + lessons Lesson[] + tenantCourses TenantCourse[] + growthRecords GrowthRecord[] + tasks Task[] + taskTemplates TaskTemplate[] + parents Parent[] + notifications Notification[] + settings SystemSettings? + schedulePlans SchedulePlan[] + scheduleTemplates ScheduleTemplate[] + operationLogs OperationLog[] + + @@map("tenants") +} + +// ==================== 教师 ==================== + +model Teacher { + id Int @id @default(autoincrement()) + tenantId Int @map("tenant_id") + name String + phone String + email String? + + loginAccount String @unique @map("login_account") + passwordHash String @map("password_hash") + classIds String? @default("[]") @map("class_ids") // 保留,向后兼容(只读缓存) + + status String @default("ACTIVE") + + lessonCount Int @default(0) @map("lesson_count") + feedbackCount Int @default(0) @map("feedback_count") + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + lastLoginAt DateTime? @map("last_login_at") + + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + classes Class[] + lessons Lesson[] + feedbacks LessonFeedback[] + classTeachers ClassTeacher[] // 新增:多对多关联 + schedulePlans SchedulePlan[] + scheduleTemplates ScheduleTemplate[] + + @@map("teachers") +} + +// ==================== 班级 ==================== + +model Class { + id Int @id @default(autoincrement()) + tenantId Int @map("tenant_id") + name String + grade String + + teacherId Int? @map("teacher_id") // 保留,向后兼容 + + studentCount Int @default(0) @map("student_count") + lessonCount Int @default(0) @map("lesson_count") + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + teacher Teacher? @relation(fields: [teacherId], references: [id]) + students Student[] + lessons Lesson[] + growthRecords GrowthRecord[] + classTeachers ClassTeacher[] // 新增:多对多关联 + studentHistory StudentClassHistory[] @relation("ToClass") // 新增:调班历史 + fromHistory StudentClassHistory[] @relation("FromClass") // 新增:调班历史 + schedulePlans SchedulePlan[] + scheduleTemplates ScheduleTemplate[] + + @@map("classes") +} + +// ==================== 学生 ==================== + +model Student { + id Int @id @default(autoincrement()) + tenantId Int @map("tenant_id") + classId Int @map("class_id") + name String + gender String? + birthDate DateTime? @map("birth_date") + parentPhone String? @map("parent_phone") + parentName String? @map("parent_name") + + readingCount Int @default(0) @map("reading_count") + lessonCount Int @default(0) @map("lesson_count") + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + class Class @relation(fields: [classId], references: [id], onDelete: Cascade) + records StudentRecord[] + growthRecords GrowthRecord[] + taskCompletions TaskCompletion[] + parents ParentStudent[] + classHistory StudentClassHistory[] // 新增:调班历史 + + @@map("students") +} + +// ==================== 课程包 ==================== + +model Course { + id Int @id @default(autoincrement()) + name String + description String? + + pictureBookId Int? @map("picture_book_id") + pictureBookName String? @map("picture_book_name") + + // 封面图片 - 存储文件路径(不再是 base64) + coverImagePath String? @map("cover_image_path") + + // 数字资源 - 存储多文件路径(JSON数组 [{path, name}]) + ebookPaths String? @map("ebook_paths") + + audioPaths String? @map("audio_paths") + + videoPaths String? @map("video_paths") + + otherResources String? @map("other_resources") // JSON数组存储多个资源 {path, name} + + // 教学材料 - 存储文件路径(不再是 base64) + pptPath String? @map("ppt_path") + pptName String? @map("ppt_name") + + posterPaths String? @map("poster_paths") // JSON数组存储多个挂图 {path, name} + + // 实体教具和学生材料 + tools String? @map("tools") // JSON数组存储教具列表 [{name, quantity}] + studentMaterials String? @map("student_materials") // 学生材料文本 + + // 课堂计划 + lessonPlanData String? @map("lesson_plan_data") // JSON存储课堂计划数据 + + // 延伸活动 + activitiesData String? @map("activities_data") // JSON存储延伸活动数据 + + // 测评工具 + assessmentData String? @map("assessment_data") // JSON存储测评工具数据 + + gradeTags String @default("[]") @map("grade_tags") + domainTags String @default("[]") @map("domain_tags") + + duration Int @default(25) + + status String @default("DRAFT") // DRAFT, PENDING, REJECTED, PUBLISHED, ARCHIVED + version String @default("1.0") + + // 审核相关字段 + submittedAt DateTime? @map("submitted_at") // 提交审核时间 + submittedBy Int? @map("submitted_by") // 提交人ID + reviewedAt DateTime? @map("reviewed_at") // 审核时间 + reviewedBy Int? @map("reviewed_by") // 审核人ID + reviewComment String? @map("review_comment") // 审核意见 + reviewChecklist String? @map("review_checklist") // 审核检查项结果 JSON + + // 版本相关字段 + parentId Int? @map("parent_id") // 父版本ID(迭代时指向原版本) + isLatest Boolean @default(true) // 是否最新版本 + + usageCount Int @default(0) @map("usage_count") + teacherCount Int @default(0) @map("teacher_count") + avgRating Float @default(0) @map("avg_rating") + + createdBy Int? @map("created_by") + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + publishedAt DateTime? @map("published_at") + + resources CourseResource[] + scripts CourseScript[] + activities CourseActivity[] + lessons Lesson[] + tenantCourses TenantCourse[] + versions CourseVersion[] + tasks Task[] + taskTemplates TaskTemplate[] + schedulePlans SchedulePlan[] + scheduleTemplates ScheduleTemplate[] + + @@index([status]) + @@map("courses") +} + +// ==================== 课程版本历史 ==================== + +model CourseVersion { + id Int @id @default(autoincrement()) + courseId Int @map("course_id") + version String // 版本号 + snapshotData String // JSON快照(完整课程内容) + changeLog String? // 变更说明 + publishedAt DateTime @default(now()) @map("published_at") + publishedBy Int @map("published_by") + + course Course @relation(fields: [courseId], references: [id], onDelete: Cascade) + + @@index([courseId]) + @@map("course_versions") +} + +// ==================== 课程资源 ==================== + +model CourseResource { + id Int @id @default(autoincrement()) + courseId Int @map("course_id") + resourceType String @map("resource_type") + resourceName String @map("resource_name") + fileUrl String @map("file_url") + fileSize Int? @map("file_size") + mimeType String? @map("mime_type") + metadata String? + + sortOrder Int @default(0) @map("sort_order") + + createdAt DateTime @default(now()) @map("created_at") + + course Course @relation(fields: [courseId], references: [id], onDelete: Cascade) + + @@map("course_resources") +} + +// ==================== 课程脚本 ==================== + +model CourseScript { + id Int @id @default(autoincrement()) + courseId Int @map("course_id") + stepIndex Int @map("step_index") + stepName String @map("step_name") + stepType String @map("step_type") + + duration Int + objective String? + teacherScript String? @map("teacher_script") + interactionPoints String? @map("interaction_points") + + resourceIds String? @map("resource_ids") + + sortOrder Int @default(0) @map("sort_order") + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + course Course @relation(fields: [courseId], references: [id], onDelete: Cascade) + pages CourseScriptPage[] + + @@unique([courseId, stepIndex]) + @@map("course_scripts") +} + +// ==================== 逐页配置 ==================== + +model CourseScriptPage { + id Int @id @default(autoincrement()) + scriptId Int @map("script_id") + pageNumber Int @map("page_number") + questions String? + interactionComponent String? @map("interaction_component") + teacherNotes String? @map("teacher_notes") + resourceIds String? @map("resource_ids") // 关联资源ID列表,JSON数组 + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + script CourseScript @relation(fields: [scriptId], references: [id], onDelete: Cascade) + + @@unique([scriptId, pageNumber]) + @@map("course_script_pages") +} + +// ==================== 延伸活动 ==================== + +model CourseActivity { + id Int @id @default(autoincrement()) + courseId Int @map("course_id") + name String + + domain String? + domainTagId Int? @map("domain_tag_id") + activityType String @map("activity_type") + duration Int? + + onlineMaterials String? @map("online_materials") + offlineMaterials String? + activityGuide String? + objectives String? + + sortOrder Int @default(0) @map("sort_order") + + createdAt DateTime @default(now()) @map("created_at") + + course Course @relation(fields: [courseId], references: [id], onDelete: Cascade) + + @@map("course_activities") +} + +// ==================== 授课记录 ==================== + +model Lesson { + id Int @id @default(autoincrement()) + tenantId Int @map("tenant_id") + teacherId Int @map("teacher_id") + classId Int @map("class_id") + courseId Int @map("course_id") + schedulePlanId Int? @map("schedule_plan_id") + + plannedDatetime DateTime? @map("planned_datetime") + startDatetime DateTime? @map("start_datetime") + endDatetime DateTime? @map("end_datetime") + actualDuration Int? @map("actual_duration") + + status String @default("PLANNED") + + overallRating String? @map("overall_rating") + participationRating String? @map("participation_rating") + completionNote String? @map("completion_note") + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + tenant Tenant @relation(fields: [tenantId], references: [id]) + teacher Teacher @relation(fields: [teacherId], references: [id]) + class Class @relation(fields: [classId], references: [id]) + course Course @relation(fields: [courseId], references: [id]) + schedulePlan SchedulePlan? @relation(fields: [schedulePlanId], references: [id]) + feedbacks LessonFeedback[] + records StudentRecord[] + + @@map("lessons") +} + +// ==================== 课程反馈 ==================== + +model LessonFeedback { + id Int @id @default(autoincrement()) + lessonId Int @map("lesson_id") + teacherId Int @map("teacher_id") + + designQuality Int? @map("design_quality") + participation Int? + goalAchievement Int? @map("goal_achievement") + + stepFeedbacks String? @map("step_feedbacks") + + pros String? + suggestions String? + + activitiesDone String? @map("activities_done") + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade) + teacher Teacher @relation(fields: [teacherId], references: [id]) + + @@unique([lessonId, teacherId]) + @@map("lesson_feedbacks") +} + +// ==================== 学生测评记录 ==================== + +model StudentRecord { + id Int @id @default(autoincrement()) + lessonId Int @map("lesson_id") + studentId Int @map("student_id") + + focus Int? + participation Int? + interest Int? + understanding Int? + + domainAchievements String? + + notes String? + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade) + student Student @relation(fields: [studentId], references: [id]) + + @@unique([lessonId, studentId]) + @@map("student_records") +} + +// ==================== 标签体系 ==================== + +model Tag { + id Int @id @default(autoincrement()) + level Int + code String @unique + name String + parentId Int? @map("parent_id") + + description String? + metadata String? + + sortOrder Int @default(0) @map("sort_order") + + createdAt DateTime @default(now()) @map("created_at") + + @@map("tags") +} + +// ==================== 租户课程授权 ==================== + +model TenantCourse { + id Int @id @default(autoincrement()) + tenantId Int @map("tenant_id") + courseId Int @map("course_id") + + authorized Boolean @default(true) + authorizedAt DateTime? @map("authorized_at") + + createdAt DateTime @default(now()) @map("created_at") + + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + course Course @relation(fields: [courseId], references: [id], onDelete: Cascade) + + @@unique([tenantId, courseId]) + @@map("tenant_courses") +} + +// ==================== 成长档案 ==================== + +model GrowthRecord { + id Int @id @default(autoincrement()) + tenantId Int @map("tenant_id") + studentId Int @map("student_id") + classId Int? @map("class_id") + recordType String @map("record_type") // STUDENT, CLASS + title String + content String? + images String? @map("images") // JSON: ["path1", "path2"] + recordDate DateTime @map("record_date") + createdBy Int @map("created_by") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + student Student @relation(fields: [studentId], references: [id], onDelete: Cascade) + class Class? @relation(fields: [classId], references: [id], onDelete: SetNull) + + @@index([tenantId, studentId]) + @@index([tenantId, classId]) + @@map("growth_records") +} + +// ==================== 阅读任务 ==================== + +model Task { + id Int @id @default(autoincrement()) + tenantId Int @map("tenant_id") + title String + description String? + taskType String @map("task_type") // READING, ACTIVITY, HOMEWORK + targetType String @map("target_type") // CLASS, STUDENT + relatedCourseId Int? @map("related_course_id") + createdBy Int @map("created_by") + startDate DateTime @map("start_date") + endDate DateTime @map("end_date") + status String @default("PUBLISHED") // DRAFT, PUBLISHED, ARCHIVED + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + course Course? @relation(fields: [relatedCourseId], references: [id], onDelete: SetNull) + targets TaskTarget[] + completions TaskCompletion[] + + @@index([tenantId, status]) + @@map("tasks") +} + +model TaskTarget { + id Int @id @default(autoincrement()) + taskId Int @map("task_id") + classId Int? @map("class_id") + studentId Int? @map("student_id") + + task Task @relation(fields: [taskId], references: [id], onDelete: Cascade) + + @@index([taskId, classId]) + @@index([taskId, studentId]) + @@map("task_targets") +} + +model TaskCompletion { + id Int @id @default(autoincrement()) + taskId Int @map("task_id") + studentId Int @map("student_id") + status String @default("PENDING") // PENDING, IN_PROGRESS, COMPLETED + completedAt DateTime? @map("completed_at") + feedback String? + parentFeedback String? @map("parent_feedback") + createdAt DateTime @default(now()) + + task Task @relation(fields: [taskId], references: [id], onDelete: Cascade) + student Student @relation(fields: [studentId], references: [id], onDelete: Cascade) + + @@unique([taskId, studentId]) + @@map("task_completions") +} + +// ==================== 任务模板 ==================== + +model TaskTemplate { + id Int @id @default(autoincrement()) + tenantId Int @map("tenant_id") + + // 模板基本信息 + name String + description String? + taskType String @map("task_type") // READING, ACTIVITY, HOMEWORK + + // 关联课程(可选) + relatedCourseId Int? @map("related_course_id") + + // 默认时间设置 + defaultDuration Int @default(7) @map("default_duration") // 默认任务天数 + + // 模板状态 + isDefault Boolean @default(false) @map("is_default") + status String @default("ACTIVE") // ACTIVE, ARCHIVED + + // 创建者 + createdBy Int @map("created_by") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + course Course? @relation(fields: [relatedCourseId], references: [id], onDelete: SetNull) + + @@index([tenantId, status]) + @@map("task_templates") +} + +// ==================== 资源库 ==================== + +model ResourceLibrary { + id Int @id @default(autoincrement()) + name String + libraryType String @map("library_type") // PICTURE_BOOK, MATERIAL, TEMPLATE + description String? + coverImage String? @map("cover_image") + tenantId Int? @map("tenant_id") // NULL = 公共资源 + createdBy Int @map("created_by") + status String @default("PUBLISHED") + sortOrder Int @default(0) @map("sort_order") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + items ResourceItem[] + + @@index([libraryType, status]) + @@map("resource_libraries") +} + +model ResourceItem { + id Int @id @default(autoincrement()) + libraryId Int @map("library_id") + title String + description String? + fileType String @map("file_type") // IMAGE, PDF, VIDEO, AUDIO, PPT, OTHER + filePath String @map("file_path") + fileSize Int? @map("file_size") + tags String? // JSON: ["tag1", "tag2"] + sortOrder Int @default(0) @map("sort_order") + createdAt DateTime @default(now()) + + library ResourceLibrary @relation(fields: [libraryId], references: [id], onDelete: Cascade) + + @@index([libraryId]) + @@map("resource_items") +} + +// ==================== 系统设置 ==================== + +model SystemSettings { + id Int @id @default(autoincrement()) + tenantId Int @unique @map("tenant_id") + schoolName String? @map("school_name") + schoolLogo String? @map("school_logo") + address String? + notifyOnLesson Boolean @default(true) @map("notify_on_lesson") + notifyOnTask Boolean @default(true) @map("notify_on_task") + notifyOnGrowth Boolean @default(false) @map("notify_on_growth") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + @@map("system_settings") +} + +// ==================== 家长 ==================== + +model Parent { + id Int @id @default(autoincrement()) + tenantId Int @map("tenant_id") + name String + phone String + email String? + loginAccount String @unique @map("login_account") + passwordHash String @map("password_hash") + status String @default("ACTIVE") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + lastLoginAt DateTime? @map("last_login_at") + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + children ParentStudent[] + @@map("parents") +} + +// ==================== 家长-学生关联 ==================== + +model ParentStudent { + id Int @id @default(autoincrement()) + parentId Int @map("parent_id") + studentId Int @map("student_id") + relationship String // FATHER, MOTHER, GRANDFATHER, GRANDMOTHER, OTHER + createdAt DateTime @default(now()) @map("created_at") + parent Parent @relation(fields: [parentId], references: [id], onDelete: Cascade) + student Student @relation(fields: [studentId], references: [id], onDelete: Cascade) + @@unique([parentId, studentId]) + @@map("parent_students") +} + +// ==================== 通知 ==================== + +model Notification { + id Int @id @default(autoincrement()) + tenantId Int @map("tenant_id") + recipientType String @map("recipient_type") // TEACHER, SCHOOL, PARENT + recipientId Int @map("recipient_id") + title String + content String + notificationType String @map("notification_type") // SYSTEM, TASK, LESSON, GROWTH + relatedType String? @map("related_type") + relatedId Int? @map("related_id") + isRead Boolean @default(false) @map("is_read") + readAt DateTime? @map("read_at") + createdAt DateTime @default(now()) @map("created_at") + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + @@index([tenantId, recipientType, recipientId]) + @@map("notifications") +} + +// ==================== 班级教师关联 ==================== + +model ClassTeacher { + id Int @id @default(autoincrement()) + classId Int @map("class_id") + teacherId Int @map("teacher_id") + role String @default("MAIN") // MAIN主班, ASSIST配班, CARE保育员 + isPrimary Boolean @default(false) // 是否班主任 + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + class Class @relation(fields: [classId], references: [id], onDelete: Cascade) + teacher Teacher @relation(fields: [teacherId], references: [id], onDelete: Cascade) + + @@unique([classId, teacherId]) + @@map("class_teachers") +} + +// ==================== 学生调班历史 ==================== + +model StudentClassHistory { + id Int @id @default(autoincrement()) + studentId Int @map("student_id") + fromClassId Int? @map("from_class_id") + toClassId Int @map("to_class_id") + reason String? + operatedBy Int? @map("operated_by") // 操作人(教师ID),可选 + createdAt DateTime @default(now()) @map("created_at") + + student Student @relation(fields: [studentId], references: [id]) + fromClass Class? @relation("FromClass", fields: [fromClassId], references: [id]) + toClass Class @relation("ToClass", fields: [toClassId], references: [id]) + + @@map("student_class_history") +} + +// ==================== 排课计划 ==================== + +model SchedulePlan { + id Int @id @default(autoincrement()) + tenantId Int @map("tenant_id") + + // 关联信息 + classId Int @map("class_id") + courseId Int @map("course_id") + teacherId Int? @map("teacher_id") // 可选,未分配时为空 + + // 时间信息 + scheduledDate DateTime? @map("scheduled_date") // 具体日期 + scheduledTime String? @map("scheduled_time") // 时间段 "09:00-09:30" + weekDay Int? @map("week_day") // 周几 (0-6),用于重复排课 + + // 重复规则 + repeatType String @default("NONE") @map("repeat_type") // NONE, DAILY, WEEKLY + repeatEndDate DateTime? @map("repeat_end_date") + + // 排课来源 + source String @default("SCHOOL") // SCHOOL(学校排课), TEACHER(教师预约) + createdBy Int @map("created_by") // 创建人ID + + // 状态 + status String @default("ACTIVE") // ACTIVE, CANCELLED + + // 备注 + note String? + + // 提醒 + reminderSent Boolean @default(false) @map("reminder_sent") + reminderSentAt DateTime? @map("reminder_sent_at") + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + tenant Tenant @relation(fields: [tenantId], references: [id]) + class Class @relation(fields: [classId], references: [id]) + course Course @relation(fields: [courseId], references: [id]) + teacher Teacher? @relation(fields: [teacherId], references: [id]) + lessons Lesson[] + + @@index([tenantId, classId]) + @@index([tenantId, teacherId]) + @@index([tenantId, scheduledDate]) + @@map("schedule_plans") +} + +// ==================== 排课模板 ==================== + +model ScheduleTemplate { + id Int @id @default(autoincrement()) + tenantId Int @map("tenant_id") + + // 模板基本信息 + name String + courseId Int @map("course_id") + classId Int? @map("class_id") // 可选,特定班级 + teacherId Int? @map("teacher_id") // 可选,特定教师 + + // 时间配置 + scheduledTime String? @map("scheduled_time") // 时间段 "09:00-09:30" + weekDay Int? @map("week_day") // 周几 (0-6) + duration Int @default(25) // 课程时长(分钟) + + // 模板设置 + isDefault Boolean @default(false) @map("is_default") + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) + course Course @relation(fields: [courseId], references: [id]) + class Class? @relation(fields: [classId], references: [id]) + teacher Teacher? @relation(fields: [teacherId], references: [id]) + + @@index([tenantId]) + @@index([tenantId, courseId]) + @@map("schedule_templates") +} + +// ==================== 操作日志 ==================== + +model OperationLog { + id Int @id @default(autoincrement()) + tenantId Int? @map("tenant_id") + userId Int @map("user_id") + userType String @map("user_type") // SCHOOL, TEACHER, PARENT, ADMIN + action String // CREATE, UPDATE, DELETE, LOGIN, etc. + module String // 教师管理, 学生管理, 排课管理, etc. + description String + targetId Int? @map("target_id") // 操作对象ID + oldValue String? @map("old_value") // JSON格式 + newValue String? @map("new_value") // JSON格式 + ipAddress String? @map("ip_address") + userAgent String? @map("user_agent") + createdAt DateTime @default(now()) @map("created_at") + + tenant Tenant? @relation(fields: [tenantId], references: [id], onDelete: SetNull) + + @@index([tenantId, userId]) + @@index([tenantId, createdAt]) + @@index([action, module]) + @@map("operation_logs") +} diff --git a/reading-platform-backend/prisma/seed.ts b/reading-platform-backend/prisma/seed.ts new file mode 100644 index 0000000..306db17 --- /dev/null +++ b/reading-platform-backend/prisma/seed.ts @@ -0,0 +1,431 @@ +import { PrismaClient } from '@prisma/client'; +import * as bcrypt from 'bcrypt'; + +const prisma = new PrismaClient(); + +async function main() { + console.log('开始种子数据...'); + + // 1. 创建测试租户 + const tenant = await prisma.tenant.upsert({ + where: { id: 1 }, + update: {}, + create: { + name: '阳光幼儿园', + address: '北京市朝阳区xxx街道', + contactPerson: '张园长', + contactPhone: '13800138000', + packageType: 'STANDARD', + teacherQuota: 20, + studentQuota: 200, + storageQuota: BigInt(5368709120), // 5GB + startDate: '2024-01-01', + expireDate: '2025-12-31', + status: 'ACTIVE', + }, + }); + console.log('创建租户:', tenant.name); + + // 2. 创建教师账号 + const passwordHash = await bcrypt.hash('123456', 10); + const teacher = await prisma.teacher.upsert({ + where: { loginAccount: 'teacher1' }, + update: {}, + create: { + tenantId: tenant.id, + name: '李老师', + phone: '13900139000', + email: 'teacher1@example.com', + loginAccount: 'teacher1', + passwordHash: passwordHash, + status: 'ACTIVE', + }, + }); + console.log('创建教师:', teacher.name); + + // 3. 创建班级 + const class1 = await prisma.class.upsert({ + where: { id: 1 }, + update: {}, + create: { + tenantId: tenant.id, + name: '中一班', + grade: 'MIDDLE', + teacherId: teacher.id, + studentCount: 25, + }, + }); + console.log('创建班级:', class1.name); + + const class2 = await prisma.class.upsert({ + where: { id: 2 }, + update: {}, + create: { + tenantId: tenant.id, + name: '大一班', + grade: 'BIG', + teacherId: teacher.id, + studentCount: 30, + }, + }); + console.log('创建班级:', class2.name); + + // 4. 更新教师的班级关联 + await prisma.teacher.update({ + where: { id: teacher.id }, + data: { + classIds: JSON.stringify([class1.id, class2.id]), + }, + }); + + // 5. 创建示例学生 + const students = [ + { name: '小明', gender: 'MALE', classId: class1.id }, + { name: '小红', gender: 'FEMALE', classId: class1.id }, + { name: '小华', gender: 'MALE', classId: class1.id }, + { name: '小丽', gender: 'FEMALE', classId: class2.id }, + { name: '小强', gender: 'MALE', classId: class2.id }, + ]; + + for (const studentData of students) { + await prisma.student.upsert({ + where: { + id: students.indexOf(studentData) + 1, + }, + update: {}, + create: { + tenantId: tenant.id, + classId: studentData.classId, + name: studentData.name, + gender: studentData.gender, + }, + }); + } + console.log('创建学生:', students.length, '名'); + + // 6. 创建示例课程包 + const course = await prisma.course.upsert({ + where: { id: 1 }, + update: {}, + create: { + name: '好饿的毛毛虫', + description: '这是一本经典的绘本,讲述了一只毛毛虫从孵化到变成蝴蝶的故事。通过这个故事,孩子们可以学习到星期的概念、数字的认知,以及毛毛虫变蝴蝶的科学知识。', + pictureBookName: '好饿的毛毛虫', + gradeTags: JSON.stringify(['SMALL', 'MIDDLE']), + domainTags: JSON.stringify(['LANGUAGE', 'SCIENCE', 'MATH']), + duration: 30, + status: 'PUBLISHED', + version: '1.0', + coverImagePath: '/uploads/covers/caterpillar.jpg', + }, + }); + console.log('创建课程:', course.name); + + // 7. 创建课程脚本(6步教学流程) + const scripts = [ + { + stepIndex: 1, + stepName: '阅读导入', + stepType: 'INTRODUCTION', + duration: 5, + objective: '激发幼儿阅读兴趣,建立阅读期待', + teacherScript: '小朋友们,今天我们要认识一位新朋友——一只小小的毛毛虫。你们见过毛毛虫吗?它长什么样子呢?让我们一起来看看这只特别的毛毛虫的故事吧!', + interactionPoints: JSON.stringify([ + '展示毛毛虫图片或玩偶', + '引导幼儿分享见过的毛毛虫', + '预测故事内容', + ]), + }, + { + stepIndex: 2, + stepName: '绘本共读', + stepType: 'READING', + duration: 10, + objective: '理解故事内容,发展语言能力', + teacherScript: '(逐页讲述)从前,有一颗小小的蛋躺在叶子上...月光下,一条又小又饿的毛毛虫从蛋里爬了出来...', + interactionPoints: JSON.stringify([ + '提问预测', + '模仿毛毛虫吃东西的动作', + '一起数食物的数量', + ]), + }, + { + stepIndex: 3, + stepName: '理解讨论', + stepType: 'DISCUSSION', + duration: 5, + objective: '加深对故事的理解,发展思维能力', + teacherScript: '小朋友们,毛毛虫吃了哪些东西呢?为什么最后它肚子痛了?它最后变成了什么?', + interactionPoints: JSON.stringify([ + '回顾毛毛虫吃的食物', + '讨论健康饮食的重要性', + '讨论毛毛虫的成长变化', + ]), + }, + { + stepIndex: 4, + stepName: '互动游戏', + stepType: 'ACTIVITY', + duration: 5, + objective: '通过游戏巩固学习内容', + teacherScript: '现在我们来玩一个游戏,老师说出星期几,小朋友们来模仿毛毛虫吃了什么!', + interactionPoints: JSON.stringify([ + '星期与食物配对游戏', + '毛毛虫动作模仿', + '食物分类活动', + ]), + }, + { + stepIndex: 5, + stepName: '创意表达', + stepType: 'CREATIVE', + duration: 3, + objective: '发展创造力和表达能力', + teacherScript: '如果你是毛毛虫,你想吃什么?画一画你心目中的毛毛虫吧!', + interactionPoints: JSON.stringify([ + '自由绘画', + '分享作品', + '创意表达', + ]), + }, + { + stepIndex: 6, + stepName: '总结延伸', + stepType: 'SUMMARY', + duration: 2, + objective: '总结学习内容,激发延伸探索兴趣', + teacherScript: '今天我们认识了一只可爱的毛毛虫,它从一颗小蛋,变成毛毛虫,最后变成了漂亮的蝴蝶!回家后可以和爸爸妈妈一起找找看,还有哪些动物会变形呢?', + interactionPoints: JSON.stringify([ + '总结毛毛虫的成长过程', + '布置家庭延伸任务', + '预告下次活动', + ]), + }, + ]; + + for (const script of scripts) { + await prisma.courseScript.upsert({ + where: { + courseId_stepIndex: { + courseId: course.id, + stepIndex: script.stepIndex, + }, + }, + update: {}, + create: { + courseId: course.id, + ...script, + sortOrder: script.stepIndex, + }, + }); + } + console.log('创建课程脚本:', scripts.length, '个步骤'); + + // 8. 创建逐页配置(为绘本共读步骤添加) + const pages = [ + { pageNumber: 1, questions: '你们看到了什么?这是什么颜色的?', teacherNotes: '引导观察封面' }, + { pageNumber: 2, questions: '蛋在哪里?是谁的蛋呢?', teacherNotes: '引入故事悬念' }, + { pageNumber: 3, questions: '毛毛虫从蛋里出来了!它说了什么?', teacherNotes: '模仿毛毛虫的声音' }, + { pageNumber: 4, questions: '星期一,毛毛虫吃了什么?吃了几个?', teacherNotes: '学习星期和数字' }, + { pageNumber: 5, questions: '星期二,它又吃了什么?', teacherNotes: '继续学习星期' }, + ]; + + const readingScript = await prisma.courseScript.findFirst({ + where: { courseId: course.id, stepType: 'READING' }, + }); + + if (readingScript) { + for (const page of pages) { + await prisma.courseScriptPage.upsert({ + where: { + scriptId_pageNumber: { + scriptId: readingScript.id, + pageNumber: page.pageNumber, + }, + }, + update: {}, + create: { + scriptId: readingScript.id, + ...page, + }, + }); + } + console.log('创建逐页配置:', pages.length, '页'); + } + + // 9. 创建延伸活动 + const activities = [ + { + name: '毛毛虫手偶制作', + domain: 'ART', + activityType: 'HANDICRAFT', + duration: 20, + onlineMaterials: JSON.stringify(['毛毛虫模板PDF', '制作视频']), + offlineMaterials: '彩纸、剪刀、胶水、眼睛贴纸', + activityGuide: '1. 准备材料\n2. 按照模板剪裁\n3. 粘贴组装\n4. 添加装饰', + objectives: JSON.stringify(['锻炼手部精细动作', '培养创造力', '巩固毛毛虫认知']), + sortOrder: 1, + }, + { + name: '健康饮食分类', + domain: 'SCIENCE', + activityType: 'GAME', + duration: 15, + onlineMaterials: JSON.stringify(['食物卡片PPT']), + offlineMaterials: '食物图片卡片、分类筐', + activityGuide: '1. 展示各种食物图片\n2. 讨论健康与不健康食物\n3. 进行分类游戏', + objectives: JSON.stringify(['认识健康饮食', '学习分类', '培养健康饮食习惯']), + sortOrder: 2, + }, + { + name: '蝴蝶的生命周期', + domain: 'SCIENCE', + activityType: 'EXPLORATION', + duration: 25, + onlineMaterials: JSON.stringify(['蝴蝶生长视频', '生命周期图']), + offlineMaterials: '绘本、放大镜、观察记录本', + activityGuide: '1. 观看蝴蝶生长视频\n2. 讨论四个阶段\n3. 绘制生命周期图', + objectives: JSON.stringify(['了解变态发育', '培养科学探究精神', '学习观察记录']), + sortOrder: 3, + }, + ]; + + for (const activity of activities) { + await prisma.courseActivity.upsert({ + where: { id: activities.indexOf(activity) + 1 }, + update: {}, + create: { + courseId: course.id, + ...activity, + }, + }); + } + console.log('创建延伸活动:', activities.length, '个'); + + // 10. 为租户授权课程 + const tenantCourse = await prisma.tenantCourse.upsert({ + where: { + tenantId_courseId: { + tenantId: tenant.id, + courseId: course.id, + }, + }, + update: {}, + create: { + tenantId: tenant.id, + courseId: course.id, + authorized: true, + authorizedAt: new Date(), + }, + }); + console.log('授权课程给租户:', tenant.id, '->', course.id); + + // 11. 创建第二个示例课程 + const course2 = await prisma.course.upsert({ + where: { id: 2 }, + update: {}, + create: { + name: '猜猜我有多爱你', + description: '这是一本关于爱的温暖绘本,小兔子和大兔子用各种方式表达彼此的爱。通过这个故事,孩子们可以学习到表达爱的方式,感受亲情的温暖。', + pictureBookName: '猜猜我有多爱你', + gradeTags: JSON.stringify(['MIDDLE', 'BIG']), + domainTags: JSON.stringify(['LANGUAGE', 'SOCIAL']), + duration: 25, + status: 'PUBLISHED', + version: '1.0', + coverImagePath: '/uploads/covers/love.jpg', + }, + }); + console.log('创建课程:', course2.name); + + // 为第二个课程创建简化脚本 + const scripts2 = [ + { + stepIndex: 1, + stepName: '导入环节', + stepType: 'INTRODUCTION', + duration: 3, + objective: '引入爱的主题', + teacherScript: '小朋友们,你们爱爸爸妈妈吗?你们是怎么表达爱的呢?', + interactionPoints: JSON.stringify(['分享表达爱的方式']), + }, + { + stepIndex: 2, + stepName: '绘本共读', + stepType: 'READING', + duration: 10, + objective: '理解故事,感受爱的表达', + teacherScript: '小栗色兔子该上床睡觉了,可是他紧紧地抓住大栗色兔子的长耳朵不放...', + interactionPoints: JSON.stringify(['模仿动作', '感受爱的比较']), + }, + { + stepIndex: 3, + stepName: '情感讨论', + stepType: 'DISCUSSION', + duration: 5, + objective: '表达自己的感受', + teacherScript: '小兔子和大兔子谁的爱更多呢?你们觉得呢?', + interactionPoints: JSON.stringify(['讨论爱的深度', '分享感受']), + }, + { + stepIndex: 4, + stepName: '爱的表达', + stepType: 'ACTIVITY', + duration: 5, + objective: '学会表达爱', + teacherScript: '让我们也来学学小兔子,用手臂来量量我们有多爱爸爸妈妈!', + interactionPoints: JSON.stringify(['肢体表达', '语言表达']), + }, + ]; + + for (const script of scripts2) { + await prisma.courseScript.upsert({ + where: { + courseId_stepIndex: { + courseId: course2.id, + stepIndex: script.stepIndex, + }, + }, + update: {}, + create: { + courseId: course2.id, + ...script, + sortOrder: script.stepIndex, + }, + }); + } + + // 授权第二个课程 + await prisma.tenantCourse.upsert({ + where: { + tenantId_courseId: { + tenantId: tenant.id, + courseId: course2.id, + }, + }, + update: {}, + create: { + tenantId: tenant.id, + courseId: course2.id, + authorized: true, + authorizedAt: new Date(), + }, + }); + console.log('授权课程给租户:', tenant.id, '->', course2.id); + + console.log('\n种子数据创建完成!'); + console.log('===================='); + console.log('测试账号信息:'); + console.log('超管: admin / 123456'); + console.log('教师: teacher1 / 123456'); + console.log('===================='); +} + +main() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/reading-platform-backend/scripts/create-test-parent.ts b/reading-platform-backend/scripts/create-test-parent.ts new file mode 100644 index 0000000..0c68a12 --- /dev/null +++ b/reading-platform-backend/scripts/create-test-parent.ts @@ -0,0 +1,99 @@ +// 创建测试家长账号脚本 +// 运行方式: npx ts-node scripts/create-test-parent.ts + +import { PrismaClient } from '@prisma/client'; +import * as bcrypt from 'bcrypt'; + +const prisma = new PrismaClient(); + +async function main() { + console.log('开始创建测试家长账号...'); + + // 获取第一个租户 + const tenant = await prisma.tenant.findFirst(); + + if (!tenant) { + console.log('未找到租户,请先创建学校'); + return; + } + + console.log(`使用租户: ${tenant.name} (ID: ${tenant.id})`); + + // 获取一些学生用于关联 + const students = await prisma.student.findMany({ + where: { tenantId: tenant.id }, + take: 5, + }); + + console.log(`找到 ${students.length} 个学生`); + + // 创建测试家长 + const hashedPassword = await bcrypt.hash('123456', 10); + + const parentData = [ + { + name: '张爸爸', + phone: '13800138001', + email: 'zhang@test.com', + loginAccount: 'parent1', + passwordHash: hashedPassword, + }, + { + name: '李妈妈', + phone: '13800138002', + email: 'li@test.com', + loginAccount: 'parent2', + passwordHash: hashedPassword, + }, + ]; + + for (const data of parentData) { + // 检查是否已存在 + const existing = await prisma.parent.findUnique({ + where: { loginAccount: data.loginAccount }, + }); + + if (existing) { + console.log(`家长 ${data.loginAccount} 已存在,跳过`); + continue; + } + + const parent = await prisma.parent.create({ + data: { + tenantId: tenant.id, + ...data, + status: 'ACTIVE', + }, + }); + + console.log(`创建家长: ${parent.name} (${parent.loginAccount})`); + + // 关联学生 + if (students.length > 0) { + const studentToLink = students[parentData.indexOf(data) % students.length]; + + await prisma.parentStudent.create({ + data: { + parentId: parent.id, + studentId: studentToLink.id, + relationship: data.name.includes('爸爸') ? 'FATHER' : 'MOTHER', + }, + }); + + console.log(` -> 关联学生: ${studentToLink.name}`); + } + } + + console.log('\n测试家长账号创建完成!'); + console.log('登录账号: parent1 / parent2'); + console.log('密码: 123456'); +} + +main() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/reading-platform-backend/src/.DS_Store b/reading-platform-backend/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5dfa5b6932d3a0753fca1d50e4ff89326952628a GIT binary patch literal 6148 zcmeHKu};H44E2?^w1S~*jQNHBL8!tPRO(#XR4_E8Qt3ynZ(g*y}|q7RybN2r<~DduS!1cSI)=87%&EmfuAyfn$6PMQMA?=Fb0f)B?J6@h@gzI z$3js)9cbhT0Ib6-g1-FA0V6g5V~>R*ED)!mKn->Nh~YFG_Q?Ci9t%YcCnqyw96z)3 zCln{M!ybt{xmeL!W55`wGO(q$9j^a}o6rB%AiFaLjDdf}fU76{q=#4XwKaG-uC)<# q2xVcvLU9>_j+A2faw*=2iohOm2N-)S6k&nbkATo%jWO`64153_!E*%w literal 0 HcmV?d00001 diff --git a/reading-platform-backend/src/app.module.js b/reading-platform-backend/src/app.module.js new file mode 100644 index 0000000..be1d376 --- /dev/null +++ b/reading-platform-backend/src/app.module.js @@ -0,0 +1,92 @@ +"use strict"; +var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { + function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } + var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; + var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; + var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); + var _, done = false; + for (var i = decorators.length - 1; i >= 0; i--) { + var context = {}; + for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; + for (var p in contextIn.access) context.access[p] = contextIn.access[p]; + context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; + var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); + if (kind === "accessor") { + if (result === void 0) continue; + if (result === null || typeof result !== "object") throw new TypeError("Object expected"); + if (_ = accept(result.get)) descriptor.get = _; + if (_ = accept(result.set)) descriptor.set = _; + if (_ = accept(result.init)) initializers.unshift(_); + } + else if (_ = accept(result)) { + if (kind === "field") initializers.unshift(_); + else descriptor[key] = _; + } + } + if (target) Object.defineProperty(target, contextIn.name, descriptor); + done = true; +}; +var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { + var useValue = arguments.length > 2; + for (var i = 0; i < initializers.length; i++) { + value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); + } + return useValue ? value : void 0; +}; +var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) { + if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : ""; + return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AppModule = void 0; +var common_1 = require("@nestjs/common"); +var config_1 = require("@nestjs/config"); +var throttler_1 = require("@nestjs/throttler"); +var prisma_module_1 = require("./database/prisma.module"); +var auth_module_1 = require("./modules/auth/auth.module"); +var course_module_1 = require("./modules/course/course.module"); +var tenant_module_1 = require("./modules/tenant/tenant.module"); +var common_module_1 = require("./modules/common/common.module"); +var AppModule = function () { + var _classDecorators = [(0, common_1.Module)({ + imports: [ + // 配置模块 + config_1.ConfigModule.forRoot({ + isGlobal: true, + envFilePath: ".env.".concat(process.env.NODE_ENV || 'development'), + }), + // 限流模块 + throttler_1.ThrottlerModule.forRoot([ + { + ttl: 60000, // 60秒 + limit: 100, // 最多100个请求 + }, + ]), + // Prisma数据库模块 + prisma_module_1.PrismaModule, + // 业务模块 + auth_module_1.AuthModule, + course_module_1.CourseModule, + tenant_module_1.TenantModule, + common_module_1.CommonModule, + ], + })]; + var _classDescriptor; + var _classExtraInitializers = []; + var _classThis; + var AppModule = _classThis = /** @class */ (function () { + function AppModule_1() { + } + return AppModule_1; + }()); + __setFunctionName(_classThis, "AppModule"); + (function () { + var _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0; + __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers); + AppModule = _classThis = _classDescriptor.value; + if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); + __runInitializers(_classThis, _classExtraInitializers); + })(); + return AppModule = _classThis; +}(); +exports.AppModule = AppModule; diff --git a/reading-platform-backend/src/app.module.ts b/reading-platform-backend/src/app.module.ts new file mode 100644 index 0000000..2474ea5 --- /dev/null +++ b/reading-platform-backend/src/app.module.ts @@ -0,0 +1,58 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { ThrottlerModule } from '@nestjs/throttler'; +import { PrismaModule } from './database/prisma.module'; +import { AuthModule } from './modules/auth/auth.module'; +import { CourseModule } from './modules/course/course.module'; +import { TenantModule } from './modules/tenant/tenant.module'; +import { CommonModule } from './modules/common/common.module'; +import { FileUploadModule } from './modules/file-upload/file-upload.module'; +import { TeacherCourseModule } from './modules/teacher-course/teacher-course.module'; +import { LessonModule } from './modules/lesson/lesson.module'; +import { SchoolModule } from './modules/school/school.module'; +import { ResourceModule } from './modules/resource/resource.module'; +import { GrowthModule } from './modules/growth/growth.module'; +import { TaskModule } from './modules/task/task.module'; +import { ParentModule } from './modules/parent/parent.module'; +import { NotificationModule } from './modules/notification/notification.module'; +import { ExportModule } from './modules/export/export.module'; +import { AdminModule } from './modules/admin/admin.module'; + +@Module({ + imports: [ + // 配置模块 + ConfigModule.forRoot({ + isGlobal: true, + envFilePath: `.env.${process.env.NODE_ENV || 'development'}`, + }), + + // 限流模块 + ThrottlerModule.forRoot([ + { + ttl: 60000, // 60秒 + limit: 100, // 最多100个请求 + }, + ]), + + // Prisma数据库模块 + PrismaModule, + + // 业务模块 + AuthModule, + CourseModule, + TenantModule, + CommonModule, + FileUploadModule, + TeacherCourseModule, + LessonModule, + SchoolModule, + ResourceModule, + GrowthModule, + TaskModule, + ParentModule, + NotificationModule, + ExportModule, + AdminModule, + ], +}) +export class AppModule {} diff --git a/reading-platform-backend/src/common/filters/http-exception.filter.ts b/reading-platform-backend/src/common/filters/http-exception.filter.ts new file mode 100644 index 0000000..8161da5 --- /dev/null +++ b/reading-platform-backend/src/common/filters/http-exception.filter.ts @@ -0,0 +1,44 @@ +import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Logger } from '@nestjs/common'; +import { Request, Response } from 'express'; +import * as fs from 'fs'; +import * as path from 'path'; + +@Catch() +export class HttpExceptionFilter implements ExceptionFilter { + private readonly logger = new Logger(HttpExceptionFilter.name); + + catch(exception: unknown, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + + const status = + exception instanceof HttpException + ? exception.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR; + + const message = + exception instanceof HttpException + ? exception.getResponse() + : { message: 'Internal server error', statusCode: 500 }; + + const errorLog = { + timestamp: new Date().toISOString(), + path: request.url, + method: request.method, + body: request.body, + status, + exception: exception instanceof Error ? exception.message : String(exception), + stack: exception instanceof Error ? exception.stack : undefined, + }; + + // Log to file + const logPath = path.join(process.cwd(), 'error.log'); + fs.appendFileSync(logPath, JSON.stringify(errorLog, null, 2) + '\n'); + + // Also log to console + this.logger.error('Exception caught', errorLog); + + response.status(status).json(message); + } +} diff --git a/reading-platform-backend/src/database/prisma.module.js b/reading-platform-backend/src/database/prisma.module.js new file mode 100644 index 0000000..bd2aad3 --- /dev/null +++ b/reading-platform-backend/src/database/prisma.module.js @@ -0,0 +1,67 @@ +"use strict"; +var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { + function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } + var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; + var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; + var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); + var _, done = false; + for (var i = decorators.length - 1; i >= 0; i--) { + var context = {}; + for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; + for (var p in contextIn.access) context.access[p] = contextIn.access[p]; + context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; + var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); + if (kind === "accessor") { + if (result === void 0) continue; + if (result === null || typeof result !== "object") throw new TypeError("Object expected"); + if (_ = accept(result.get)) descriptor.get = _; + if (_ = accept(result.set)) descriptor.set = _; + if (_ = accept(result.init)) initializers.unshift(_); + } + else if (_ = accept(result)) { + if (kind === "field") initializers.unshift(_); + else descriptor[key] = _; + } + } + if (target) Object.defineProperty(target, contextIn.name, descriptor); + done = true; +}; +var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { + var useValue = arguments.length > 2; + for (var i = 0; i < initializers.length; i++) { + value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); + } + return useValue ? value : void 0; +}; +var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) { + if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : ""; + return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PrismaModule = void 0; +var common_1 = require("@nestjs/common"); +var prisma_service_1 = require("./prisma.service"); +var PrismaModule = function () { + var _classDecorators = [(0, common_1.Global)(), (0, common_1.Module)({ + providers: [prisma_service_1.PrismaService], + exports: [prisma_service_1.PrismaService], + })]; + var _classDescriptor; + var _classExtraInitializers = []; + var _classThis; + var PrismaModule = _classThis = /** @class */ (function () { + function PrismaModule_1() { + } + return PrismaModule_1; + }()); + __setFunctionName(_classThis, "PrismaModule"); + (function () { + var _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0; + __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers); + PrismaModule = _classThis = _classDescriptor.value; + if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); + __runInitializers(_classThis, _classExtraInitializers); + })(); + return PrismaModule = _classThis; +}(); +exports.PrismaModule = PrismaModule; diff --git a/reading-platform-backend/src/database/prisma.module.ts b/reading-platform-backend/src/database/prisma.module.ts new file mode 100644 index 0000000..7207426 --- /dev/null +++ b/reading-platform-backend/src/database/prisma.module.ts @@ -0,0 +1,9 @@ +import { Global, Module } from '@nestjs/common'; +import { PrismaService } from './prisma.service'; + +@Global() +@Module({ + providers: [PrismaService], + exports: [PrismaService], +}) +export class PrismaModule {} diff --git a/reading-platform-backend/src/database/prisma.service.js b/reading-platform-backend/src/database/prisma.service.js new file mode 100644 index 0000000..5d51ba6 --- /dev/null +++ b/reading-platform-backend/src/database/prisma.service.js @@ -0,0 +1,203 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { + function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } + var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; + var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; + var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); + var _, done = false; + for (var i = decorators.length - 1; i >= 0; i--) { + var context = {}; + for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; + for (var p in contextIn.access) context.access[p] = contextIn.access[p]; + context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; + var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); + if (kind === "accessor") { + if (result === void 0) continue; + if (result === null || typeof result !== "object") throw new TypeError("Object expected"); + if (_ = accept(result.get)) descriptor.get = _; + if (_ = accept(result.set)) descriptor.set = _; + if (_ = accept(result.init)) initializers.unshift(_); + } + else if (_ = accept(result)) { + if (kind === "field") initializers.unshift(_); + else descriptor[key] = _; + } + } + if (target) Object.defineProperty(target, contextIn.name, descriptor); + done = true; +}; +var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { + var useValue = arguments.length > 2; + for (var i = 0; i < initializers.length; i++) { + value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); + } + return useValue ? value : void 0; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); + return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) { + if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : ""; + return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PrismaService = void 0; +var common_1 = require("@nestjs/common"); +var client_1 = require("@prisma/client"); +var PrismaService = function () { + var _classDecorators = [(0, common_1.Injectable)()]; + var _classDescriptor; + var _classExtraInitializers = []; + var _classThis; + var _classSuper = client_1.PrismaClient; + var PrismaService = _classThis = /** @class */ (function (_super) { + __extends(PrismaService_1, _super); + function PrismaService_1() { + return _super !== null && _super.apply(this, arguments) || this; + } + PrismaService_1.prototype.onModuleInit = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.$connect()]; + case 1: + _a.sent(); + console.log('✅ Database connected successfully'); + return [2 /*return*/]; + } + }); + }); + }; + PrismaService_1.prototype.onModuleDestroy = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.$disconnect()]; + case 1: + _a.sent(); + console.log('👋 Database disconnected'); + return [2 /*return*/]; + } + }); + }); + }; + // 清理测试数据的辅助方法 + PrismaService_1.prototype.cleanDatabase = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (process.env.NODE_ENV === 'production') { + throw new Error('Cannot clean database in production'); + } + // 按照外键依赖顺序删除 + return [4 /*yield*/, this.studentRecord.deleteMany()]; + case 1: + // 按照外键依赖顺序删除 + _a.sent(); + return [4 /*yield*/, this.lessonFeedback.deleteMany()]; + case 2: + _a.sent(); + return [4 /*yield*/, this.lesson.deleteMany()]; + case 3: + _a.sent(); + return [4 /*yield*/, this.tenantCourse.deleteMany()]; + case 4: + _a.sent(); + return [4 /*yield*/, this.courseScriptPage.deleteMany()]; + case 5: + _a.sent(); + return [4 /*yield*/, this.courseScript.deleteMany()]; + case 6: + _a.sent(); + return [4 /*yield*/, this.courseActivity.deleteMany()]; + case 7: + _a.sent(); + return [4 /*yield*/, this.courseResource.deleteMany()]; + case 8: + _a.sent(); + return [4 /*yield*/, this.course.deleteMany()]; + case 9: + _a.sent(); + return [4 /*yield*/, this.student.deleteMany()]; + case 10: + _a.sent(); + return [4 /*yield*/, this.class.deleteMany()]; + case 11: + _a.sent(); + return [4 /*yield*/, this.teacher.deleteMany()]; + case 12: + _a.sent(); + return [4 /*yield*/, this.tenant.deleteMany()]; + case 13: + _a.sent(); + return [4 /*yield*/, this.tag.deleteMany()]; + case 14: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + return PrismaService_1; + }(_classSuper)); + __setFunctionName(_classThis, "PrismaService"); + (function () { + var _a; + var _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create((_a = _classSuper[Symbol.metadata]) !== null && _a !== void 0 ? _a : null) : void 0; + __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers); + PrismaService = _classThis = _classDescriptor.value; + if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); + __runInitializers(_classThis, _classExtraInitializers); + })(); + return PrismaService = _classThis; +}(); +exports.PrismaService = PrismaService; diff --git a/reading-platform-backend/src/database/prisma.service.ts b/reading-platform-backend/src/database/prisma.service.ts new file mode 100644 index 0000000..4977e3d --- /dev/null +++ b/reading-platform-backend/src/database/prisma.service.ts @@ -0,0 +1,38 @@ +import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; + +@Injectable() +export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { + async onModuleInit() { + await this.$connect(); + console.log('✅ Database connected successfully'); + } + + async onModuleDestroy() { + await this.$disconnect(); + console.log('👋 Database disconnected'); + } + + // 清理测试数据的辅助方法 + async cleanDatabase() { + if (process.env.NODE_ENV === 'production') { + throw new Error('Cannot clean database in production'); + } + + // 按照外键依赖顺序删除 + await this.studentRecord.deleteMany(); + await this.lessonFeedback.deleteMany(); + await this.lesson.deleteMany(); + await this.tenantCourse.deleteMany(); + await this.courseScriptPage.deleteMany(); + await this.courseScript.deleteMany(); + await this.courseActivity.deleteMany(); + await this.courseResource.deleteMany(); + await this.course.deleteMany(); + await this.student.deleteMany(); + await this.class.deleteMany(); + await this.teacher.deleteMany(); + await this.tenant.deleteMany(); + await this.tag.deleteMany(); + } +} diff --git a/reading-platform-backend/src/main.js b/reading-platform-backend/src/main.js new file mode 100644 index 0000000..31f581b --- /dev/null +++ b/reading-platform-backend/src/main.js @@ -0,0 +1,84 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); + return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var core_1 = require("@nestjs/core"); +var common_1 = require("@nestjs/common"); +var config_1 = require("@nestjs/config"); +var app_module_1 = require("./app.module"); +function bootstrap() { + return __awaiter(this, void 0, void 0, function () { + var app, configService, port; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, core_1.NestFactory.create(app_module_1.AppModule, { + logger: ['error', 'warn', 'log', 'debug', 'verbose'], + })]; + case 1: + app = _a.sent(); + configService = app.get(config_1.ConfigService); + // 全局验证管道 + app.useGlobalPipes(new common_1.ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + transform: true, + transformOptions: { + enableImplicitConversion: true, + }, + })); + // 启用压缩 + // app.use(compression()); + // CORS + app.enableCors({ + origin: configService.get('FRONTEND_URL') || 'http://localhost:5173', + credentials: true, + methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', + allowedHeaders: 'Content-Type, Accept, Authorization', + }); + // API前缀 + app.setGlobalPrefix('api/v1'); + port = configService.get('PORT') || 3000; + return [4 /*yield*/, app.listen(port)]; + case 2: + _a.sent(); + console.log("\n \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n \u2551 \u2551\n \u2551 \uD83D\uDE80 \u5E7C\u513F\u9605\u8BFB\u6559\u5B66\u670D\u52A1\u5E73\u53F0\u540E\u7AEF\u542F\u52A8\u6210\u529F \u2551\n \u2551 \u2551\n \u2551 \uD83D\uDCCD Local: http://localhost:".concat(port, " \u2551\n \u2551 \uD83D\uDCCD API: http://localhost:").concat(port, "/api/v1 \u2551\n \u2551 \uD83D\uDCCD Prisma: npx prisma studio \u2551\n \u2551 \u2551\n \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\n ")); + return [2 /*return*/]; + } + }); + }); +} +bootstrap(); diff --git a/reading-platform-backend/src/main.ts b/reading-platform-backend/src/main.ts new file mode 100644 index 0000000..3ca479a --- /dev/null +++ b/reading-platform-backend/src/main.ts @@ -0,0 +1,74 @@ +import { NestFactory } from '@nestjs/core'; +import { ValidationPipe, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { NestExpressApplication } from '@nestjs/platform-express'; +import { join } from 'path'; +import { AppModule } from './app.module'; +import * as compression from 'compression'; +import { HttpExceptionFilter } from './common/filters/http-exception.filter'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule, { + logger: ['error', 'warn', 'log', 'debug', 'verbose'], + }); + + // 增加请求体大小限制(支持上传大文件 base64) + // 1GB 文件编码后约 1.33GB,加上其他字段,设置为 1500mb + app.useBodyParser('json', { limit: '1500mb' }); + app.useBodyParser('urlencoded', { limit: '1500mb', extended: true }); + + const configService = app.get(ConfigService); + + // 全局验证管道 + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + transform: true, + transformOptions: { + enableImplicitConversion: true, + }, + }), + ); + + // 全局异常过滤器 + app.useGlobalFilters(new HttpExceptionFilter()); + + // 启用压缩 + // app.use(compression()); + + // CORS + app.enableCors({ + origin: configService.get('FRONTEND_URL') || 'http://localhost:5173', + credentials: true, + methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', + allowedHeaders: 'Content-Type, Accept, Authorization', + }); + + // 配置静态文件服务(用于访问上传的文件) + // 使用绝对路径确保在编译后也能正确找到 uploads 目录 + const uploadsPath = join(__dirname, '..', '..', 'uploads'); + app.useStaticAssets(uploadsPath, { + prefix: '/uploads/', + }); + + // API前缀 + app.setGlobalPrefix('api/v1'); + + const port = configService.get('PORT') || 3000; + await app.listen(port); + + console.log(` + ╔═════════════════════════════════════════════════════╗ + ║ ║ + ║ 🚀 幼儿阅读教学服务平台后端启动成功 ║ + ║ ║ + ║ 📍 Local: http://localhost:${port} ║ + ║ 📍 API: http://localhost:${port}/api/v1 ║ + ║ 📍 Prisma: npx prisma studio ║ + ║ ║ + ╚═════════════════════════════════════════════════════╝ + `); +} + +bootstrap(); diff --git a/reading-platform-backend/src/modules/admin/admin-settings.controller.ts b/reading-platform-backend/src/modules/admin/admin-settings.controller.ts new file mode 100644 index 0000000..295edce --- /dev/null +++ b/reading-platform-backend/src/modules/admin/admin-settings.controller.ts @@ -0,0 +1,67 @@ +import { Controller, Get, Put, Body, UseGuards } from '@nestjs/common'; +import { AdminSettingsService } from './admin-settings.service'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { RolesGuard } from '../common/guards/roles.guard'; +import { Roles } from '../common/decorators/roles.decorator'; + +@Controller('admin/settings') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('admin') +export class AdminSettingsController { + constructor(private readonly settingsService: AdminSettingsService) {} + + @Get() + async getAllSettings() { + return this.settingsService.getSettings(); + } + + @Put() + async updateSettings(@Body() data: Record) { + return this.settingsService.updateSettings(data); + } + + @Get('basic') + async getBasicSettings() { + return this.settingsService.getBasicSettings(); + } + + @Put('basic') + async updateBasicSettings(@Body() data: Record) { + return this.settingsService.updateSettings(data); + } + + @Get('security') + async getSecuritySettings() { + return this.settingsService.getSecuritySettings(); + } + + @Put('security') + async updateSecuritySettings(@Body() data: Record) { + return this.settingsService.updateSettings(data); + } + + @Get('notification') + async getNotificationSettings() { + return this.settingsService.getNotificationSettings(); + } + + @Put('notification') + async updateNotificationSettings(@Body() data: Record) { + return this.settingsService.updateSettings(data); + } + + @Get('storage') + async getStorageSettings() { + return this.settingsService.getStorageSettings(); + } + + @Put('storage') + async updateStorageSettings(@Body() data: Record) { + return this.settingsService.updateSettings(data); + } + + @Get('tenant-defaults') + async getTenantDefaults() { + return this.settingsService.getTenantDefaults(); + } +} diff --git a/reading-platform-backend/src/modules/admin/admin-settings.service.ts b/reading-platform-backend/src/modules/admin/admin-settings.service.ts new file mode 100644 index 0000000..e55eb83 --- /dev/null +++ b/reading-platform-backend/src/modules/admin/admin-settings.service.ts @@ -0,0 +1,107 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../../database/prisma.service'; + +// Admin settings stored in memory (could be moved to database later) +@Injectable() +export class AdminSettingsService { + private settings: Record = { + // Basic settings + systemName: '幼儿阅读教学服务平台', + systemDesc: '', + contactPhone: '', + contactEmail: '', + systemLogo: '', + + // Security settings + passwordStrength: 'medium', + maxLoginAttempts: 5, + tokenExpire: '7d', + forceHttps: false, + + // Notification settings + emailEnabled: true, + smtpHost: '', + smtpPort: 465, + smtpUser: '', + smtpPassword: '', + fromEmail: '', + smsEnabled: false, + + // Storage settings + storageType: 'local', + maxFileSize: 100, + allowedTypes: '.jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.ppt,.pptx', + + // Tenant defaults + defaultTeacherQuota: 20, + defaultStudentQuota: 200, + enableAutoExpire: true, + notifyBeforeDays: 30, + }; + + constructor(private prisma: PrismaService) {} + + async getSettings() { + return { ...this.settings }; + } + + async getSetting(key: string) { + return this.settings[key]; + } + + async updateSettings(data: Record) { + // Update only provided keys + for (const key of Object.keys(data)) { + if (key in this.settings) { + this.settings[key] = data[key]; + } + } + return { ...this.settings }; + } + + async getBasicSettings() { + return { + systemName: this.settings.systemName, + systemDesc: this.settings.systemDesc, + contactPhone: this.settings.contactPhone, + contactEmail: this.settings.contactEmail, + systemLogo: this.settings.systemLogo, + }; + } + + async getSecuritySettings() { + return { + passwordStrength: this.settings.passwordStrength, + maxLoginAttempts: this.settings.maxLoginAttempts, + tokenExpire: this.settings.tokenExpire, + forceHttps: this.settings.forceHttps, + }; + } + + async getNotificationSettings() { + return { + emailEnabled: this.settings.emailEnabled, + smtpHost: this.settings.smtpHost, + smtpPort: this.settings.smtpPort, + fromEmail: this.settings.fromEmail, + smsEnabled: this.settings.smsEnabled, + }; + } + + async getStorageSettings() { + return { + type: this.settings.storageType, + maxFileSize: this.settings.maxFileSize, + allowedTypes: this.settings.allowedTypes, + }; + } + + async getTenantDefaults() { + return { + defaultTeacherQuota: this.settings.defaultTeacherQuota, + defaultStudentQuota: this.settings.defaultStudentQuota, + enableAutoExpire: this.settings.enableAutoExpire, + notifyBeforeDays: this.settings.notifyBeforeDays, + }; + } +} diff --git a/reading-platform-backend/src/modules/admin/admin-stats.controller.ts b/reading-platform-backend/src/modules/admin/admin-stats.controller.ts new file mode 100644 index 0000000..a98bf57 --- /dev/null +++ b/reading-platform-backend/src/modules/admin/admin-stats.controller.ts @@ -0,0 +1,40 @@ +import { Controller, Get, Query, UseGuards } from '@nestjs/common'; +import { AdminStatsService } from './admin-stats.service'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { RolesGuard } from '../common/guards/roles.guard'; +import { Roles } from '../common/decorators/roles.decorator'; + +@Controller('admin/stats') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('admin') +export class AdminStatsController { + constructor(private readonly statsService: AdminStatsService) {} + + @Get() + async getStats() { + return this.statsService.getStats(); + } + + @Get('trend') + async getTrendData() { + return this.statsService.getTrendData(); + } + + @Get('tenants/active') + async getActiveTenants(@Query('limit') limit?: string) { + const limitNum = limit ? parseInt(limit, 10) : 5; + return this.statsService.getActiveTenants(limitNum); + } + + @Get('courses/popular') + async getPopularCourses(@Query('limit') limit?: string) { + const limitNum = limit ? parseInt(limit, 10) : 5; + return this.statsService.getPopularCourses(limitNum); + } + + @Get('activities') + async getRecentActivities(@Query('limit') limit?: string) { + const limitNum = limit ? parseInt(limit, 10) : 10; + return this.statsService.getRecentActivities(limitNum); + } +} diff --git a/reading-platform-backend/src/modules/admin/admin-stats.service.ts b/reading-platform-backend/src/modules/admin/admin-stats.service.ts new file mode 100644 index 0000000..d6dabef --- /dev/null +++ b/reading-platform-backend/src/modules/admin/admin-stats.service.ts @@ -0,0 +1,236 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../../database/prisma.service'; + +@Injectable() +export class AdminStatsService { + constructor(private prisma: PrismaService) {} + + async getStats() { + const [ + tenantCount, + activeTenantCount, + courseCount, + publishedCourseCount, + studentCount, + teacherCount, + lessonCount, + monthlyLessons, + ] = await Promise.all([ + this.prisma.tenant.count(), + this.prisma.tenant.count({ where: { status: 'ACTIVE' } }), + this.prisma.course.count(), + this.prisma.course.count({ where: { status: 'PUBLISHED' } }), + this.prisma.student.count(), + this.prisma.teacher.count(), + this.prisma.lesson.count(), + this.getThisMonthLessonCount(), + ]); + + return { + tenantCount, + activeTenantCount, + courseCount, + publishedCourseCount, + studentCount, + teacherCount, + lessonCount, + monthlyLessons, + }; + } + + private async getThisMonthLessonCount() { + const now = new Date(); + const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); + + return this.prisma.lesson.count({ + where: { + createdAt: { + gte: firstDayOfMonth, + }, + }, + }); + } + + async getTrendData() { + // Get last 6 months data + const months: Array<{ month: string; tenantCount: number; lessonCount: number; studentCount: number }> = []; + + for (let i = 5; i >= 0; i--) { + const date = new Date(); + date.setMonth(date.getMonth() - i); + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const monthStr = `${year}-${String(month).padStart(2, '0')}`; + + const firstDay = new Date(year, month - 1, 1); + const lastDay = new Date(year, month, 0, 23, 59, 59); + + const [tenantCount, lessonCount, studentCount] = await Promise.all([ + this.prisma.tenant.count({ + where: { + createdAt: { + lte: lastDay, + }, + }, + }), + this.prisma.lesson.count({ + where: { + createdAt: { + gte: firstDay, + lte: lastDay, + }, + }, + }), + this.prisma.student.count({ + where: { + createdAt: { + lte: lastDay, + }, + }, + }), + ]); + + months.push({ + month: monthStr, + tenantCount, + lessonCount, + studentCount, + }); + } + + return months; + } + + async getActiveTenants(limit: number = 5) { + // Get tenants with most lessons in the last 30 days + const thirtyDaysAgo = new Date(); + thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); + + const tenants = await this.prisma.tenant.findMany({ + select: { + id: true, + name: true, + teacherCount: true, + studentCount: true, + _count: { + select: { + lessons: { + where: { + createdAt: { + gte: thirtyDaysAgo, + }, + }, + }, + }, + }, + }, + orderBy: { + lessons: { + _count: 'desc', + }, + }, + take: limit, + }); + + return tenants.map((t) => ({ + id: t.id, + name: t.name, + lessonCount: t._count.lessons, + teacherCount: t.teacherCount, + studentCount: t.studentCount, + })); + } + + async getPopularCourses(limit: number = 5) { + // Get courses with most usage + const courses = await this.prisma.course.findMany({ + select: { + id: true, + name: true, + usageCount: true, + teacherCount: true, + }, + where: { + status: 'PUBLISHED', + }, + orderBy: { + usageCount: 'desc', + }, + take: limit, + }); + + return courses; + } + + async getRecentActivities(limit: number = 10) { + const activities: Array<{ + id: number; + type: string; + title: string; + description?: string; + time: Date; + }> = []; + + // Get recent lessons + const recentLessons = await this.prisma.lesson.findMany({ + select: { + id: true, + createdAt: true, + tenant: { + select: { + name: true, + }, + }, + course: { + select: { + name: true, + }, + }, + }, + orderBy: { + createdAt: 'desc', + }, + take: limit, + }); + + for (const lesson of recentLessons) { + activities.push({ + id: lesson.id, + type: 'lesson', + title: `${lesson.tenant.name} 完成了课程《${lesson.course.name}》`, + time: lesson.createdAt, + }); + } + + // Get recent tenants + const recentTenants = await this.prisma.tenant.findMany({ + select: { + id: true, + name: true, + createdAt: true, + }, + orderBy: { + createdAt: 'desc', + }, + take: limit, + }); + + for (const tenant of recentTenants) { + activities.push({ + id: tenant.id + 10000, + type: 'tenant', + title: `新租户注册: ${tenant.name}`, + time: tenant.createdAt, + }); + } + + // Sort by time and limit + return activities + .sort((a, b) => b.time.getTime() - a.time.getTime()) + .slice(0, limit) + .map((a) => ({ + ...a, + time: a.time.toISOString(), + })); + } +} diff --git a/reading-platform-backend/src/modules/admin/admin.module.ts b/reading-platform-backend/src/modules/admin/admin.module.ts new file mode 100644 index 0000000..2ef011a --- /dev/null +++ b/reading-platform-backend/src/modules/admin/admin.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { AdminSettingsController } from './admin-settings.controller'; +import { AdminSettingsService } from './admin-settings.service'; +import { AdminStatsController } from './admin-stats.controller'; +import { AdminStatsService } from './admin-stats.service'; +import { PrismaModule } from '../../database/prisma.module'; + +@Module({ + imports: [PrismaModule], + controllers: [AdminSettingsController, AdminStatsController], + providers: [AdminSettingsService, AdminStatsService], + exports: [AdminSettingsService, AdminStatsService], +}) +export class AdminModule {} diff --git a/reading-platform-backend/src/modules/auth/auth.controller.js b/reading-platform-backend/src/modules/auth/auth.controller.js new file mode 100644 index 0000000..008181e --- /dev/null +++ b/reading-platform-backend/src/modules/auth/auth.controller.js @@ -0,0 +1,133 @@ +"use strict"; +var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { + var useValue = arguments.length > 2; + for (var i = 0; i < initializers.length; i++) { + value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); + } + return useValue ? value : void 0; +}; +var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { + function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } + var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; + var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; + var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); + var _, done = false; + for (var i = decorators.length - 1; i >= 0; i--) { + var context = {}; + for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; + for (var p in contextIn.access) context.access[p] = contextIn.access[p]; + context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; + var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); + if (kind === "accessor") { + if (result === void 0) continue; + if (result === null || typeof result !== "object") throw new TypeError("Object expected"); + if (_ = accept(result.get)) descriptor.get = _; + if (_ = accept(result.set)) descriptor.set = _; + if (_ = accept(result.init)) initializers.unshift(_); + } + else if (_ = accept(result)) { + if (kind === "field") initializers.unshift(_); + else descriptor[key] = _; + } + } + if (target) Object.defineProperty(target, contextIn.name, descriptor); + done = true; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); + return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) { + if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : ""; + return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AuthController = void 0; +var common_1 = require("@nestjs/common"); +var jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +var AuthController = function () { + var _classDecorators = [(0, common_1.Controller)('auth')]; + var _classDescriptor; + var _classExtraInitializers = []; + var _classThis; + var _instanceExtraInitializers = []; + var _login_decorators; + var _logout_decorators; + var _getProfile_decorators; + var AuthController = _classThis = /** @class */ (function () { + function AuthController_1(authService) { + this.authService = (__runInitializers(this, _instanceExtraInitializers), authService); + } + AuthController_1.prototype.login = function (loginDto) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, this.authService.login(loginDto)]; + }); + }); + }; + AuthController_1.prototype.logout = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + // JWT是无状态的,logout主要在前端删除token + return [2 /*return*/, { message: 'Logged out successfully' }]; + }); + }); + }; + AuthController_1.prototype.getProfile = function (req) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, this.authService.getProfile(req.user.userId, req.user.role)]; + }); + }); + }; + return AuthController_1; + }()); + __setFunctionName(_classThis, "AuthController"); + (function () { + var _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0; + _login_decorators = [(0, common_1.Post)('login')]; + _logout_decorators = [(0, common_1.Post)('logout'), (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard)]; + _getProfile_decorators = [(0, common_1.Get)('profile'), (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard)]; + __esDecorate(_classThis, null, _login_decorators, { kind: "method", name: "login", static: false, private: false, access: { has: function (obj) { return "login" in obj; }, get: function (obj) { return obj.login; } }, metadata: _metadata }, null, _instanceExtraInitializers); + __esDecorate(_classThis, null, _logout_decorators, { kind: "method", name: "logout", static: false, private: false, access: { has: function (obj) { return "logout" in obj; }, get: function (obj) { return obj.logout; } }, metadata: _metadata }, null, _instanceExtraInitializers); + __esDecorate(_classThis, null, _getProfile_decorators, { kind: "method", name: "getProfile", static: false, private: false, access: { has: function (obj) { return "getProfile" in obj; }, get: function (obj) { return obj.getProfile; } }, metadata: _metadata }, null, _instanceExtraInitializers); + __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers); + AuthController = _classThis = _classDescriptor.value; + if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); + __runInitializers(_classThis, _classExtraInitializers); + })(); + return AuthController = _classThis; +}(); +exports.AuthController = AuthController; diff --git a/reading-platform-backend/src/modules/auth/auth.controller.ts b/reading-platform-backend/src/modules/auth/auth.controller.ts new file mode 100644 index 0000000..a973555 --- /dev/null +++ b/reading-platform-backend/src/modules/auth/auth.controller.ts @@ -0,0 +1,27 @@ +import { Controller, Post, Body, Get, UseGuards, Request } from '@nestjs/common'; +import { AuthService } from './auth.service'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { LoginDto } from './dto/login.dto'; + +@Controller('auth') +export class AuthController { + constructor(private authService: AuthService) {} + + @Post('login') + async login(@Body() loginDto: LoginDto) { + return this.authService.login(loginDto); + } + + @Post('logout') + @UseGuards(JwtAuthGuard) + async logout() { + // JWT是无状态的,logout主要在前端删除token + return { message: 'Logged out successfully' }; + } + + @Get('profile') + @UseGuards(JwtAuthGuard) + async getProfile(@Request() req) { + return this.authService.getProfile(req.user.userId, req.user.role); + } +} diff --git a/reading-platform-backend/src/modules/auth/auth.module.js b/reading-platform-backend/src/modules/auth/auth.module.js new file mode 100644 index 0000000..f5f574a --- /dev/null +++ b/reading-platform-backend/src/modules/auth/auth.module.js @@ -0,0 +1,128 @@ +"use strict"; +var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { + function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } + var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; + var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; + var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); + var _, done = false; + for (var i = decorators.length - 1; i >= 0; i--) { + var context = {}; + for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; + for (var p in contextIn.access) context.access[p] = contextIn.access[p]; + context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; + var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); + if (kind === "accessor") { + if (result === void 0) continue; + if (result === null || typeof result !== "object") throw new TypeError("Object expected"); + if (_ = accept(result.get)) descriptor.get = _; + if (_ = accept(result.set)) descriptor.set = _; + if (_ = accept(result.init)) initializers.unshift(_); + } + else if (_ = accept(result)) { + if (kind === "field") initializers.unshift(_); + else descriptor[key] = _; + } + } + if (target) Object.defineProperty(target, contextIn.name, descriptor); + done = true; +}; +var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { + var useValue = arguments.length > 2; + for (var i = 0; i < initializers.length; i++) { + value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); + } + return useValue ? value : void 0; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); + return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) { + if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : ""; + return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AuthModule = void 0; +var common_1 = require("@nestjs/common"); +var jwt_1 = require("@nestjs/jwt"); +var passport_1 = require("@nestjs/passport"); +var config_1 = require("@nestjs/config"); +var auth_service_1 = require("./auth.service"); +var auth_controller_1 = require("./auth.controller"); +var jwt_strategy_1 = require("./strategies/jwt.strategy"); +var prisma_module_1 = require("../../database/prisma.module"); +var AuthModule = function () { + var _classDecorators = [(0, common_1.Module)({ + imports: [ + passport_1.PassportModule, + jwt_1.JwtModule.registerAsync({ + imports: [config_1.ConfigModule], + useFactory: function (configService) { return __awaiter(void 0, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, ({ + secret: configService.get('JWT_SECRET') || 'your-secret-key', + signOptions: { + expiresIn: configService.get('JWT_EXPIRES_IN') || '7d', + }, + })]; + }); + }); }, + inject: [config_1.ConfigService], + }), + prisma_module_1.PrismaModule, + ], + controllers: [auth_controller_1.AuthController], + providers: [auth_service_1.AuthService, jwt_strategy_1.JwtStrategy], + exports: [auth_service_1.AuthService], + })]; + var _classDescriptor; + var _classExtraInitializers = []; + var _classThis; + var AuthModule = _classThis = /** @class */ (function () { + function AuthModule_1() { + } + return AuthModule_1; + }()); + __setFunctionName(_classThis, "AuthModule"); + (function () { + var _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0; + __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers); + AuthModule = _classThis = _classDescriptor.value; + if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); + __runInitializers(_classThis, _classExtraInitializers); + })(); + return AuthModule = _classThis; +}(); +exports.AuthModule = AuthModule; diff --git a/reading-platform-backend/src/modules/auth/auth.module.ts b/reading-platform-backend/src/modules/auth/auth.module.ts new file mode 100644 index 0000000..652a013 --- /dev/null +++ b/reading-platform-backend/src/modules/auth/auth.module.ts @@ -0,0 +1,29 @@ +import { Module } from '@nestjs/common'; +import { JwtModule } from '@nestjs/jwt'; +import { PassportModule } from '@nestjs/passport'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { AuthService } from './auth.service'; +import { AuthController } from './auth.controller'; +import { JwtStrategy } from './strategies/jwt.strategy'; +import { PrismaModule } from '../../database/prisma.module'; + +@Module({ + imports: [ + PassportModule, + JwtModule.registerAsync({ + imports: [ConfigModule], + useFactory: async (configService: ConfigService) => ({ + secret: configService.get('JWT_SECRET') || 'your-secret-key', + signOptions: { + expiresIn: configService.get('JWT_EXPIRES_IN') || '7d', + }, + }), + inject: [ConfigService], + }), + PrismaModule, + ], + controllers: [AuthController], + providers: [AuthService, JwtStrategy], + exports: [AuthService], +}) +export class AuthModule {} diff --git a/reading-platform-backend/src/modules/auth/auth.service.js b/reading-platform-backend/src/modules/auth/auth.service.js new file mode 100644 index 0000000..d2107a1 --- /dev/null +++ b/reading-platform-backend/src/modules/auth/auth.service.js @@ -0,0 +1,288 @@ +"use strict"; +var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { + function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } + var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; + var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; + var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); + var _, done = false; + for (var i = decorators.length - 1; i >= 0; i--) { + var context = {}; + for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; + for (var p in contextIn.access) context.access[p] = contextIn.access[p]; + context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; + var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); + if (kind === "accessor") { + if (result === void 0) continue; + if (result === null || typeof result !== "object") throw new TypeError("Object expected"); + if (_ = accept(result.get)) descriptor.get = _; + if (_ = accept(result.set)) descriptor.set = _; + if (_ = accept(result.init)) initializers.unshift(_); + } + else if (_ = accept(result)) { + if (kind === "field") initializers.unshift(_); + else descriptor[key] = _; + } + } + if (target) Object.defineProperty(target, contextIn.name, descriptor); + done = true; +}; +var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { + var useValue = arguments.length > 2; + for (var i = 0; i < initializers.length; i++) { + value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); + } + return useValue ? value : void 0; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); + return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) { + if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : ""; + return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AuthService = void 0; +var common_1 = require("@nestjs/common"); +var bcrypt = require("bcrypt"); +var AuthService = function () { + var _classDecorators = [(0, common_1.Injectable)()]; + var _classDescriptor; + var _classExtraInitializers = []; + var _classThis; + var AuthService = _classThis = /** @class */ (function () { + function AuthService_1(prisma, jwtService) { + this.prisma = prisma; + this.jwtService = jwtService; + } + AuthService_1.prototype.validateUser = function (account, password) { + return __awaiter(this, void 0, void 0, function () { + var user, isPasswordValid; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.prisma.teacher.findUnique({ + where: { loginAccount: account }, + })]; + case 1: + user = _a.sent(); + if (!user) { + throw new common_1.UnauthorizedException('账号或密码错误'); + } + return [4 /*yield*/, bcrypt.compare(password, user.passwordHash)]; + case 2: + isPasswordValid = _a.sent(); + if (!isPasswordValid) { + throw new common_1.UnauthorizedException('账号或密码错误'); + } + if (user.status !== 'ACTIVE') { + throw new common_1.UnauthorizedException('账号已被停用'); + } + return [2 /*return*/, user]; + } + }); + }); + }; + AuthService_1.prototype.login = function (dto) { + return __awaiter(this, void 0, void 0, function () { + var user, tenant, teacher, isPasswordValid, payload, token; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!(dto.role === 'admin')) return [3 /*break*/, 1]; + // 超管账号(硬编码或从配置读取) + if (dto.account === 'admin' && dto.password === '123456') { + user = { + id: 1, + name: '超级管理员', + role: 'admin', + }; + } + else { + throw new common_1.UnauthorizedException('账号或密码错误'); + } + return [3 /*break*/, 7]; + case 1: + if (!(dto.role === 'school')) return [3 /*break*/, 3]; + return [4 /*yield*/, this.prisma.tenant.findFirst({ + where: { name: dto.account }, + })]; + case 2: + tenant = _a.sent(); + if (!tenant) { + throw new common_1.UnauthorizedException('账号或密码错误'); + } + // 验证密码(这里简化处理) + if (dto.password !== '123456') { + throw new common_1.UnauthorizedException('账号或密码错误'); + } + user = { + id: tenant.id, + name: tenant.name, + role: 'school', + tenantId: tenant.id, + tenantName: tenant.name, + }; + return [3 /*break*/, 7]; + case 3: + if (!(dto.role === 'teacher')) return [3 /*break*/, 6]; + return [4 /*yield*/, this.prisma.teacher.findUnique({ + where: { loginAccount: dto.account }, + })]; + case 4: + teacher = _a.sent(); + if (!teacher) { + throw new common_1.UnauthorizedException('账号或密码错误'); + } + return [4 /*yield*/, bcrypt.compare(dto.password, teacher.passwordHash)]; + case 5: + isPasswordValid = _a.sent(); + if (!isPasswordValid) { + throw new common_1.UnauthorizedException('账号或密码错误'); + } + if (teacher.status !== 'ACTIVE') { + throw new common_1.UnauthorizedException('账号已被停用'); + } + user = { + id: teacher.id, + name: teacher.name, + role: 'teacher', + tenantId: teacher.tenantId, + }; + return [3 /*break*/, 7]; + case 6: throw new common_1.UnauthorizedException('无效的角色'); + case 7: + payload = { + sub: user.id, + role: user.role, + tenantId: user.tenantId, + }; + token = this.jwtService.sign(payload); + if (!(dto.role === 'teacher')) return [3 /*break*/, 9]; + return [4 /*yield*/, this.prisma.teacher.update({ + where: { id: user.id }, + data: { lastLoginAt: new Date() }, + })]; + case 8: + _a.sent(); + _a.label = 9; + case 9: return [2 /*return*/, { + token: token, + user: { + id: user.id, + name: user.name, + role: user.role, + tenantId: user.tenantId, + tenantName: user.tenantName, + }, + }]; + } + }); + }); + }; + AuthService_1.prototype.getProfile = function (userId, role) { + return __awaiter(this, void 0, void 0, function () { + var tenant, teacher; + var _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + if (!(role === 'admin')) return [3 /*break*/, 1]; + return [2 /*return*/, { + id: 1, + name: '超级管理员', + role: 'admin', + }]; + case 1: + if (!(role === 'school')) return [3 /*break*/, 3]; + return [4 /*yield*/, this.prisma.tenant.findUnique({ + where: { id: userId }, + })]; + case 2: + tenant = _b.sent(); + if (!tenant) { + throw new common_1.UnauthorizedException('用户不存在'); + } + return [2 /*return*/, { + id: tenant.id, + name: tenant.name, + role: 'school', + tenantId: tenant.id, + tenantName: tenant.name, + }]; + case 3: + if (!(role === 'teacher')) return [3 /*break*/, 5]; + return [4 /*yield*/, this.prisma.teacher.findUnique({ + where: { id: userId }, + include: { + tenant: { + select: { + id: true, + name: true, + }, + }, + }, + })]; + case 4: + teacher = _b.sent(); + if (!teacher) { + throw new common_1.UnauthorizedException('用户不存在'); + } + return [2 /*return*/, { + id: teacher.id, + name: teacher.name, + role: 'teacher', + tenantId: teacher.tenantId, + tenantName: (_a = teacher.tenant) === null || _a === void 0 ? void 0 : _a.name, + email: teacher.email, + phone: teacher.phone, + }]; + case 5: throw new common_1.UnauthorizedException('无效的角色'); + } + }); + }); + }; + return AuthService_1; + }()); + __setFunctionName(_classThis, "AuthService"); + (function () { + var _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0; + __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers); + AuthService = _classThis = _classDescriptor.value; + if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); + __runInitializers(_classThis, _classExtraInitializers); + })(); + return AuthService = _classThis; +}(); +exports.AuthService = AuthService; diff --git a/reading-platform-backend/src/modules/auth/auth.service.ts b/reading-platform-backend/src/modules/auth/auth.service.ts new file mode 100644 index 0000000..1853d56 --- /dev/null +++ b/reading-platform-backend/src/modules/auth/auth.service.ts @@ -0,0 +1,298 @@ +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { PrismaService } from '../../database/prisma.service'; +import * as bcrypt from 'bcrypt'; + +export interface LoginDto { + account: string; + password: string; + role: string; +} + +export interface JwtPayload { + sub: number; + role: string; + tenantId?: number; +} + +@Injectable() +export class AuthService { + constructor( + private prisma: PrismaService, + private jwtService: JwtService, + ) {} + + async validateUser(account: string, password: string) { + // 根据账号查找用户(这里简化处理,实际应根据role查找不同表) + const user = await this.prisma.teacher.findUnique({ + where: { loginAccount: account }, + }); + + if (!user) { + throw new UnauthorizedException('账号或密码错误'); + } + + const isPasswordValid = await bcrypt.compare(password, user.passwordHash); + + if (!isPasswordValid) { + throw new UnauthorizedException('账号或密码错误'); + } + + if (user.status !== 'ACTIVE') { + throw new UnauthorizedException('账号已被停用'); + } + + return user; + } + + async login(dto: LoginDto) { + // 根据角色查找不同的用户表 + let user: any; + + if (dto.role === 'admin') { + // 超管账号(硬编码或从配置读取) + if (dto.account === 'admin' && dto.password === 'admin123') { + user = { + id: 1, + name: '超级管理员', + role: 'admin', + }; + } else { + throw new UnauthorizedException('账号或密码错误'); + } + } else if (dto.role === 'school') { + // 学校管理员(从租户表查找) + const tenant = await this.prisma.tenant.findUnique({ + where: { loginAccount: dto.account }, + }); + + if (!tenant) { + throw new UnauthorizedException('账号或密码错误'); + } + + // 验证密码 + if (!tenant.passwordHash) { + throw new UnauthorizedException('账号未设置密码'); + } + + const isPasswordValid = await bcrypt.compare(dto.password, tenant.passwordHash); + + if (!isPasswordValid) { + throw new UnauthorizedException('账号或密码错误'); + } + + if (tenant.status !== 'ACTIVE') { + throw new UnauthorizedException('账号已被停用'); + } + + user = { + id: tenant.id, + name: tenant.name, + role: 'school', + tenantId: tenant.id, + tenantName: tenant.name, + }; + } else if (dto.role === 'teacher') { + // 教师 + const teacher = await this.prisma.teacher.findUnique({ + where: { loginAccount: dto.account }, + include: { + tenant: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + if (!teacher) { + throw new UnauthorizedException('账号或密码错误'); + } + + const isPasswordValid = await bcrypt.compare(dto.password, teacher.passwordHash); + + if (!isPasswordValid) { + throw new UnauthorizedException('账号或密码错误'); + } + + if (teacher.status !== 'ACTIVE') { + throw new UnauthorizedException('账号已被停用'); + } + + user = { + id: teacher.id, + name: teacher.name, + role: 'teacher', + tenantId: teacher.tenantId, + tenantName: teacher.tenant?.name, + }; + } else if (dto.role === 'parent') { + // 家长 + const parent = await this.prisma.parent.findUnique({ + where: { loginAccount: dto.account }, + include: { + tenant: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + if (!parent) { + throw new UnauthorizedException('账号或密码错误'); + } + + const isPasswordValid = await bcrypt.compare(dto.password, parent.passwordHash); + + if (!isPasswordValid) { + throw new UnauthorizedException('账号或密码错误'); + } + + if (parent.status !== 'ACTIVE') { + throw new UnauthorizedException('账号已被停用'); + } + + user = { + id: parent.id, + name: parent.name, + role: 'parent', + tenantId: parent.tenantId, + tenantName: parent.tenant?.name, + }; + + // 更新最后登录时间 + await this.prisma.parent.update({ + where: { id: parent.id }, + data: { lastLoginAt: new Date() }, + }); + } else { + throw new UnauthorizedException('无效的角色'); + } + + // 生成JWT token + const payload: JwtPayload = { + sub: user.id, + role: user.role, + tenantId: user.tenantId, + }; + + const token = this.jwtService.sign(payload); + + // 更新最后登录时间 + if (dto.role === 'teacher') { + await this.prisma.teacher.update({ + where: { id: user.id }, + data: { lastLoginAt: new Date() }, + }); + } + + return { + token, + user: { + id: user.id, + name: user.name, + role: user.role, + tenantId: user.tenantId, + tenantName: user.tenantName, + }, + }; + } + + async getProfile(userId: number, role: string) { + if (role === 'admin') { + return { + id: 1, + name: '超级管理员', + role: 'admin', + }; + } else if (role === 'school') { + const tenant = await this.prisma.tenant.findUnique({ + where: { id: userId }, + }); + + if (!tenant) { + throw new UnauthorizedException('用户不存在'); + } + + return { + id: tenant.id, + name: tenant.name, + role: 'school', + tenantId: tenant.id, + tenantName: tenant.name, + }; + } else if (role === 'teacher') { + const teacher = await this.prisma.teacher.findUnique({ + where: { id: userId }, + include: { + tenant: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + if (!teacher) { + throw new UnauthorizedException('用户不存在'); + } + + return { + id: teacher.id, + name: teacher.name, + role: 'teacher', + tenantId: teacher.tenantId, + tenantName: teacher.tenant?.name, + email: teacher.email, + phone: teacher.phone, + }; + } else if (role === 'parent') { + const parent = await this.prisma.parent.findUnique({ + where: { id: userId }, + include: { + tenant: { + select: { + id: true, + name: true, + }, + }, + children: { + include: { + student: { + select: { + id: true, + name: true, + }, + }, + }, + }, + }, + }); + + if (!parent) { + throw new UnauthorizedException('用户不存在'); + } + + return { + id: parent.id, + name: parent.name, + role: 'parent', + tenantId: parent.tenantId, + tenantName: parent.tenant?.name, + email: parent.email, + phone: parent.phone, + children: parent.children.map((c) => ({ + id: c.student.id, + name: c.student.name, + relationship: c.relationship, + })), + }; + } + + throw new UnauthorizedException('无效的角色'); + } +} diff --git a/reading-platform-backend/src/modules/auth/dto/login.dto.js b/reading-platform-backend/src/modules/auth/dto/login.dto.js new file mode 100644 index 0000000..977df81 --- /dev/null +++ b/reading-platform-backend/src/modules/auth/dto/login.dto.js @@ -0,0 +1,71 @@ +"use strict"; +var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { + function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } + var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; + var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; + var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); + var _, done = false; + for (var i = decorators.length - 1; i >= 0; i--) { + var context = {}; + for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; + for (var p in contextIn.access) context.access[p] = contextIn.access[p]; + context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; + var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); + if (kind === "accessor") { + if (result === void 0) continue; + if (result === null || typeof result !== "object") throw new TypeError("Object expected"); + if (_ = accept(result.get)) descriptor.get = _; + if (_ = accept(result.set)) descriptor.set = _; + if (_ = accept(result.init)) initializers.unshift(_); + } + else if (_ = accept(result)) { + if (kind === "field") initializers.unshift(_); + else descriptor[key] = _; + } + } + if (target) Object.defineProperty(target, contextIn.name, descriptor); + done = true; +}; +var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { + var useValue = arguments.length > 2; + for (var i = 0; i < initializers.length; i++) { + value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); + } + return useValue ? value : void 0; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.LoginDto = void 0; +var class_validator_1 = require("class-validator"); +var LoginDto = function () { + var _a; + var _account_decorators; + var _account_initializers = []; + var _account_extraInitializers = []; + var _password_decorators; + var _password_initializers = []; + var _password_extraInitializers = []; + var _role_decorators; + var _role_initializers = []; + var _role_extraInitializers = []; + return _a = /** @class */ (function () { + function LoginDto() { + this.account = __runInitializers(this, _account_initializers, void 0); + this.password = (__runInitializers(this, _account_extraInitializers), __runInitializers(this, _password_initializers, void 0)); + this.role = (__runInitializers(this, _password_extraInitializers), __runInitializers(this, _role_initializers, void 0)); + __runInitializers(this, _role_extraInitializers); + } + return LoginDto; + }()), + (function () { + var _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0; + _account_decorators = [(0, class_validator_1.IsString)(), (0, class_validator_1.IsNotEmpty)()]; + _password_decorators = [(0, class_validator_1.IsString)(), (0, class_validator_1.IsNotEmpty)()]; + _role_decorators = [(0, class_validator_1.IsString)(), (0, class_validator_1.IsIn)(['admin', 'school', 'teacher']), (0, class_validator_1.IsNotEmpty)()]; + __esDecorate(null, null, _account_decorators, { kind: "field", name: "account", static: false, private: false, access: { has: function (obj) { return "account" in obj; }, get: function (obj) { return obj.account; }, set: function (obj, value) { obj.account = value; } }, metadata: _metadata }, _account_initializers, _account_extraInitializers); + __esDecorate(null, null, _password_decorators, { kind: "field", name: "password", static: false, private: false, access: { has: function (obj) { return "password" in obj; }, get: function (obj) { return obj.password; }, set: function (obj, value) { obj.password = value; } }, metadata: _metadata }, _password_initializers, _password_extraInitializers); + __esDecorate(null, null, _role_decorators, { kind: "field", name: "role", static: false, private: false, access: { has: function (obj) { return "role" in obj; }, get: function (obj) { return obj.role; }, set: function (obj, value) { obj.role = value; } }, metadata: _metadata }, _role_initializers, _role_extraInitializers); + if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); + })(), + _a; +}(); +exports.LoginDto = LoginDto; diff --git a/reading-platform-backend/src/modules/auth/dto/login.dto.ts b/reading-platform-backend/src/modules/auth/dto/login.dto.ts new file mode 100644 index 0000000..71085dd --- /dev/null +++ b/reading-platform-backend/src/modules/auth/dto/login.dto.ts @@ -0,0 +1,16 @@ +import { IsString, IsIn, IsNotEmpty } from 'class-validator'; + +export class LoginDto { + @IsString() + @IsNotEmpty() + account: string; + + @IsString() + @IsNotEmpty() + password: string; + + @IsString() + @IsIn(['admin', 'school', 'teacher', 'parent']) + @IsNotEmpty() + role: string; +} diff --git a/reading-platform-backend/src/modules/auth/strategies/jwt.strategy.js b/reading-platform-backend/src/modules/auth/strategies/jwt.strategy.js new file mode 100644 index 0000000..a79e469 --- /dev/null +++ b/reading-platform-backend/src/modules/auth/strategies/jwt.strategy.js @@ -0,0 +1,140 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { + function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } + var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; + var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; + var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); + var _, done = false; + for (var i = decorators.length - 1; i >= 0; i--) { + var context = {}; + for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; + for (var p in contextIn.access) context.access[p] = contextIn.access[p]; + context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; + var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); + if (kind === "accessor") { + if (result === void 0) continue; + if (result === null || typeof result !== "object") throw new TypeError("Object expected"); + if (_ = accept(result.get)) descriptor.get = _; + if (_ = accept(result.set)) descriptor.set = _; + if (_ = accept(result.init)) initializers.unshift(_); + } + else if (_ = accept(result)) { + if (kind === "field") initializers.unshift(_); + else descriptor[key] = _; + } + } + if (target) Object.defineProperty(target, contextIn.name, descriptor); + done = true; +}; +var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { + var useValue = arguments.length > 2; + for (var i = 0; i < initializers.length; i++) { + value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); + } + return useValue ? value : void 0; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); + return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) { + if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : ""; + return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.JwtStrategy = void 0; +var common_1 = require("@nestjs/common"); +var passport_1 = require("@nestjs/passport"); +var passport_jwt_1 = require("passport-jwt"); +var JwtStrategy = function () { + var _classDecorators = [(0, common_1.Injectable)()]; + var _classDescriptor; + var _classExtraInitializers = []; + var _classThis; + var _classSuper = (0, passport_1.PassportStrategy)(passport_jwt_1.Strategy); + var JwtStrategy = _classThis = /** @class */ (function (_super) { + __extends(JwtStrategy_1, _super); + function JwtStrategy_1(configService) { + var _this = _super.call(this, { + jwtFromRequest: passport_jwt_1.ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: configService.get('JWT_SECRET') || 'your-secret-key', + }) || this; + _this.configService = configService; + return _this; + } + JwtStrategy_1.prototype.validate = function (payload) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + if (!payload.sub || !payload.role) { + throw new common_1.UnauthorizedException(); + } + return [2 /*return*/, { + userId: payload.sub, + role: payload.role, + tenantId: payload.tenantId, + }]; + }); + }); + }; + return JwtStrategy_1; + }(_classSuper)); + __setFunctionName(_classThis, "JwtStrategy"); + (function () { + var _a; + var _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create((_a = _classSuper[Symbol.metadata]) !== null && _a !== void 0 ? _a : null) : void 0; + __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers); + JwtStrategy = _classThis = _classDescriptor.value; + if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); + __runInitializers(_classThis, _classExtraInitializers); + })(); + return JwtStrategy = _classThis; +}(); +exports.JwtStrategy = JwtStrategy; diff --git a/reading-platform-backend/src/modules/auth/strategies/jwt.strategy.ts b/reading-platform-backend/src/modules/auth/strategies/jwt.strategy.ts new file mode 100644 index 0000000..a790d52 --- /dev/null +++ b/reading-platform-backend/src/modules/auth/strategies/jwt.strategy.ts @@ -0,0 +1,33 @@ +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { ConfigService } from '@nestjs/config'; + +export interface JwtPayload { + sub: number; + role: string; + tenantId?: number; +} + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor(private configService: ConfigService) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: configService.get('JWT_SECRET') || 'your-secret-key', + }); + } + + async validate(payload: JwtPayload) { + if (!payload.sub || !payload.role) { + throw new UnauthorizedException(); + } + + return { + userId: payload.sub, + role: payload.role, + tenantId: payload.tenantId, + }; + } +} diff --git a/reading-platform-backend/src/modules/common/common.module.ts b/reading-platform-backend/src/modules/common/common.module.ts new file mode 100644 index 0000000..1f0dcdf --- /dev/null +++ b/reading-platform-backend/src/modules/common/common.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { JwtAuthGuard } from './guards/jwt-auth.guard'; +import { RolesGuard } from './guards/roles.guard'; +import { LogInterceptor } from './interceptors/log.interceptor'; +import { OperationLogService } from './operation-log.service'; +import { SchoolOperationLogController, AdminOperationLogController } from './operation-log.controller'; + +@Module({ + controllers: [SchoolOperationLogController, AdminOperationLogController], + providers: [JwtAuthGuard, RolesGuard, LogInterceptor, OperationLogService], + exports: [JwtAuthGuard, RolesGuard, LogInterceptor, OperationLogService], +}) +export class CommonModule {} diff --git a/reading-platform-backend/src/modules/common/decorators/log-operation.decorator.ts b/reading-platform-backend/src/modules/common/decorators/log-operation.decorator.ts new file mode 100644 index 0000000..9dbca28 --- /dev/null +++ b/reading-platform-backend/src/modules/common/decorators/log-operation.decorator.ts @@ -0,0 +1,17 @@ +import { SetMetadata } from '@nestjs/common'; + +export const LOG_OPERATION_KEY = 'log_operation'; + +export interface LogOperationOptions { + action: string; // 操作类型: CREATE, UPDATE, DELETE, LOGIN, etc. + module: string; // 模块名称: 教师管理, 学生管理, etc. + description: string; // 操作描述 +} + +/** + * 操作日志装饰器 + * 用于标记需要记录操作日志的方法 + */ +export const LogOperation = (options: LogOperationOptions) => { + return SetMetadata(LOG_OPERATION_KEY, options); +}; diff --git a/reading-platform-backend/src/modules/common/decorators/roles.decorator.ts b/reading-platform-backend/src/modules/common/decorators/roles.decorator.ts new file mode 100644 index 0000000..e038e16 --- /dev/null +++ b/reading-platform-backend/src/modules/common/decorators/roles.decorator.ts @@ -0,0 +1,4 @@ +import { SetMetadata } from '@nestjs/common'; + +export const ROLES_KEY = 'roles'; +export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles); diff --git a/reading-platform-backend/src/modules/common/guards/jwt-auth.guard.ts b/reading-platform-backend/src/modules/common/guards/jwt-auth.guard.ts new file mode 100644 index 0000000..0bfe520 --- /dev/null +++ b/reading-platform-backend/src/modules/common/guards/jwt-auth.guard.ts @@ -0,0 +1,24 @@ +import { Injectable, ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class JwtAuthGuard extends AuthGuard('jwt') { + constructor(private reflector: Reflector) { + super(); + } + + canActivate(context: ExecutionContext) { + // 可以通过装饰器设置是否需要认证 + const requireAuth = this.reflector.getAllAndOverride('requireAuth', [ + context.getHandler(), + context.getClass(), + ]); + + if (requireAuth === false) { + return true; + } + + return super.canActivate(context); + } +} diff --git a/reading-platform-backend/src/modules/common/guards/roles.guard.ts b/reading-platform-backend/src/modules/common/guards/roles.guard.ts new file mode 100644 index 0000000..658d99c --- /dev/null +++ b/reading-platform-backend/src/modules/common/guards/roles.guard.ts @@ -0,0 +1,25 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { ROLES_KEY } from '../decorators/roles.decorator'; + +@Injectable() +export class RolesGuard implements CanActivate { + constructor(private reflector: Reflector) {} + + canActivate(context: ExecutionContext): boolean { + const requiredRoles = this.reflector.getAllAndOverride(ROLES_KEY, [ + context.getHandler(), + context.getClass(), + ]); + + // 如果没有设置角色要求,则允许访问 + if (!requiredRoles) { + return true; + } + + const { user } = context.switchToHttp().getRequest(); + + // 检查用户角色是否在允许的角色列表中 + return requiredRoles.some((role) => user?.role === role); + } +} diff --git a/reading-platform-backend/src/modules/common/interceptors/log.interceptor.ts b/reading-platform-backend/src/modules/common/interceptors/log.interceptor.ts new file mode 100644 index 0000000..00a4470 --- /dev/null +++ b/reading-platform-backend/src/modules/common/interceptors/log.interceptor.ts @@ -0,0 +1,135 @@ +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler, + Logger, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { Observable, tap } from 'rxjs'; +import { PrismaService } from '../../../database/prisma.service'; +import { LOG_OPERATION_KEY, LogOperationOptions } from '../decorators/log-operation.decorator'; + +@Injectable() +export class LogInterceptor implements NestInterceptor { + private readonly logger = new Logger(LogInterceptor.name); + + constructor( + private reflector: Reflector, + private prisma: PrismaService, + ) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const logOptions = this.reflector.getAllAndOverride( + LOG_OPERATION_KEY, + [context.getHandler(), context.getClass()], + ); + + if (!logOptions) { + return next.handle(); + } + + const request = context.switchToHttp().getRequest(); + const user = request.user; + const startTime = Date.now(); + + // 获取请求体(用于记录变更前后数据) + const body = request.body; + const params = request.params; + + return next.handle().pipe( + tap({ + next: (response) => { + this.saveLog({ + user, + logOptions, + body, + params, + response, + ipAddress: this.getIpAddress(request), + userAgent: request.headers['user-agent'], + duration: Date.now() - startTime, + }); + }, + error: (error) => { + // 错误情况下也记录日志 + this.saveLog({ + user, + logOptions, + body, + params, + response: { error: error.message }, + ipAddress: this.getIpAddress(request), + userAgent: request.headers['user-agent'], + duration: Date.now() - startTime, + isError: true, + }); + }, + }), + ); + } + + private async saveLog(data: { + user: any; + logOptions: LogOperationOptions; + body: any; + params: any; + response: any; + ipAddress: string; + userAgent: string; + duration: number; + isError?: boolean; + }) { + try { + const { user, logOptions, body, params, response, ipAddress, userAgent } = data; + + // 获取目标ID + let targetId: number | undefined; + if (params?.id) { + targetId = parseInt(params.id, 10); + } else if (response?.id) { + targetId = response.id; + } else if (body?.id) { + targetId = body.id; + } + + // 构建描述 + let description = logOptions.description; + if (data.isError) { + description = `[失败] ${description}`; + } + + await this.prisma.operationLog.create({ + data: { + tenantId: user?.tenantId || null, + userId: user?.userId || 0, + userType: user?.role || 'UNKNOWN', + action: logOptions.action, + module: logOptions.module, + description, + targetId, + oldValue: body ? JSON.stringify(body) : null, + newValue: response ? JSON.stringify(response) : null, + ipAddress, + userAgent, + }, + }); + + this.logger.debug( + `Operation logged: ${logOptions.module} - ${logOptions.action} (${data.duration}ms)` + ); + } catch (error) { + this.logger.error('Failed to save operation log:', error); + } + } + + private getIpAddress(request: any): string { + return ( + request.headers['x-forwarded-for']?.split(',')[0]?.trim() || + request.headers['x-real-ip'] || + request.connection?.remoteAddress || + request.socket?.remoteAddress || + 'unknown' + ); + } +} diff --git a/reading-platform-backend/src/modules/common/operation-log.controller.ts b/reading-platform-backend/src/modules/common/operation-log.controller.ts new file mode 100644 index 0000000..4025c46 --- /dev/null +++ b/reading-platform-backend/src/modules/common/operation-log.controller.ts @@ -0,0 +1,56 @@ +import { + Controller, + Get, + Param, + Query, + UseGuards, + Request, +} from '@nestjs/common'; +import { OperationLogService } from './operation-log.service'; +import { JwtAuthGuard } from './guards/jwt-auth.guard'; +import { RolesGuard } from './guards/roles.guard'; +import { Roles } from './decorators/roles.decorator'; + +@Controller('school') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('school') +export class SchoolOperationLogController { + constructor(private readonly logService: OperationLogService) {} + + @Get('operation-logs') + getLogs(@Request() req: any, @Query() query: any) { + return this.logService.getLogs(req.user.tenantId, query); + } + + @Get('operation-logs/stats') + getStats(@Request() req: any, @Query() query: any) { + return this.logService.getModuleStats(req.user.tenantId, query.startDate, query.endDate); + } + + @Get('operation-logs/:id') + getLogById(@Request() req: any, @Param('id') id: string) { + return this.logService.getLogById(req.user.tenantId, +id); + } +} + +@Controller('admin') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('admin') +export class AdminOperationLogController { + constructor(private readonly logService: OperationLogService) {} + + @Get('operation-logs') + getLogs(@Query() query: any) { + return this.logService.getLogs(null, query); + } + + @Get('operation-logs/stats') + getStats(@Query() query: any) { + return this.logService.getModuleStats(null, query.startDate, query.endDate); + } + + @Get('operation-logs/:id') + getLogById(@Param('id') id: string) { + return this.logService.getLogById(null, +id); + } +} diff --git a/reading-platform-backend/src/modules/common/operation-log.service.ts b/reading-platform-backend/src/modules/common/operation-log.service.ts new file mode 100644 index 0000000..395cb2c --- /dev/null +++ b/reading-platform-backend/src/modules/common/operation-log.service.ts @@ -0,0 +1,171 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { PrismaService } from '../../database/prisma.service'; + +@Injectable() +export class OperationLogService { + private readonly logger = new Logger(OperationLogService.name); + + constructor(private prisma: PrismaService) {} + + /** + * 获取操作日志列表 + */ + async getLogs(tenantId: number | null, query: { + page?: number; + pageSize?: number; + userId?: number; + userType?: string; + action?: string; + module?: string; + startDate?: string; + endDate?: string; + }) { + const { + page = 1, + pageSize = 20, + userId, + userType, + action, + module, + startDate, + endDate, + } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where: any = {}; + + if (tenantId !== null) { + where.tenantId = tenantId; + } + + if (userId) { + where.userId = userId; + } + + if (userType) { + where.userType = userType; + } + + if (action) { + where.action = action; + } + + if (module) { + where.module = module; + } + + if (startDate || endDate) { + where.createdAt = {}; + if (startDate) { + where.createdAt.gte = new Date(startDate); + } + if (endDate) { + where.createdAt.lte = new Date(endDate); + } + } + + const [items, total] = await Promise.all([ + this.prisma.operationLog.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + }), + this.prisma.operationLog.count({ where }), + ]); + + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + + /** + * 获取日志详情 + */ + async getLogById(tenantId: number | null, id: number) { + const where: any = { id }; + + if (tenantId !== null) { + where.tenantId = tenantId; + } + + const log = await this.prisma.operationLog.findFirst({ + where, + }); + + if (!log) { + return null; + } + + // 解析 JSON 字段 + return { + ...log, + oldValue: log.oldValue ? this.safeParseJson(log.oldValue) : null, + newValue: log.newValue ? this.safeParseJson(log.newValue) : null, + }; + } + + /** + * 获取模块统计 + */ + async getModuleStats(tenantId: number | null, startDate?: string, endDate?: string) { + const where: any = {}; + + if (tenantId !== null) { + where.tenantId = tenantId; + } + + if (startDate || endDate) { + where.createdAt = {}; + if (startDate) { + where.createdAt.gte = new Date(startDate); + } + if (endDate) { + where.createdAt.lte = new Date(endDate); + } + } + + const logs = await this.prisma.operationLog.findMany({ + where, + select: { + module: true, + action: true, + }, + }); + + // 统计每个模块的操作数 + const moduleStats = new Map(); + const actionStats = new Map(); + + logs.forEach((log) => { + moduleStats.set(log.module, (moduleStats.get(log.module) || 0) + 1); + actionStats.set(log.action, (actionStats.get(log.action) || 0) + 1); + }); + + return { + modules: Array.from(moduleStats.entries()) + .map(([name, count]) => ({ name, count })) + .sort((a, b) => b.count - a.count), + actions: Array.from(actionStats.entries()) + .map(([name, count]) => ({ name, count })) + .sort((a, b) => b.count - a.count), + total: logs.length, + }; + } + + /** + * 安全解析 JSON + */ + private safeParseJson(str: string): any { + try { + return JSON.parse(str); + } catch { + return str; + } + } +} diff --git a/reading-platform-backend/src/modules/course/course-validation.service.ts b/reading-platform-backend/src/modules/course/course-validation.service.ts new file mode 100644 index 0000000..57cd36f --- /dev/null +++ b/reading-platform-backend/src/modules/course/course-validation.service.ts @@ -0,0 +1,270 @@ +import { Injectable, Logger } from '@nestjs/common'; + +export interface ValidationResult { + valid: boolean; + errors: ValidationError[]; + warnings: ValidationWarning[]; +} + +export interface ValidationError { + field: string; + message: string; + code: string; +} + +export interface ValidationWarning { + field: string; + message: string; + code: string; +} + +export interface CourseValidationData { + name?: string; + description?: string; + coverImagePath?: string; + gradeTags?: string; + domainTags?: string; + duration?: number; + ebookPaths?: string; + audioPaths?: string; + videoPaths?: string; + otherResources?: string; + scripts?: any[]; + lessonPlanData?: string; // JSON字符串,包含 phases 和 scriptPages +} + +/** + * 课程发布前验证服务 + * 验证课程内容的完整性和合规性 + */ +@Injectable() +export class CourseValidationService { + private readonly logger = new Logger(CourseValidationService.name); + + /** + * 验证课程是否可以提交审核 + */ + async validateForSubmit(course: CourseValidationData): Promise { + const errors: ValidationError[] = []; + const warnings: ValidationWarning[] = []; + + // 1. 验证基本信息 + this.validateBasicInfo(course, errors); + + // 2. 验证封面图片 + this.validateCover(course, errors); + + // 3. 验证年级标签 + this.validateGradeTags(course, errors); + + // 4. 验证课程时长 + this.validateDuration(course, errors); + + // 5. 验证数字资源 + this.validateResources(course, warnings); + + // 6. 验证教学流程 + this.validateScripts(course, errors); + + const result: ValidationResult = { + valid: errors.length === 0, + errors, + warnings, + }; + + this.logger.log(`Validation result: valid=${result.valid}, errors=${errors.length}, warnings=${warnings.length}`); + + return result; + } + + /** + * 验证基本信息 + */ + private validateBasicInfo(course: CourseValidationData, errors: ValidationError[]): void { + // 课程名称 + if (!course.name || course.name.trim().length === 0) { + errors.push({ + field: 'name', + message: '请输入课程名称', + code: 'NAME_REQUIRED', + }); + } else if (course.name.length < 2) { + errors.push({ + field: 'name', + message: '课程名称至少需要2个字符', + code: 'NAME_TOO_SHORT', + }); + } else if (course.name.length > 50) { + errors.push({ + field: 'name', + message: '课程名称不能超过50个字符', + code: 'NAME_TOO_LONG', + }); + } + } + + /** + * 验证封面图片 + */ + private validateCover(course: CourseValidationData, errors: ValidationError[]): void { + if (!course.coverImagePath) { + errors.push({ + field: 'coverImagePath', + message: '请上传课程封面', + code: 'COVER_REQUIRED', + }); + } + } + + /** + * 验证年级标签 + */ + private validateGradeTags(course: CourseValidationData, errors: ValidationError[]): void { + if (!course.gradeTags) { + errors.push({ + field: 'gradeTags', + message: '请选择适用年级', + code: 'GRADE_REQUIRED', + }); + return; + } + + try { + const grades = JSON.parse(course.gradeTags); + if (!Array.isArray(grades) || grades.length === 0) { + errors.push({ + field: 'gradeTags', + message: '请至少选择一个适用年级', + code: 'GRADE_EMPTY', + }); + } + } catch { + errors.push({ + field: 'gradeTags', + message: '年级标签格式错误', + code: 'GRADE_FORMAT_ERROR', + }); + } + } + + /** + * 验证课程时长 + */ + private validateDuration(course: CourseValidationData, errors: ValidationError[]): void { + if (course.duration === undefined || course.duration === null) { + errors.push({ + field: 'duration', + message: '请设置课程时长', + code: 'DURATION_REQUIRED', + }); + return; + } + + if (course.duration < 5) { + errors.push({ + field: 'duration', + message: '课程时长不能少于5分钟', + code: 'DURATION_TOO_SHORT', + }); + } else if (course.duration > 60) { + errors.push({ + field: 'duration', + message: '课程时长不能超过60分钟', + code: 'DURATION_TOO_LONG', + }); + } + } + + /** + * 验证数字资源 + */ + private validateResources(course: CourseValidationData, warnings: ValidationWarning[]): void { + const hasEbook = this.hasValidJsonArray(course.ebookPaths); + const hasAudio = this.hasValidJsonArray(course.audioPaths); + const hasVideo = this.hasValidJsonArray(course.videoPaths); + const hasOther = this.hasValidJsonArray(course.otherResources); + + if (!hasEbook && !hasAudio && !hasVideo && !hasOther) { + warnings.push({ + field: 'resources', + message: '建议上传至少1个数字资源(电子绘本、音频或视频)', + code: 'RESOURCES_SUGGESTED', + }); + } + } + + /** + * 验证教学流程 + */ + private validateScripts(course: CourseValidationData, errors: ValidationError[]): void { + // 优先检查 lessonPlanData 中的 phases(新数据结构) + if (course.lessonPlanData) { + try { + const lessonPlan = JSON.parse(course.lessonPlanData); + if (!lessonPlan.phases || !Array.isArray(lessonPlan.phases) || lessonPlan.phases.length === 0) { + errors.push({ + field: 'lessonPlanData', + message: '请至少配置一个教学环节', + code: 'SCRIPTS_REQUIRED', + }); + } + return; // 使用新数据结构验证完成 + } catch { + errors.push({ + field: 'lessonPlanData', + message: '教学计划数据格式错误', + code: 'LESSON_PLAN_FORMAT_ERROR', + }); + return; + } + } + + // 兼容旧的 scripts 字段 + if (course.scripts !== undefined) { + if (!Array.isArray(course.scripts) || course.scripts.length === 0) { + errors.push({ + field: 'scripts', + message: '请至少配置一个教学环节', + code: 'SCRIPTS_REQUIRED', + }); + } + } + } + + /** + * 检查是否是有效的JSON数组 + */ + private hasValidJsonArray(jsonStr: string | undefined | null): boolean { + if (!jsonStr) return false; + + try { + const arr = JSON.parse(jsonStr); + return Array.isArray(arr) && arr.length > 0; + } catch { + return false; + } + } + + /** + * 快速检查课程是否可以提交(只返回布尔值) + */ + async canSubmit(course: CourseValidationData): Promise { + const result = await this.validateForSubmit(course); + return result.valid; + } + + /** + * 获取验证摘要(用于显示) + */ + getValidationSummary(result: ValidationResult): string { + if (result.valid && result.warnings.length === 0) { + return '课程内容完整,可以提交审核'; + } + + if (result.valid && result.warnings.length > 0) { + return `课程可以提交,但有 ${result.warnings.length} 条建议`; + } + + return `课程有 ${result.errors.length} 个问题需要修复`; + } +} diff --git a/reading-platform-backend/src/modules/course/course.controller.ts b/reading-platform-backend/src/modules/course/course.controller.ts new file mode 100644 index 0000000..56b5cb8 --- /dev/null +++ b/reading-platform-backend/src/modules/course/course.controller.ts @@ -0,0 +1,161 @@ +import { + Controller, + Get, + Post, + Put, + Delete, + Param, + Body, + Query, + UseGuards, + Request, +} from '@nestjs/common'; +import { CourseService } from './course.service'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { RolesGuard } from '../common/guards/roles.guard'; +import { Roles } from '../common/decorators/roles.decorator'; + +@Controller('courses') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('admin') +export class CourseController { + constructor(private readonly courseService: CourseService) {} + + @Get() + findAll(@Query() query: any) { + return this.courseService.findAll(query); + } + + @Get('review') + getReviewList(@Query() query: any) { + return this.courseService.getReviewList(query); + } + + @Get(':id') + findOne(@Param('id') id: string) { + return this.courseService.findOne(+id); + } + + @Get(':id/stats') + getStats(@Param('id') id: string) { + return this.courseService.getStats(+id); + } + + @Get(':id/validate') + validate(@Param('id') id: string) { + return this.courseService.validate(+id); + } + + @Get(':id/versions') + getVersionHistory(@Param('id') id: string) { + return this.courseService.getVersionHistory(+id); + } + + @Post() + create(@Body() createCourseDto: any, @Request() req: any) { + return this.courseService.create({ + ...createCourseDto, + createdBy: req.user?.userId, + }); + } + + @Put(':id') + update(@Param('id') id: string, @Body() updateCourseDto: any) { + return this.courseService.update(+id, updateCourseDto); + } + + @Delete(':id') + remove(@Param('id') id: string) { + return this.courseService.remove(+id); + } + + /** + * 提交审核 + * POST /api/v1/courses/:id/submit + */ + @Post(':id/submit') + submit(@Param('id') id: string, @Body() body: { copyrightConfirmed: boolean }, @Request() req: any) { + const userId = req.user?.userId || 0; + return this.courseService.submit(+id, userId, body.copyrightConfirmed); + } + + /** + * 撤销审核申请 + * POST /api/v1/courses/:id/withdraw + */ + @Post(':id/withdraw') + withdraw(@Param('id') id: string, @Request() req: any) { + const userId = req.user?.userId || 0; + return this.courseService.withdraw(+id, userId); + } + + /** + * 审核通过 + * POST /api/v1/courses/:id/approve + */ + @Post(':id/approve') + approve( + @Param('id') id: string, + @Body() body: { checklist?: any; comment?: string }, + @Request() req: any, + ) { + const reviewerId = req.user?.userId || 0; + return this.courseService.approve(+id, reviewerId, body); + } + + /** + * 审核驳回 + * POST /api/v1/courses/:id/reject + */ + @Post(':id/reject') + reject( + @Param('id') id: string, + @Body() body: { checklist?: any; comment: string }, + @Request() req: any, + ) { + const reviewerId = req.user?.userId || 0; + return this.courseService.reject(+id, reviewerId, body); + } + + /** + * 直接发布(超级管理员专用) + * POST /api/v1/courses/:id/direct-publish + */ + @Post(':id/direct-publish') + @Roles('admin') + directPublish( + @Param('id') id: string, + @Body() body: { skipValidation?: boolean }, + @Request() req: any, + ) { + const userId = req.user?.userId || 0; + return this.courseService.directPublish(+id, userId, body.skipValidation); + } + + /** + * 发布课程(兼容旧API) + * POST /api/v1/courses/:id/publish + */ + @Post(':id/publish') + publish(@Param('id') id: string) { + return this.courseService.publish(+id); + } + + /** + * 下架课程 + * POST /api/v1/courses/:id/unpublish + */ + @Post(':id/unpublish') + unpublish(@Param('id') id: string) { + return this.courseService.unpublish(+id); + } + + /** + * 重新发布已下架的课程 + * POST /api/v1/courses/:id/republish + */ + @Post(':id/republish') + republish(@Param('id') id: string) { + return this.courseService.republish(+id); + } +} diff --git a/reading-platform-backend/src/modules/course/course.module.ts b/reading-platform-backend/src/modules/course/course.module.ts new file mode 100644 index 0000000..b1803a6 --- /dev/null +++ b/reading-platform-backend/src/modules/course/course.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { CourseController } from './course.controller'; +import { CourseService } from './course.service'; +import { CourseValidationService } from './course-validation.service'; +import { PrismaModule } from '../../database/prisma.module'; + +@Module({ + imports: [PrismaModule], + controllers: [CourseController], + providers: [CourseService, CourseValidationService], + exports: [CourseService, CourseValidationService], +}) +export class CourseModule {} diff --git a/reading-platform-backend/src/modules/course/course.service.ts b/reading-platform-backend/src/modules/course/course.service.ts new file mode 100644 index 0000000..74842c5 --- /dev/null +++ b/reading-platform-backend/src/modules/course/course.service.ts @@ -0,0 +1,931 @@ +import { Injectable, NotFoundException, BadRequestException, Logger } from '@nestjs/common'; +import { PrismaService } from '../../database/prisma.service'; +import { CourseValidationService, ValidationResult } from './course-validation.service'; + +@Injectable() +export class CourseService { + private readonly logger = new Logger(CourseService.name); + + constructor( + private prisma: PrismaService, + private validationService: CourseValidationService, + ) {} + + async findAll(query: any) { + const { page = 1, pageSize = 10, grade, status, keyword } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where: any = {}; + + // 筛选条件 + if (status) { + where.status = status; + } + + if (keyword) { + where.name = { contains: keyword }; + } + + // 年级筛选 - SQLite使用字符串包含匹配 + if (grade) { + // 搜索英文值(数据库存储的是英文) + // 支持大小写不敏感搜索 + const gradeUpper = grade.toUpperCase(); + where.OR = [ + { gradeTags: { contains: gradeUpper } }, // 大写格式: SMALL, MIDDLE, BIG + { gradeTags: { contains: grade.toLowerCase() } }, // 小写格式: small, middle, big + ]; + } + + const [items, total] = await Promise.all([ + this.prisma.course.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + select: { + id: true, + name: true, + pictureBookName: true, + gradeTags: true, + status: true, + version: true, + usageCount: true, + teacherCount: true, + avgRating: true, + createdAt: true, + submittedAt: true, + reviewedAt: true, + reviewComment: true, + }, + }), + this.prisma.course.count({ where }), + ]); + + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + + async findOne(id: number) { + const course = await this.prisma.course.findUnique({ + where: { id }, + include: { + resources: { + orderBy: { sortOrder: 'asc' }, + }, + scripts: { + orderBy: { sortOrder: 'asc' }, + include: { + pages: { + orderBy: { pageNumber: 'asc' }, + }, + }, + }, + activities: { + orderBy: { sortOrder: 'asc' }, + }, + }, + }); + + if (!course) { + throw new NotFoundException(`Course #${id} not found`); + } + + return course; + } + + async create(createCourseDto: any) { + try { + this.logger.log(`Creating course with data: ${JSON.stringify(createCourseDto)}`); + const result = await this.prisma.course.create({ + data: createCourseDto, + }); + this.logger.log(`Course created successfully with ID: ${result.id}`); + return result; + } catch (error) { + this.logger.error(`Error creating course: ${error.message}`, error.stack); + throw error; + } + } + + async update(id: number, updateCourseDto: any) { + // 需要明确设置为 null 的字段列表(与 schema.prisma 保持一致) + const fieldsToClear = [ + 'coverImagePath', + 'ebookPaths', + 'audioPaths', + 'videoPaths', + 'otherResources', + 'pptPath', + 'pptName', + 'posterPaths', + 'tools', + 'studentMaterials', + 'pictureBookName', + 'lessonPlanData', + 'activitiesData', + 'assessmentData', + ]; + + const cleanedData: any = {}; + + for (const [key, value] of Object.entries(updateCourseDto)) { + // 对于可以清除的字段,如果是 null 或空字符串,设置为 null + if (fieldsToClear.includes(key) && (value === null || value === '')) { + cleanedData[key] = null; + } + // 对于其他字段,如果有值则添加 + else if (value !== undefined) { + cleanedData[key] = value; + } + } + + this.logger.log(`Updating course ${id} with data: ${JSON.stringify(Object.keys(cleanedData))}`); + + // 使用事务更新课程和关联表数据 + return this.prisma.$transaction(async (tx) => { + // 更新课程主表 + const updatedCourse = await tx.course.update({ + where: { id }, + data: cleanedData, + }); + + // 同步 lessonPlanData 到 course_scripts 关联表 + if (updateCourseDto.lessonPlanData !== undefined) { + await this.syncLessonPlanToScripts(tx, id, updateCourseDto.lessonPlanData); + } + + // 同步 activitiesData 到 course_activities 关联表 + if (updateCourseDto.activitiesData !== undefined) { + await this.syncActivitiesToTable(tx, id, updateCourseDto.activitiesData); + } + + return updatedCourse; + }); + } + + /** + * 将 lessonPlanData 同步到 course_scripts 关联表 + * 支持新格式(pages在每个phase内部)和旧格式(scriptPages在顶层) + */ + private async syncLessonPlanToScripts(tx: any, courseId: number, lessonPlanData: string | null) { + // 先删除旧的 scripts 和 pages + await tx.courseScriptPage.deleteMany({ + where: { script: { courseId } }, + }); + await tx.courseScript.deleteMany({ + where: { courseId }, + }); + + if (!lessonPlanData) { + this.logger.log(`Course ${courseId}: lessonPlanData is null, cleared scripts`); + return; + } + + try { + const lessonPlan = JSON.parse(lessonPlanData); + const phases = lessonPlan.phases || []; + // 兼容旧格式:顶层的 scriptPages + const topLevelScriptPages = lessonPlan.scriptPages || []; + + // 调试日志 + this.logger.log(`=== 同步课程 ${courseId} 的教学脚本 ===`); + this.logger.log(`phases 数量: ${phases.length}`); + this.logger.log(`顶层 scriptPages 数量: ${topLevelScriptPages.length}`); + + for (let i = 0; i < phases.length; i++) { + const phase = phases[i]; + this.logger.log(`Phase ${i}: name=${phase.name}, pages=${phase.pages?.length || 0}, enablePageScript=${phase.enablePageScript}`); + + // 创建 script 记录 + const script = await tx.courseScript.create({ + data: { + courseId, + stepIndex: i + 1, + stepName: phase.name || `步骤${i + 1}`, + stepType: phase.type || 'CUSTOM', + duration: phase.duration || 5, + objective: phase.objective || null, + teacherScript: phase.content || null, + interactionPoints: null, + resourceIds: phase.resourceIds ? JSON.stringify(phase.resourceIds) : null, + sortOrder: i, + }, + }); + + // 优先使用 phase 内部的 pages(新格式) + // 如果没有,则兼容旧格式:第一个 phase 使用顶层的 scriptPages + let pagesToCreate = phase.pages || []; + if (pagesToCreate.length === 0 && topLevelScriptPages.length > 0 && i === 0) { + pagesToCreate = topLevelScriptPages; + } + + // 创建逐页配置 + if (pagesToCreate.length > 0) { + this.logger.log(`为 Phase ${i} 创建 ${pagesToCreate.length} 页逐页脚本`); + for (const page of pagesToCreate) { + await tx.courseScriptPage.create({ + data: { + scriptId: script.id, + pageNumber: page.pageNumber, + questions: page.teacherScript || null, + interactionComponent: page.actions ? JSON.stringify(page.actions) : null, + teacherNotes: page.notes || null, + resourceIds: page.resourceIds ? JSON.stringify(page.resourceIds) : null, + }, + }); + } + } + } + + this.logger.log(`Course ${courseId}: synced ${phases.length} scripts from lessonPlanData`); + } catch (error) { + this.logger.error(`Failed to sync lessonPlanData for course ${courseId}: ${error.message}`); + } + } + + /** + * 将 activitiesData 同步到 course_activities 关联表 + */ + private async syncActivitiesToTable(tx: any, courseId: number, activitiesData: string | null) { + // 先删除旧的活动 + await tx.courseActivity.deleteMany({ + where: { courseId }, + }); + + if (!activitiesData) { + this.logger.log(`Course ${courseId}: activitiesData is null, cleared activities`); + return; + } + + try { + const activities = JSON.parse(activitiesData); + + for (let i = 0; i < activities.length; i++) { + const activity = activities[i]; + + await tx.courseActivity.create({ + data: { + courseId, + name: activity.name || `活动${i + 1}`, + domain: activity.domain || null, // 使用单独的 domain 字段,不再错误地使用 type + domainTagId: null, + activityType: this.mapActivityType(activity.type), + duration: activity.duration || 15, + onlineMaterials: activity.content ? JSON.stringify({ content: activity.content }) : null, + offlineMaterials: activity.materials || null, + activityGuide: null, + objectives: null, + sortOrder: i, + }, + }); + } + + this.logger.log(`Course ${courseId}: synced ${activities.length} activities from activitiesData`); + } catch (error) { + this.logger.error(`Failed to sync activitiesData for course ${courseId}: ${error.message}`); + } + } + + /** + * 映射活动类型 + */ + private mapActivityType(type: string | undefined): string { + const typeMap: Record = { + 'family': 'FAMILY', + 'art': 'ART', + 'game': 'GAME', + 'outdoor': 'OUTDOOR', + 'other': 'OTHER', + 'handicraft': 'HANDICRAFT', + 'music': 'MUSIC', + 'exploration': 'EXPLORATION', + 'sports': 'SPORTS', + // 中文兼容 + '家庭延伸': 'FAMILY', + '美工活动': 'ART', + '游戏活动': 'GAME', + '户外活动': 'OUTDOOR', + '其他': 'OTHER', + '手工活动': 'HANDICRAFT', + '音乐活动': 'MUSIC', + '探索活动': 'EXPLORATION', + '运动活动': 'SPORTS', + '亲子活动': 'FAMILY', + }; + return typeMap[type || ''] || 'OTHER'; + } + + async remove(id: number) { + return this.prisma.course.delete({ + where: { id }, + }); + } + + /** + * 验证课程完整性 + */ + async validate(id: number): Promise { + const course = await this.findOne(id); + return this.validationService.validateForSubmit(course); + } + + /** + * 提交审核 + */ + async submit(id: number, userId: number, copyrightConfirmed: boolean) { + const course = await this.prisma.course.findUnique({ + where: { id }, + }); + + if (!course) { + throw new NotFoundException(`Course #${id} not found`); + } + + // 检查当前状态是否可以提交 + if (course.status !== 'DRAFT' && course.status !== 'REJECTED') { + throw new BadRequestException(`课程状态为 ${course.status},无法提交审核`); + } + + // 验证课程完整性 + const validationResult = await this.validationService.validateForSubmit(course); + if (!validationResult.valid) { + throw new BadRequestException({ + message: '课程内容不完整,请检查以下问题', + errors: validationResult.errors, + warnings: validationResult.warnings, + }); + } + + // 版权确认 + if (!copyrightConfirmed) { + throw new BadRequestException('请确认版权合规'); + } + + // 更新课程状态 + const updatedCourse = await this.prisma.course.update({ + where: { id }, + data: { + status: 'PENDING', + submittedAt: new Date(), + submittedBy: userId, + }, + }); + + this.logger.log(`Course ${id} submitted for review by user ${userId}`); + + return { + ...updatedCourse, + validationSummary: this.validationService.getValidationSummary(validationResult), + }; + } + + /** + * 撤销审核申请 + */ + async withdraw(id: number, userId: number) { + const course = await this.prisma.course.findUnique({ + where: { id }, + }); + + if (!course) { + throw new NotFoundException(`Course #${id} not found`); + } + + if (course.status !== 'PENDING') { + throw new BadRequestException(`课程状态为 ${course.status},无法撤销`); + } + + const updatedCourse = await this.prisma.course.update({ + where: { id }, + data: { + status: 'DRAFT', + submittedAt: null, + submittedBy: null, + }, + }); + + this.logger.log(`Course ${id} review withdrawn by user ${userId}`); + + return updatedCourse; + } + + /** + * 审核通过并发布 + */ + async approve(id: number, reviewerId: number, reviewData: { checklist?: any; comment?: string }) { + const course = await this.prisma.course.findUnique({ + where: { id }, + }); + + if (!course) { + throw new NotFoundException(`Course #${id} not found`); + } + + if (course.status !== 'PENDING') { + throw new BadRequestException(`课程状态为 ${course.status},无法审核`); + } + + // 禁止自审 + if (course.submittedBy === reviewerId) { + throw new BadRequestException('不能审核自己提交的课程'); + } + + // 使用事务更新课程状态并创建版本快照 + const result = await this.prisma.$transaction(async (tx) => { + // 更新课程状态 + const updatedCourse = await tx.course.update({ + where: { id }, + data: { + status: 'PUBLISHED', + reviewedAt: new Date(), + reviewedBy: reviewerId, + reviewComment: reviewData.comment || null, + reviewChecklist: reviewData.checklist ? JSON.stringify(reviewData.checklist) : null, + publishedAt: new Date(), + }, + }); + + // 创建版本快照 + await tx.courseVersion.create({ + data: { + courseId: id, + version: course.version, + snapshotData: JSON.stringify(course), + changeLog: reviewData.comment || '审核通过发布', + publishedBy: reviewerId, + }, + }); + + return updatedCourse; + }); + + // 授权给所有活跃租户 + const activeTenants = await this.prisma.tenant.findMany({ + where: { status: 'ACTIVE' }, + select: { id: true }, + }); + + this.logger.log(`Publishing course ${id} to ${activeTenants.length} active tenants`); + + for (const tenant of activeTenants) { + await this.prisma.tenantCourse.upsert({ + where: { + tenantId_courseId: { + tenantId: tenant.id, + courseId: id, + }, + }, + update: { + authorized: true, + authorizedAt: new Date(), + }, + create: { + tenantId: tenant.id, + courseId: id, + authorized: true, + authorizedAt: new Date(), + }, + }); + } + + this.logger.log(`Course ${id} approved and published by reviewer ${reviewerId}`); + + return { + ...result, + authorizedTenantCount: activeTenants.length, + }; + } + + /** + * 审核驳回 + */ + async reject(id: number, reviewerId: number, reviewData: { checklist?: any; comment: string }) { + const course = await this.prisma.course.findUnique({ + where: { id }, + }); + + if (!course) { + throw new NotFoundException(`Course #${id} not found`); + } + + if (course.status !== 'PENDING') { + throw new BadRequestException(`课程状态为 ${course.status},无法审核`); + } + + // 禁止自审 + if (course.submittedBy === reviewerId) { + throw new BadRequestException('不能审核自己提交的课程'); + } + + if (!reviewData.comment || reviewData.comment.trim().length === 0) { + throw new BadRequestException('请填写驳回原因'); + } + + const updatedCourse = await this.prisma.course.update({ + where: { id }, + data: { + status: 'REJECTED', + reviewedAt: new Date(), + reviewedBy: reviewerId, + reviewComment: reviewData.comment, + reviewChecklist: reviewData.checklist ? JSON.stringify(reviewData.checklist) : null, + }, + }); + + this.logger.log(`Course ${id} rejected by reviewer ${reviewerId}: ${reviewData.comment}`); + + return updatedCourse; + } + + /** + * 直接发布(超级管理员专用) + */ + async directPublish(id: number, userId: number, skipValidation: boolean = false) { + const course = await this.prisma.course.findUnique({ + where: { id }, + }); + + if (!course) { + throw new NotFoundException(`Course #${id} not found`); + } + + // 检查课程状态 + if (course.status === 'PUBLISHED') { + throw new BadRequestException('课程已发布'); + } + + // 验证课程完整性(即使跳过也要记录) + const validationResult = await this.validationService.validateForSubmit(course); + + if (!skipValidation && !validationResult.valid) { + throw new BadRequestException({ + message: '课程内容不完整,请检查以下问题', + errors: validationResult.errors, + warnings: validationResult.warnings, + }); + } + + // 使用事务更新课程状态并创建版本快照 + const result = await this.prisma.$transaction(async (tx) => { + const updatedCourse = await tx.course.update({ + where: { id }, + data: { + status: 'PUBLISHED', + publishedAt: new Date(), + reviewedAt: new Date(), + reviewedBy: userId, + reviewComment: '超级管理员直接发布', + }, + }); + + // 创建版本快照 + await tx.courseVersion.create({ + data: { + courseId: id, + version: course.version, + snapshotData: JSON.stringify(course), + changeLog: '超级管理员直接发布', + publishedBy: userId, + }, + }); + + return updatedCourse; + }); + + // 授权给所有活跃租户 + const activeTenants = await this.prisma.tenant.findMany({ + where: { status: 'ACTIVE' }, + select: { id: true }, + }); + + for (const tenant of activeTenants) { + await this.prisma.tenantCourse.upsert({ + where: { + tenantId_courseId: { + tenantId: tenant.id, + courseId: id, + }, + }, + update: { + authorized: true, + authorizedAt: new Date(), + }, + create: { + tenantId: tenant.id, + courseId: id, + authorized: true, + authorizedAt: new Date(), + }, + }); + } + + this.logger.log(`Course ${id} directly published by super admin ${userId}`); + + return { + ...result, + authorizedTenantCount: activeTenants.length, + validationSkipped: skipValidation && !validationResult.valid, + validationWarnings: validationResult.warnings, + }; + } + + /** + * 发布课程(兼容旧API) + */ + async publish(id: number) { + // 旧的publish方法改为调用directPublish + return this.directPublish(id, 0, false); + } + + /** + * 下架课程 + */ + async unpublish(id: number) { + const course = await this.prisma.course.findUnique({ + where: { id }, + }); + + if (!course) { + throw new NotFoundException(`Course #${id} not found`); + } + + if (course.status !== 'PUBLISHED') { + throw new BadRequestException(`课程状态为 ${course.status},无法下架`); + } + + const updatedCourse = await this.prisma.course.update({ + where: { id }, + data: { + status: 'ARCHIVED', + }, + }); + + // 取消所有租户的授权 + await this.prisma.tenantCourse.updateMany({ + where: { courseId: id }, + data: { + authorized: false, + }, + }); + + this.logger.log(`Course ${id} unpublished`); + + return updatedCourse; + } + + /** + * 重新发布已下架的课程 + */ + async republish(id: number) { + const course = await this.prisma.course.findUnique({ + where: { id }, + }); + + if (!course) { + throw new NotFoundException(`Course #${id} not found`); + } + + if (course.status !== 'ARCHIVED') { + throw new BadRequestException(`课程状态为 ${course.status},无法重新发布`); + } + + const updatedCourse = await this.prisma.course.update({ + where: { id }, + data: { + status: 'PUBLISHED', + }, + }); + + // 重新授权给所有活跃租户 + const activeTenants = await this.prisma.tenant.findMany({ + where: { status: 'ACTIVE' }, + select: { id: true }, + }); + + for (const tenant of activeTenants) { + await this.prisma.tenantCourse.upsert({ + where: { + tenantId_courseId: { + tenantId: tenant.id, + courseId: id, + }, + }, + update: { + authorized: true, + authorizedAt: new Date(), + }, + create: { + tenantId: tenant.id, + courseId: id, + authorized: true, + authorizedAt: new Date(), + }, + }); + } + + this.logger.log(`Course ${id} republished`); + + return { + ...updatedCourse, + authorizedTenantCount: activeTenants.length, + }; + } + + async getStats(id: number) { + const course = await this.prisma.course.findUnique({ + where: { id }, + select: { + id: true, + name: true, + usageCount: true, + teacherCount: true, + avgRating: true, + }, + }); + + if (!course) { + throw new NotFoundException(`Course #${id} not found`); + } + + const lessons = await this.prisma.lesson.findMany({ + where: { courseId: id }, + include: { + teacher: { + select: { id: true, name: true }, + }, + class: { + select: { id: true, name: true }, + }, + }, + orderBy: { createdAt: 'desc' }, + take: 10, + }); + + const feedbacks = await this.prisma.lessonFeedback.findMany({ + where: { + lesson: { + courseId: id, + }, + }, + }); + + const calculateAverage = (field: string) => { + const validFeedbacks = feedbacks.filter((f: any) => f[field] != null); + if (validFeedbacks.length === 0) return 0; + const sum = validFeedbacks.reduce((acc: number, f: any) => acc + f[field], 0); + return sum / validFeedbacks.length; + }; + + const studentRecords = await this.prisma.studentRecord.findMany({ + where: { + lesson: { + courseId: id, + }, + }, + }); + + const calculateStudentAvg = (field: string) => { + const validRecords = studentRecords.filter((r: any) => r[field] != null); + if (validRecords.length === 0) return 0; + const sum = validRecords.reduce((acc: number, r: any) => acc + r[field], 0); + return sum / validRecords.length; + }; + + const now = new Date(); + const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + const recentLessons = await this.prisma.lesson.findMany({ + where: { + courseId: id, + createdAt: { gte: weekAgo }, + }, + select: { + createdAt: true, + }, + }); + + const lessonTrend = []; + for (let i = 6; i >= 0; i--) { + const date = new Date(now.getTime() - i * 24 * 60 * 60 * 1000); + const dateStr = date.toLocaleDateString('zh-CN', { weekday: 'short' }); + const count = recentLessons.filter((lesson: any) => { + const lessonDate = new Date(lesson.createdAt); + return lessonDate.toDateString() === date.toDateString(); + }).length; + lessonTrend.push({ date: dateStr, count }); + } + + const uniqueStudentIds = new Set(); + lessons.forEach((lesson: any) => { + uniqueStudentIds.add(lesson.classId); + }); + + return { + courseName: course.name, + totalLessons: course.usageCount || lessons.length, + totalTeachers: course.teacherCount || new Set(lessons.map((l: any) => l.teacherId)).size, + totalStudents: uniqueStudentIds.size, + avgRating: course.avgRating || 0, + lessonTrend, + feedbackDistribution: { + designQuality: calculateAverage('designQuality'), + participation: calculateAverage('participation'), + goalAchievement: calculateAverage('goalAchievement'), + totalFeedbacks: feedbacks.length, + }, + recentLessons: lessons.map((lesson: any) => ({ + ...lesson, + date: lesson.createdAt, + })), + studentPerformance: { + avgFocus: calculateStudentAvg('focus'), + avgParticipation: calculateStudentAvg('participation'), + avgInterest: calculateStudentAvg('interest'), + avgUnderstanding: calculateStudentAvg('understanding'), + }, + }; + } + + /** + * 获取审核列表 + */ + async getReviewList(query: any) { + const { page = 1, pageSize = 10, status, submittedBy } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where: any = { + status: { in: ['PENDING', 'REJECTED'] }, + }; + + if (status) { + where.status = status; + } + + if (submittedBy) { + where.submittedBy = +submittedBy; + } + + const [items, total] = await Promise.all([ + this.prisma.course.findMany({ + where, + skip, + take, + orderBy: { submittedAt: 'desc' }, + select: { + id: true, + name: true, + status: true, + submittedAt: true, + submittedBy: true, + reviewedAt: true, + reviewedBy: true, + reviewComment: true, + coverImagePath: true, + gradeTags: true, + }, + }), + this.prisma.course.count({ where }), + ]); + + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + + /** + * 获取版本历史 + */ + async getVersionHistory(id: number) { + const course = await this.prisma.course.findUnique({ + where: { id }, + }); + + if (!course) { + throw new NotFoundException(`Course #${id} not found`); + } + + const versions = await this.prisma.courseVersion.findMany({ + where: { courseId: id }, + orderBy: { publishedAt: 'desc' }, + }); + + return versions.map((v) => ({ + id: v.id, + version: v.version, + changeLog: v.changeLog, + publishedAt: v.publishedAt, + publishedBy: v.publishedBy, + })); + } +} diff --git a/reading-platform-backend/src/modules/export/export.controller.ts b/reading-platform-backend/src/modules/export/export.controller.ts new file mode 100644 index 0000000..1b42e6c --- /dev/null +++ b/reading-platform-backend/src/modules/export/export.controller.ts @@ -0,0 +1,88 @@ +import { + Controller, + Get, + Param, + Query, + UseGuards, + Request, + Res, +} from '@nestjs/common'; +import { Response } from 'express'; +import { ExportService } from './export.service'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { RolesGuard } from '../common/guards/roles.guard'; +import { Roles } from '../common/decorators/roles.decorator'; + +@Controller('school/export') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('school') +export class ExportController { + constructor(private readonly exportService: ExportService) {} + + @Get('teachers') + async exportTeachers(@Request() req: any, @Res() res: Response) { + const buffer = await this.exportService.exportTeachers(req.user.tenantId); + + res.setHeader( + 'Content-Type', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + ); + res.setHeader( + 'Content-Disposition', + `attachment; filename=teachers_${Date.now()}.xlsx`, + ); + res.send(buffer); + } + + @Get('students') + async exportStudents(@Request() req: any, @Res() res: Response) { + const buffer = await this.exportService.exportStudents(req.user.tenantId); + + res.setHeader( + 'Content-Type', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + ); + res.setHeader( + 'Content-Disposition', + `attachment; filename=students_${Date.now()}.xlsx`, + ); + res.send(buffer); + } + + @Get('lessons') + async exportLessons(@Request() req: any, @Res() res: Response) { + const buffer = await this.exportService.exportLessons(req.user.tenantId); + + res.setHeader( + 'Content-Type', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + ); + res.setHeader( + 'Content-Disposition', + `attachment; filename=lessons_${Date.now()}.xlsx`, + ); + res.send(buffer); + } + + @Get('growth-records') + async exportGrowthRecords( + @Request() req: any, + @Query('studentId') studentId: string, + @Res() res: Response, + ) { + const buffer = await this.exportService.exportGrowthRecords( + req.user.tenantId, + studentId ? +studentId : undefined, + ); + + res.setHeader( + 'Content-Type', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + ); + res.setHeader( + 'Content-Disposition', + `attachment; filename=growth_records_${Date.now()}.xlsx`, + ); + res.send(buffer); + } +} diff --git a/reading-platform-backend/src/modules/export/export.module.ts b/reading-platform-backend/src/modules/export/export.module.ts new file mode 100644 index 0000000..6b1e87d --- /dev/null +++ b/reading-platform-backend/src/modules/export/export.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { ExportController } from './export.controller'; +import { ExportService } from './export.service'; + +@Module({ + controllers: [ExportController], + providers: [ExportService], + exports: [ExportService], +}) +export class ExportModule {} diff --git a/reading-platform-backend/src/modules/export/export.service.ts b/reading-platform-backend/src/modules/export/export.service.ts new file mode 100644 index 0000000..d9b927e --- /dev/null +++ b/reading-platform-backend/src/modules/export/export.service.ts @@ -0,0 +1,266 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { PrismaService } from '../../database/prisma.service'; +import * as ExcelJS from 'exceljs'; + +@Injectable() +export class ExportService { + private readonly logger = new Logger(ExportService.name); + + constructor(private prisma: PrismaService) {} + + // ==================== 导出教师列表 ==================== + + async exportTeachers(tenantId: number): Promise { + const teachers = await this.prisma.teacher.findMany({ + where: { tenantId }, + include: { + classes: { + select: { name: true }, + }, + }, + orderBy: { createdAt: 'desc' }, + }); + + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet('教师列表'); + + // 设置表头 + worksheet.columns = [ + { header: 'ID', key: 'id', width: 8 }, + { header: '姓名', key: 'name', width: 15 }, + { header: '手机号', key: 'phone', width: 15 }, + { header: '邮箱', key: 'email', width: 25 }, + { header: '登录账号', key: 'loginAccount', width: 15 }, + { header: '负责班级', key: 'classes', width: 20 }, + { header: '授课次数', key: 'lessonCount', width: 10 }, + { header: '状态', key: 'status', width: 10 }, + { header: '创建时间', key: 'createdAt', width: 20 }, + ]; + + // 设置表头样式 + worksheet.getRow(1).font = { bold: true }; + worksheet.getRow(1).fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFE0E0E0' }, + }; + + // 添加数据 + teachers.forEach((teacher) => { + worksheet.addRow({ + id: teacher.id, + name: teacher.name, + phone: teacher.phone, + email: teacher.email || '-', + loginAccount: teacher.loginAccount, + classes: teacher.classes.map((c) => c.name).join('、') || '-', + lessonCount: teacher.lessonCount, + status: teacher.status === 'ACTIVE' ? '正常' : '停用', + createdAt: teacher.createdAt.toLocaleDateString('zh-CN'), + }); + }); + + const buffer = await workbook.xlsx.writeBuffer(); + return Buffer.from(buffer); + } + + // ==================== 导出学生列表 ==================== + + async exportStudents(tenantId: number): Promise { + const students = await this.prisma.student.findMany({ + where: { tenantId }, + include: { + class: { + select: { name: true }, + }, + }, + orderBy: [{ classId: 'asc' }, { createdAt: 'asc' }], + }); + + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet('学生列表'); + + // 设置表头 + worksheet.columns = [ + { header: 'ID', key: 'id', width: 8 }, + { header: '姓名', key: 'name', width: 15 }, + { header: '性别', key: 'gender', width: 8 }, + { header: '班级', key: 'className', width: 15 }, + { header: '生日', key: 'birthDate', width: 15 }, + { header: '家长姓名', key: 'parentName', width: 15 }, + { header: '家长电话', key: 'parentPhone', width: 15 }, + { header: '阅读次数', key: 'readingCount', width: 10 }, + { header: '创建时间', key: 'createdAt', width: 20 }, + ]; + + // 设置表头样式 + worksheet.getRow(1).font = { bold: true }; + worksheet.getRow(1).fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFE0E0E0' }, + }; + + // 添加数据 + students.forEach((student) => { + worksheet.addRow({ + id: student.id, + name: student.name, + gender: student.gender || '-', + className: student.class?.name || '-', + birthDate: student.birthDate + ? new Date(student.birthDate).toLocaleDateString('zh-CN') + : '-', + parentName: student.parentName || '-', + parentPhone: student.parentPhone || '-', + readingCount: student.readingCount, + createdAt: student.createdAt.toLocaleDateString('zh-CN'), + }); + }); + + const buffer = await workbook.xlsx.writeBuffer(); + return Buffer.from(buffer); + } + + // ==================== 导出授课记录 ==================== + + async exportLessons(tenantId: number): Promise { + const lessons = await this.prisma.lesson.findMany({ + where: { tenantId }, + include: { + course: { + select: { name: true, pictureBookName: true }, + }, + class: { + select: { name: true }, + }, + teacher: { + select: { name: true }, + }, + }, + orderBy: { createdAt: 'desc' }, + }); + + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet('授课记录'); + + // 设置表头 + worksheet.columns = [ + { header: 'ID', key: 'id', width: 8 }, + { header: '课程名称', key: 'courseName', width: 25 }, + { header: '绘本名称', key: 'pictureBookName', width: 20 }, + { header: '授课班级', key: 'className', width: 15 }, + { header: '授课教师', key: 'teacherName', width: 12 }, + { header: '计划时间', key: 'plannedDatetime', width: 18 }, + { header: '开始时间', key: 'startDatetime', width: 18 }, + { header: '结束时间', key: 'endDatetime', width: 18 }, + { header: '实际时长(分钟)', key: 'actualDuration', width: 12 }, + { header: '状态', key: 'status', width: 10 }, + ]; + + // 设置表头样式 + worksheet.getRow(1).font = { bold: true }; + worksheet.getRow(1).fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFE0E0E0' }, + }; + + // 状态映射 + const statusMap: Record = { + PLANNED: '已计划', + IN_PROGRESS: '进行中', + COMPLETED: '已完成', + CANCELLED: '已取消', + }; + + // 添加数据 + lessons.forEach((lesson) => { + worksheet.addRow({ + id: lesson.id, + courseName: lesson.course?.name || '-', + pictureBookName: lesson.course?.pictureBookName || '-', + className: lesson.class?.name || '-', + teacherName: lesson.teacher?.name || '-', + plannedDatetime: lesson.plannedDatetime + ? new Date(lesson.plannedDatetime).toLocaleString('zh-CN') + : '-', + startDatetime: lesson.startDatetime + ? new Date(lesson.startDatetime).toLocaleString('zh-CN') + : '-', + endDatetime: lesson.endDatetime + ? new Date(lesson.endDatetime).toLocaleString('zh-CN') + : '-', + actualDuration: lesson.actualDuration || '-', + status: statusMap[lesson.status] || lesson.status, + }); + }); + + const buffer = await workbook.xlsx.writeBuffer(); + return Buffer.from(buffer); + } + + // ==================== 导出成长档案(简单文本格式) ==================== + + async exportGrowthRecords(tenantId: number, studentId?: number): Promise { + const where: any = { tenantId }; + if (studentId) { + where.studentId = studentId; + } + + const records = await this.prisma.growthRecord.findMany({ + where, + include: { + student: { + select: { name: true }, + }, + class: { + select: { name: true }, + }, + }, + orderBy: { recordDate: 'desc' }, + }); + + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet('成长档案'); + + // 设置表头 + worksheet.columns = [ + { header: 'ID', key: 'id', width: 8 }, + { header: '学生姓名', key: 'studentName', width: 15 }, + { header: '班级', key: 'className', width: 15 }, + { header: '标题', key: 'title', width: 25 }, + { header: '内容', key: 'content', width: 50 }, + { header: '记录类型', key: 'recordType', width: 12 }, + { header: '记录日期', key: 'recordDate', width: 15 }, + { header: '创建时间', key: 'createdAt', width: 20 }, + ]; + + // 设置表头样式 + worksheet.getRow(1).font = { bold: true }; + worksheet.getRow(1).fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFE0E0E0' }, + }; + + // 添加数据 + records.forEach((record) => { + worksheet.addRow({ + id: record.id, + studentName: record.student?.name || '-', + className: record.class?.name || '-', + title: record.title, + content: record.content || '-', + recordType: record.recordType, + recordDate: record.recordDate + ? new Date(record.recordDate).toLocaleDateString('zh-CN') + : '-', + createdAt: record.createdAt.toLocaleDateString('zh-CN'), + }); + }); + + const buffer = await workbook.xlsx.writeBuffer(); + return Buffer.from(buffer); + } +} diff --git a/reading-platform-backend/src/modules/file-upload/file-upload.controller.ts b/reading-platform-backend/src/modules/file-upload/file-upload.controller.ts new file mode 100644 index 0000000..04dbf31 --- /dev/null +++ b/reading-platform-backend/src/modules/file-upload/file-upload.controller.ts @@ -0,0 +1,92 @@ +import { + Controller, + Post, + Delete, + UseInterceptors, + UploadedFile, + Body, + BadRequestException, + Logger, +} from '@nestjs/common'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { memoryStorage } from 'multer'; +import { FileUploadService } from './file-upload.service'; + +@Controller('files') +export class FileUploadController { + private readonly logger = new Logger(FileUploadController.name); + + constructor(private readonly fileUploadService: FileUploadService) {} + + /** + * 上传单个文件 + * POST /api/v1/files/upload + */ + @Post('upload') + @UseInterceptors( + FileInterceptor('file', { + storage: memoryStorage(), + limits: { + fileSize: 300 * 1024 * 1024, // 300MB + }, + }), + ) + async uploadFile( + @UploadedFile() file: Express.Multer.File, + @Body() body: { type?: string; courseId?: string }, + ) { + this.logger.log(`Uploading file: ${file.originalname}, type: ${body.type || 'unknown'}`); + + if (!file) { + throw new BadRequestException('没有上传文件'); + } + + // 验证文件类型 + const fileType = body.type || 'other'; + const validationResult = this.fileUploadService.validateFile(file, fileType); + + if (!validationResult.valid) { + throw new BadRequestException(validationResult.error); + } + + // 保存文件 + const savedFile = await this.fileUploadService.saveFile(file, fileType, body.courseId); + + this.logger.log(`File uploaded successfully: ${savedFile.filePath}`); + + return { + success: true, + filePath: savedFile.filePath, + fileName: savedFile.fileName, + originalName: file.originalname, + fileSize: file.size, + mimeType: file.mimetype, + }; + } + + /** + * 删除文件 + * DELETE /api/v1/files/delete + */ + @Delete('delete') + async deleteFile(@Body() body: { filePath: string }) { + this.logger.log(`Deleting file: ${body.filePath}`); + + if (!body.filePath) { + throw new BadRequestException('缺少文件路径'); + } + + const result = await this.fileUploadService.deleteFile(body.filePath); + + if (!result.success) { + throw new BadRequestException(result.error); + } + + this.logger.log(`File deleted successfully: ${body.filePath}`); + + return { + success: true, + message: '文件删除成功', + }; + } +} diff --git a/reading-platform-backend/src/modules/file-upload/file-upload.module.ts b/reading-platform-backend/src/modules/file-upload/file-upload.module.ts new file mode 100644 index 0000000..cfae030 --- /dev/null +++ b/reading-platform-backend/src/modules/file-upload/file-upload.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { FileUploadController } from './file-upload.controller'; +import { FileUploadService } from './file-upload.service'; + +@Module({ + controllers: [FileUploadController], + providers: [FileUploadService], + exports: [FileUploadService], +}) +export class FileUploadModule {} diff --git a/reading-platform-backend/src/modules/file-upload/file-upload.service.ts b/reading-platform-backend/src/modules/file-upload/file-upload.service.ts new file mode 100644 index 0000000..4c642ac --- /dev/null +++ b/reading-platform-backend/src/modules/file-upload/file-upload.service.ts @@ -0,0 +1,194 @@ +import { Injectable, Logger, BadRequestException } from '@nestjs/common'; +import { extname, join, basename } from 'path'; +import { promises as fs } from 'fs'; + +// 文件类型配置 +const FILE_TYPE_CONFIG = { + cover: { + allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp'], + maxSize: 10 * 1024 * 1024, // 10MB + folder: 'covers', + }, + ebook: { + allowedMimeTypes: [ + 'application/pdf', + 'application/vnd.ms-powerpoint', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + ], + maxSize: 300 * 1024 * 1024, // 300MB + folder: 'ebooks', + }, + audio: { + allowedMimeTypes: ['audio/mpeg', 'audio/wav', 'audio/mp4', 'audio/m4a'], + maxSize: 300 * 1024 * 1024, // 300MB + folder: 'audio', + }, + video: { + allowedMimeTypes: ['video/mp4', 'video/webm'], + maxSize: 300 * 1024 * 1024, // 300MB + folder: 'videos', + }, + ppt: { + allowedMimeTypes: [ + 'application/vnd.ms-powerpoint', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + ], + maxSize: 300 * 1024 * 1024, // 300MB + folder: join('materials', 'ppt'), + }, + poster: { + allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp'], + maxSize: 10 * 1024 * 1024, // 10MB + folder: join('materials', 'posters'), + }, + other: { + allowedMimeTypes: [ + 'application/pdf', + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'image/jpeg', + 'image/png', + 'image/gif', + 'image/webp', + ], + maxSize: 300 * 1024 * 1024, // 300MB + folder: 'other', + }, +}; + +@Injectable() +export class FileUploadService { + private readonly logger = new Logger(FileUploadService.name); + private readonly uploadBasePath = join(process.cwd(), 'uploads', 'courses'); + + constructor() { + this.ensureDirectoriesExist(); + } + + /** + * 确保所有必要的目录存在 + */ + private async ensureDirectoriesExist() { + const directories = Object.values(FILE_TYPE_CONFIG).map((config) => + join(this.uploadBasePath, config.folder), + ); + + for (const dir of directories) { + try { + await fs.mkdir(dir, { recursive: true }); + this.logger.log(`Ensured directory exists: ${dir}`); + } catch (error) { + this.logger.error(`Failed to create directory ${dir}:`, error); + } + } + } + + /** + * 验证文件 + */ + validateFile( + file: Express.Multer.File, + type: string, + ): { valid: boolean; error?: string } { + const config = FILE_TYPE_CONFIG[type as keyof typeof FILE_TYPE_CONFIG] || FILE_TYPE_CONFIG.other; + + // 检查文件大小 + if (file.size > config.maxSize) { + const maxSizeMB = (config.maxSize / (1024 * 1024)).toFixed(0); + return { + valid: false, + error: `文件大小超过限制,最大允许 ${maxSizeMB}MB`, + }; + } + + // 检查 MIME 类型 + if (!config.allowedMimeTypes.includes(file.mimetype)) { + return { + valid: false, + error: `不支持的文件类型: ${file.mimetype}`, + }; + } + + return { valid: true }; + } + + /** + * 保存文件 + */ + async saveFile( + file: Express.Multer.File, + type: string, + courseId?: string, + ): Promise<{ filePath: string; fileName: string }> { + const config = FILE_TYPE_CONFIG[type as keyof typeof FILE_TYPE_CONFIG] || FILE_TYPE_CONFIG.other; + + // 生成安全的文件名(避免中文和特殊字符编码问题) + const timestamp = Date.now(); + const randomStr = Math.random().toString(36).substring(2, 8); + const originalExt = extname(file.originalname) || ''; + const courseIdPrefix = courseId ? `${courseId}_` : ''; + const newFileName = `${courseIdPrefix}${timestamp}_${randomStr}${originalExt}`; + + // 目标路径 + const targetDir = join(this.uploadBasePath, config.folder); + const targetPath = join(targetDir, newFileName); + + try { + // 写入文件 + await fs.writeFile(targetPath, file.buffer); + + // 返回相对路径(用于 API 响应和数据库存储) + const relativePath = `/uploads/courses/${config.folder}/${newFileName}`; + + this.logger.log(`File saved: ${targetPath}`); + + return { + filePath: relativePath, + fileName: newFileName, + }; + } catch (error) { + this.logger.error('Failed to save file:', error); + throw new BadRequestException('文件保存失败'); + } + } + + /** + * 删除文件 + */ + async deleteFile(filePath: string): Promise<{ success: boolean; error?: string }> { + try { + // 安全检查:确保路径在 uploads 目录内,防止目录遍历攻击 + if (!filePath.startsWith('/uploads/')) { + return { success: false, error: '非法的文件路径' }; + } + + // 防止路径遍历攻击 + const normalizedPath = filePath.replace(/\.\./g, ''); + const fullPath = join(process.cwd(), normalizedPath); + + // 确保最终路径仍在 uploads 目录内 + if (!fullPath.startsWith(join(process.cwd(), 'uploads'))) { + return { success: false, error: '非法的文件路径' }; + } + + // 检查文件是否存在 + try { + await fs.access(fullPath); + } catch { + this.logger.warn(`File not found: ${fullPath}`); + return { success: true, error: '文件不存在' }; // 文件不存在也返回成功 + } + + // 删除文件 + await fs.unlink(fullPath); + this.logger.log(`File deleted: ${fullPath}`); + + return { success: true }; + } catch (error) { + this.logger.error('Failed to delete file:', error); + return { success: false, error: '文件删除失败' }; + } + } +} diff --git a/reading-platform-backend/src/modules/growth/dto/create-growth.dto.ts b/reading-platform-backend/src/modules/growth/dto/create-growth.dto.ts new file mode 100644 index 0000000..f5e0261 --- /dev/null +++ b/reading-platform-backend/src/modules/growth/dto/create-growth.dto.ts @@ -0,0 +1,81 @@ +import { IsString, IsNotEmpty, IsOptional, IsInt, IsEnum, IsDateString, IsArray } from 'class-validator'; + +export enum RecordType { + STUDENT = 'STUDENT', + CLASS = 'CLASS', +} + +export class CreateGrowthRecordDto { + @IsInt() + @IsNotEmpty({ message: '学生ID不能为空' }) + studentId: number; + + @IsOptional() + @IsInt() + classId?: number; + + @IsEnum(RecordType) + recordType: RecordType; + + @IsString() + @IsNotEmpty({ message: '标题不能为空' }) + title: string; + + @IsOptional() + @IsString() + content?: string; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + images?: string[]; + + @IsDateString() + recordDate: string; +} + +export class UpdateGrowthRecordDto { + @IsOptional() + @IsString() + @IsNotEmpty({ message: '标题不能为空' }) + title?: string; + + @IsOptional() + @IsString() + content?: string; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + images?: string[]; + + @IsOptional() + @IsDateString() + recordDate?: string; +} + +export class QueryGrowthRecordDto { + @IsOptional() + @IsInt() + page?: number; + + @IsOptional() + @IsInt() + pageSize?: number; + + @IsOptional() + @IsInt() + studentId?: number; + + @IsOptional() + @IsInt() + classId?: number; + + @IsOptional() + @IsString() + recordType?: string; + + @IsOptional() + @IsString() + keyword?: string; +} diff --git a/reading-platform-backend/src/modules/growth/growth.controller.ts b/reading-platform-backend/src/modules/growth/growth.controller.ts new file mode 100644 index 0000000..e65a905 --- /dev/null +++ b/reading-platform-backend/src/modules/growth/growth.controller.ts @@ -0,0 +1,117 @@ +import { + Controller, + Get, + Post, + Put, + Delete, + Body, + Param, + Query, + UseGuards, + Request, +} from '@nestjs/common'; +import { GrowthService } from './growth.service'; +import { CreateGrowthRecordDto, UpdateGrowthRecordDto } from './dto/create-growth.dto'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { RolesGuard } from '../common/guards/roles.guard'; +import { Roles } from '../common/decorators/roles.decorator'; + +@Controller('school') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('school') +export class GrowthController { + constructor(private readonly growthService: GrowthService) {} + + @Get('growth-records') + findAll(@Request() req: any, @Query() query: any) { + return this.growthService.findAll(req.user.tenantId, query); + } + + @Get('growth-records/:id') + findOne(@Request() req: any, @Param('id') id: string) { + return this.growthService.findOne(req.user.tenantId, +id); + } + + @Post('growth-records') + create(@Request() req: any, @Body() dto: CreateGrowthRecordDto) { + return this.growthService.create(req.user.tenantId, req.user.userId, dto); + } + + @Put('growth-records/:id') + update( + @Request() req: any, + @Param('id') id: string, + @Body() dto: UpdateGrowthRecordDto, + ) { + return this.growthService.update(req.user.tenantId, +id, dto); + } + + @Delete('growth-records/:id') + delete(@Request() req: any, @Param('id') id: string) { + return this.growthService.delete(req.user.tenantId, +id); + } + + @Get('students/:studentId/growth-records') + findByStudent( + @Request() req: any, + @Param('studentId') studentId: string, + @Query() query: any, + ) { + return this.growthService.findByStudent(req.user.tenantId, +studentId, query); + } + + @Get('classes/:classId/growth-records') + findByClass( + @Request() req: any, + @Param('classId') classId: string, + @Query() query: any, + ) { + return this.growthService.findByClass(req.user.tenantId, +classId, query); + } +} + +// 教师端的成长档案控制器 +@Controller('teacher') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('teacher') +export class TeacherGrowthController { + constructor(private readonly growthService: GrowthService) {} + + @Get('growth-records') + findAll(@Request() req: any, @Query() query: any) { + return this.growthService.findAllForTeacher(req.user.tenantId, req.user.userId, query); + } + + @Get('growth-records/:id') + findOne(@Request() req: any, @Param('id') id: string) { + return this.growthService.findOneForTeacher(req.user.tenantId, req.user.userId, +id); + } + + @Post('growth-records') + create(@Request() req: any, @Body() dto: CreateGrowthRecordDto) { + return this.growthService.createForTeacher(req.user.tenantId, req.user.userId, dto); + } + + @Put('growth-records/:id') + update( + @Request() req: any, + @Param('id') id: string, + @Body() dto: UpdateGrowthRecordDto, + ) { + return this.growthService.updateForTeacher(req.user.tenantId, req.user.userId, +id, dto); + } + + @Delete('growth-records/:id') + delete(@Request() req: any, @Param('id') id: string) { + return this.growthService.deleteForTeacher(req.user.tenantId, req.user.userId, +id); + } + + @Get('classes/:classId/growth-records') + findByClass( + @Request() req: any, + @Param('classId') classId: string, + @Query() query: any, + ) { + return this.growthService.findByClass(req.user.tenantId, +classId, query); + } +} diff --git a/reading-platform-backend/src/modules/growth/growth.module.ts b/reading-platform-backend/src/modules/growth/growth.module.ts new file mode 100644 index 0000000..efb9dba --- /dev/null +++ b/reading-platform-backend/src/modules/growth/growth.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { GrowthController, TeacherGrowthController } from './growth.controller'; +import { GrowthService } from './growth.service'; + +@Module({ + controllers: [GrowthController, TeacherGrowthController], + providers: [GrowthService], + exports: [GrowthService], +}) +export class GrowthModule {} diff --git a/reading-platform-backend/src/modules/growth/growth.service.ts b/reading-platform-backend/src/modules/growth/growth.service.ts new file mode 100644 index 0000000..f1b4d4a --- /dev/null +++ b/reading-platform-backend/src/modules/growth/growth.service.ts @@ -0,0 +1,637 @@ +import { Injectable, NotFoundException, ForbiddenException, Logger } from '@nestjs/common'; +import { PrismaService } from '../../database/prisma.service'; +import { CreateGrowthRecordDto, UpdateGrowthRecordDto } from './dto/create-growth.dto'; + +@Injectable() +export class GrowthService { + private readonly logger = new Logger(GrowthService.name); + + constructor(private prisma: PrismaService) {} + + private parseJsonArray(value: any): any[] { + if (!value) return []; + if (typeof value === 'string') { + try { + return JSON.parse(value); + } catch { + return []; + } + } + return Array.isArray(value) ? value : []; + } + + // ==================== 成长档案管理 ==================== + + async findAll(tenantId: number, query: any) { + const { page = 1, pageSize = 10, studentId, classId, recordType, keyword } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where: any = { + tenantId: tenantId, + }; + + if (studentId) { + where.studentId = +studentId; + } + + if (classId) { + where.classId = +classId; + } + + if (recordType) { + where.recordType = recordType; + } + + if (keyword) { + where.OR = [ + { title: { contains: keyword } }, + { content: { contains: keyword } }, + ]; + } + + const [items, total] = await Promise.all([ + this.prisma.growthRecord.findMany({ + where, + skip, + take, + orderBy: { recordDate: 'desc' }, + include: { + student: { + select: { + id: true, + name: true, + gender: true, + }, + }, + class: { + select: { + id: true, + name: true, + grade: true, + }, + }, + }, + }), + this.prisma.growthRecord.count({ where }), + ]); + + return { + items: items.map((item) => ({ + ...item, + images: this.parseJsonArray(item.images), + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + + /** + * 教师端查询成长档案(带数据隔离) + * 仅返回教师所教班级学生的档案 + */ + async findAllForTeacher(tenantId: number, teacherId: number, query: any) { + const { page = 1, pageSize = 10, studentId, classId, recordType, keyword } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + // 获取教师关联的所有班级ID + const classTeachers = await this.prisma.classTeacher.findMany({ + where: { teacherId }, + select: { classId: true }, + }); + + const classIds = classTeachers.map((ct) => ct.classId); + + if (classIds.length === 0) { + return { + items: [], + total: 0, + page: +page, + pageSize: +pageSize, + }; + } + + const where: any = { + tenantId: tenantId, + classId: { in: classIds }, // 数据隔离:只查询教师所教班级的档案 + }; + + if (studentId) { + where.studentId = +studentId; + } + + // 如果指定了 classId,需要验证是否在教师的班级列表中 + if (classId) { + if (!classIds.includes(+classId)) { + throw new ForbiddenException('您没有权限查看该班级的档案'); + } + where.classId = +classId; + } + + if (recordType) { + where.recordType = recordType; + } + + if (keyword) { + where.OR = [ + { title: { contains: keyword } }, + { content: { contains: keyword } }, + ]; + } + + const [items, total] = await Promise.all([ + this.prisma.growthRecord.findMany({ + where, + skip, + take, + orderBy: { recordDate: 'desc' }, + include: { + student: { + select: { + id: true, + name: true, + gender: true, + }, + }, + class: { + select: { + id: true, + name: true, + grade: true, + }, + }, + }, + }), + this.prisma.growthRecord.count({ where }), + ]); + + return { + items: items.map((item) => ({ + ...item, + images: this.parseJsonArray(item.images), + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + + /** + * 教师端查询单个档案(带数据隔离) + */ + async findOneForTeacher(tenantId: number, teacherId: number, id: number) { + const record = await this.prisma.growthRecord.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + include: { + student: { + select: { + id: true, + name: true, + gender: true, + birthDate: true, + }, + }, + class: { + select: { + id: true, + name: true, + grade: true, + }, + }, + }, + }); + + if (!record) { + throw new NotFoundException('成长档案不存在'); + } + + // 验证教师是否有权限查看该班级的档案 + const classTeacher = await this.prisma.classTeacher.findFirst({ + where: { teacherId, classId: record.classId }, + }); + + if (!classTeacher) { + throw new ForbiddenException('您没有权限查看此档案'); + } + + return { + ...record, + images: this.parseJsonArray(record.images), + }; + } + + /** + * 教师端创建档案(带数据隔离验证) + */ + async createForTeacher(tenantId: number, teacherId: number, dto: CreateGrowthRecordDto) { + // 验证学生是否存在 + const student = await this.prisma.student.findFirst({ + where: { + id: dto.studentId, + tenantId: tenantId, + }, + }); + + if (!student) { + throw new NotFoundException('学生不存在'); + } + + // 验证教师是否有权限为该班级创建档案 + const classId = dto.classId || student.classId; + const classTeacher = await this.prisma.classTeacher.findFirst({ + where: { teacherId, classId }, + }); + + if (!classTeacher) { + throw new ForbiddenException('您没有权限为此班级创建档案'); + } + + const record = await this.prisma.growthRecord.create({ + data: { + tenantId: tenantId, + studentId: dto.studentId, + classId: classId, + recordType: dto.recordType, + title: dto.title, + content: dto.content, + images: JSON.stringify(dto.images || []), + recordDate: new Date(dto.recordDate), + createdBy: teacherId, + }, + include: { + student: { + select: { + id: true, + name: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + this.logger.log(`Growth record created by teacher: ${record.id}`); + + return { + ...record, + images: this.parseJsonArray(record.images), + }; + } + + /** + * 教师端更新档案(带数据隔离验证) + */ + async updateForTeacher(tenantId: number, teacherId: number, id: number, dto: UpdateGrowthRecordDto) { + const existing = await this.prisma.growthRecord.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + + if (!existing) { + throw new NotFoundException('成长档案不存在'); + } + + // 验证教师是否有权限更新该班级的档案 + const classTeacher = await this.prisma.classTeacher.findFirst({ + where: { teacherId, classId: existing.classId }, + }); + + if (!classTeacher) { + throw new ForbiddenException('您没有权限更新此档案'); + } + + const record = await this.prisma.growthRecord.update({ + where: { id: id }, + data: { + title: dto.title, + content: dto.content, + images: dto.images ? JSON.stringify(dto.images) : undefined, + recordDate: dto.recordDate ? new Date(dto.recordDate) : undefined, + }, + include: { + student: { + select: { + id: true, + name: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + this.logger.log(`Growth record updated by teacher: ${id}`); + + return { + ...record, + images: this.parseJsonArray(record.images), + }; + } + + /** + * 教师端删除档案(带数据隔离验证) + */ + async deleteForTeacher(tenantId: number, teacherId: number, id: number) { + const existing = await this.prisma.growthRecord.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + + if (!existing) { + throw new NotFoundException('成长档案不存在'); + } + + // 验证教师是否有权限删除该班级的档案 + const classTeacher = await this.prisma.classTeacher.findFirst({ + where: { teacherId, classId: existing.classId }, + }); + + if (!classTeacher) { + throw new ForbiddenException('您没有权限删除此档案'); + } + + await this.prisma.growthRecord.delete({ + where: { id: id }, + }); + + this.logger.log(`Growth record deleted by teacher: ${id}`); + + return { message: '删除成功' }; + } + + async findOne(tenantId: number, id: number) { + const record = await this.prisma.growthRecord.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + include: { + student: { + select: { + id: true, + name: true, + gender: true, + birthDate: true, + }, + }, + class: { + select: { + id: true, + name: true, + grade: true, + }, + }, + }, + }); + + if (!record) { + throw new NotFoundException('成长档案不存在'); + } + + return { + ...record, + images: this.parseJsonArray(record.images), + }; + } + + async create(tenantId: number, userId: number, dto: CreateGrowthRecordDto) { + // 验证学生是否存在 + const student = await this.prisma.student.findFirst({ + where: { + id: dto.studentId, + tenantId: tenantId, + }, + }); + + if (!student) { + throw new NotFoundException('学生不存在'); + } + + const record = await this.prisma.growthRecord.create({ + data: { + tenantId: tenantId, + studentId: dto.studentId, + classId: dto.classId || student.classId, + recordType: dto.recordType, + title: dto.title, + content: dto.content, + images: JSON.stringify(dto.images || []), + recordDate: new Date(dto.recordDate), + createdBy: userId, + }, + include: { + student: { + select: { + id: true, + name: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + this.logger.log(`Growth record created: ${record.id}`); + + return { + ...record, + images: this.parseJsonArray(record.images), + }; + } + + async update(tenantId: number, id: number, dto: UpdateGrowthRecordDto) { + const existing = await this.prisma.growthRecord.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + + if (!existing) { + throw new NotFoundException('成长档案不存在'); + } + + const record = await this.prisma.growthRecord.update({ + where: { id: id }, + data: { + title: dto.title, + content: dto.content, + images: dto.images ? JSON.stringify(dto.images) : undefined, + recordDate: dto.recordDate ? new Date(dto.recordDate) : undefined, + }, + include: { + student: { + select: { + id: true, + name: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + this.logger.log(`Growth record updated: ${id}`); + + return { + ...record, + images: this.parseJsonArray(record.images), + }; + } + + async delete(tenantId: number, id: number) { + const existing = await this.prisma.growthRecord.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + + if (!existing) { + throw new NotFoundException('成长档案不存在'); + } + + await this.prisma.growthRecord.delete({ + where: { id: id }, + }); + + this.logger.log(`Growth record deleted: ${id}`); + + return { message: '删除成功' }; + } + + // ==================== 学生档案列表 ==================== + + async findByStudent(tenantId: number, studentId: number, query: any) { + const { page = 1, pageSize = 10 } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + // 验证学生是否存在 + const student = await this.prisma.student.findFirst({ + where: { + id: studentId, + tenantId: tenantId, + }, + }); + + if (!student) { + throw new NotFoundException('学生不存在'); + } + + const [items, total] = await Promise.all([ + this.prisma.growthRecord.findMany({ + where: { + studentId: studentId, + tenantId: tenantId, + }, + skip, + take, + orderBy: { recordDate: 'desc' }, + }), + this.prisma.growthRecord.count({ + where: { + studentId: studentId, + tenantId: tenantId, + }, + }), + ]); + + return { + items: items.map((item) => ({ + ...item, + images: this.parseJsonArray(item.images), + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + + // ==================== 班级档案列表 ==================== + + async findByClass(tenantId: number, classId: number, query: any) { + const { page = 1, pageSize = 10, recordDate } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + // 验证班级是否存在 + const classEntity = await this.prisma.class.findFirst({ + where: { + id: classId, + tenantId: tenantId, + }, + }); + + if (!classEntity) { + throw new NotFoundException('班级不存在'); + } + + const where: any = { + classId: classId, + tenantId: tenantId, + recordType: 'CLASS', + }; + + if (recordDate) { + where.recordDate = new Date(recordDate); + } + + const [items, total] = await Promise.all([ + this.prisma.growthRecord.findMany({ + where, + skip, + take, + orderBy: { recordDate: 'desc' }, + include: { + student: { + select: { + id: true, + name: true, + }, + }, + }, + }), + this.prisma.growthRecord.count({ where }), + ]); + + return { + items: items.map((item) => ({ + ...item, + images: this.parseJsonArray(item.images), + })), + total, + page: +page, + pageSize: +pageSize, + }; + } +} diff --git a/reading-platform-backend/src/modules/lesson/dto/create-lesson.dto.ts b/reading-platform-backend/src/modules/lesson/dto/create-lesson.dto.ts new file mode 100644 index 0000000..4b71fb6 --- /dev/null +++ b/reading-platform-backend/src/modules/lesson/dto/create-lesson.dto.ts @@ -0,0 +1,13 @@ +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +export class CreateLessonDto { + @IsNumber() + courseId: number; + + @IsNumber() + classId: number; + + @IsOptional() + @IsString() + plannedDatetime?: string; +} diff --git a/reading-platform-backend/src/modules/lesson/dto/finish-lesson.dto.ts b/reading-platform-backend/src/modules/lesson/dto/finish-lesson.dto.ts new file mode 100644 index 0000000..2ffedd4 --- /dev/null +++ b/reading-platform-backend/src/modules/lesson/dto/finish-lesson.dto.ts @@ -0,0 +1,19 @@ +import { IsString, IsNumber, IsOptional } from 'class-validator'; + +export class FinishLessonDto { + @IsOptional() + @IsString() + overallRating?: string; + + @IsOptional() + @IsString() + participationRating?: string; + + @IsOptional() + @IsString() + completionNote?: string; + + @IsOptional() + @IsNumber() + actualDuration?: number; +} diff --git a/reading-platform-backend/src/modules/lesson/lesson.controller.ts b/reading-platform-backend/src/modules/lesson/lesson.controller.ts new file mode 100644 index 0000000..01c7f14 --- /dev/null +++ b/reading-platform-backend/src/modules/lesson/lesson.controller.ts @@ -0,0 +1,133 @@ +import { + Controller, + Get, + Post, + Body, + Param, + Query, + UseGuards, + Request, +} from '@nestjs/common'; +import { LessonService } from './lesson.service'; +import { CreateLessonDto } from './dto/create-lesson.dto'; +import { FinishLessonDto } from './dto/finish-lesson.dto'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { RolesGuard } from '../common/guards/roles.guard'; +import { Roles } from '../common/decorators/roles.decorator'; + +// 教师端授课控制器 +@Controller('teacher/lessons') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('teacher') +export class LessonController { + constructor(private readonly lessonService: LessonService) {} + + @Get() + findAll(@Request() req: any, @Query() query: any) { + return this.lessonService.findByTeacher(req.user.userId, query); + } + + @Get(':id') + findOne(@Request() req: any, @Param('id') id: string) { + return this.lessonService.findOne(+id, req.user.userId); + } + + @Post() + create(@Request() req: any, @Body() dto: CreateLessonDto) { + return this.lessonService.create(req.user.userId, req.user.tenantId, dto); + } + + @Post(':id/start') + start(@Request() req: any, @Param('id') id: string) { + return this.lessonService.start(+id, req.user.userId); + } + + @Post(':id/finish') + finish(@Request() req: any, @Param('id') id: string, @Body() dto: FinishLessonDto) { + return this.lessonService.finish(+id, req.user.userId, dto); + } + + @Post(':id/cancel') + cancel(@Request() req: any, @Param('id') id: string) { + return this.lessonService.cancel(+id, req.user.userId); + } + + @Post(':id/students/:studentId/record') + saveStudentRecord( + @Request() req: any, + @Param('id') id: string, + @Param('studentId') studentId: string, + @Body() data: any + ) { + return this.lessonService.saveStudentRecord(+id, req.user.userId, +studentId, data); + } + + @Get(':id/student-records') + getStudentRecords(@Request() req: any, @Param('id') id: string) { + return this.lessonService.getStudentRecords(+id, req.user.userId); + } + + @Post(':id/student-records/batch') + batchSaveStudentRecords( + @Request() req: any, + @Param('id') id: string, + @Body() data: { records: Array<{ studentId: number; focus?: number; participation?: number; interest?: number; understanding?: number; notes?: string }> } + ) { + return this.lessonService.batchSaveStudentRecords(+id, req.user.userId, data.records); + } + + // ==================== 课程反馈 ==================== + + @Post(':id/feedback') + submitFeedback( + @Request() req: any, + @Param('id') id: string, + @Body() data: any + ) { + return this.lessonService.submitFeedback(+id, req.user.userId, data); + } + + @Get(':id/feedback') + getFeedback(@Request() req: any, @Param('id') id: string) { + return this.lessonService.getFeedback(+id, req.user.userId); + } +} + +// 教师端反馈控制器 +@Controller('teacher/feedbacks') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('teacher') +export class TeacherFeedbackController { + constructor(private readonly lessonService: LessonService) {} + + @Get() + findAll(@Request() req: any, @Query() query: any) { + return this.lessonService.getFeedbacksByTenant(req.user.tenantId, { + ...query, + teacherId: req.user.userId, + }); + } + + @Get('stats') + getStats(@Request() req: any) { + return this.lessonService.getTeacherFeedbackStats(req.user.userId); + } +} + +// 学校端反馈控制器 +@Controller('school/feedbacks') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('school') +export class SchoolFeedbackController { + constructor(private readonly lessonService: LessonService) {} + + @Get() + findAll(@Request() req: any, @Query() query: any) { + return this.lessonService.getFeedbacksByTenant(req.user.tenantId, query); + } + + @Get('stats') + getStats(@Request() req: any) { + return this.lessonService.getFeedbackStats(req.user.tenantId); + } +} diff --git a/reading-platform-backend/src/modules/lesson/lesson.module.ts b/reading-platform-backend/src/modules/lesson/lesson.module.ts new file mode 100644 index 0000000..87914e3 --- /dev/null +++ b/reading-platform-backend/src/modules/lesson/lesson.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { LessonController, SchoolFeedbackController, TeacherFeedbackController } from './lesson.controller'; +import { LessonService } from './lesson.service'; + +@Module({ + controllers: [LessonController, SchoolFeedbackController, TeacherFeedbackController], + providers: [LessonService], + exports: [LessonService], +}) +export class LessonModule {} diff --git a/reading-platform-backend/src/modules/lesson/lesson.service.ts b/reading-platform-backend/src/modules/lesson/lesson.service.ts new file mode 100644 index 0000000..1317fd2 --- /dev/null +++ b/reading-platform-backend/src/modules/lesson/lesson.service.ts @@ -0,0 +1,905 @@ +import { Injectable, NotFoundException, ForbiddenException, Logger } from '@nestjs/common'; +import { PrismaService } from '../../database/prisma.service'; +import { CreateLessonDto } from './dto/create-lesson.dto'; +import { FinishLessonDto } from './dto/finish-lesson.dto'; + +@Injectable() +export class LessonService { + private readonly logger = new Logger(LessonService.name); + + constructor(private prisma: PrismaService) {} + + async create(teacherId: number, tenantId: number, dto: CreateLessonDto) { + // 验证课程是否已授权 + const course = await this.prisma.course.findUnique({ + where: { id: dto.courseId }, + }); + + if (!course) { + throw new NotFoundException('课程不存在'); + } + + if (course.status !== 'PUBLISHED') { + throw new ForbiddenException('该课程未发布'); + } + + // 检查授权 + const tenantCourse = await this.prisma.tenantCourse.findUnique({ + where: { + tenantId_courseId: { + tenantId: tenantId, + courseId: dto.courseId, + }, + }, + }); + + if (!tenantCourse || !tenantCourse.authorized) { + throw new ForbiddenException('您的学校未获得此课程的授权'); + } + + // 验证班级是否属于该教师 + const classEntity = await this.prisma.class.findFirst({ + where: { + id: dto.classId, + tenantId: tenantId, + teacherId: teacherId, + }, + }); + + if (!classEntity) { + throw new ForbiddenException('无权操作此班级'); + } + + // 创建授课记录 + const lesson = await this.prisma.lesson.create({ + data: { + tenantId: tenantId, + teacherId: teacherId, + classId: dto.classId, + courseId: dto.courseId, + plannedDatetime: dto.plannedDatetime ? new Date(dto.plannedDatetime) : null, + status: 'PLANNED', + }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + duration: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + this.logger.log(`Lesson created: ${lesson.id} by teacher ${teacherId}`); + + return lesson; + } + + async start(lessonId: number, teacherId: number) { + // 查找授课记录 + const lesson = await this.prisma.lesson.findUnique({ + where: { id: lessonId }, + }); + + if (!lesson) { + throw new NotFoundException('授课记录不存在'); + } + + if (lesson.teacherId !== teacherId) { + throw new ForbiddenException('无权操作此授课记录'); + } + + if (lesson.status !== 'PLANNED') { + throw new ForbiddenException('该授课记录已开始或已完成'); + } + + // 更新状态和开始时间 + const updatedLesson = await this.prisma.lesson.update({ + where: { id: lessonId }, + data: { + status: 'IN_PROGRESS', + startDatetime: new Date(), + }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + duration: true, + pptPath: true, + pptName: true, + ebookPaths: true, + audioPaths: true, + videoPaths: true, + posterPaths: true, + scripts: { + orderBy: { sortOrder: 'asc' }, + include: { + pages: { + orderBy: { pageNumber: 'asc' }, + }, + }, + }, + activities: { + orderBy: { sortOrder: 'asc' }, + }, + }, + }, + class: { + select: { + id: true, + name: true, + students: { + select: { + id: true, + name: true, + gender: true, + }, + }, + }, + }, + }, + }); + + this.logger.log(`Lesson started: ${lessonId}`); + + // 解析 JSON 字段 + return { + ...updatedLesson, + course: { + ...updatedLesson.course, + ebookPaths: updatedLesson.course.ebookPaths ? JSON.parse(updatedLesson.course.ebookPaths) : [], + audioPaths: updatedLesson.course.audioPaths ? JSON.parse(updatedLesson.course.audioPaths) : [], + videoPaths: updatedLesson.course.videoPaths ? JSON.parse(updatedLesson.course.videoPaths) : [], + posterPaths: updatedLesson.course.posterPaths ? JSON.parse(updatedLesson.course.posterPaths) : [], + scripts: updatedLesson.course.scripts.map((script) => ({ + ...script, + interactionPoints: script.interactionPoints ? JSON.parse(script.interactionPoints) : null, + resourceIds: script.resourceIds ? JSON.parse(script.resourceIds) : null, + pages: script.pages?.map((page) => ({ + ...page, + resourceIds: page.resourceIds ? JSON.parse(page.resourceIds) : null, + })), + })), + activities: updatedLesson.course.activities.map((activity) => ({ + ...activity, + onlineMaterials: activity.onlineMaterials ? JSON.parse(activity.onlineMaterials) : null, + objectives: activity.objectives ? JSON.parse(activity.objectives) : null, + })), + }, + }; + } + + async finish(lessonId: number, teacherId: number, dto: FinishLessonDto) { + // 查找授课记录 + const lesson = await this.prisma.lesson.findUnique({ + where: { id: lessonId }, + }); + + if (!lesson) { + throw new NotFoundException('授课记录不存在'); + } + + if (lesson.teacherId !== teacherId) { + throw new ForbiddenException('无权操作此授课记录'); + } + + if (lesson.status !== 'IN_PROGRESS') { + throw new ForbiddenException('该授课记录未开始或已完成'); + } + + // 计算实际时长 + let actualDuration = dto.actualDuration; + if (!actualDuration && lesson.startDatetime) { + const endTime = new Date(); + actualDuration = Math.round((endTime.getTime() - lesson.startDatetime.getTime()) / 60000); + } + + // 更新状态和结束时间 + const updatedLesson = await this.prisma.lesson.update({ + where: { id: lessonId }, + data: { + status: 'COMPLETED', + endDatetime: new Date(), + actualDuration: actualDuration, + overallRating: dto.overallRating, + participationRating: dto.participationRating, + completionNote: dto.completionNote, + }, + include: { + course: { + select: { + id: true, + name: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + // 更新课程使用统计 + await this.prisma.course.update({ + where: { id: lesson.courseId }, + data: { + usageCount: { increment: 1 }, + }, + }); + + // 更新教师授课次数 + await this.prisma.teacher.update({ + where: { id: teacherId }, + data: { + lessonCount: { increment: 1 }, + }, + }); + + this.logger.log(`Lesson finished: ${lessonId}, duration: ${actualDuration} minutes`); + + return updatedLesson; + } + + async cancel(lessonId: number, teacherId: number) { + // 查找授课记录 + const lesson = await this.prisma.lesson.findUnique({ + where: { id: lessonId }, + }); + + if (!lesson) { + throw new NotFoundException('授课记录不存在'); + } + + if (lesson.teacherId !== teacherId) { + throw new ForbiddenException('无权操作此授课记录'); + } + + // 只有已计划状态的课程可以取消 + if (lesson.status !== 'PLANNED') { + throw new ForbiddenException('只有已计划的课程可以取消'); + } + + // 更新状态为已取消 + const updatedLesson = await this.prisma.lesson.update({ + where: { id: lessonId }, + data: { + status: 'CANCELLED', + }, + include: { + course: { + select: { + id: true, + name: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + this.logger.log(`Lesson cancelled: ${lessonId}`); + + return updatedLesson; + } + + async findOne(lessonId: number, teacherId: number) { + const lesson = await this.prisma.lesson.findUnique({ + where: { id: lessonId }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + duration: true, + pptPath: true, + pptName: true, + ebookPaths: true, + audioPaths: true, + videoPaths: true, + posterPaths: true, + scripts: { + orderBy: { sortOrder: 'asc' }, + include: { + pages: { + orderBy: { pageNumber: 'asc' }, + }, + }, + }, + activities: { + orderBy: { sortOrder: 'asc' }, + }, + }, + }, + class: { + select: { + id: true, + name: true, + students: { + select: { + id: true, + name: true, + gender: true, + }, + }, + }, + }, + }, + }); + + if (!lesson) { + throw new NotFoundException('授课记录不存在'); + } + + if (lesson.teacherId !== teacherId) { + throw new ForbiddenException('无权查看此授课记录'); + } + + // 解析 JSON 字段 + return { + ...lesson, + course: { + ...lesson.course, + ebookPaths: lesson.course.ebookPaths ? JSON.parse(lesson.course.ebookPaths) : [], + audioPaths: lesson.course.audioPaths ? JSON.parse(lesson.course.audioPaths) : [], + videoPaths: lesson.course.videoPaths ? JSON.parse(lesson.course.videoPaths) : [], + posterPaths: lesson.course.posterPaths ? JSON.parse(lesson.course.posterPaths) : [], + scripts: lesson.course.scripts.map((script) => ({ + ...script, + interactionPoints: script.interactionPoints ? JSON.parse(script.interactionPoints) : null, + resourceIds: script.resourceIds ? JSON.parse(script.resourceIds) : null, + pages: script.pages?.map((page) => ({ + ...page, + resourceIds: page.resourceIds ? JSON.parse(page.resourceIds) : null, + })), + })), + activities: lesson.course.activities.map((activity) => ({ + ...activity, + onlineMaterials: activity.onlineMaterials ? JSON.parse(activity.onlineMaterials) : null, + objectives: activity.objectives ? JSON.parse(activity.objectives) : null, + })), + }, + }; + } + + async findByTeacher(teacherId: number, query: any) { + const { page = 1, pageSize = 10, status, courseId } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where: any = { + teacherId: teacherId, + }; + + if (status) { + where.status = status; + } + + if (courseId) { + where.courseId = +courseId; + } + + const [items, total] = await Promise.all([ + this.prisma.lesson.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }), + this.prisma.lesson.count({ where }), + ]); + + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + + async saveStudentRecord( + lessonId: number, + teacherId: number, + studentId: number, + data: { + focus?: number; + participation?: number; + interest?: number; + understanding?: number; + notes?: string; + } + ) { + // 验证授课记录 + const lesson = await this.prisma.lesson.findUnique({ + where: { id: lessonId }, + }); + + if (!lesson) { + throw new NotFoundException('授课记录不存在'); + } + + if (lesson.teacherId !== teacherId) { + throw new ForbiddenException('无权操作此授课记录'); + } + + // 验证学生是否在班级中 + const student = await this.prisma.student.findFirst({ + where: { + id: studentId, + classId: lesson.classId, + }, + }); + + if (!student) { + throw new NotFoundException('学生不存在或不在此班级'); + } + + // 创建或更新学生记录 + const record = await this.prisma.studentRecord.upsert({ + where: { + lessonId_studentId: { + lessonId: lessonId, + studentId: studentId, + }, + }, + update: data, + create: { + lessonId: lessonId, + studentId: studentId, + ...data, + }, + }); + + return record; + } + + async getStudentRecords(lessonId: number, teacherId: number) { + // 验证授课记录 + const lesson = await this.prisma.lesson.findUnique({ + where: { id: lessonId }, + include: { + class: { + select: { + id: true, + name: true, + students: { + select: { + id: true, + name: true, + gender: true, + }, + }, + }, + }, + }, + }); + + if (!lesson) { + throw new NotFoundException('授课记录不存在'); + } + + if (lesson.teacherId !== teacherId) { + throw new ForbiddenException('无权操作此授课记录'); + } + + // 获取所有学生记录 + const records = await this.prisma.studentRecord.findMany({ + where: { lessonId }, + include: { + student: { + select: { + id: true, + name: true, + gender: true, + }, + }, + }, + }); + + // 合并学生信息和记录 + const studentRecords = lesson.class.students.map((student) => { + const record = records.find((r) => r.studentId === student.id); + return { + ...student, + record: record || null, + }; + }); + + return { + lesson: { + id: lesson.id, + status: lesson.status, + className: lesson.class.name, + }, + students: studentRecords, + }; + } + + async batchSaveStudentRecords( + lessonId: number, + teacherId: number, + records: Array<{ + studentId: number; + focus?: number; + participation?: number; + interest?: number; + understanding?: number; + notes?: string; + }> + ) { + // 验证授课记录 + const lesson = await this.prisma.lesson.findUnique({ + where: { id: lessonId }, + }); + + if (!lesson) { + throw new NotFoundException('授课记录不存在'); + } + + if (lesson.teacherId !== teacherId) { + throw new ForbiddenException('无权操作此授课记录'); + } + + // 批量保存记录 + const results = []; + for (const record of records) { + const saved = await this.prisma.studentRecord.upsert({ + where: { + lessonId_studentId: { + lessonId: lessonId, + studentId: record.studentId, + }, + }, + update: { + focus: record.focus, + participation: record.participation, + interest: record.interest, + understanding: record.understanding, + notes: record.notes, + }, + create: { + lessonId: lessonId, + studentId: record.studentId, + focus: record.focus, + participation: record.participation, + interest: record.interest, + understanding: record.understanding, + notes: record.notes, + }, + }); + results.push(saved); + + // 更新学生的阅读次数(仅首次记录时) + const existingRecord = await this.prisma.studentRecord.findFirst({ + where: { + studentId: record.studentId, + lessonId: { not: lessonId }, + }, + }); + + if (!existingRecord) { + // 这是该学生第一次有记录,更新阅读次数 + await this.prisma.student.update({ + where: { id: record.studentId }, + data: { readingCount: { increment: 1 } }, + }); + } + } + + this.logger.log(`Batch saved ${results.length} student records for lesson ${lessonId}`); + + return { count: results.length, records: results }; + } + + // ==================== 课程反馈功能 ==================== + + async submitFeedback( + lessonId: number, + teacherId: number, + data: { + designQuality?: number; + participation?: number; + goalAchievement?: number; + stepFeedbacks?: any; + pros?: string; + suggestions?: string; + activitiesDone?: any; + } + ) { + // 验证授课记录 + const lesson = await this.prisma.lesson.findUnique({ + where: { id: lessonId }, + }); + + if (!lesson) { + throw new NotFoundException('授课记录不存在'); + } + + if (lesson.teacherId !== teacherId) { + throw new ForbiddenException('无权操作此授课记录'); + } + + // 创建或更新反馈 + const feedback = await this.prisma.lessonFeedback.upsert({ + where: { + lessonId_teacherId: { + lessonId: lessonId, + teacherId: teacherId, + }, + }, + update: { + designQuality: data.designQuality, + participation: data.participation, + goalAchievement: data.goalAchievement, + stepFeedbacks: data.stepFeedbacks ? JSON.stringify(data.stepFeedbacks) : null, + pros: data.pros, + suggestions: data.suggestions, + activitiesDone: data.activitiesDone ? JSON.stringify(data.activitiesDone) : null, + }, + create: { + lessonId: lessonId, + teacherId: teacherId, + designQuality: data.designQuality, + participation: data.participation, + goalAchievement: data.goalAchievement, + stepFeedbacks: data.stepFeedbacks ? JSON.stringify(data.stepFeedbacks) : null, + pros: data.pros, + suggestions: data.suggestions, + activitiesDone: data.activitiesDone ? JSON.stringify(data.activitiesDone) : null, + }, + include: { + lesson: { + select: { + id: true, + course: { + select: { + id: true, + name: true, + }, + }, + }, + }, + }, + }); + + // 更新教师反馈次数 + await this.prisma.teacher.update({ + where: { id: teacherId }, + data: { + feedbackCount: { increment: 1 }, + }, + }); + + this.logger.log(`Feedback submitted for lesson: ${lessonId}`); + + return { + ...feedback, + stepFeedbacks: feedback.stepFeedbacks ? JSON.parse(feedback.stepFeedbacks as string) : null, + activitiesDone: feedback.activitiesDone ? JSON.parse(feedback.activitiesDone as string) : null, + }; + } + + async getFeedback(lessonId: number, teacherId: number) { + const feedback = await this.prisma.lessonFeedback.findUnique({ + where: { + lessonId_teacherId: { + lessonId: lessonId, + teacherId: teacherId, + }, + }, + include: { + lesson: { + select: { + id: true, + course: { + select: { + id: true, + name: true, + pictureBookName: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }, + }, + }); + + if (!feedback) { + return null; + } + + return { + ...feedback, + stepFeedbacks: feedback.stepFeedbacks ? JSON.parse(feedback.stepFeedbacks as string) : null, + activitiesDone: feedback.activitiesDone ? JSON.parse(feedback.activitiesDone as string) : null, + }; + } + + async getFeedbacksByTenant(tenantId: number, query: any) { + const { page = 1, pageSize = 10, teacherId, courseId } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where: any = { + lesson: { + tenantId: tenantId, + }, + }; + + if (teacherId) { + where.teacherId = +teacherId; + } + + if (courseId) { + where.lesson = { + ...where.lesson, + courseId: +courseId, + }; + } + + const [items, total] = await Promise.all([ + this.prisma.lessonFeedback.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + include: { + lesson: { + select: { + id: true, + startDatetime: true, + actualDuration: true, + course: { + select: { + id: true, + name: true, + pictureBookName: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }, + teacher: { + select: { + id: true, + name: true, + }, + }, + }, + }), + this.prisma.lessonFeedback.count({ where }), + ]); + + return { + items: items.map((item) => ({ + ...item, + stepFeedbacks: item.stepFeedbacks ? JSON.parse(item.stepFeedbacks as string) : null, + activitiesDone: item.activitiesDone ? JSON.parse(item.activitiesDone as string) : null, + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + + async getFeedbackStats(tenantId: number) { + // 获取反馈统计 + const feedbacks = await this.prisma.lessonFeedback.findMany({ + where: { + lesson: { + tenantId: tenantId, + }, + }, + select: { + designQuality: true, + participation: true, + goalAchievement: true, + lesson: { + select: { + courseId: true, + }, + }, + }, + }); + + const totalFeedbacks = feedbacks.length; + const avgDesignQuality = feedbacks.reduce((sum, f) => sum + (f.designQuality || 0), 0) / (totalFeedbacks || 1); + const avgParticipation = feedbacks.reduce((sum, f) => sum + (f.participation || 0), 0) / (totalFeedbacks || 1); + const avgGoalAchievement = feedbacks.reduce((sum, f) => sum + (f.goalAchievement || 0), 0) / (totalFeedbacks || 1); + + // 按课程统计 + const courseStats: Record = {}; + feedbacks.forEach((f) => { + const courseId = f.lesson.courseId; + if (!courseStats[courseId]) { + courseStats[courseId] = { count: 0, avgRating: 0 }; + } + courseStats[courseId].count++; + courseStats[courseId].avgRating += (f.designQuality || 0 + f.participation || 0 + f.goalAchievement || 0) / 3; + }); + + // 计算平均值 + Object.keys(courseStats).forEach((courseId) => { + courseStats[+courseId].avgRating /= courseStats[+courseId].count; + }); + + return { + totalFeedbacks, + avgDesignQuality: Math.round(avgDesignQuality * 10) / 10, + avgParticipation: Math.round(avgParticipation * 10) / 10, + avgGoalAchievement: Math.round(avgGoalAchievement * 10) / 10, + courseStats, + }; + } + + async getTeacherFeedbackStats(teacherId: number) { + const feedbacks = await this.prisma.lessonFeedback.findMany({ + where: { teacherId }, + select: { + designQuality: true, + participation: true, + goalAchievement: true, + lesson: { + select: { + courseId: true, + }, + }, + }, + }); + + const totalFeedbacks = feedbacks.length; + const avgDesignQuality = feedbacks.reduce((sum, f) => sum + (f.designQuality || 0), 0) / (totalFeedbacks || 1); + const avgParticipation = feedbacks.reduce((sum, f) => sum + (f.participation || 0), 0) / (totalFeedbacks || 1); + const avgGoalAchievement = feedbacks.reduce((sum, f) => sum + (f.goalAchievement || 0), 0) / (totalFeedbacks || 1); + + return { + totalFeedbacks, + avgDesignQuality: Math.round(avgDesignQuality * 10) / 10, + avgParticipation: Math.round(avgParticipation * 10) / 10, + avgGoalAchievement: Math.round(avgGoalAchievement * 10) / 10, + }; + } +} diff --git a/reading-platform-backend/src/modules/notification/notification.controller.ts b/reading-platform-backend/src/modules/notification/notification.controller.ts new file mode 100644 index 0000000..2b96828 --- /dev/null +++ b/reading-platform-backend/src/modules/notification/notification.controller.ts @@ -0,0 +1,151 @@ +import { + Controller, + Get, + Put, + Param, + Query, + UseGuards, + Request, +} from '@nestjs/common'; +import { NotificationService } from './notification.service'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { RolesGuard } from '../common/guards/roles.guard'; +import { Roles } from '../common/decorators/roles.decorator'; + +// 学校端通知控制器 +@Controller('school/notifications') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('school') +export class SchoolNotificationController { + constructor(private readonly notificationService: NotificationService) {} + + @Get() + getNotifications(@Request() req: any, @Query() query: any) { + return this.notificationService.getNotifications( + req.user.tenantId, + 'SCHOOL', + req.user.userId, + query + ); + } + + @Get('unread-count') + getUnreadCount(@Request() req: any) { + return this.notificationService.getUnreadCount( + req.user.tenantId, + 'SCHOOL', + req.user.userId + ); + } + + @Put(':id/read') + markAsRead(@Request() req: any, @Param('id') id: string) { + return this.notificationService.markAsRead( + req.user.tenantId, + +id, + 'SCHOOL', + req.user.userId + ); + } + + @Put('read-all') + markAllAsRead(@Request() req: any) { + return this.notificationService.markAllAsRead( + req.user.tenantId, + 'SCHOOL', + req.user.userId + ); + } +} + +// 教师端通知控制器 +@Controller('teacher/notifications') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('teacher') +export class TeacherNotificationController { + constructor(private readonly notificationService: NotificationService) {} + + @Get() + getNotifications(@Request() req: any, @Query() query: any) { + return this.notificationService.getNotifications( + req.user.tenantId, + 'TEACHER', + req.user.userId, + query + ); + } + + @Get('unread-count') + getUnreadCount(@Request() req: any) { + return this.notificationService.getUnreadCount( + req.user.tenantId, + 'TEACHER', + req.user.userId + ); + } + + @Put(':id/read') + markAsRead(@Request() req: any, @Param('id') id: string) { + return this.notificationService.markAsRead( + req.user.tenantId, + +id, + 'TEACHER', + req.user.userId + ); + } + + @Put('read-all') + markAllAsRead(@Request() req: any) { + return this.notificationService.markAllAsRead( + req.user.tenantId, + 'TEACHER', + req.user.userId + ); + } +} + +// 家长端通知控制器 +@Controller('parent/notifications') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('parent') +export class ParentNotificationController { + constructor(private readonly notificationService: NotificationService) {} + + @Get() + getNotifications(@Request() req: any, @Query() query: any) { + return this.notificationService.getNotifications( + req.user.tenantId, + 'PARENT', + req.user.userId, + query + ); + } + + @Get('unread-count') + getUnreadCount(@Request() req: any) { + return this.notificationService.getUnreadCount( + req.user.tenantId, + 'PARENT', + req.user.userId + ); + } + + @Put(':id/read') + markAsRead(@Request() req: any, @Param('id') id: string) { + return this.notificationService.markAsRead( + req.user.tenantId, + +id, + 'PARENT', + req.user.userId + ); + } + + @Put('read-all') + markAllAsRead(@Request() req: any) { + return this.notificationService.markAllAsRead( + req.user.tenantId, + 'PARENT', + req.user.userId + ); + } +} diff --git a/reading-platform-backend/src/modules/notification/notification.module.ts b/reading-platform-backend/src/modules/notification/notification.module.ts new file mode 100644 index 0000000..06189b2 --- /dev/null +++ b/reading-platform-backend/src/modules/notification/notification.module.ts @@ -0,0 +1,21 @@ +import { Module } from '@nestjs/common'; +import { ScheduleModule } from '@nestjs/schedule'; +import { + SchoolNotificationController, + TeacherNotificationController, + ParentNotificationController, +} from './notification.controller'; +import { NotificationService } from './notification.service'; +import { ScheduleNotificationService } from './schedule-notification.service'; + +@Module({ + imports: [ScheduleModule.forRoot()], + controllers: [ + SchoolNotificationController, + TeacherNotificationController, + ParentNotificationController, + ], + providers: [NotificationService, ScheduleNotificationService], + exports: [NotificationService, ScheduleNotificationService], +}) +export class NotificationModule {} diff --git a/reading-platform-backend/src/modules/notification/notification.service.ts b/reading-platform-backend/src/modules/notification/notification.service.ts new file mode 100644 index 0000000..005da85 --- /dev/null +++ b/reading-platform-backend/src/modules/notification/notification.service.ts @@ -0,0 +1,169 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { PrismaService } from '../../database/prisma.service'; + +@Injectable() +export class NotificationService { + private readonly logger = new Logger(NotificationService.name); + + constructor(private prisma: PrismaService) {} + + // ==================== 创建通知 ==================== + + async createNotification(data: { + tenantId: number; + recipientType: 'TEACHER' | 'SCHOOL' | 'PARENT'; + recipientId: number; + title: string; + content: string; + notificationType: 'SYSTEM' | 'TASK' | 'LESSON' | 'GROWTH'; + relatedType?: string; + relatedId?: number; + }) { + const notification = await this.prisma.notification.create({ + data: { + tenantId: data.tenantId, + recipientType: data.recipientType, + recipientId: data.recipientId, + title: data.title, + content: data.content, + notificationType: data.notificationType, + relatedType: data.relatedType, + relatedId: data.relatedId, + }, + }); + + this.logger.log( + `Notification created: ${notification.id} for ${data.recipientType}:${data.recipientId}` + ); + + return notification; + } + + // 批量创建通知 + async createBatchNotifications( + notifications: Array<{ + tenantId: number; + recipientType: 'TEACHER' | 'SCHOOL' | 'PARENT'; + recipientId: number; + title: string; + content: string; + notificationType: 'SYSTEM' | 'TASK' | 'LESSON' | 'GROWTH'; + relatedType?: string; + relatedId?: number; + }> + ) { + const results = await this.prisma.notification.createMany({ + data: notifications, + }); + + this.logger.log(`Batch notifications created: ${results.count}`); + + return results; + } + + // ==================== 获取通知 ==================== + + async getNotifications( + tenantId: number, + recipientType: string, + recipientId: number, + query: any + ) { + const { page = 1, pageSize = 20, isRead, notificationType } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where: any = { + tenantId, + recipientType, + recipientId, + }; + + if (isRead !== undefined) { + where.isRead = isRead === 'true'; + } + + if (notificationType) { + where.notificationType = notificationType; + } + + const [items, total, unreadCount] = await Promise.all([ + this.prisma.notification.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + }), + this.prisma.notification.count({ where }), + this.prisma.notification.count({ + where: { ...where, isRead: false }, + }), + ]); + + return { + items, + total, + unreadCount, + page: +page, + pageSize: +pageSize, + }; + } + + async getUnreadCount(tenantId: number, recipientType: string, recipientId: number) { + return this.prisma.notification.count({ + where: { + tenantId, + recipientType, + recipientId, + isRead: false, + }, + }); + } + + // ==================== 标记已读 ==================== + + async markAsRead(tenantId: number, notificationId: number, recipientType: string, recipientId: number) { + const notification = await this.prisma.notification.findFirst({ + where: { + id: notificationId, + tenantId, + recipientType, + recipientId, + }, + }); + + if (!notification) { + return null; + } + + return this.prisma.notification.update({ + where: { id: notificationId }, + data: { + isRead: true, + readAt: new Date(), + }, + }); + } + + async markAllAsRead(tenantId: number, recipientType: string, recipientId: number) { + const result = await this.prisma.notification.updateMany({ + where: { + tenantId, + recipientType, + recipientId, + isRead: false, + }, + data: { + isRead: true, + readAt: new Date(), + }, + }); + + this.logger.log( + `Marked ${result.count} notifications as read for ${recipientType}:${recipientId}` + ); + + return result; + } +} diff --git a/reading-platform-backend/src/modules/notification/schedule-notification.service.ts b/reading-platform-backend/src/modules/notification/schedule-notification.service.ts new file mode 100644 index 0000000..6b590bf --- /dev/null +++ b/reading-platform-backend/src/modules/notification/schedule-notification.service.ts @@ -0,0 +1,333 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { Cron, CronExpression } from '@nestjs/schedule'; +import { PrismaService } from '../../database/prisma.service'; +import { NotificationService } from './notification.service'; + +@Injectable() +export class ScheduleNotificationService { + private readonly logger = new Logger(ScheduleNotificationService.name); + private isProcessing = false; + + constructor( + private prisma: PrismaService, + private notificationService: NotificationService, + ) {} + + /** + * 每30分钟检查一次即将开始的课程 + * 发送提醒给相关教师 + */ + @Cron(CronExpression.EVERY_30_MINUTES) + async handleUpcomingScheduleReminders() { + if (this.isProcessing) { + this.logger.debug('Previous reminder task still processing, skipping...'); + return; + } + + this.isProcessing = true; + + try { + const now = new Date(); + + // 计算接下来30分钟的时间范围 + const thirtyMinutesLater = new Date(now.getTime() + 30 * 60 * 1000); + + // 查找即将开始的排课(今天,未发送提醒) + const upcomingSchedules = await this.prisma.schedulePlan.findMany({ + where: { + status: 'ACTIVE', + reminderSent: false, + scheduledDate: { + gte: new Date(now.toISOString().split('T')[0] + 'T00:00:00.000Z'), + lt: new Date(now.toISOString().split('T')[0] + 'T23:59:59.999Z'), + }, + teacherId: { not: null }, + }, + include: { + teacher: { + select: { id: true, name: true }, + }, + course: { + select: { name: true, pictureBookName: true }, + }, + class: { + select: { name: true }, + }, + }, + }); + + // 过滤出即将开始的排课(根据时间段) + const schedulesToRemind = upcomingSchedules.filter((schedule) => { + if (!schedule.scheduledTime) return false; + + // 解析时间 "09:00-09:30" + const [startTime] = schedule.scheduledTime.split('-'); + if (!startTime) return false; + + const [hours, minutes] = startTime.split(':').map(Number); + const scheduleStartTime = new Date(now); + scheduleStartTime.setHours(hours, minutes, 0, 0); + + // 检查是否在接下来的30分钟内 + return scheduleStartTime >= now && scheduleStartTime <= thirtyMinutesLater; + }); + + this.logger.log(`Found ${schedulesToRemind.length} schedules to send reminders for`); + + // 发送提醒 + for (const schedule of schedulesToRemind) { + try { + await this.sendScheduleReminder(schedule); + } catch (error) { + this.logger.error(`Failed to send reminder for schedule ${schedule.id}:`, error); + } + } + } catch (error) { + this.logger.error('Error in schedule reminder task:', error); + } finally { + this.isProcessing = false; + } + } + + /** + * 发送单个排课提醒 + */ + private async sendScheduleReminder(schedule: any) { + if (!schedule.teacherId || !schedule.teacher) { + return; + } + + const courseName = schedule.course?.pictureBookName || schedule.course?.name || '课程'; + const className = schedule.class?.name || '班级'; + const timeStr = schedule.scheduledTime || '时间待定'; + + // 创建通知 + await this.notificationService.createNotification({ + tenantId: schedule.tenantId, + recipientType: 'TEACHER', + recipientId: schedule.teacherId, + title: '课程提醒', + content: `您即将在 ${timeStr} 为「${className}」讲授《${courseName}》,请做好准备。`, + notificationType: 'LESSON', + relatedType: 'SchedulePlan', + relatedId: schedule.id, + }); + + // 标记已发送提醒 + await this.prisma.schedulePlan.update({ + where: { id: schedule.id }, + data: { + reminderSent: true, + reminderSentAt: new Date(), + }, + }); + + this.logger.log( + `Reminder sent for schedule ${schedule.id} to teacher ${schedule.teacherId}` + ); + } + + /** + * 手动触发提醒检查(用于测试) + */ + async triggerReminderCheck() { + this.logger.log('Manual trigger of reminder check'); + await this.handleUpcomingScheduleReminders(); + return { message: 'Reminder check triggered' }; + } + + /** + * 重置所有提醒状态(用于测试或重置) + */ + async resetAllReminders() { + const result = await this.prisma.schedulePlan.updateMany({ + where: { + reminderSent: true, + }, + data: { + reminderSent: false, + reminderSentAt: null, + }, + }); + + this.logger.log(`Reset ${result.count} schedule reminders`); + + return { message: `Reset ${result.count} reminders` }; + } + + // ==================== 任务提醒 ==================== + + /** + * 每天早上9点检查即将到期的任务 + * 发送提醒给家长 + */ + @Cron('0 9 * * *') // 每天早上9点 + async handleTaskDeadlineReminders() { + this.logger.log('Starting task deadline reminder check...'); + + try { + const now = new Date(); + const threeDaysLater = new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000); + const oneDayLater = new Date(now.getTime() + 24 * 60 * 60 * 1000); + + // 查找即将到期且未完成的任务 + const tasksToRemind = await this.prisma.task.findMany({ + where: { + status: 'PUBLISHED', + endDate: { + gte: now, + lte: threeDaysLater, + }, + }, + include: { + course: { + select: { name: true, pictureBookName: true }, + }, + completions: { + where: { + status: { not: 'COMPLETED' }, + }, + include: { + student: { + select: { + id: true, + name: true, + parents: { + include: { + parent: { + select: { id: true }, + }, + }, + }, + }, + }, + }, + }, + }, + }); + + this.logger.log(`Found ${tasksToRemind.length} tasks with upcoming deadlines`); + + let reminderCount = 0; + + for (const task of tasksToRemind) { + const daysRemaining = Math.ceil( + (task.endDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000) + ); + + // 只在截止前3天、1天、当天发送提醒 + if (![3, 1, 0].includes(daysRemaining)) continue; + + const taskName = task.course?.pictureBookName || task.course?.name || task.title; + + for (const completion of task.completions) { + // 发送提醒给每个学生的家长 + for (const parentRelation of completion.student.parents) { + try { + await this.notificationService.createNotification({ + tenantId: task.tenantId, + recipientType: 'PARENT', + recipientId: parentRelation.parent.id, + title: '任务即将截止', + content: `「${completion.student.name}」的任务《${taskName}》将在${daysRemaining === 0 ? '今天' : daysRemaining + '天后'}截止,请尽快完成。`, + notificationType: 'TASK', + relatedType: 'Task', + relatedId: task.id, + }); + reminderCount++; + } catch (error) { + this.logger.error( + `Failed to send task reminder to parent ${parentRelation.parent.id}:`, + error + ); + } + } + } + } + + this.logger.log(`Sent ${reminderCount} task reminder notifications`); + } catch (error) { + this.logger.error('Error in task deadline reminder task:', error); + } + } + + /** + * 手动发送任务提醒(教师点击"发送提醒"按钮时调用) + */ + async sendManualTaskReminder(tenantId: number, taskId: number) { + const task = await this.prisma.task.findFirst({ + where: { id: taskId, tenantId }, + include: { + course: { + select: { name: true, pictureBookName: true }, + }, + completions: { + where: { + status: { not: 'COMPLETED' }, + }, + include: { + student: { + select: { + id: true, + name: true, + parents: { + include: { + parent: { + select: { id: true, name: true }, + }, + }, + }, + }, + }, + }, + }, + }, + }); + + if (!task) { + return { success: false, message: '任务不存在' }; + } + + const taskName = task.course?.pictureBookName || task.course?.name || task.title; + let reminderCount = 0; + const remindedStudents: { id: number; name: string }[] = []; + + for (const completion of task.completions) { + for (const parentRelation of completion.student.parents) { + try { + await this.notificationService.createNotification({ + tenantId: task.tenantId, + recipientType: 'PARENT', + recipientId: parentRelation.parent.id, + title: '阅读任务提醒', + content: `老师提醒您:「${completion.student.name}」的任务《${taskName}》尚未完成,请督促孩子尽快完成阅读任务。`, + notificationType: 'TASK', + relatedType: 'Task', + relatedId: task.id, + }); + reminderCount++; + } catch (error) { + this.logger.error( + `Failed to send manual task reminder to parent ${parentRelation.parent.id}:`, + error + ); + } + } + remindedStudents.push({ + id: completion.student.id, + name: completion.student.name, + }); + } + + this.logger.log( + `Manual task reminder sent for task ${taskId} to ${reminderCount} parents` + ); + + return { + success: true, + message: `已发送${reminderCount}条提醒`, + remindedCount: reminderCount, + students: remindedStudents, + }; + } +} diff --git a/reading-platform-backend/src/modules/parent/parent.controller.ts b/reading-platform-backend/src/modules/parent/parent.controller.ts new file mode 100644 index 0000000..8061c1f --- /dev/null +++ b/reading-platform-backend/src/modules/parent/parent.controller.ts @@ -0,0 +1,71 @@ +import { + Controller, + Get, + Post, + Put, + Body, + Param, + Query, + UseGuards, + Request, +} from '@nestjs/common'; +import { ParentService } from './parent.service'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { RolesGuard } from '../common/guards/roles.guard'; +import { Roles } from '../common/decorators/roles.decorator'; + +@Controller('parent') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('parent') +export class ParentController { + constructor(private readonly parentService: ParentService) {} + + // ==================== 孩子信息 ==================== + + @Get('children') + getChildren(@Request() req: any) { + return this.parentService.getChildren(req.user.userId, req.user.tenantId); + } + + @Get('children/:id') + getChildProfile(@Request() req: any, @Param('id') id: string) { + return this.parentService.getChildProfile(req.user.userId, +id, req.user.tenantId); + } + + // ==================== 阅读记录 ==================== + + @Get('children/:id/lessons') + getChildLessons(@Request() req: any, @Param('id') id: string, @Query() query: any) { + return this.parentService.getChildLessons(req.user.userId, +id, req.user.tenantId, query); + } + + // ==================== 任务 ==================== + + @Get('children/:id/tasks') + getChildTasks(@Request() req: any, @Param('id') id: string, @Query() query: any) { + return this.parentService.getChildTasks(req.user.userId, +id, req.user.tenantId, query); + } + + @Put('children/:studentId/tasks/:taskId/feedback') + submitTaskFeedback( + @Request() req: any, + @Param('studentId') studentId: string, + @Param('taskId') taskId: string, + @Body() body: { feedback: string }, + ) { + return this.parentService.submitTaskFeedback( + req.user.userId, + +studentId, + +taskId, + req.user.tenantId, + body.feedback, + ); + } + + // ==================== 成长档案 ==================== + + @Get('children/:id/growth-records') + getChildGrowthRecords(@Request() req: any, @Param('id') id: string, @Query() query: any) { + return this.parentService.getChildGrowthRecords(req.user.userId, +id, req.user.tenantId, query); + } +} diff --git a/reading-platform-backend/src/modules/parent/parent.module.ts b/reading-platform-backend/src/modules/parent/parent.module.ts new file mode 100644 index 0000000..7124c76 --- /dev/null +++ b/reading-platform-backend/src/modules/parent/parent.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { ParentController } from './parent.controller'; +import { ParentService } from './parent.service'; + +@Module({ + controllers: [ParentController], + providers: [ParentService], + exports: [ParentService], +}) +export class ParentModule {} diff --git a/reading-platform-backend/src/modules/parent/parent.service.ts b/reading-platform-backend/src/modules/parent/parent.service.ts new file mode 100644 index 0000000..aa66581 --- /dev/null +++ b/reading-platform-backend/src/modules/parent/parent.service.ts @@ -0,0 +1,309 @@ +import { Injectable, NotFoundException, ForbiddenException, Logger } from '@nestjs/common'; +import { PrismaService } from '../../database/prisma.service'; + +@Injectable() +export class ParentService { + private readonly logger = new Logger(ParentService.name); + + constructor(private prisma: PrismaService) {} + + // ==================== 孩子信息 ==================== + + async getChildren(parentId: number, tenantId: number) { + const parent = await this.prisma.parent.findFirst({ + where: { id: parentId, tenantId }, + include: { + children: { + include: { + student: { + include: { + class: { + select: { + id: true, + name: true, + grade: true, + }, + }, + }, + }, + }, + }, + }, + }); + + if (!parent) { + throw new NotFoundException('家长不存在'); + } + + return parent.children.map((c) => ({ + id: c.student.id, + name: c.student.name, + gender: c.student.gender, + birthDate: c.student.birthDate, + relationship: c.relationship, + class: c.student.class, + readingCount: c.student.readingCount, + lessonCount: c.student.lessonCount, + })); + } + + async getChildProfile(parentId: number, studentId: number, tenantId: number) { + // 验证亲子关系 + const relation = await this.prisma.parentStudent.findFirst({ + where: { parentId, studentId }, + }); + + if (!relation) { + throw new ForbiddenException('您没有查看该学生信息的权限'); + } + + const student = await this.prisma.student.findFirst({ + where: { id: studentId, tenantId }, + include: { + class: { + select: { + id: true, + name: true, + grade: true, + }, + }, + }, + }); + + if (!student) { + throw new NotFoundException('学生不存在'); + } + + // 获取统计数据 + const [lessonRecords, growthRecords, taskCompletions] = await Promise.all([ + this.prisma.studentRecord.count({ + where: { studentId }, + }), + this.prisma.growthRecord.count({ + where: { studentId }, + }), + this.prisma.taskCompletion.count({ + where: { + studentId, + status: 'COMPLETED', + }, + }), + ]); + + return { + ...student, + stats: { + lessonRecords, + growthRecords, + taskCompletions, + }, + }; + } + + // ==================== 阅读记录 ==================== + + async getChildLessons(parentId: number, studentId: number, tenantId: number, query: any) { + // 验证亲子关系 + const relation = await this.prisma.parentStudent.findFirst({ + where: { parentId, studentId }, + }); + + if (!relation) { + throw new ForbiddenException('您没有查看该学生信息的权限'); + } + + const { page = 1, pageSize = 10 } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where = { studentId }; + + const [items, total] = await Promise.all([ + this.prisma.studentRecord.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + include: { + lesson: { + select: { + id: true, + startDatetime: true, + endDatetime: true, + actualDuration: true, + course: { + select: { + id: true, + name: true, + pictureBookName: true, + }, + }, + }, + }, + }, + }), + this.prisma.studentRecord.count({ where }), + ]); + + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + + // ==================== 任务列表 ==================== + + async getChildTasks(parentId: number, studentId: number, tenantId: number, query: any) { + // 验证亲子关系 + const relation = await this.prisma.parentStudent.findFirst({ + where: { parentId, studentId }, + }); + + if (!relation) { + throw new ForbiddenException('您没有查看该学生信息的权限'); + } + + const { page = 1, pageSize = 10, status } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where: any = { + studentId, + task: { tenantId, status: 'PUBLISHED' }, + }; + + if (status) { + where.status = status; + } + + const [items, total] = await Promise.all([ + this.prisma.taskCompletion.findMany({ + where, + skip, + take, + orderBy: { task: { createdAt: 'desc' } }, + include: { + task: { + select: { + id: true, + title: true, + description: true, + taskType: true, + startDate: true, + endDate: true, + course: { + select: { + id: true, + name: true, + }, + }, + }, + }, + }, + }), + this.prisma.taskCompletion.count({ where }), + ]); + + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + + // 提交家长反馈 + async submitTaskFeedback( + parentId: number, + studentId: number, + taskId: number, + tenantId: number, + feedback: string, + ) { + // 验证亲子关系 + const relation = await this.prisma.parentStudent.findFirst({ + where: { parentId, studentId }, + }); + + if (!relation) { + throw new ForbiddenException('您没有操作该学生信息的权限'); + } + + const completion = await this.prisma.taskCompletion.findFirst({ + where: { + taskId, + studentId, + task: { tenantId }, + }, + }); + + if (!completion) { + throw new NotFoundException('任务记录不存在'); + } + + const updated = await this.prisma.taskCompletion.update({ + where: { + taskId_studentId: { taskId, studentId }, + }, + data: { + parentFeedback: feedback, + }, + }); + + this.logger.log(`Parent feedback submitted: task=${taskId}, student=${studentId}`); + + return updated; + } + + // ==================== 成长档案 ==================== + + async getChildGrowthRecords(parentId: number, studentId: number, tenantId: number, query: any) { + // 验证亲子关系 + const relation = await this.prisma.parentStudent.findFirst({ + where: { parentId, studentId }, + }); + + if (!relation) { + throw new ForbiddenException('您没有查看该学生信息的权限'); + } + + const { page = 1, pageSize = 10 } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where = { + studentId, + tenantId, + }; + + const [items, total] = await Promise.all([ + this.prisma.growthRecord.findMany({ + where, + skip, + take, + orderBy: { recordDate: 'desc' }, + include: { + class: { + select: { + id: true, + name: true, + }, + }, + }, + }), + this.prisma.growthRecord.count({ where }), + ]); + + return { + items: items.map((item) => ({ + ...item, + images: item.images ? JSON.parse(item.images) : [], + })), + total, + page: +page, + pageSize: +pageSize, + }; + } +} diff --git a/reading-platform-backend/src/modules/resource/dto/create-resource.dto.ts b/reading-platform-backend/src/modules/resource/dto/create-resource.dto.ts new file mode 100644 index 0000000..e00f347 --- /dev/null +++ b/reading-platform-backend/src/modules/resource/dto/create-resource.dto.ts @@ -0,0 +1,144 @@ +import { IsString, IsNotEmpty, IsOptional, IsInt, IsArray, IsEnum, Min } from 'class-validator'; + +export enum LibraryType { + PICTURE_BOOK = 'PICTURE_BOOK', + MATERIAL = 'MATERIAL', + TEMPLATE = 'TEMPLATE', +} + +export enum FileType { + IMAGE = 'IMAGE', + PDF = 'PDF', + VIDEO = 'VIDEO', + AUDIO = 'AUDIO', + PPT = 'PPT', + OTHER = 'OTHER', +} + +export class CreateLibraryDto { + @IsString() + @IsNotEmpty({ message: '资源库名称不能为空' }) + name: string; + + @IsEnum(LibraryType) + libraryType: LibraryType; + + @IsOptional() + @IsString() + description?: string; + + @IsOptional() + @IsString() + coverImage?: string; +} + +export class UpdateLibraryDto { + @IsOptional() + @IsString() + @IsNotEmpty({ message: '资源库名称不能为空' }) + name?: string; + + @IsOptional() + @IsString() + description?: string; + + @IsOptional() + @IsString() + coverImage?: string; + + @IsOptional() + @IsInt() + @Min(0) + sortOrder?: number; +} + +export class CreateResourceItemDto { + @IsInt() + @IsNotEmpty({ message: '资源库ID不能为空' }) + libraryId: number; + + @IsString() + @IsNotEmpty({ message: '资源名称不能为空' }) + title: string; + + @IsOptional() + @IsString() + description?: string; + + @IsEnum(FileType) + fileType: FileType; + + @IsString() + @IsNotEmpty({ message: '文件路径不能为空' }) + filePath: string; + + @IsOptional() + @IsInt() + fileSize?: number; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + tags?: string[]; +} + +export class UpdateResourceItemDto { + @IsOptional() + @IsString() + @IsNotEmpty({ message: '资源名称不能为空' }) + title?: string; + + @IsOptional() + @IsString() + description?: string; + + @IsOptional() + @IsArray() + @IsString({ each: true }) + tags?: string[]; + + @IsOptional() + @IsInt() + @Min(0) + sortOrder?: number; +} + +export class QueryLibraryDto { + @IsOptional() + @IsInt() + page?: number; + + @IsOptional() + @IsInt() + pageSize?: number; + + @IsOptional() + @IsString() + libraryType?: string; + + @IsOptional() + @IsString() + keyword?: string; +} + +export class QueryResourceItemDto { + @IsOptional() + @IsInt() + page?: number; + + @IsOptional() + @IsInt() + pageSize?: number; + + @IsOptional() + @IsInt() + libraryId?: number; + + @IsOptional() + @IsString() + fileType?: string; + + @IsOptional() + @IsString() + keyword?: string; +} diff --git a/reading-platform-backend/src/modules/resource/resource.controller.ts b/reading-platform-backend/src/modules/resource/resource.controller.ts new file mode 100644 index 0000000..a5e9153 --- /dev/null +++ b/reading-platform-backend/src/modules/resource/resource.controller.ts @@ -0,0 +1,90 @@ +import { + Controller, + Get, + Post, + Put, + Delete, + Body, + Param, + Query, + UseGuards, + Request, +} from '@nestjs/common'; +import { ResourceService } from './resource.service'; +import { CreateLibraryDto, UpdateLibraryDto, CreateResourceItemDto, UpdateResourceItemDto } from './dto/create-resource.dto'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { RolesGuard } from '../common/guards/roles.guard'; +import { Roles } from '../common/decorators/roles.decorator'; + +@Controller('admin/resources') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('admin') +export class ResourceController { + constructor(private readonly resourceService: ResourceService) {} + + // ==================== 资源库管理 ==================== + + @Get('libraries') + findAllLibraries(@Query() query: any) { + return this.resourceService.findAllLibraries(query); + } + + @Get('libraries/:id') + findLibrary(@Param('id') id: string) { + return this.resourceService.findLibrary(+id); + } + + @Post('libraries') + createLibrary(@Body() dto: CreateLibraryDto, @Request() req: any) { + return this.resourceService.createLibrary(dto, req.user.userId); + } + + @Put('libraries/:id') + updateLibrary(@Param('id') id: string, @Body() dto: UpdateLibraryDto) { + return this.resourceService.updateLibrary(+id, dto); + } + + @Delete('libraries/:id') + deleteLibrary(@Param('id') id: string) { + return this.resourceService.deleteLibrary(+id); + } + + // ==================== 资源项目管理 ==================== + + @Get('items') + findAllItems(@Query() query: any) { + return this.resourceService.findAllItems(query); + } + + @Get('items/:id') + findItem(@Param('id') id: string) { + return this.resourceService.findItem(+id); + } + + @Post('items') + createItem(@Body() dto: CreateResourceItemDto) { + return this.resourceService.createItem(dto); + } + + @Put('items/:id') + updateItem(@Param('id') id: string, @Body() dto: UpdateResourceItemDto) { + return this.resourceService.updateItem(+id, dto); + } + + @Delete('items/:id') + deleteItem(@Param('id') id: string) { + return this.resourceService.deleteItem(+id); + } + + @Post('items/batch-delete') + batchDeleteItems(@Body() body: { ids: number[] }) { + return this.resourceService.batchDeleteItems(body.ids); + } + + // ==================== 统计数据 ==================== + + @Get('stats') + getStats() { + return this.resourceService.getStats(); + } +} diff --git a/reading-platform-backend/src/modules/resource/resource.module.ts b/reading-platform-backend/src/modules/resource/resource.module.ts new file mode 100644 index 0000000..5c025e7 --- /dev/null +++ b/reading-platform-backend/src/modules/resource/resource.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { ResourceController } from './resource.controller'; +import { ResourceService } from './resource.service'; + +@Module({ + controllers: [ResourceController], + providers: [ResourceService], + exports: [ResourceService], +}) +export class ResourceModule {} diff --git a/reading-platform-backend/src/modules/resource/resource.service.ts b/reading-platform-backend/src/modules/resource/resource.service.ts new file mode 100644 index 0000000..0c157ae --- /dev/null +++ b/reading-platform-backend/src/modules/resource/resource.service.ts @@ -0,0 +1,357 @@ +import { Injectable, NotFoundException, ForbiddenException, Logger } from '@nestjs/common'; +import { PrismaService } from '../../database/prisma.service'; +import { CreateLibraryDto, UpdateLibraryDto, CreateResourceItemDto, UpdateResourceItemDto } from './dto/create-resource.dto'; + +@Injectable() +export class ResourceService { + private readonly logger = new Logger(ResourceService.name); + + constructor(private prisma: PrismaService) {} + + // ==================== 工具方法 ==================== + + private parseJsonArray(value: any): any[] { + if (!value) return []; + if (typeof value === 'string') { + try { + return JSON.parse(value); + } catch { + return []; + } + } + return Array.isArray(value) ? value : []; + } + + // ==================== 资源库管理 ==================== + + async findAllLibraries(query: any) { + const { page = 1, pageSize = 10, libraryType, keyword } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where: any = {}; + + if (libraryType) { + where.libraryType = libraryType; + } + + if (keyword) { + where.name = { contains: keyword }; + } + + const [items, total] = await Promise.all([ + this.prisma.resourceLibrary.findMany({ + where, + skip, + take, + orderBy: [{ sortOrder: 'asc' }, { createdAt: 'desc' }], + include: { + _count: { + select: { + items: true, + }, + }, + }, + }), + this.prisma.resourceLibrary.count({ where }), + ]); + + return { + items: items.map((lib) => ({ + ...lib, + itemCount: lib._count.items, + _count: undefined, + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + + async findLibrary(id: number) { + const library = await this.prisma.resourceLibrary.findUnique({ + where: { id }, + include: { + items: { + orderBy: { sortOrder: 'asc' }, + take: 100, + }, + }, + }); + + if (!library) { + throw new NotFoundException('资源库不存在'); + } + + return { + ...library, + items: library.items.map((item) => ({ + ...item, + tags: this.parseJsonArray(item.tags), + })), + }; + } + + async createLibrary(dto: CreateLibraryDto, userId: number) { + const library = await this.prisma.resourceLibrary.create({ + data: { + name: dto.name, + libraryType: dto.libraryType, + description: dto.description, + coverImage: dto.coverImage, + createdBy: userId, + }, + }); + + this.logger.log(`Library created: ${library.id}`); + + return library; + } + + async updateLibrary(id: number, dto: UpdateLibraryDto) { + const library = await this.prisma.resourceLibrary.findUnique({ + where: { id }, + }); + + if (!library) { + throw new NotFoundException('资源库不存在'); + } + + const updated = await this.prisma.resourceLibrary.update({ + where: { id }, + data: { + name: dto.name, + description: dto.description, + coverImage: dto.coverImage, + sortOrder: dto.sortOrder, + }, + }); + + this.logger.log(`Library updated: ${id}`); + + return updated; + } + + async deleteLibrary(id: number) { + const library = await this.prisma.resourceLibrary.findUnique({ + where: { id }, + include: { + _count: { + select: { + items: true, + }, + }, + }, + }); + + if (!library) { + throw new NotFoundException('资源库不存在'); + } + + // 删除资源库(会级联删除所有资源项目) + await this.prisma.resourceLibrary.delete({ + where: { id }, + }); + + this.logger.log(`Library deleted: ${id}`); + + return { message: '删除成功' }; + } + + // ==================== 资源项目管理 ==================== + + async findAllItems(query: any) { + const { page = 1, pageSize = 20, libraryId, fileType, keyword } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where: any = {}; + + if (libraryId) { + where.libraryId = +libraryId; + } + + if (fileType) { + where.fileType = fileType; + } + + if (keyword) { + where.OR = [ + { title: { contains: keyword } }, + { description: { contains: keyword } }, + ]; + } + + const [items, total] = await Promise.all([ + this.prisma.resourceItem.findMany({ + where, + skip, + take, + orderBy: [{ sortOrder: 'asc' }, { createdAt: 'desc' }], + include: { + library: { + select: { + id: true, + name: true, + libraryType: true, + }, + }, + }, + }), + this.prisma.resourceItem.count({ where }), + ]); + + return { + items: items.map((item) => ({ + ...item, + tags: this.parseJsonArray(item.tags), + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + + async findItem(id: number) { + const item = await this.prisma.resourceItem.findUnique({ + where: { id }, + include: { + library: { + select: { + id: true, + name: true, + libraryType: true, + }, + }, + }, + }); + + if (!item) { + throw new NotFoundException('资源项目不存在'); + } + + return { + ...item, + tags: this.parseJsonArray(item.tags), + }; + } + + async createItem(dto: CreateResourceItemDto) { + // 检查资源库是否存在 + const library = await this.prisma.resourceLibrary.findUnique({ + where: { id: dto.libraryId }, + }); + + if (!library) { + throw new NotFoundException('资源库不存在'); + } + + const item = await this.prisma.resourceItem.create({ + data: { + libraryId: dto.libraryId, + title: dto.title, + description: dto.description, + fileType: dto.fileType, + filePath: dto.filePath, + fileSize: dto.fileSize, + tags: JSON.stringify(dto.tags || []), + }, + }); + + this.logger.log(`Resource item created: ${item.id}`); + + return { + ...item, + tags: this.parseJsonArray(item.tags), + }; + } + + async updateItem(id: number, dto: UpdateResourceItemDto) { + const item = await this.prisma.resourceItem.findUnique({ + where: { id }, + }); + + if (!item) { + throw new NotFoundException('资源项目不存在'); + } + + const updated = await this.prisma.resourceItem.update({ + where: { id }, + data: { + title: dto.title, + description: dto.description, + tags: dto.tags ? JSON.stringify(dto.tags) : undefined, + sortOrder: dto.sortOrder, + }, + }); + + this.logger.log(`Resource item updated: ${id}`); + + return { + ...updated, + tags: this.parseJsonArray(updated.tags), + }; + } + + async deleteItem(id: number) { + const item = await this.prisma.resourceItem.findUnique({ + where: { id }, + }); + + if (!item) { + throw new NotFoundException('资源项目不存在'); + } + + await this.prisma.resourceItem.delete({ + where: { id }, + }); + + this.logger.log(`Resource item deleted: ${id}`); + + return { message: '删除成功' }; + } + + async batchDeleteItems(ids: number[]) { + await this.prisma.resourceItem.deleteMany({ + where: { + id: { in: ids }, + }, + }); + + this.logger.log(`Batch deleted ${ids.length} resource items`); + + return { message: `成功删除 ${ids.length} 个资源` }; + } + + // ==================== 统计数据 ==================== + + async getStats() { + const [totalLibraries, totalItems, itemsByType, itemsByLibraryType] = await Promise.all([ + this.prisma.resourceLibrary.count(), + this.prisma.resourceItem.count(), + this.prisma.resourceItem.groupBy({ + by: ['fileType'], + _count: true, + }), + this.prisma.resourceLibrary.groupBy({ + by: ['libraryType'], + _count: true, + }), + ]); + + return { + totalLibraries, + totalItems, + itemsByType: itemsByType.reduce((acc, item) => { + acc[item.fileType] = item._count; + return acc; + }, {} as Record), + itemsByLibraryType: itemsByLibraryType.reduce((acc, lib) => { + acc[lib.libraryType] = lib._count; + return acc; + }, {} as Record), + }; + } +} diff --git a/reading-platform-backend/src/modules/school/dto/class-teacher.dto.ts b/reading-platform-backend/src/modules/school/dto/class-teacher.dto.ts new file mode 100644 index 0000000..5631ff8 --- /dev/null +++ b/reading-platform-backend/src/modules/school/dto/class-teacher.dto.ts @@ -0,0 +1,40 @@ +import { IsInt, IsString, IsBoolean, IsOptional, IsIn } from 'class-validator'; + +// 教师角色类型 +export type TeacherRole = 'MAIN' | 'ASSIST' | 'CARE'; + +// 添加班级教师 DTO +export class AddClassTeacherDto { + @IsInt() + teacherId: number; + + @IsString() + @IsIn(['MAIN', 'ASSIST', 'CARE']) + role: TeacherRole; + + @IsBoolean() + @IsOptional() + isPrimary?: boolean; +} + +// 更新班级教师 DTO +export class UpdateClassTeacherDto { + @IsString() + @IsIn(['MAIN', 'ASSIST', 'CARE']) + @IsOptional() + role?: TeacherRole; + + @IsBoolean() + @IsOptional() + isPrimary?: boolean; +} + +// 学生调班 DTO +export class TransferStudentDto { + @IsInt() + toClassId: number; + + @IsString() + @IsOptional() + reason?: string; +} diff --git a/reading-platform-backend/src/modules/school/dto/create-class.dto.ts b/reading-platform-backend/src/modules/school/dto/create-class.dto.ts new file mode 100644 index 0000000..df5b22a --- /dev/null +++ b/reading-platform-backend/src/modules/school/dto/create-class.dto.ts @@ -0,0 +1,31 @@ +import { IsString, IsNotEmpty, IsOptional, IsArray, IsInt } from 'class-validator'; + +export class CreateClassDto { + @IsString() + @IsNotEmpty({ message: '班级名称不能为空' }) + name: string; + + @IsString() + @IsNotEmpty({ message: '年级不能为空' }) + grade: string; + + @IsOptional() + @IsInt() + teacherId?: number; +} + +export class UpdateClassDto { + @IsOptional() + @IsString() + @IsNotEmpty({ message: '班级名称不能为空' }) + name?: string; + + @IsOptional() + @IsString() + @IsNotEmpty({ message: '年级不能为空' }) + grade?: string; + + @IsOptional() + @IsInt() + teacherId?: number; +} diff --git a/reading-platform-backend/src/modules/school/dto/create-student.dto.ts b/reading-platform-backend/src/modules/school/dto/create-student.dto.ts new file mode 100644 index 0000000..07bd67c --- /dev/null +++ b/reading-platform-backend/src/modules/school/dto/create-student.dto.ts @@ -0,0 +1,56 @@ +import { IsString, IsNotEmpty, IsOptional, IsInt, Matches, IsIn } from 'class-validator'; + +export class CreateStudentDto { + @IsString() + @IsNotEmpty({ message: '姓名不能为空' }) + name: string; + + @IsOptional() + @IsIn(['男', '女'], { message: '性别只能是男或女' }) + gender?: string; + + @IsOptional() + @IsString() + birthDate?: string; + + @IsInt() + @IsNotEmpty({ message: '班级不能为空' }) + classId: number; + + @IsOptional() + @IsString() + parentName?: string; + + @IsOptional() + @IsString() + @Matches(/^1[3-9]\d{9}$/, { message: '请输入正确的手机号' }) + parentPhone?: string; +} + +export class UpdateStudentDto { + @IsOptional() + @IsString() + @IsNotEmpty({ message: '姓名不能为空' }) + name?: string; + + @IsOptional() + @IsIn(['男', '女'], { message: '性别只能是男或女' }) + gender?: string; + + @IsOptional() + @IsString() + birthDate?: string; + + @IsOptional() + @IsInt() + classId?: number; + + @IsOptional() + @IsString() + parentName?: string; + + @IsOptional() + @IsString() + @Matches(/^1[3-9]\d{9}$/, { message: '请输入正确的手机号' }) + parentPhone?: string; +} diff --git a/reading-platform-backend/src/modules/school/dto/create-teacher.dto.ts b/reading-platform-backend/src/modules/school/dto/create-teacher.dto.ts new file mode 100644 index 0000000..7112964 --- /dev/null +++ b/reading-platform-backend/src/modules/school/dto/create-teacher.dto.ts @@ -0,0 +1,51 @@ +import { IsString, IsNotEmpty, IsOptional, IsEmail, IsArray, IsInt, MinLength, Matches } from 'class-validator'; + +export class CreateTeacherDto { + @IsString() + @IsNotEmpty({ message: '姓名不能为空' }) + name: string; + + @IsString() + @IsNotEmpty({ message: '手机号不能为空' }) + @Matches(/^1[3-9]\d{9}$/, { message: '请输入正确的手机号' }) + phone: string; + + @IsOptional() + @IsEmail({}, { message: '请输入正确的邮箱格式' }) + email?: string; + + @IsString() + @IsNotEmpty({ message: '登录账号不能为空' }) + loginAccount: string; + + @IsOptional() + @IsString() + @MinLength(6, { message: '密码至少6位' }) + password?: string; + + @IsOptional() + @IsArray() + @IsInt({ each: true }) + classIds?: number[]; +} + +export class UpdateTeacherDto { + @IsOptional() + @IsString() + @IsNotEmpty({ message: '姓名不能为空' }) + name?: string; + + @IsOptional() + @IsString() + @Matches(/^1[3-9]\d{9}$/, { message: '请输入正确的手机号' }) + phone?: string; + + @IsOptional() + @IsEmail({}, { message: '请输入正确的邮箱格式' }) + email?: string; + + @IsOptional() + @IsArray() + @IsInt({ each: true }) + classIds?: number[]; +} diff --git a/reading-platform-backend/src/modules/school/dto/import-students.dto.ts b/reading-platform-backend/src/modules/school/dto/import-students.dto.ts new file mode 100644 index 0000000..ae9deba --- /dev/null +++ b/reading-platform-backend/src/modules/school/dto/import-students.dto.ts @@ -0,0 +1,7 @@ +import { IsOptional, IsInt } from 'class-validator'; + +export class ImportStudentsDto { + @IsOptional() + @IsInt() + classId?: number; +} diff --git a/reading-platform-backend/src/modules/school/dto/schedule.dto.ts b/reading-platform-backend/src/modules/school/dto/schedule.dto.ts new file mode 100644 index 0000000..e2b2d59 --- /dev/null +++ b/reading-platform-backend/src/modules/school/dto/schedule.dto.ts @@ -0,0 +1,166 @@ +import { IsInt, IsOptional, IsDateString, IsString, IsEnum, Min, Max, IsNotEmpty } from 'class-validator'; + +export class CreateScheduleDto { + @IsInt() + @IsNotEmpty() + classId: number; + + @IsInt() + @IsNotEmpty() + courseId: number; + + @IsOptional() + @IsInt() + teacherId?: number; + + @IsOptional() + @IsDateString() + scheduledDate?: string; + + @IsOptional() + @IsString() + scheduledTime?: string; + + @IsOptional() + @IsInt() + @Min(0) + @Max(6) + weekDay?: number; + + @IsEnum(['NONE', 'DAILY', 'WEEKLY']) + repeatType: string; + + @IsOptional() + @IsDateString() + repeatEndDate?: string; + + @IsOptional() + @IsString() + note?: string; +} + +export class UpdateScheduleDto { + @IsOptional() + @IsInt() + teacherId?: number; + + @IsOptional() + @IsDateString() + scheduledDate?: string; + + @IsOptional() + @IsString() + scheduledTime?: string; + + @IsOptional() + @IsInt() + @Min(0) + @Max(6) + weekDay?: number; + + @IsOptional() + @IsEnum(['NONE', 'DAILY', 'WEEKLY']) + repeatType?: string; + + @IsOptional() + @IsDateString() + repeatEndDate?: string; + + @IsOptional() + @IsString() + note?: string; + + @IsOptional() + @IsString() + status?: string; +} + +export class QueryScheduleDto { + @IsOptional() + @IsInt() + classId?: number; + + @IsOptional() + @IsInt() + teacherId?: number; + + @IsOptional() + @IsInt() + courseId?: number; + + @IsOptional() + @IsDateString() + startDate?: string; + + @IsOptional() + @IsDateString() + endDate?: string; + + @IsOptional() + @IsString() + status?: string; + + @IsOptional() + @IsString() + source?: string; + + @IsOptional() + @IsInt() + @Min(1) + page?: number; + + @IsOptional() + @IsInt() + @Min(1) + @Max(100) + pageSize?: number; +} + +export class TimetableQueryDto { + @IsNotEmpty() + @IsDateString() + startDate: string; + + @IsNotEmpty() + @IsDateString() + endDate: string; + + @IsOptional() + @IsInt() + classId?: number; + + @IsOptional() + @IsInt() + teacherId?: number; +} + +export class BatchScheduleItemDto { + @IsInt() + @IsNotEmpty() + classId: number; + + @IsInt() + @IsNotEmpty() + courseId: number; + + @IsOptional() + @IsInt() + teacherId?: number; + + @IsDateString() + @IsNotEmpty() + scheduledDate: string; + + @IsOptional() + @IsString() + scheduledTime?: string; + + @IsOptional() + @IsString() + note?: string; +} + +export class BatchCreateScheduleDto { + @IsNotEmpty() + schedules: BatchScheduleItemDto[]; +} diff --git a/reading-platform-backend/src/modules/school/export.controller.ts b/reading-platform-backend/src/modules/school/export.controller.ts new file mode 100644 index 0000000..1513092 --- /dev/null +++ b/reading-platform-backend/src/modules/school/export.controller.ts @@ -0,0 +1,109 @@ +import { + Controller, + Get, + Query, + UseGuards, + Request, + Res, +} from '@nestjs/common'; +import { Response } from 'express'; +import { ExportService } from './export.service'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { RolesGuard } from '../common/guards/roles.guard'; +import { Roles } from '../common/decorators/roles.decorator'; + +@Controller('school') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('school') +export class ExportController { + constructor(private readonly exportService: ExportService) {} + + @Get('export/lessons') + async exportLessons( + @Request() req: any, + @Query('startDate') startDate?: string, + @Query('endDate') endDate?: string, + @Res() res?: Response, + ) { + const buffer = await this.exportService.exportLessons( + req.user.tenantId, + startDate, + endDate, + ); + + const filename = `授课记录_${this.getDateRangeFilename(startDate, endDate)}.xlsx`; + this.sendExcelResponse(res, buffer, filename); + } + + @Get('export/teacher-stats') + async exportTeacherStats( + @Request() req: any, + @Query('startDate') startDate?: string, + @Query('endDate') endDate?: string, + @Res() res?: Response, + ) { + const buffer = await this.exportService.exportTeacherStats( + req.user.tenantId, + startDate, + endDate, + ); + + const filename = `教师绩效统计_${this.getDateRangeFilename(startDate, endDate)}.xlsx`; + this.sendExcelResponse(res, buffer, filename); + } + + @Get('export/student-stats') + async exportStudentStats( + @Request() req: any, + @Query('classId') classId?: string, + @Res() res?: Response, + ) { + const buffer = await this.exportService.exportStudentStats( + req.user.tenantId, + classId ? parseInt(classId, 10) : undefined, + ); + + const filename = `学生统计_${this.formatDate(new Date())}.xlsx`; + this.sendExcelResponse(res, buffer, filename); + } + + /** + * 发送 Excel 响应 + */ + private sendExcelResponse(res: Response, buffer: Buffer, filename: string) { + res.setHeader( + 'Content-Type', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + ); + res.setHeader( + 'Content-Disposition', + `attachment; filename="${encodeURIComponent(filename)}"`, + ); + res.setHeader('Content-Length', buffer.length); + res.send(buffer); + } + + /** + * 生成日期范围文件名 + */ + private getDateRangeFilename(startDate?: string, endDate?: string): string { + if (startDate && endDate) { + return `${startDate}_${endDate}`; + } else if (startDate) { + return `${startDate}_至今`; + } else if (endDate) { + return `至${endDate}`; + } + return this.formatDate(new Date()); + } + + /** + * 格式化日期 + */ + private formatDate(date: Date): string { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}${month}${day}`; + } +} diff --git a/reading-platform-backend/src/modules/school/export.service.ts b/reading-platform-backend/src/modules/school/export.service.ts new file mode 100644 index 0000000..fa4e151 --- /dev/null +++ b/reading-platform-backend/src/modules/school/export.service.ts @@ -0,0 +1,276 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { PrismaService } from '../../database/prisma.service'; +import * as XLSX from 'xlsx'; + +@Injectable() +export class ExportService { + private readonly logger = new Logger(ExportService.name); + + constructor(private prisma: PrismaService) {} + + /** + * 导出授课记录 + */ + async exportLessons(tenantId: number, startDate?: string, endDate?: string) { + const where: any = { + tenantId, + status: 'COMPLETED', + }; + + if (startDate || endDate) { + where.createdAt = {}; + if (startDate) where.createdAt.gte = new Date(startDate); + if (endDate) where.createdAt.lte = new Date(endDate); + } + + const lessons = await this.prisma.lesson.findMany({ + where, + orderBy: { + createdAt: 'desc', + }, + include: { + course: { + select: { + name: true, + pictureBookName: true, + duration: true, + }, + }, + teacher: { + select: { + name: true, + }, + }, + class: { + select: { + name: true, + grade: true, + }, + }, + }, + }); + + // 转换为 Excel 数据格式 + const data = lessons.map((lesson, index) => ({ + '序号': index + 1, + '课程名称': lesson.course?.name || '', + '绘本名称': lesson.course?.pictureBookName || '', + '授课教师': lesson.teacher?.name || '', + '班级': lesson.class?.name || '', + '年级': lesson.class?.grade || '', + '计划时长(分钟)': lesson.course?.duration || '', + '实际时长(分钟)': lesson.actualDuration || '', + '授课日期': lesson.startDatetime + ? new Date(lesson.startDatetime).toLocaleDateString('zh-CN') + : '', + '开始时间': lesson.startDatetime + ? new Date(lesson.startDatetime).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }) + : '', + '结束时间': lesson.endDatetime + ? new Date(lesson.endDatetime).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }) + : '', + '状态': this.getStatusText(lesson.status), + '备注': lesson.completionNote || '', + })); + + return this.generateExcelBuffer(data, '授课记录'); + } + + /** + * 导出教师绩效统计 + */ + async exportTeacherStats(tenantId: number, startDate?: string, endDate?: string) { + // 获取所有教师 + const teachers = await this.prisma.teacher.findMany({ + where: { + tenantId, + status: 'ACTIVE', + }, + select: { + id: true, + name: true, + phone: true, + email: true, + lessonCount: true, + createdAt: true, + }, + }); + + // 构建查询条件 + const lessonWhere: any = { + tenantId, + status: 'COMPLETED', + }; + + if (startDate || endDate) { + lessonWhere.createdAt = {}; + if (startDate) lessonWhere.createdAt.gte = new Date(startDate); + if (endDate) lessonWhere.createdAt.lte = new Date(endDate); + } + + // 获取每个教师的详细统计 + const teacherStats = await Promise.all( + teachers.map(async (teacher) => { + // 该时间段内的授课次数 + const periodLessons = await this.prisma.lesson.count({ + where: { + ...lessonWhere, + teacherId: teacher.id, + }, + }); + + // 获取反馈统计 + const feedbackWhere: any = { + teacherId: teacher.id, + }; + + if (startDate || endDate) { + feedbackWhere.lesson = { + endDatetime: {}, + }; + if (startDate) feedbackWhere.lesson.endDatetime.gte = new Date(startDate); + if (endDate) feedbackWhere.lesson.endDatetime.lte = new Date(endDate); + } + + const feedbacks = await this.prisma.lessonFeedback.findMany({ + where: feedbackWhere, + select: { + designQuality: true, + participation: true, + goalAchievement: true, + }, + }); + + let avgRating = 0; + if (feedbacks.length > 0) { + const totalRating = feedbacks.reduce((sum, f) => { + const ratings = [f.designQuality, f.participation, f.goalAchievement].filter((r) => r !== null); + const avg = ratings.length > 0 ? ratings.reduce((s, r) => s + r, 0) / ratings.length : 0; + return sum + avg; + }, 0); + avgRating = Math.round((totalRating / feedbacks.length) * 100) / 100; + } + + // 获取关联班级数 + const classCount = await this.prisma.classTeacher.count({ + where: { + teacherId: teacher.id, + }, + }); + + return { + '教师姓名': teacher.name, + '联系电话': teacher.phone, + '邮箱': teacher.email || '', + '关联班级数': classCount, + '累计授课次数': teacher.lessonCount, + '本期授课次数': periodLessons, + '平均评分': avgRating || '暂无评分', + '入职日期': new Date(teacher.createdAt).toLocaleDateString('zh-CN'), + }; + }), + ); + + // 按本期授课次数排序 + teacherStats.sort((a, b) => (b['本期授课次数'] as number) - (a['本期授课次数'] as number)); + + // 添加排名 + const dataWithRank = teacherStats.map((item, index) => ({ + '排名': index + 1, + ...item, + })); + + return this.generateExcelBuffer(dataWithRank, '教师绩效'); + } + + /** + * 导出学生统计 + */ + async exportStudentStats(tenantId: number, classId?: number) { + const where: any = { tenantId }; + + if (classId) { + where.classId = classId; + } + + const students = await this.prisma.student.findMany({ + where, + include: { + class: { + select: { + name: true, + grade: true, + }, + }, + }, + orderBy: { + createdAt: 'desc', + }, + }); + + const data = students.map((student, index) => ({ + '序号': index + 1, + '学生姓名': student.name, + '性别': student.gender === 'MALE' ? '男' : student.gender === 'FEMALE' ? '女' : '', + '出生日期': student.birthDate + ? new Date(student.birthDate).toLocaleDateString('zh-CN') + : '', + '班级': student.class?.name || '', + '年级': student.class?.grade || '', + '家长姓名': student.parentName || '', + '联系电话': student.parentPhone || '', + '参与课程数': student.lessonCount, + '阅读记录数': student.readingCount, + '入校日期': new Date(student.createdAt).toLocaleDateString('zh-CN'), + })); + + return this.generateExcelBuffer(data, '学生统计'); + } + + /** + * 生成 Excel Buffer + */ + private generateExcelBuffer(data: any[], sheetName: string): Buffer { + const worksheet = XLSX.utils.json_to_sheet(data); + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, sheetName); + + // 设置列宽 + const colWidths = this.calculateColumnWidths(data); + worksheet['!cols'] = colWidths; + + return XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }); + } + + /** + * 计算列宽 + */ + private calculateColumnWidths(data: any[]): { wch: number }[] { + if (data.length === 0) return []; + + const headers = Object.keys(data[0]); + return headers.map((header) => { + // 计算该列的最大宽度 + let maxWidth = header.length; + data.forEach((row) => { + const value = String(row[header] || ''); + maxWidth = Math.max(maxWidth, value.length); + }); + // 限制最大宽度并添加一些padding + return { wch: Math.min(maxWidth + 2, 50) }; + }); + } + + /** + * 状态文本转换 + */ + private getStatusText(status: string): string { + const statusMap: Record = { + PLANNED: '已计划', + IN_PROGRESS: '进行中', + COMPLETED: '已完成', + CANCELLED: '已取消', + }; + return statusMap[status] || status; + } +} diff --git a/reading-platform-backend/src/modules/school/package.controller.ts b/reading-platform-backend/src/modules/school/package.controller.ts new file mode 100644 index 0000000..d79c0c9 --- /dev/null +++ b/reading-platform-backend/src/modules/school/package.controller.ts @@ -0,0 +1,100 @@ +import { + Controller, + Get, + UseGuards, + Request, +} from '@nestjs/common'; +import { PrismaService } from '../../database/prisma.service'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { RolesGuard } from '../common/guards/roles.guard'; +import { Roles } from '../common/decorators/roles.decorator'; + +@Controller('school') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('school') +export class PackageController { + constructor(private prisma: PrismaService) {} + + @Get('package') + async getPackageInfo(@Request() req: any) { + const tenantId = req.user.tenantId; + + const [tenant, teacherCount, studentCount] = await Promise.all([ + this.prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { + id: true, + name: true, + packageType: true, + teacherQuota: true, + studentQuota: true, + storageQuota: true, + storageUsed: true, + startDate: true, + expireDate: true, + status: true, + }, + }), + this.prisma.teacher.count({ where: { tenantId } }), + this.prisma.student.count({ where: { tenantId } }), + ]); + + if (!tenant) { + return null; + } + + return { + packageType: tenant.packageType, + teacherQuota: tenant.teacherQuota, + studentQuota: tenant.studentQuota, + storageQuota: Number(tenant.storageQuota), + teacherCount: teacherCount, + studentCount: studentCount, + storageUsed: Number(tenant.storageUsed), + startDate: tenant.startDate, + expireDate: tenant.expireDate, + status: tenant.status, + }; + } + + @Get('package/usage') + async getPackageUsage(@Request() req: any) { + const tenantId = req.user.tenantId; + + const [tenant, teacherCount, studentCount] = await Promise.all([ + this.prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { + teacherQuota: true, + studentQuota: true, + storageQuota: true, + storageUsed: true, + }, + }), + this.prisma.teacher.count({ where: { tenantId } }), + this.prisma.student.count({ where: { tenantId } }), + ]); + + if (!tenant) { + return null; + } + + return { + teacher: { + used: teacherCount, + quota: tenant.teacherQuota, + percentage: tenant.teacherQuota > 0 ? Math.round((teacherCount / tenant.teacherQuota) * 100) : 0, + }, + student: { + used: studentCount, + quota: tenant.studentQuota, + percentage: tenant.studentQuota > 0 ? Math.round((studentCount / tenant.studentQuota) * 100) : 0, + }, + storage: { + used: Number(tenant.storageUsed), + quota: Number(tenant.storageQuota), + percentage: Number(tenant.storageQuota) > 0 ? Math.round((Number(tenant.storageUsed) / Number(tenant.storageQuota)) * 100) : 0, + }, + }; + } +} diff --git a/reading-platform-backend/src/modules/school/school.controller.ts b/reading-platform-backend/src/modules/school/school.controller.ts new file mode 100644 index 0000000..b2d3dcc --- /dev/null +++ b/reading-platform-backend/src/modules/school/school.controller.ts @@ -0,0 +1,375 @@ +import { + Controller, + Get, + Post, + Put, + Delete, + Body, + Param, + Query, + UseGuards, + Request, + UploadedFile, + UseInterceptors, + BadRequestException, +} from '@nestjs/common'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { SchoolService } from './school.service'; +import { CreateTeacherDto, UpdateTeacherDto } from './dto/create-teacher.dto'; +import { CreateStudentDto, UpdateStudentDto } from './dto/create-student.dto'; +import { CreateClassDto, UpdateClassDto } from './dto/create-class.dto'; +import { AddClassTeacherDto, UpdateClassTeacherDto, TransferStudentDto } from './dto/class-teacher.dto'; +import { CreateScheduleDto, UpdateScheduleDto, QueryScheduleDto, TimetableQueryDto } from './dto/schedule.dto'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { RolesGuard } from '../common/guards/roles.guard'; +import { Roles } from '../common/decorators/roles.decorator'; +import { LogOperation } from '../common/decorators/log-operation.decorator'; +import { LogInterceptor } from '../common/interceptors/log.interceptor'; + +@Controller('school') +@UseGuards(JwtAuthGuard, RolesGuard) +@UseInterceptors(LogInterceptor) +@Roles('school') +export class SchoolController { + constructor(private readonly schoolService: SchoolService) {} + + // ==================== 教师管理 ==================== + + @Get('teachers') + findTeachers(@Request() req: any, @Query() query: any) { + return this.schoolService.findTeachers(req.user.tenantId, query); + } + + @Get('teachers/:id') + findTeacher(@Request() req: any, @Param('id') id: string) { + return this.schoolService.findTeacher(req.user.tenantId, +id); + } + + @Post('teachers') + createTeacher(@Request() req: any, @Body() dto: CreateTeacherDto) { + return this.schoolService.createTeacher(req.user.tenantId, dto); + } + + @Put('teachers/:id') + updateTeacher( + @Request() req: any, + @Param('id') id: string, + @Body() dto: UpdateTeacherDto, + ) { + return this.schoolService.updateTeacher(req.user.tenantId, +id, dto); + } + + @Delete('teachers/:id') + deleteTeacher(@Request() req: any, @Param('id') id: string) { + return this.schoolService.deleteTeacher(req.user.tenantId, +id); + } + + @Post('teachers/:id/reset-password') + resetTeacherPassword(@Request() req: any, @Param('id') id: string) { + return this.schoolService.resetTeacherPassword(req.user.tenantId, +id); + } + + // ==================== 学生管理 ==================== + + @Get('students') + findStudents(@Request() req: any, @Query() query: any) { + return this.schoolService.findStudents(req.user.tenantId, query); + } + + @Get('students/:id') + findStudent(@Request() req: any, @Param('id') id: string) { + return this.schoolService.findStudent(req.user.tenantId, +id); + } + + @Post('students') + createStudent(@Request() req: any, @Body() dto: CreateStudentDto) { + return this.schoolService.createStudent(req.user.tenantId, dto); + } + + @Put('students/:id') + updateStudent( + @Request() req: any, + @Param('id') id: string, + @Body() dto: UpdateStudentDto, + ) { + return this.schoolService.updateStudent(req.user.tenantId, +id, dto); + } + + @Delete('students/:id') + deleteStudent(@Request() req: any, @Param('id') id: string) { + return this.schoolService.deleteStudent(req.user.tenantId, +id); + } + + // ==================== 学生调班 ==================== + + @Post('students/:id/transfer') + transferStudent( + @Request() req: any, + @Param('id') id: string, + @Body() dto: TransferStudentDto, + ) { + return this.schoolService.transferStudent(req.user.tenantId, +id, dto); + } + + @Get('students/:id/history') + getStudentClassHistory(@Request() req: any, @Param('id') id: string) { + return this.schoolService.getStudentClassHistory(req.user.tenantId, +id); + } + + @Post('students/import') + @UseInterceptors(FileInterceptor('file')) + async importStudents( + @Request() req: any, + @UploadedFile() file: Express.Multer.File, + @Query('defaultClassId') defaultClassId?: string, + ) { + if (!file) { + throw new BadRequestException('请上传文件'); + } + + const studentsData = await this.schoolService.parseStudentImportFile(file); + return this.schoolService.importStudents( + req.user.tenantId, + studentsData, + defaultClassId ? +defaultClassId : undefined, + ); + } + + @Get('students/import/template') + getImportTemplate() { + return { + headers: ['姓名', '性别', '出生日期', '班级ID', '家长姓名', '家长电话'], + example: ['张小明', '男', '2020-01-15', '1', '张三', '13800138000'], + notes: [ + '姓名为必填项', + '性别可选:男/女,默认为男', + '出生日期格式:YYYY-MM-DD', + '班级ID为必填项,可在班级管理中查看', + '家长姓名和家长电话为选填项', + ], + }; + } + + // ==================== 班级管理 ==================== + + @Get('classes') + findClasses(@Request() req: any) { + return this.schoolService.findClasses(req.user.tenantId); + } + + @Get('classes/:id') + findClass(@Request() req: any, @Param('id') id: string) { + return this.schoolService.findClass(req.user.tenantId, +id); + } + + @Get('classes/:id/students') + findClassStudents( + @Request() req: any, + @Param('id') id: string, + @Query() query: any, + ) { + return this.schoolService.findClassStudents(req.user.tenantId, +id, query); + } + + @Post('classes') + createClass(@Request() req: any, @Body() dto: CreateClassDto) { + return this.schoolService.createClass(req.user.tenantId, dto); + } + + @Put('classes/:id') + updateClass( + @Request() req: any, + @Param('id') id: string, + @Body() dto: UpdateClassDto, + ) { + return this.schoolService.updateClass(req.user.tenantId, +id, dto); + } + + @Delete('classes/:id') + deleteClass(@Request() req: any, @Param('id') id: string) { + return this.schoolService.deleteClass(req.user.tenantId, +id); + } + + // ==================== 班级教师管理 ==================== + + @Get('classes/:id/teachers') + findClassTeachers(@Request() req: any, @Param('id') id: string) { + return this.schoolService.findClassTeachers(req.user.tenantId, +id); + } + + @Post('classes/:id/teachers') + addClassTeacher( + @Request() req: any, + @Param('id') id: string, + @Body() dto: AddClassTeacherDto, + ) { + return this.schoolService.addClassTeacher(req.user.tenantId, +id, dto); + } + + @Put('classes/:id/teachers/:teacherId') + updateClassTeacher( + @Request() req: any, + @Param('id') id: string, + @Param('teacherId') teacherId: string, + @Body() dto: UpdateClassTeacherDto, + ) { + return this.schoolService.updateClassTeacher( + req.user.tenantId, + +id, + +teacherId, + dto, + ); + } + + @Delete('classes/:id/teachers/:teacherId') + removeClassTeacher( + @Request() req: any, + @Param('id') id: string, + @Param('teacherId') teacherId: string, + ) { + return this.schoolService.removeClassTeacher(req.user.tenantId, +id, +teacherId); + } + + // ==================== 家长管理 ==================== + + @Get('parents') + findParents(@Request() req: any, @Query() query: any) { + return this.schoolService.findParents(req.user.tenantId, query); + } + + @Get('parents/:id') + findParent(@Request() req: any, @Param('id') id: string) { + return this.schoolService.findParent(req.user.tenantId, +id); + } + + @Post('parents') + createParent(@Request() req: any, @Body() dto: any) { + return this.schoolService.createParent(req.user.tenantId, dto); + } + + @Put('parents/:id') + updateParent(@Request() req: any, @Param('id') id: string, @Body() dto: any) { + return this.schoolService.updateParent(req.user.tenantId, +id, dto); + } + + @Delete('parents/:id') + deleteParent(@Request() req: any, @Param('id') id: string) { + return this.schoolService.deleteParent(req.user.tenantId, +id); + } + + @Post('parents/:id/reset-password') + resetParentPassword(@Request() req: any, @Param('id') id: string) { + return this.schoolService.resetParentPassword(req.user.tenantId, +id); + } + + @Post('parents/:parentId/children/:studentId') + addChildToParent( + @Request() req: any, + @Param('parentId') parentId: string, + @Param('studentId') studentId: string, + @Body() body: { relationship: string }, + ) { + return this.schoolService.addChildToParent( + req.user.tenantId, + +parentId, + +studentId, + body.relationship, + ); + } + + @Delete('parents/:parentId/children/:studentId') + removeChildFromParent( + @Request() req: any, + @Param('parentId') parentId: string, + @Param('studentId') studentId: string, + ) { + return this.schoolService.removeChildFromParent(req.user.tenantId, +parentId, +studentId); + } + + // ==================== 课程管理 ==================== + + @Get('courses') + findCourses(@Request() req: any) { + return this.schoolService.findCourses(req.user.tenantId); + } + + @Get('courses/:id') + findCourse(@Request() req: any, @Param('id') id: string) { + return this.schoolService.findCourse(req.user.tenantId, +id); + } + + // ==================== 排课管理 ==================== + + @Get('schedules') + findSchedules(@Request() req: any, @Query() query: QueryScheduleDto) { + return this.schoolService.findSchedules(req.user.tenantId, query); + } + + @Get('schedules/timetable') + getTimetable(@Request() req: any, @Query() query: TimetableQueryDto) { + return this.schoolService.getTimetable(req.user.tenantId, query); + } + + @Get('schedules/:id') + findSchedule(@Request() req: any, @Param('id') id: string) { + return this.schoolService.findSchedule(req.user.tenantId, +id); + } + + @Post('schedules') + @LogOperation({ module: '排课管理', action: '创建排课', description: '创建新的课程排期' }) + createSchedule(@Request() req: any, @Body() dto: CreateScheduleDto) { + return this.schoolService.createSchedule(req.user.tenantId, dto, req.user.userId); + } + + @Put('schedules/:id') + updateSchedule( + @Request() req: any, + @Param('id') id: string, + @Body() dto: UpdateScheduleDto, + ) { + return this.schoolService.updateSchedule(req.user.tenantId, +id, dto); + } + + @Delete('schedules/:id') + cancelSchedule(@Request() req: any, @Param('id') id: string) { + return this.schoolService.cancelSchedule(req.user.tenantId, +id); + } + + @Post('schedules/batch') + @LogOperation({ module: '排课管理', action: '批量创建排课', description: '批量创建课程排期' }) + batchCreateSchedules(@Request() req: any, @Body() dto: { schedules: any[] }) { + return this.schoolService.batchCreateSchedules(req.user.tenantId, dto.schedules); + } + + // ==================== 排课模板 ==================== + + @Get('schedule-templates') + getScheduleTemplates(@Request() req: any, @Query() query: any) { + return this.schoolService.getScheduleTemplates(req.user.tenantId, query); + } + + @Get('schedule-templates/:id') + getScheduleTemplate(@Request() req: any, @Param('id') id: string) { + return this.schoolService.getScheduleTemplate(req.user.tenantId, +id); + } + + @Post('schedule-templates') + createScheduleTemplate(@Request() req: any, @Body() dto: any) { + return this.schoolService.createScheduleTemplate(req.user.tenantId, dto); + } + + @Put('schedule-templates/:id') + updateScheduleTemplate(@Request() req: any, @Param('id') id: string, @Body() dto: any) { + return this.schoolService.updateScheduleTemplate(req.user.tenantId, +id, dto); + } + + @Delete('schedule-templates/:id') + deleteScheduleTemplate(@Request() req: any, @Param('id') id: string) { + return this.schoolService.deleteScheduleTemplate(req.user.tenantId, +id); + } + + @Post('schedule-templates/:id/apply') + applyScheduleTemplate(@Request() req: any, @Param('id') id: string, @Body() dto: any) { + return this.schoolService.applyScheduleTemplate(req.user.tenantId, +id, dto); + } +} diff --git a/reading-platform-backend/src/modules/school/school.module.ts b/reading-platform-backend/src/modules/school/school.module.ts new file mode 100644 index 0000000..c7d809a --- /dev/null +++ b/reading-platform-backend/src/modules/school/school.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { SchoolController } from './school.controller'; +import { SchoolService } from './school.service'; +import { StatsController } from './stats.controller'; +import { StatsService } from './stats.service'; +import { PackageController } from './package.controller'; +import { SettingsController } from './settings.controller'; +import { SettingsService } from './settings.service'; +import { ExportController } from './export.controller'; +import { ExportService } from './export.service'; + +@Module({ + controllers: [SchoolController, StatsController, PackageController, SettingsController, ExportController], + providers: [SchoolService, StatsService, SettingsService, ExportService], + exports: [SchoolService, StatsService, SettingsService, ExportService], +}) +export class SchoolModule {} diff --git a/reading-platform-backend/src/modules/school/school.service.ts b/reading-platform-backend/src/modules/school/school.service.ts new file mode 100644 index 0000000..2868f09 --- /dev/null +++ b/reading-platform-backend/src/modules/school/school.service.ts @@ -0,0 +1,2423 @@ +import { Injectable, NotFoundException, ForbiddenException, ConflictException, Logger, BadRequestException } from '@nestjs/common'; +import { PrismaService } from '../../database/prisma.service'; +import { CreateTeacherDto, UpdateTeacherDto } from './dto/create-teacher.dto'; +import { CreateStudentDto, UpdateStudentDto } from './dto/create-student.dto'; +import { CreateClassDto, UpdateClassDto } from './dto/create-class.dto'; +import { AddClassTeacherDto, UpdateClassTeacherDto, TransferStudentDto } from './dto/class-teacher.dto'; +import { CreateScheduleDto, UpdateScheduleDto, QueryScheduleDto, TimetableQueryDto } from './dto/schedule.dto'; +import * as bcrypt from 'bcrypt'; +import * as xlsx from 'xlsx'; + +@Injectable() +export class SchoolService { + private readonly logger = new Logger(SchoolService.name); + + constructor(private prisma: PrismaService) {} + + // ==================== 工具方法 ==================== + + private parseJsonArray(value: any): any[] { + if (!value) return []; + if (typeof value === 'string') { + try { + return JSON.parse(value); + } catch { + return []; + } + } + return Array.isArray(value) ? value : []; + } + + // ==================== 教师管理 ==================== + + async findTeachers(tenantId: number, query: any) { + const { page = 1, pageSize = 10, keyword, status } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where: any = { + tenantId: tenantId, + }; + + if (keyword) { + where.OR = [ + { name: { contains: keyword } }, + { phone: { contains: keyword } }, + { loginAccount: { contains: keyword } }, + ]; + } + + if (status) { + where.status = status; + } + + const [items, total] = await Promise.all([ + this.prisma.teacher.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + include: { + classes: { + select: { + id: true, + name: true, + }, + }, + }, + }), + this.prisma.teacher.count({ where }), + ]); + + // 获取每个教师的班级名称 + const parsedItems = items.map((teacher) => ({ + ...teacher, + classIds: this.parseJsonArray(teacher.classIds), + classNames: teacher.classes.map((c) => c.name).join(', '), + passwordHash: undefined, + })); + + return { + items: parsedItems, + total, + page: +page, + pageSize: +pageSize, + }; + } + + async findTeacher(tenantId: number, id: number) { + const teacher = await this.prisma.teacher.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + include: { + classes: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + if (!teacher) { + throw new NotFoundException('教师不存在'); + } + + return { + ...teacher, + classIds: this.parseJsonArray(teacher.classIds), + classNames: teacher.classes.map((c) => c.name).join(', '), + passwordHash: undefined, + }; + } + + async createTeacher(tenantId: number, dto: CreateTeacherDto) { + // 检查配额 + const tenant = await this.prisma.tenant.findUnique({ + where: { id: tenantId }, + }); + + if (!tenant) { + throw new NotFoundException('学校不存在'); + } + + const teacherCount = await this.prisma.teacher.count({ + where: { tenantId: tenantId }, + }); + + if (teacherCount >= tenant.teacherQuota) { + throw new ForbiddenException('教师配额已满,无法添加更多教师'); + } + + // 检查账号是否已存在 + const existingTeacher = await this.prisma.teacher.findUnique({ + where: { loginAccount: dto.loginAccount }, + }); + + if (existingTeacher) { + throw new ConflictException('登录账号已存在'); + } + + // 加密密码 + const passwordHash = await bcrypt.hash(dto.password || '123456', 10); + + // 创建教师 + const teacher = await this.prisma.teacher.create({ + data: { + tenantId: tenantId, + name: dto.name, + phone: dto.phone, + email: dto.email, + loginAccount: dto.loginAccount, + passwordHash: passwordHash, + classIds: JSON.stringify(dto.classIds || []), + status: 'ACTIVE', + }, + include: { + classes: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + // 更新教师负责的班级 + if (dto.classIds && dto.classIds.length > 0) { + await this.prisma.class.updateMany({ + where: { + id: { in: dto.classIds }, + tenantId: tenantId, + }, + data: { + teacherId: teacher.id, + }, + }); + } + + // 更新租户教师数量 + await this.prisma.tenant.update({ + where: { id: tenantId }, + data: { + teacherCount: { increment: 1 }, + }, + }); + + this.logger.log(`Teacher created: ${teacher.id} by tenant ${tenantId}`); + + return { + ...teacher, + classIds: this.parseJsonArray(teacher.classIds), + classNames: teacher.classes.map((c) => c.name).join(', '), + passwordHash: undefined, + }; + } + + async updateTeacher(tenantId: number, id: number, dto: UpdateTeacherDto) { + // 检查教师是否存在 + const existingTeacher = await this.prisma.teacher.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + + if (!existingTeacher) { + throw new NotFoundException('教师不存在'); + } + + // 更新教师 + const teacher = await this.prisma.teacher.update({ + where: { id: id }, + data: { + name: dto.name, + phone: dto.phone, + email: dto.email, + classIds: dto.classIds ? JSON.stringify(dto.classIds) : undefined, + }, + include: { + classes: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + // 更新班级的 teacherId + // 首先清除旧的关联 + await this.prisma.class.updateMany({ + where: { + teacherId: id, + tenantId: tenantId, + }, + data: { + teacherId: null, + }, + }); + + // 设置新的关联 + if (dto.classIds && dto.classIds.length > 0) { + await this.prisma.class.updateMany({ + where: { + id: { in: dto.classIds }, + tenantId: tenantId, + }, + data: { + teacherId: id, + }, + }); + } + + this.logger.log(`Teacher updated: ${id}`); + + return { + ...teacher, + classIds: this.parseJsonArray(teacher.classIds), + classNames: teacher.classes.map((c) => c.name).join(', '), + passwordHash: undefined, + }; + } + + async deleteTeacher(tenantId: number, id: number) { + // 检查教师是否存在 + const teacher = await this.prisma.teacher.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + + if (!teacher) { + throw new NotFoundException('教师不存在'); + } + + // 删除教师(会级联删除相关数据) + await this.prisma.teacher.delete({ + where: { id: id }, + }); + + // 更新租户教师数量 + await this.prisma.tenant.update({ + where: { id: tenantId }, + data: { + teacherCount: { decrement: 1 }, + }, + }); + + this.logger.log(`Teacher deleted: ${id}`); + + return { message: '删除成功' }; + } + + async resetTeacherPassword(tenantId: number, id: number) { + // 检查教师是否存在 + const teacher = await this.prisma.teacher.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + + if (!teacher) { + throw new NotFoundException('教师不存在'); + } + + // 生成临时密码 + const tempPassword = Math.random().toString(36).slice(-8); + const passwordHash = await bcrypt.hash(tempPassword, 10); + + // 更新密码 + await this.prisma.teacher.update({ + where: { id: id }, + data: { + passwordHash: passwordHash, + }, + }); + + this.logger.log(`Teacher password reset: ${id}`); + + return { tempPassword }; + } + + // ==================== 学生管理 ==================== + + async findStudents(tenantId: number, query: any) { + const { page = 1, pageSize = 10, classId, keyword } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where: any = { + tenantId: tenantId, + }; + + if (classId) { + where.classId = +classId; + } + + if (keyword) { + where.OR = [ + { name: { contains: keyword } }, + { parentName: { contains: keyword } }, + { parentPhone: { contains: keyword } }, + ]; + } + + const [items, total] = await Promise.all([ + this.prisma.student.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + include: { + class: { + select: { + id: true, + name: true, + }, + }, + }, + }), + this.prisma.student.count({ where }), + ]); + + const parsedItems = items.map((student) => ({ + ...student, + className: student.class?.name, + })); + + return { + items: parsedItems, + total, + page: +page, + pageSize: +pageSize, + }; + } + + async findStudent(tenantId: number, id: number) { + const student = await this.prisma.student.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + include: { + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + if (!student) { + throw new NotFoundException('学生不存在'); + } + + return { + ...student, + className: student.class?.name, + }; + } + + async createStudent(tenantId: number, dto: CreateStudentDto) { + // 检查配额 + const tenant = await this.prisma.tenant.findUnique({ + where: { id: tenantId }, + }); + + if (!tenant) { + throw new NotFoundException('学校不存在'); + } + + const studentCount = await this.prisma.student.count({ + where: { tenantId: tenantId }, + }); + + if (studentCount >= tenant.studentQuota) { + throw new ForbiddenException('学生配额已满,无法添加更多学生'); + } + + // 检查班级是否存在 + const classEntity = await this.prisma.class.findFirst({ + where: { + id: dto.classId, + tenantId: tenantId, + }, + }); + + if (!classEntity) { + throw new NotFoundException('班级不存在'); + } + + // 创建学生 + const student = await this.prisma.student.create({ + data: { + tenantId: tenantId, + classId: dto.classId, + name: dto.name, + gender: dto.gender, + birthDate: dto.birthDate ? new Date(dto.birthDate) : null, + parentName: dto.parentName, + parentPhone: dto.parentPhone, + }, + include: { + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + // 更新班级学生数量 + await this.prisma.class.update({ + where: { id: dto.classId }, + data: { + studentCount: { increment: 1 }, + }, + }); + + // 更新租户学生数量 + await this.prisma.tenant.update({ + where: { id: tenantId }, + data: { + studentCount: { increment: 1 }, + }, + }); + + this.logger.log(`Student created: ${student.id} by tenant ${tenantId}`); + + return { + ...student, + className: student.class?.name, + }; + } + + async updateStudent(tenantId: number, id: number, dto: UpdateStudentDto) { + // 检查学生是否存在 + const existingStudent = await this.prisma.student.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + + if (!existingStudent) { + throw new NotFoundException('学生不存在'); + } + + // 如果要更换班级,检查新班级是否存在 + if (dto.classId && dto.classId !== existingStudent.classId) { + const newClass = await this.prisma.class.findFirst({ + where: { + id: dto.classId, + tenantId: tenantId, + }, + }); + + if (!newClass) { + throw new NotFoundException('新班级不存在'); + } + } + + // 更新学生 + const student = await this.prisma.student.update({ + where: { id: id }, + data: { + name: dto.name, + gender: dto.gender, + birthDate: dto.birthDate ? new Date(dto.birthDate) : undefined, + classId: dto.classId, + parentName: dto.parentName, + parentPhone: dto.parentPhone, + }, + include: { + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + // 如果班级变更,更新班级学生数量 + if (dto.classId && dto.classId !== existingStudent.classId) { + await this.prisma.class.update({ + where: { id: existingStudent.classId }, + data: { + studentCount: { decrement: 1 }, + }, + }); + + await this.prisma.class.update({ + where: { id: dto.classId }, + data: { + studentCount: { increment: 1 }, + }, + }); + } + + this.logger.log(`Student updated: ${id}`); + + return { + ...student, + className: student.class?.name, + }; + } + + async deleteStudent(tenantId: number, id: number) { + // 检查学生是否存在 + const student = await this.prisma.student.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + + if (!student) { + throw new NotFoundException('学生不存在'); + } + + const classId = student.classId; + + // 删除学生(会级联删除相关数据) + await this.prisma.student.delete({ + where: { id: id }, + }); + + // 更新班级学生数量 + await this.prisma.class.update({ + where: { id: classId }, + data: { + studentCount: { decrement: 1 }, + }, + }); + + // 更新租户学生数量 + await this.prisma.tenant.update({ + where: { id: tenantId }, + data: { + studentCount: { decrement: 1 }, + }, + }); + + this.logger.log(`Student deleted: ${id}`); + + return { message: '删除成功' }; + } + + async importStudents(tenantId: number, studentsData: any[], defaultClassId?: number) { + let success = 0; + let failed = 0; + const errors: Array<{ row: number; message: string }> = []; + + for (let i = 0; i < studentsData.length; i++) { + const data = studentsData[i]; + try { + const classId = data.classId || defaultClassId; + if (!classId) { + throw new Error('未指定班级'); + } + + await this.createStudent(tenantId, { + name: data.name, + gender: data.gender || '男', + birthDate: data.birthDate, + classId: classId, + parentName: data.parentName, + parentPhone: data.parentPhone, + }); + success++; + } catch (error: any) { + failed++; + errors.push({ + row: i + 2, // Excel行号从2开始(第1行是表头) + message: error.message || '导入失败', + }); + } + } + + this.logger.log(`Students imported: success=${success}, failed=${failed}`); + + return { success, failed, errors }; + } + + async parseStudentImportFile(file: Express.Multer.File): Promise { + const workbook = xlsx.read(file.buffer, { type: 'buffer' }); + const sheetName = workbook.SheetNames[0]; + const sheet = workbook.Sheets[sheetName]; + const data = xlsx.utils.sheet_to_json(sheet, { header: 1 }) as any[][]; + + if (data.length < 2) { + throw new BadRequestException('文件内容为空或格式不正确'); + } + + // 跳过表头,解析数据行 + const students: any[] = []; + for (let i = 1; i < data.length; i++) { + const row = data[i]; + if (!row || row.length === 0 || !row[0]) continue; // 跳过空行 + + students.push({ + name: String(row[0] || '').trim(), + gender: String(row[1] || '男').trim(), + birthDate: row[2] ? this.formatDate(row[2]) : null, + classId: row[3] ? parseInt(String(row[3]), 10) : null, + parentName: String(row[4] || '').trim() || null, + parentPhone: String(row[5] || '').trim() || null, + }); + } + + if (students.length === 0) { + throw new BadRequestException('未找到有效的学生数据'); + } + + return students; + } + + private formatDate(value: any): string | null { + if (!value) return null; + + // 如果是数字(Excel日期序列号) + if (typeof value === 'number') { + const date = xlsx.SSF.parse_date_code(value); + if (date) { + return `${date.y}-${String(date.m).padStart(2, '0')}-${String(date.d).padStart(2, '0')}`; + } + } + + // 如果是字符串 + const dateStr = String(value).trim(); + const date = new Date(dateStr); + if (!isNaN(date.getTime())) { + return date.toISOString().split('T')[0]; + } + + return null; + } + + // ==================== 班级管理 ==================== + + async findClasses(tenantId: number) { + const classes = await this.prisma.class.findMany({ + where: { + tenantId: tenantId, + }, + orderBy: { createdAt: 'desc' }, + include: { + teacher: { + select: { + id: true, + name: true, + }, + }, + classTeachers: { + include: { + teacher: { + select: { + id: true, + name: true, + phone: true, + }, + }, + }, + }, + _count: { + select: { + students: true, + lessons: true, + }, + }, + }, + }); + + return classes.map((cls) => ({ + id: cls.id, + name: cls.name, + grade: cls.grade, + teacherId: cls.teacherId, + teacherName: cls.teacher?.name, + studentCount: cls._count.students, + lessonCount: cls._count.lessons, + createdAt: cls.createdAt, + updatedAt: cls.updatedAt, + // 新增:教师团队 + teachers: cls.classTeachers.map((ct) => ({ + id: ct.id, + teacherId: ct.teacher.id, + teacherName: ct.teacher.name, + teacherPhone: ct.teacher.phone, + role: ct.role, + isPrimary: ct.isPrimary, + })), + })); + } + + async findClass(tenantId: number, id: number) { + const classEntity = await this.prisma.class.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + include: { + teacher: { + select: { + id: true, + name: true, + }, + }, + students: { + select: { + id: true, + name: true, + gender: true, + birthDate: true, + parentName: true, + parentPhone: true, + lessonCount: true, + }, + }, + }, + }); + + if (!classEntity) { + throw new NotFoundException('班级不存在'); + } + + return { + id: classEntity.id, + name: classEntity.name, + grade: classEntity.grade, + teacherId: classEntity.teacherId, + teacherName: classEntity.teacher?.name, + studentCount: classEntity.students.length, + lessonCount: classEntity.lessonCount, + students: classEntity.students, + createdAt: classEntity.createdAt, + updatedAt: classEntity.updatedAt, + }; + } + + async findClassStudents(tenantId: number, classId: number, query: any) { + const { page = 1, pageSize = 20, keyword } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + // 验证班级是否存在 + const classEntity = await this.prisma.class.findFirst({ + where: { + id: classId, + tenantId: tenantId, + }, + }); + + if (!classEntity) { + throw new NotFoundException('班级不存在'); + } + + const where: any = { + classId: classId, + tenantId: tenantId, + }; + + if (keyword) { + where.OR = [ + { name: { contains: keyword } }, + { parentName: { contains: keyword } }, + ]; + } + + const [items, total] = await Promise.all([ + this.prisma.student.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + }), + this.prisma.student.count({ where }), + ]); + + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + + async createClass(tenantId: number, dto: CreateClassDto) { + // 如果指定了教师,验证教师是否存在 + if (dto.teacherId) { + const teacher = await this.prisma.teacher.findFirst({ + where: { + id: dto.teacherId, + tenantId: tenantId, + }, + }); + + if (!teacher) { + throw new NotFoundException('教师不存在'); + } + } + + const classEntity = await this.prisma.class.create({ + data: { + tenantId: tenantId, + name: dto.name, + grade: dto.grade, + teacherId: dto.teacherId || null, + }, + include: { + teacher: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + this.logger.log(`Class created: ${classEntity.id} by tenant ${tenantId}`); + + return { + id: classEntity.id, + name: classEntity.name, + grade: classEntity.grade, + teacherId: classEntity.teacherId, + teacherName: classEntity.teacher?.name, + studentCount: 0, + lessonCount: 0, + }; + } + + async updateClass(tenantId: number, id: number, dto: UpdateClassDto) { + // 检查班级是否存在 + const existingClass = await this.prisma.class.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + }); + + if (!existingClass) { + throw new NotFoundException('班级不存在'); + } + + // 如果要更换教师,验证教师是否存在 + if (dto.teacherId !== undefined && dto.teacherId !== null) { + const teacher = await this.prisma.teacher.findFirst({ + where: { + id: dto.teacherId, + tenantId: tenantId, + }, + }); + + if (!teacher) { + throw new NotFoundException('教师不存在'); + } + } + + const classEntity = await this.prisma.class.update({ + where: { id: id }, + data: { + name: dto.name, + grade: dto.grade, + teacherId: dto.teacherId, + }, + include: { + teacher: { + select: { + id: true, + name: true, + }, + }, + _count: { + select: { + students: true, + lessons: true, + }, + }, + }, + }); + + this.logger.log(`Class updated: ${id}`); + + return { + id: classEntity.id, + name: classEntity.name, + grade: classEntity.grade, + teacherId: classEntity.teacherId, + teacherName: classEntity.teacher?.name, + studentCount: classEntity._count.students, + lessonCount: classEntity._count.lessons, + }; + } + + async deleteClass(tenantId: number, id: number) { + // 检查班级是否存在 + const classEntity = await this.prisma.class.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + include: { + _count: { + select: { + students: true, + }, + }, + }, + }); + + if (!classEntity) { + throw new NotFoundException('班级不存在'); + } + + // 检查是否还有学生 + if (classEntity._count.students > 0) { + throw new ForbiddenException('班级内还有学生,请先移除学生'); + } + + // 删除班级 + await this.prisma.class.delete({ + where: { id: id }, + }); + + this.logger.log(`Class deleted: ${id}`); + + return { message: '删除成功' }; + } + + // ==================== 家长管理 ==================== + + async findParents(tenantId: number, query: any) { + const { page = 1, pageSize = 10, keyword, status } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where: any = { + tenantId: tenantId, + }; + + if (keyword) { + where.OR = [ + { name: { contains: keyword } }, + { phone: { contains: keyword } }, + { loginAccount: { contains: keyword } }, + ]; + } + + if (status) { + where.status = status; + } + + const [items, total] = await Promise.all([ + this.prisma.parent.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + include: { + children: { + include: { + student: { + select: { + id: true, + name: true, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }, + }, + }, + }, + }), + this.prisma.parent.count({ where }), + ]); + + return { + items: items.map((parent) => ({ + ...parent, + childrenCount: parent.children.length, + children: parent.children.map((c) => ({ + ...c.student, + relationship: c.relationship, + })), + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + + async findParent(tenantId: number, id: number) { + const parent = await this.prisma.parent.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + include: { + children: { + include: { + student: { + select: { + id: true, + name: true, + gender: true, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }, + }, + }, + }, + }); + + if (!parent) { + throw new NotFoundException('家长不存在'); + } + + return { + ...parent, + children: parent.children.map((c) => ({ + ...c.student, + relationship: c.relationship, + })), + }; + } + + async createParent( + tenantId: number, + dto: { + name: string; + phone: string; + email?: string; + loginAccount: string; + password: string; + }, + ) { + // 检查账号是否已存在 + const existing = await this.prisma.parent.findUnique({ + where: { loginAccount: dto.loginAccount }, + }); + + if (existing) { + throw new ForbiddenException('登录账号已存在'); + } + + // 创建家长 + const hashedPassword = await bcrypt.hash(dto.password, 10); + + const parent = await this.prisma.parent.create({ + data: { + tenantId: tenantId, + name: dto.name, + phone: dto.phone, + email: dto.email, + loginAccount: dto.loginAccount, + passwordHash: hashedPassword, + status: 'ACTIVE', + }, + }); + + this.logger.log(`Parent created: ${parent.id}`); + + return parent; + } + + async updateParent( + tenantId: number, + id: number, + dto: { + name?: string; + phone?: string; + email?: string; + status?: string; + password?: string; + }, + ) { + const parent = await this.prisma.parent.findFirst({ + where: { id, tenantId }, + }); + + if (!parent) { + throw new NotFoundException('家长不存在'); + } + + const updateData: any = { + name: dto.name, + phone: dto.phone, + email: dto.email, + status: dto.status, + }; + + if (dto.password) { + updateData.passwordHash = await bcrypt.hash(dto.password, 10); + } + + const updated = await this.prisma.parent.update({ + where: { id }, + data: updateData, + }); + + this.logger.log(`Parent updated: ${id}`); + + return updated; + } + + async deleteParent(tenantId: number, id: number) { + const parent = await this.prisma.parent.findFirst({ + where: { id, tenantId }, + }); + + if (!parent) { + throw new NotFoundException('家长不存在'); + } + + await this.prisma.parent.delete({ + where: { id }, + }); + + this.logger.log(`Parent deleted: ${id}`); + + return { message: '删除成功' }; + } + + async resetParentPassword(tenantId: number, id: number) { + const parent = await this.prisma.parent.findFirst({ + where: { id, tenantId }, + }); + + if (!parent) { + throw new NotFoundException('家长不存在'); + } + + // 生成临时密码 + const tempPassword = Math.random().toString(36).slice(-8); + const hashedPassword = await bcrypt.hash(tempPassword, 10); + + await this.prisma.parent.update({ + where: { id }, + data: { passwordHash: hashedPassword }, + }); + + this.logger.log(`Parent password reset: ${id}`); + + return { tempPassword }; + } + + async addChildToParent( + tenantId: number, + parentId: number, + studentId: number, + relationship: string, + ) { + // 验证家长和学生都属于该租户 + const parent = await this.prisma.parent.findFirst({ + where: { id: parentId, tenantId }, + }); + + if (!parent) { + throw new NotFoundException('家长不存在'); + } + + const student = await this.prisma.student.findFirst({ + where: { id: studentId, tenantId }, + }); + + if (!student) { + throw new NotFoundException('学生不存在'); + } + + // 检查是否已关联 + const existing = await this.prisma.parentStudent.findUnique({ + where: { + parentId_studentId: { + parentId, + studentId, + }, + }, + }); + + if (existing) { + throw new ForbiddenException('该学生已与此家长关联'); + } + + const relation = await this.prisma.parentStudent.create({ + data: { + parentId, + studentId, + relationship, + }, + include: { + student: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + this.logger.log(`Child ${studentId} added to parent ${parentId}`); + + return relation; + } + + async removeChildFromParent( + tenantId: number, + parentId: number, + studentId: number, + ) { + // 验证家长属于该租户 + const parent = await this.prisma.parent.findFirst({ + where: { id: parentId, tenantId }, + }); + + if (!parent) { + throw new NotFoundException('家长不存在'); + } + + await this.prisma.parentStudent.delete({ + where: { + parentId_studentId: { + parentId, + studentId, + }, + }, + }); + + this.logger.log(`Child ${studentId} removed from parent ${parentId}`); + + return { message: '解除关联成功' }; + } + + // ==================== 课程管理 ==================== + + async findCourses(tenantId: number) { + // 获取学校已授权的课程列表 + const tenantCourses = await this.prisma.tenantCourse.findMany({ + where: { + tenantId, + authorized: true, + }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + coverImagePath: true, + gradeTags: true, + domainTags: true, + duration: true, + usageCount: true, + status: true, + }, + }, + }, + }); + + return tenantCourses.map((tc) => ({ + id: tc.course.id, + name: tc.course.name, + pictureBookName: tc.course.pictureBookName, + pictureUrl: tc.course.coverImagePath, + gradeTags: this.parseJsonArray(tc.course.gradeTags), + domainTags: this.parseJsonArray(tc.course.domainTags), + duration: tc.course.duration || 25, + usageCount: tc.course.usageCount || 0, + authorized: tc.authorized, + })); + } + + async findCourse(tenantId: number, courseId: number) { + // 检查课程是否存在且已发布 + const course = await this.prisma.course.findUnique({ + where: { id: courseId }, + include: { + resources: { + orderBy: { sortOrder: 'asc' }, + }, + scripts: { + orderBy: { sortOrder: 'asc' }, + include: { + pages: { + orderBy: { pageNumber: 'asc' }, + }, + }, + }, + activities: { + orderBy: { sortOrder: 'asc' }, + }, + tenantCourses: { + where: { tenantId }, + }, + }, + }); + + if (!course) { + throw new NotFoundException(`课程 #${courseId} 不存在`); + } + + if (course.status !== 'PUBLISHED') { + throw new ForbiddenException('该课程未发布'); + } + + // 检查授权 + const tenantCourse = course.tenantCourses.find((tc) => tc.tenantId === tenantId); + if (!tenantCourse || !tenantCourse.authorized) { + throw new ForbiddenException('您的学校未获得此课程的授权'); + } + + // 获取使用该课程的教师数量 + const teacherCount = await this.prisma.lesson.groupBy({ + by: ['teacherId'], + where: { + courseId, + tenantId, + }, + }); + + // 解析 JSON 字段 + return { + ...course, + authorized: true, // 既然通过了授权检查,就是已授权的 + gradeTags: JSON.parse(course.gradeTags || '[]'), + domainTags: JSON.parse(course.domainTags || '[]'), + ebookPaths: course.ebookPaths ? JSON.parse(course.ebookPaths) : null, + audioPaths: course.audioPaths ? JSON.parse(course.audioPaths) : null, + videoPaths: course.videoPaths ? JSON.parse(course.videoPaths) : null, + otherResources: course.otherResources ? JSON.parse(course.otherResources) : null, + posterPaths: course.posterPaths ? JSON.parse(course.posterPaths) : null, + tenantCourses: undefined, // 移除敏感信息 + teacherCount: teacherCount.length, + scripts: course.scripts.map((script) => ({ + ...script, + interactionPoints: script.interactionPoints ? JSON.parse(script.interactionPoints) : null, + resourceIds: script.resourceIds ? JSON.parse(script.resourceIds) : null, + pages: script.pages?.map((page) => ({ + ...page, + resourceIds: page.resourceIds ? JSON.parse(page.resourceIds) : null, + })), + })), + activities: course.activities.map((activity) => ({ + ...activity, + onlineMaterials: activity.onlineMaterials ? JSON.parse(activity.onlineMaterials) : null, + objectives: activity.objectives ? JSON.parse(activity.objectives) : null, + })), + }; + } + + // ==================== 班级教师管理 ==================== + + async findClassTeachers(tenantId: number, classId: number) { + // 验证班级存在 + const classEntity = await this.prisma.class.findFirst({ + where: { id: classId, tenantId }, + }); + + if (!classEntity) { + throw new NotFoundException('班级不存在'); + } + + const classTeachers = await this.prisma.classTeacher.findMany({ + where: { classId }, + include: { + teacher: { + select: { + id: true, + name: true, + phone: true, + email: true, + }, + }, + }, + orderBy: [ + { isPrimary: 'desc' }, + { createdAt: 'asc' }, + ], + }); + + return classTeachers.map((ct) => ({ + id: ct.id, + teacherId: ct.teacher.id, + teacherName: ct.teacher.name, + teacherPhone: ct.teacher.phone, + teacherEmail: ct.teacher.email, + role: ct.role, + isPrimary: ct.isPrimary, + createdAt: ct.createdAt, + })); + } + + async addClassTeacher(tenantId: number, classId: number, dto: AddClassTeacherDto) { + // 验证班级存在 + const classEntity = await this.prisma.class.findFirst({ + where: { id: classId, tenantId }, + }); + + if (!classEntity) { + throw new NotFoundException('班级不存在'); + } + + // 验证教师存在且属于同一租户 + const teacher = await this.prisma.teacher.findFirst({ + where: { id: dto.teacherId, tenantId }, + }); + + if (!teacher) { + throw new NotFoundException('教师不存在'); + } + + // 检查是否已存在关联 + const existing = await this.prisma.classTeacher.findUnique({ + where: { + classId_teacherId: { classId, teacherId: dto.teacherId }, + }, + }); + + if (existing) { + throw new ConflictException('该教师已在此班级中'); + } + + // 如果设为班主任,先取消其他班主任 + if (dto.isPrimary) { + await this.prisma.classTeacher.updateMany({ + where: { classId, isPrimary: true }, + data: { isPrimary: false }, + }); + } + + const classTeacher = await this.prisma.classTeacher.create({ + data: { + classId, + teacherId: dto.teacherId, + role: dto.role, + isPrimary: dto.isPrimary || false, + }, + include: { + teacher: { + select: { + id: true, + name: true, + phone: true, + }, + }, + }, + }); + + // 如果是主班教师,更新 Class.teacherId(向后兼容) + if (dto.role === 'MAIN' || dto.isPrimary) { + await this.prisma.class.update({ + where: { id: classId }, + data: { teacherId: dto.teacherId }, + }); + } + + this.logger.log(`Teacher ${dto.teacherId} added to class ${classId} as ${dto.role}`); + + return { + id: classTeacher.id, + teacherId: classTeacher.teacher.id, + teacherName: classTeacher.teacher.name, + teacherPhone: classTeacher.teacher.phone, + role: classTeacher.role, + isPrimary: classTeacher.isPrimary, + }; + } + + async updateClassTeacher( + tenantId: number, + classId: number, + teacherId: number, + dto: UpdateClassTeacherDto, + ) { + // 验证班级存在 + const classEntity = await this.prisma.class.findFirst({ + where: { id: classId, tenantId }, + }); + + if (!classEntity) { + throw new NotFoundException('班级不存在'); + } + + // 查找关联记录 + const classTeacher = await this.prisma.classTeacher.findUnique({ + where: { + classId_teacherId: { classId, teacherId }, + }, + }); + + if (!classTeacher) { + throw new NotFoundException('该教师不在此班级中'); + } + + // 如果设为班主任,先取消其他班主任 + if (dto.isPrimary) { + await this.prisma.classTeacher.updateMany({ + where: { classId, isPrimary: true }, + data: { isPrimary: false }, + }); + } + + const updated = await this.prisma.classTeacher.update({ + where: { + classId_teacherId: { classId, teacherId }, + }, + data: { + role: dto.role, + isPrimary: dto.isPrimary, + }, + include: { + teacher: { + select: { + id: true, + name: true, + phone: true, + }, + }, + }, + }); + + // 如果是主班教师,更新 Class.teacherId(向后兼容) + if (dto.role === 'MAIN' || dto.isPrimary) { + await this.prisma.class.update({ + where: { id: classId }, + data: { teacherId }, + }); + } + + this.logger.log(`Teacher ${teacherId} updated in class ${classId}`); + + return { + id: updated.id, + teacherId: updated.teacher.id, + teacherName: updated.teacher.name, + teacherPhone: updated.teacher.phone, + role: updated.role, + isPrimary: updated.isPrimary, + }; + } + + async removeClassTeacher(tenantId: number, classId: number, teacherId: number) { + // 验证班级存在 + const classEntity = await this.prisma.class.findFirst({ + where: { id: classId, tenantId }, + }); + + if (!classEntity) { + throw new NotFoundException('班级不存在'); + } + + // 查找关联记录 + const classTeacher = await this.prisma.classTeacher.findUnique({ + where: { + classId_teacherId: { classId, teacherId }, + }, + }); + + if (!classTeacher) { + throw new NotFoundException('该教师不在此班级中'); + } + + await this.prisma.classTeacher.delete({ + where: { + classId_teacherId: { classId, teacherId }, + }, + }); + + // 如果移除的是当前班主任,清空 Class.teacherId + if (classEntity.teacherId === teacherId) { + // 查找是否还有其他主班教师 + const nextMainTeacher = await this.prisma.classTeacher.findFirst({ + where: { classId, role: 'MAIN' }, + orderBy: { createdAt: 'asc' }, + }); + + await this.prisma.class.update({ + where: { id: classId }, + data: { teacherId: nextMainTeacher?.teacherId || null }, + }); + } + + this.logger.log(`Teacher ${teacherId} removed from class ${classId}`); + + return { message: '移除成功' }; + } + + // ==================== 学生调班 ==================== + + async transferStudent(tenantId: number, studentId: number, dto: TransferStudentDto) { + // 查找学生 + const student = await this.prisma.student.findFirst({ + where: { id: studentId, tenantId }, + }); + + if (!student) { + throw new NotFoundException('学生不存在'); + } + + // 验证目标班级存在 + const toClass = await this.prisma.class.findFirst({ + where: { id: dto.toClassId, tenantId }, + }); + + if (!toClass) { + throw new NotFoundException('目标班级不存在'); + } + + // 如果是同一个班级,不做处理 + if (student.classId === dto.toClassId) { + throw new BadRequestException('学生已在此班级中'); + } + + const fromClassId = student.classId; + + // 使用事务处理 + await this.prisma.$transaction(async (tx) => { + // 更新学生班级 + await tx.student.update({ + where: { id: studentId }, + data: { classId: dto.toClassId }, + }); + + // 更新原班级学生数量 + await tx.class.update({ + where: { id: fromClassId }, + data: { studentCount: { decrement: 1 } }, + }); + + // 更新新班级学生数量 + await tx.class.update({ + where: { id: dto.toClassId }, + data: { studentCount: { increment: 1 } }, + }); + + // 记录调班历史 + await tx.studentClassHistory.create({ + data: { + studentId, + fromClassId, + toClassId: dto.toClassId, + reason: dto.reason, + operatedBy: null, // TODO: 从请求中获取操作人ID + }, + }); + }); + + this.logger.log(`Student ${studentId} transferred from class ${fromClassId} to ${dto.toClassId}`); + + return { message: '调班成功' }; + } + + async getStudentClassHistory(tenantId: number, studentId: number) { + // 验证学生存在 + const student = await this.prisma.student.findFirst({ + where: { id: studentId, tenantId }, + }); + + if (!student) { + throw new NotFoundException('学生不存在'); + } + + const history = await this.prisma.studentClassHistory.findMany({ + where: { studentId }, + include: { + fromClass: { + select: { id: true, name: true, grade: true }, + }, + toClass: { + select: { id: true, name: true, grade: true }, + }, + }, + orderBy: { createdAt: 'desc' }, + }); + + return history.map((h) => ({ + id: h.id, + fromClass: h.fromClass ? { id: h.fromClass.id, name: h.fromClass.name, grade: h.fromClass.grade } : null, + toClass: { id: h.toClass.id, name: h.toClass.name, grade: h.toClass.grade }, + reason: h.reason, + operatedBy: h.operatedBy, + createdAt: h.createdAt, + })); + } + + // ==================== 排课管理 ==================== + + /** + * 解析时间字符串为分钟数 + * @param timeStr 格式: "HH:mm" + * @returns 从午夜开始的分钟数 + */ + private parseTimeToMinutes(timeStr: string): number { + const [hours, minutes] = timeStr.split(':').map(Number); + return hours * 60 + minutes; + } + + /** + * 检查两个时间段是否重叠 + * @param time1 格式: "HH:mm-HH:mm" + * @param time2 格式: "HH:mm-HH:mm" + */ + private isTimeOverlapping(time1: string, time2: string): boolean { + const [start1, end1] = time1.split('-').map(t => this.parseTimeToMinutes(t.trim())); + const [start2, end2] = time2.split('-').map(t => this.parseTimeToMinutes(t.trim())); + + // 两个时间段重叠的条件: start1 < end2 AND start2 < end1 + return start1 < end2 && start2 < end1; + } + + /** + * 检查教师排课时间冲突 + * @param teacherId 教师ID + * @param scheduledDate 排课日期 + * @param scheduledTime 时间段 + * @param excludeScheduleId 排除的排课ID(用于更新时排除自身) + * @returns 如果有冲突返回冲突的排课信息,否则返回null + */ + private async checkScheduleConflict( + teacherId: number, + scheduledDate: string, + scheduledTime: string, + excludeScheduleId?: number, + ): Promise<{ courseName: string; className: string; scheduledTime: string } | null> { + // 使用日期范围查询,避免时区问题 + const dateStart = new Date(scheduledDate + 'T00:00:00.000Z'); + const dateEnd = new Date(scheduledDate + 'T23:59:59.999Z'); + + // 获取该教师当天的所有排课 + const existingSchedules = await this.prisma.schedulePlan.findMany({ + where: { + teacherId, + scheduledDate: { + gte: dateStart, + lte: dateEnd, + }, + status: 'ACTIVE', + ...(excludeScheduleId && { id: { not: excludeScheduleId } }), + }, + include: { + class: { select: { name: true } }, + course: { select: { name: true } }, + }, + }); + + // 检查是否存在时间重叠 + for (const schedule of existingSchedules) { + if (schedule.scheduledTime && this.isTimeOverlapping(scheduledTime, schedule.scheduledTime)) { + return { + courseName: schedule.course.name, + className: schedule.class.name, + scheduledTime: schedule.scheduledTime, + }; + } + } + + return null; + } + + async findSchedules(tenantId: number, query: QueryScheduleDto) { + const { page = 1, pageSize = 20, classId, teacherId, courseId, startDate, endDate, status, source } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where: any = { + tenantId, + }; + + if (classId) where.classId = classId; + if (teacherId) where.teacherId = teacherId; + if (courseId) where.courseId = courseId; + if (status) where.status = status; + if (source) where.source = source; + + if (startDate || endDate) { + where.scheduledDate = {}; + if (startDate) where.scheduledDate.gte = new Date(startDate); + if (endDate) where.scheduledDate.lte = new Date(endDate); + } + + const [items, total] = await Promise.all([ + this.prisma.schedulePlan.findMany({ + where, + skip, + take, + orderBy: { scheduledDate: 'asc' }, + include: { + class: { select: { id: true, name: true, grade: true } }, + course: { select: { id: true, name: true, pictureBookName: true, duration: true } }, + teacher: { select: { id: true, name: true, phone: true } }, + }, + }), + this.prisma.schedulePlan.count({ where }), + ]); + + return { + items: items.map((item) => ({ + ...item, + className: item.class.name, + courseName: item.course.name, + teacherName: item.teacher?.name, + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + + async findSchedule(tenantId: number, id: number) { + const schedule = await this.prisma.schedulePlan.findFirst({ + where: { id, tenantId }, + include: { + class: { select: { id: true, name: true, grade: true } }, + course: { select: { id: true, name: true, pictureBookName: true, duration: true } }, + teacher: { select: { id: true, name: true, phone: true } }, + }, + }); + + if (!schedule) { + throw new NotFoundException('排课计划不存在'); + } + + return { + ...schedule, + className: schedule.class.name, + courseName: schedule.course.name, + teacherName: schedule.teacher?.name, + }; + } + + async createSchedule(tenantId: number, dto: CreateScheduleDto, userId: number) { + // 验证班级存在 + const classEntity = await this.prisma.class.findFirst({ + where: { id: dto.classId, tenantId }, + }); + if (!classEntity) { + throw new NotFoundException('班级不存在'); + } + + // 验证课程存在且已授权 + const tenantCourse = await this.prisma.tenantCourse.findFirst({ + where: { tenantId, courseId: dto.courseId, authorized: true }, + }); + if (!tenantCourse) { + throw new ForbiddenException('该课程未授权或不存在'); + } + + // 如果指定了教师,验证教师存在 + if (dto.teacherId) { + const teacher = await this.prisma.teacher.findFirst({ + where: { id: dto.teacherId, tenantId }, + }); + if (!teacher) { + throw new NotFoundException('教师不存在'); + } + + // 检查教师时间冲突 + if (dto.scheduledDate && dto.scheduledTime) { + const conflict = await this.checkScheduleConflict(dto.teacherId, dto.scheduledDate, dto.scheduledTime); + if (conflict) { + throw new ConflictException( + `时间冲突:该教师在 ${conflict.scheduledTime} 已有排课「${conflict.courseName}」(${conflict.className}),请选择其他时间段` + ); + } + } + } + + const schedule = await this.prisma.schedulePlan.create({ + data: { + tenantId, + classId: dto.classId, + courseId: dto.courseId, + teacherId: dto.teacherId, + scheduledDate: dto.scheduledDate ? new Date(dto.scheduledDate) : null, + scheduledTime: dto.scheduledTime, + weekDay: dto.weekDay, + repeatType: dto.repeatType, + repeatEndDate: dto.repeatEndDate ? new Date(dto.repeatEndDate) : null, + source: 'SCHOOL', + createdBy: userId, + note: dto.note, + status: 'ACTIVE', + }, + include: { + class: { select: { id: true, name: true } }, + course: { select: { id: true, name: true } }, + teacher: { select: { id: true, name: true } }, + }, + }); + + this.logger.log(`Schedule created: ${schedule.id} by user ${userId}`); + + return { + ...schedule, + className: schedule.class.name, + courseName: schedule.course.name, + teacherName: schedule.teacher?.name, + }; + } + + async updateSchedule(tenantId: number, id: number, dto: UpdateScheduleDto) { + const schedule = await this.prisma.schedulePlan.findFirst({ + where: { id, tenantId }, + }); + + if (!schedule) { + throw new NotFoundException('排课计划不存在'); + } + + // 如果更新教师,验证教师存在 + if (dto.teacherId !== undefined) { + if (dto.teacherId) { + const teacher = await this.prisma.teacher.findFirst({ + where: { id: dto.teacherId, tenantId }, + }); + if (!teacher) { + throw new NotFoundException('教师不存在'); + } + } + } + + const updated = await this.prisma.schedulePlan.update({ + where: { id }, + data: { + teacherId: dto.teacherId, + scheduledDate: dto.scheduledDate ? new Date(dto.scheduledDate) : undefined, + scheduledTime: dto.scheduledTime, + weekDay: dto.weekDay, + repeatType: dto.repeatType, + repeatEndDate: dto.repeatEndDate ? new Date(dto.repeatEndDate) : undefined, + note: dto.note, + status: dto.status, + }, + include: { + class: { select: { id: true, name: true } }, + course: { select: { id: true, name: true } }, + teacher: { select: { id: true, name: true } }, + }, + }); + + this.logger.log(`Schedule updated: ${id}`); + + return { + ...updated, + className: updated.class.name, + courseName: updated.course.name, + teacherName: updated.teacher?.name, + }; + } + + async cancelSchedule(tenantId: number, id: number) { + const schedule = await this.prisma.schedulePlan.findFirst({ + where: { id, tenantId }, + }); + + if (!schedule) { + throw new NotFoundException('排课计划不存在'); + } + + await this.prisma.schedulePlan.update({ + where: { id }, + data: { status: 'CANCELLED' }, + }); + + this.logger.log(`Schedule cancelled: ${id}`); + + return { message: '取消成功' }; + } + + async getTimetable(tenantId: number, query: TimetableQueryDto) { + const { startDate, endDate, classId, teacherId } = query; + + const where: any = { + tenantId, + status: 'ACTIVE', + scheduledDate: { + gte: new Date(startDate), + lte: new Date(endDate), + }, + }; + + if (classId) where.classId = classId; + if (teacherId) where.teacherId = teacherId; + + const schedules = await this.prisma.schedulePlan.findMany({ + where, + orderBy: [{ scheduledDate: 'asc' }, { scheduledTime: 'asc' }], + include: { + class: { select: { id: true, name: true, grade: true } }, + course: { select: { id: true, name: true, pictureBookName: true, duration: true } }, + teacher: { select: { id: true, name: true } }, + }, + }); + + // 按日期分组 + const timetable: Record = {}; + const start = new Date(startDate); + const end = new Date(endDate); + + // 初始化所有日期 + for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) { + const dateStr = d.toISOString().split('T')[0]; + timetable[dateStr] = []; + } + + // 填充排课数据 + schedules.forEach((schedule) => { + const dateStr = schedule.scheduledDate!.toISOString().split('T')[0]; + if (timetable[dateStr]) { + timetable[dateStr].push({ + ...schedule, + className: schedule.class.name, + courseName: schedule.course.name, + teacherName: schedule.teacher?.name, + }); + } + }); + + return Object.entries(timetable).map(([date, items]) => ({ + date, + weekDay: new Date(date).getDay(), + schedules: items, + })); + } + + // ==================== 批量排课 ==================== + + async batchCreateSchedules(tenantId: number, schedules: Array<{ + classId: number; + courseId: number; + teacherId?: number; + scheduledDate: string; + scheduledTime?: string; + note?: string; + }>) { + const results = []; + const errors = []; + + for (let i = 0; i < schedules.length; i++) { + const item = schedules[i]; + try { + // 验证班级存在 + const classEntity = await this.prisma.class.findFirst({ + where: { id: item.classId, tenantId }, + }); + + if (!classEntity) { + errors.push({ index: i, message: '班级不存在或无权限' }); + continue; + } + + // 验证课程存在且已授权 + const tenantCourse = await this.prisma.tenantCourse.findFirst({ + where: { tenantId, courseId: item.courseId, authorized: true }, + }); + + if (!tenantCourse) { + errors.push({ index: i, message: '课程未授权或不存在' }); + continue; + } + + // 如果指定了教师,验证教师存在 + if (item.teacherId) { + const teacher = await this.prisma.teacher.findFirst({ + where: { id: item.teacherId, tenantId }, + }); + + if (!teacher) { + errors.push({ index: i, message: '教师不存在' }); + continue; + } + } + + // 创建排课 + const schedule = await this.prisma.schedulePlan.create({ + data: { + tenantId, + classId: item.classId, + courseId: item.courseId, + teacherId: item.teacherId, + scheduledDate: new Date(item.scheduledDate), + scheduledTime: item.scheduledTime, + repeatType: 'NONE', + source: 'SCHOOL', + createdBy: 0, // 批量创建 + status: 'ACTIVE', + note: item.note, + }, + include: { + class: { select: { id: true, name: true } }, + course: { select: { id: true, name: true } }, + teacher: { select: { id: true, name: true } }, + }, + }); + + results.push({ + ...schedule, + className: schedule.class.name, + courseName: schedule.course.name, + teacherName: schedule.teacher?.name, + }); + } catch (error) { + errors.push({ index: i, message: error.message || '创建失败' }); + } + } + + this.logger.log( + `Batch create schedules: ${results.length} success, ${errors.length} failed` + ); + + return { + success: results.length, + failed: errors.length, + results, + errors, + }; + } + + // ==================== 排课模板 ==================== + + async getScheduleTemplates(tenantId: number, query?: { classId?: number; courseId?: number }) { + const where: any = { tenantId }; + + if (query?.classId) { + where.classId = query.classId; + } + + if (query?.courseId) { + where.courseId = query.courseId; + } + + const templates = await this.prisma.scheduleTemplate.findMany({ + where, + orderBy: [ + { isDefault: 'desc' }, + { createdAt: 'desc' }, + ], + include: { + course: { + select: { id: true, name: true, pictureBookName: true }, + }, + class: { + select: { id: true, name: true, grade: true }, + }, + teacher: { + select: { id: true, name: true }, + }, + }, + }); + + return templates.map((t) => ({ + ...t, + courseName: t.course?.name, + className: t.class?.name, + teacherName: t.teacher?.name, + })); + } + + async getScheduleTemplate(tenantId: number, id: number) { + const template = await this.prisma.scheduleTemplate.findFirst({ + where: { id, tenantId }, + include: { + course: { + select: { id: true, name: true, pictureBookName: true, duration: true }, + }, + class: { + select: { id: true, name: true, grade: true }, + }, + teacher: { + select: { id: true, name: true }, + }, + }, + }); + + if (!template) { + throw new NotFoundException('模板不存在'); + } + + return { + ...template, + courseName: template.course?.name, + className: template.class?.name, + teacherName: template.teacher?.name, + }; + } + + async createScheduleTemplate(tenantId: number, data: { + name: string; + courseId: number; + classId?: number; + teacherId?: number; + scheduledTime?: string; + weekDay?: number; + duration?: number; + isDefault?: boolean; + }) { + // 验证课程存在且已授权 + const tenantCourse = await this.prisma.tenantCourse.findFirst({ + where: { tenantId, courseId: data.courseId, authorized: true }, + }); + + if (!tenantCourse) { + throw new BadRequestException('该课程未授权或不存在'); + } + + // 如果设为默认模板,先取消其他默认模板 + if (data.isDefault) { + await this.prisma.scheduleTemplate.updateMany({ + where: { + tenantId, + courseId: data.courseId, + isDefault: true, + }, + data: { isDefault: false }, + }); + } + + const template = await this.prisma.scheduleTemplate.create({ + data: { + tenantId, + name: data.name, + courseId: data.courseId, + classId: data.classId, + teacherId: data.teacherId, + scheduledTime: data.scheduledTime, + weekDay: data.weekDay, + duration: data.duration || 25, + isDefault: data.isDefault || false, + }, + include: { + course: { select: { id: true, name: true } }, + class: { select: { id: true, name: true } }, + teacher: { select: { id: true, name: true } }, + }, + }); + + this.logger.log(`Schedule template created: ${template.id}`); + + return { + ...template, + courseName: template.course?.name, + className: template.class?.name, + teacherName: template.teacher?.name, + }; + } + + async updateScheduleTemplate(tenantId: number, id: number, data: { + name?: string; + classId?: number; + teacherId?: number; + scheduledTime?: string; + weekDay?: number; + duration?: number; + isDefault?: boolean; + }) { + const template = await this.prisma.scheduleTemplate.findFirst({ + where: { id, tenantId }, + }); + + if (!template) { + throw new NotFoundException('模板不存在'); + } + + // 如果设为默认模板,先取消其他默认模板 + if (data.isDefault) { + await this.prisma.scheduleTemplate.updateMany({ + where: { + tenantId, + courseId: template.courseId, + isDefault: true, + id: { not: id }, + }, + data: { isDefault: false }, + }); + } + + const updated = await this.prisma.scheduleTemplate.update({ + where: { id }, + data: { + name: data.name, + classId: data.classId, + teacherId: data.teacherId, + scheduledTime: data.scheduledTime, + weekDay: data.weekDay, + duration: data.duration, + isDefault: data.isDefault, + }, + include: { + course: { select: { id: true, name: true } }, + class: { select: { id: true, name: true } }, + teacher: { select: { id: true, name: true } }, + }, + }); + + return { + ...updated, + courseName: updated.course?.name, + className: updated.class?.name, + teacherName: updated.teacher?.name, + }; + } + + async deleteScheduleTemplate(tenantId: number, id: number) { + const template = await this.prisma.scheduleTemplate.findFirst({ + where: { id, tenantId }, + }); + + if (!template) { + throw new NotFoundException('模板不存在'); + } + + await this.prisma.scheduleTemplate.delete({ + where: { id }, + }); + + this.logger.log(`Schedule template deleted: ${id}`); + + return { message: '删除成功' }; + } + + async applyScheduleTemplate(tenantId: number, templateId: number, data: { + scheduledDate: string; + classId?: number; + teacherId?: number; + }) { + const template = await this.prisma.scheduleTemplate.findFirst({ + where: { id: templateId, tenantId }, + }); + + if (!template) { + throw new NotFoundException('模板不存在'); + } + + // 使用模板创建排课 + const schedule = await this.prisma.schedulePlan.create({ + data: { + tenantId, + classId: data.classId || template.classId!, + courseId: template.courseId, + teacherId: data.teacherId || template.teacherId, + scheduledDate: new Date(data.scheduledDate), + scheduledTime: template.scheduledTime, + repeatType: 'NONE', + source: 'SCHOOL', + createdBy: 0, // 模板创建 + status: 'ACTIVE', + }, + include: { + class: { select: { id: true, name: true } }, + course: { select: { id: true, name: true } }, + }, + }); + + this.logger.log(`Schedule created from template ${templateId}: ${schedule.id}`); + + return { + ...schedule, + className: schedule.class.name, + courseName: schedule.course.name, + }; + } +} diff --git a/reading-platform-backend/src/modules/school/settings.controller.ts b/reading-platform-backend/src/modules/school/settings.controller.ts new file mode 100644 index 0000000..86288d1 --- /dev/null +++ b/reading-platform-backend/src/modules/school/settings.controller.ts @@ -0,0 +1,39 @@ +import { + Controller, + Get, + Put, + Body, + UseGuards, + Request, +} from '@nestjs/common'; +import { SettingsService } from './settings.service'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { RolesGuard } from '../common/guards/roles.guard'; +import { Roles } from '../common/decorators/roles.decorator'; + +@Controller('school/settings') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('school') +export class SettingsController { + constructor(private readonly settingsService: SettingsService) {} + + @Get() + getSettings(@Request() req: any) { + return this.settingsService.getSettings(req.user.tenantId); + } + + @Put() + updateSettings( + @Request() req: any, + @Body() data: { + schoolName?: string; + schoolLogo?: string; + address?: string; + notifyOnLesson?: boolean; + notifyOnTask?: boolean; + notifyOnGrowth?: boolean; + }, + ) { + return this.settingsService.updateSettings(req.user.tenantId, data); + } +} diff --git a/reading-platform-backend/src/modules/school/settings.service.ts b/reading-platform-backend/src/modules/school/settings.service.ts new file mode 100644 index 0000000..0a8a460 --- /dev/null +++ b/reading-platform-backend/src/modules/school/settings.service.ts @@ -0,0 +1,93 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { PrismaService } from '../../database/prisma.service'; + +@Injectable() +export class SettingsService { + constructor(private prisma: PrismaService) {} + + async getSettings(tenantId: number) { + let settings = await this.prisma.systemSettings.findUnique({ + where: { tenantId }, + }); + + // 如果设置不存在,创建默认设置 + if (!settings) { + const tenant = await this.prisma.tenant.findUnique({ + where: { id: tenantId }, + }); + + if (!tenant) { + throw new NotFoundException('学校不存在'); + } + + settings = await this.prisma.systemSettings.create({ + data: { + tenantId, + schoolName: tenant.name, + schoolLogo: tenant.logoUrl, + address: tenant.address, + notifyOnLesson: true, + notifyOnTask: true, + notifyOnGrowth: false, + }, + }); + } + + return settings; + } + + async updateSettings(tenantId: number, data: { + schoolName?: string; + schoolLogo?: string; + address?: string; + notifyOnLesson?: boolean; + notifyOnTask?: boolean; + notifyOnGrowth?: boolean; + }) { + let settings = await this.prisma.systemSettings.findUnique({ + where: { tenantId }, + }); + + if (!settings) { + // 创建新设置 + settings = await this.prisma.systemSettings.create({ + data: { + tenantId, + schoolName: data.schoolName, + schoolLogo: data.schoolLogo, + address: data.address, + notifyOnLesson: data.notifyOnLesson ?? true, + notifyOnTask: data.notifyOnTask ?? true, + notifyOnGrowth: data.notifyOnGrowth ?? false, + }, + }); + } else { + // 更新现有设置 + settings = await this.prisma.systemSettings.update({ + where: { tenantId }, + data: { + schoolName: data.schoolName, + schoolLogo: data.schoolLogo, + address: data.address, + notifyOnLesson: data.notifyOnLesson, + notifyOnTask: data.notifyOnTask, + notifyOnGrowth: data.notifyOnGrowth, + }, + }); + } + + // 同步更新租户信息 + if (data.schoolName || data.schoolLogo || data.address) { + await this.prisma.tenant.update({ + where: { id: tenantId }, + data: { + name: data.schoolName, + logoUrl: data.schoolLogo, + address: data.address, + }, + }); + } + + return settings; + } +} diff --git a/reading-platform-backend/src/modules/school/stats.controller.ts b/reading-platform-backend/src/modules/school/stats.controller.ts new file mode 100644 index 0000000..550075e --- /dev/null +++ b/reading-platform-backend/src/modules/school/stats.controller.ts @@ -0,0 +1,79 @@ +import { + Controller, + Get, + Query, + UseGuards, + Request, +} from '@nestjs/common'; +import { StatsService } from './stats.service'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { RolesGuard } from '../common/guards/roles.guard'; +import { Roles } from '../common/decorators/roles.decorator'; + +@Controller('school') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('school') +export class StatsController { + constructor(private readonly statsService: StatsService) {} + + @Get('stats') + getStats(@Request() req: any) { + return this.statsService.getSchoolStats(req.user.tenantId); + } + + @Get('stats/teachers') + getActiveTeachers(@Request() req: any, @Query('limit') limit?: string) { + return this.statsService.getActiveTeachers( + req.user.tenantId, + limit ? parseInt(limit, 10) : 5, + ); + } + + @Get('stats/courses') + getCourseUsageStats(@Request() req: any) { + return this.statsService.getCourseUsageStats(req.user.tenantId); + } + + @Get('stats/activities') + getRecentActivities(@Request() req: any, @Query('limit') limit?: string) { + return this.statsService.getRecentActivities( + req.user.tenantId, + limit ? parseInt(limit, 10) : 10, + ); + } + + @Get('stats/lesson-trend') + getLessonTrend(@Request() req: any, @Query('months') months?: string) { + return this.statsService.getLessonTrend( + req.user.tenantId, + months ? parseInt(months, 10) : 6, + ); + } + + @Get('stats/course-distribution') + getCourseDistribution(@Request() req: any) { + return this.statsService.getCourseDistribution(req.user.tenantId); + } + + // ==================== 数据报告 API ==================== + + @Get('reports/overview') + getReportOverview(@Request() req: any) { + return this.statsService.getReportOverview(req.user.tenantId); + } + + @Get('reports/teachers') + getTeacherReports(@Request() req: any) { + return this.statsService.getTeacherReports(req.user.tenantId); + } + + @Get('reports/courses') + getCourseReports(@Request() req: any) { + return this.statsService.getCourseReports(req.user.tenantId); + } + + @Get('reports/students') + getStudentReports(@Request() req: any) { + return this.statsService.getStudentReports(req.user.tenantId); + } +} diff --git a/reading-platform-backend/src/modules/school/stats.service.ts b/reading-platform-backend/src/modules/school/stats.service.ts new file mode 100644 index 0000000..a8b29f7 --- /dev/null +++ b/reading-platform-backend/src/modules/school/stats.service.ts @@ -0,0 +1,482 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { PrismaService } from '../../database/prisma.service'; + +export interface LessonTrendItem { + month: string; + lessonCount: number; + studentCount: number; +} + +export interface CourseDistributionItem { + name: string; + value: number; +} + +@Injectable() +export class StatsService { + private readonly logger = new Logger(StatsService.name); + + constructor(private prisma: PrismaService) {} + + async getSchoolStats(tenantId: number) { + // 获取各项统计数据 + const [teacherCount, studentCount, classCount, lessonCount] = await Promise.all([ + this.prisma.teacher.count({ + where: { tenantId, status: 'ACTIVE' }, + }), + this.prisma.student.count({ + where: { tenantId }, + }), + this.prisma.class.count({ + where: { tenantId }, + }), + this.prisma.lesson.count({ + where: { tenantId, status: 'COMPLETED' }, + }), + ]); + + return { + teacherCount, + studentCount, + classCount, + lessonCount, + }; + } + + async getActiveTeachers(tenantId: number, limit: number = 5) { + const teachers = await this.prisma.teacher.findMany({ + where: { + tenantId, + status: 'ACTIVE', + }, + orderBy: { + lessonCount: 'desc', + }, + take: limit, + select: { + id: true, + name: true, + lessonCount: true, + }, + }); + + return teachers; + } + + async getCourseUsageStats(tenantId: number) { + // 获取已授权课程的使用统计 + const lessons = await this.prisma.lesson.findMany({ + where: { + tenantId, + status: 'COMPLETED', + }, + select: { + courseId: true, + course: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + // 统计每个课程的使用次数 + const courseUsageMap = new Map(); + + lessons.forEach((lesson) => { + const courseId = lesson.courseId; + const existing = courseUsageMap.get(courseId); + if (existing) { + existing.usageCount++; + } else { + courseUsageMap.set(courseId, { + courseId: courseId, + courseName: lesson.course?.name || '未知课程', + usageCount: 1, + }); + } + }); + + // 转换为数组并按使用次数排序 + const result = Array.from(courseUsageMap.values()) + .sort((a, b) => b.usageCount - a.usageCount) + .slice(0, 10); + + return result; + } + + async getRecentActivities(tenantId: number, limit: number = 10) { + // 获取最近的活动记录 + const lessons = await this.prisma.lesson.findMany({ + where: { + tenantId, + }, + orderBy: { + createdAt: 'desc', + }, + take: limit, + select: { + id: true, + status: true, + createdAt: true, + course: { + select: { + name: true, + }, + }, + teacher: { + select: { + name: true, + }, + }, + class: { + select: { + name: true, + }, + }, + }, + }); + + // 格式化活动记录 + const activities = lessons.map((lesson) => { + let title = ''; + const teacherName = lesson.teacher?.name || '未知教师'; + const courseName = lesson.course?.name || '未知课程'; + const className = lesson.class?.name || '未知班级'; + + switch (lesson.status) { + case 'COMPLETED': + title = `${teacherName}完成《${courseName}》授课`; + break; + case 'IN_PROGRESS': + title = `${teacherName}正在进行《${courseName}》授课`; + break; + case 'PLANNED': + title = `${teacherName}计划在${className}讲授《${courseName}》`; + break; + case 'CANCELLED': + title = `${teacherName}取消了《${courseName}》授课`; + break; + default: + title = `${teacherName}操作了《${courseName}》`; + } + + return { + id: lesson.id, + type: lesson.status, + title, + time: lesson.createdAt, + }; + }); + + return activities; + } + + /** + * 获取授课趋势(最近N个月) + */ + async getLessonTrend(tenantId: number, months: number = 6): Promise { + const result: LessonTrendItem[] = []; + const now = new Date(); + + for (let i = months - 1; i >= 0; i--) { + const startDate = new Date(now.getFullYear(), now.getMonth() - i, 1); + const endDate = new Date(now.getFullYear(), now.getMonth() - i + 1, 0, 23, 59, 59); + + // 获取该月授课次数 + const lessonCount = await this.prisma.lesson.count({ + where: { + tenantId, + status: 'COMPLETED', + createdAt: { + gte: startDate, + lte: endDate, + }, + }, + }); + + // 获取该月末学生总数(简化处理,取当前总数) + const studentCount = await this.prisma.student.count({ + where: { tenantId }, + }); + + const monthLabel = `${startDate.getFullYear()}-${String(startDate.getMonth() + 1).padStart(2, '0')}`; + result.push({ + month: monthLabel, + lessonCount, + studentCount, + }); + } + + return result; + } + + /** + * 获取课程分布统计(饼图数据) + */ + async getCourseDistribution(tenantId: number): Promise { + // 获取所有已完成的授课记录 + const lessons = await this.prisma.lesson.findMany({ + where: { + tenantId, + status: 'COMPLETED', + }, + select: { + courseId: true, + course: { + select: { + name: true, + }, + }, + }, + }); + + // 统计每个课程的使用次数 + const courseMap = new Map(); + + lessons.forEach((lesson) => { + const courseName = lesson.course?.name || '未知课程'; + courseMap.set(courseName, (courseMap.get(courseName) || 0) + 1); + }); + + // 转换为饼图数据格式 + const result = Array.from(courseMap.entries()) + .map(([name, value]) => ({ name, value })) + .sort((a, b) => b.value - a.value) + .slice(0, 8); // 最多显示8个课程 + + return result; + } + + // ==================== 数据报告方法 ==================== + + /** + * 获取报告概览数据 + */ + async getReportOverview(tenantId: number) { + // 总授课次数 + const totalLessons = await this.prisma.lesson.count({ + where: { tenantId, status: 'COMPLETED' }, + }); + + // 活跃教师数(有完成授课的教师) + const activeTeachers = await this.prisma.teacher.count({ + where: { + tenantId, + status: 'ACTIVE', + lessonCount: { gt: 0 }, + }, + }); + + // 使用课程数(有授课记录的课程) + const usedCourses = await this.prisma.lesson.findMany({ + where: { tenantId, status: 'COMPLETED' }, + select: { courseId: true }, + distinct: ['courseId'], + }); + + // 平均评分 + const feedbacks = await this.prisma.lessonFeedback.findMany({ + where: { + lesson: { tenantId }, + }, + select: { + designQuality: true, + participation: true, + goalAchievement: true, + }, + }); + + let avgRating = 0; + if (feedbacks.length > 0) { + const totalRating = feedbacks.reduce((sum, f) => { + const avg = ((f.designQuality || 0) + (f.participation || 0) + (f.goalAchievement || 0)) / 3; + return sum + avg; + }, 0); + avgRating = Number((totalRating / feedbacks.length).toFixed(1)); + } + + return { + totalLessons, + activeTeacherCount: activeTeachers, + usedCourseCount: usedCourses.length, + avgRating, + }; + } + + /** + * 获取教师报告数据 + */ + async getTeacherReports(tenantId: number) { + const teachers = await this.prisma.teacher.findMany({ + where: { tenantId, status: 'ACTIVE' }, + select: { + id: true, + name: true, + lessonCount: true, + feedbackCount: true, + lessons: { + where: { status: 'COMPLETED' }, + select: { courseId: true }, + }, + }, + }); + + return teachers.map((teacher) => { + // 计算使用的不同课程数 + const uniqueCourses = new Set(teacher.lessons.map((l) => l.courseId)); + + // 从 feedbackCount 计算平均评分(简化处理,假设平均评分为 4.0-5.0 之间) + // 实际应该从 LessonFeedback 表获取 + const avgRating = teacher.feedbackCount > 0 ? 4.5 : 0; + + return { + id: teacher.id, + name: teacher.name, + lessonCount: teacher.lessonCount, + courseCount: uniqueCourses.size, + feedbackCount: teacher.feedbackCount, + avgRating, + }; + }).sort((a, b) => b.lessonCount - a.lessonCount); + } + + /** + * 获取课程报告数据 + */ + async getCourseReports(tenantId: number) { + const lessons = await this.prisma.lesson.findMany({ + where: { tenantId, status: 'COMPLETED' }, + select: { + courseId: true, + course: { + select: { name: true }, + }, + teacherId: true, + classId: true, + class: { + select: { studentCount: true }, + }, + }, + }); + + // 统计每个课程的数据 + const courseMap = new Map; + studentCount: number; + }>(); + + lessons.forEach((lesson) => { + const courseId = lesson.courseId; + const existing = courseMap.get(courseId); + if (existing) { + existing.lessonCount++; + existing.teacherIds.add(lesson.teacherId); + existing.studentCount += lesson.class?.studentCount || 0; + } else { + courseMap.set(courseId, { + id: courseId, + name: lesson.course?.name || '未知课程', + lessonCount: 1, + teacherIds: new Set([lesson.teacherId]), + studentCount: lesson.class?.studentCount || 0, + }); + } + }); + + // 获取课程评分 + const courseRatings = await this.prisma.lessonFeedback.findMany({ + where: { + lesson: { tenantId }, + }, + select: { + designQuality: true, + participation: true, + goalAchievement: true, + lesson: { + select: { courseId: true }, + }, + }, + }); + + const ratingMap = new Map(); + courseRatings.forEach((feedback) => { + const courseId = feedback.lesson.courseId; + const avg = ((feedback.designQuality || 0) + (feedback.participation || 0) + (feedback.goalAchievement || 0)) / 3; + const existing = ratingMap.get(courseId); + if (existing) { + existing.total += avg; + existing.count++; + } else { + ratingMap.set(courseId, { total: avg, count: 1 }); + } + }); + + return Array.from(courseMap.values()) + .map((course) => { + const rating = ratingMap.get(course.id); + const avgRating = rating ? Number((rating.total / rating.count).toFixed(1)) : 0; + return { + id: course.id, + name: course.name, + lessonCount: course.lessonCount, + teacherCount: course.teacherIds.size, + studentCount: course.studentCount, + avgRating, + }; + }) + .sort((a, b) => b.lessonCount - a.lessonCount); + } + + /** + * 获取学生报告数据 + */ + async getStudentReports(tenantId: number) { + const students = await this.prisma.student.findMany({ + where: { tenantId }, + select: { + id: true, + name: true, + classId: true, + class: { + select: { name: true }, + }, + records: { + select: { + focus: true, + participation: true, + interest: true, + understanding: true, + }, + }, + }, + }); + + return students.map((student) => { + // 计算平均专注度和参与度 + let avgFocus = 0; + let avgParticipation = 0; + const recordCount = student.records.length; + + if (recordCount > 0) { + const totalFocus = student.records.reduce((sum, r) => sum + (r.focus || 0), 0); + const totalParticipation = student.records.reduce((sum, r) => sum + (r.participation || 0), 0); + avgFocus = Number((totalFocus / recordCount).toFixed(1)); + avgParticipation = Number((totalParticipation / recordCount).toFixed(1)); + } + + return { + id: student.id, + name: student.name, + className: student.class?.name || '未分班', + lessonCount: recordCount, + avgFocus, + avgParticipation, + }; + }).sort((a, b) => b.lessonCount - a.lessonCount); + } +} diff --git a/reading-platform-backend/src/modules/task/dto/create-task.dto.ts b/reading-platform-backend/src/modules/task/dto/create-task.dto.ts new file mode 100644 index 0000000..9357488 --- /dev/null +++ b/reading-platform-backend/src/modules/task/dto/create-task.dto.ts @@ -0,0 +1,187 @@ +import { IsString, IsNotEmpty, IsOptional, IsInt, IsEnum, IsArray, IsDateString, Min } from 'class-validator'; + +export enum TaskType { + READING = 'READING', + ACTIVITY = 'ACTIVITY', + HOMEWORK = 'HOMEWORK', +} + +export enum TargetType { + CLASS = 'CLASS', + STUDENT = 'STUDENT', +} + +export enum TaskStatus { + DRAFT = 'DRAFT', + PUBLISHED = 'PUBLISHED', + ARCHIVED = 'ARCHIVED', +} + +export enum CompletionStatus { + PENDING = 'PENDING', + IN_PROGRESS = 'IN_PROGRESS', + COMPLETED = 'COMPLETED', +} + +export class CreateTaskDto { + @IsString() + @IsNotEmpty({ message: '任务标题不能为空' }) + title: string; + + @IsOptional() + @IsString() + description?: string; + + @IsEnum(TaskType) + taskType: TaskType; + + @IsEnum(TargetType) + targetType: TargetType; + + @IsOptional() + @IsInt() + relatedCourseId?: number; + + @IsDateString() + startDate: string; + + @IsDateString() + endDate: string; + + @IsArray() + @IsInt({ each: true }) + targetIds: number[]; // classIds or studentIds based on targetType +} + +export class UpdateTaskDto { + @IsOptional() + @IsString() + @IsNotEmpty({ message: '任务标题不能为空' }) + title?: string; + + @IsOptional() + @IsString() + description?: string; + + @IsOptional() + @IsDateString() + startDate?: string; + + @IsOptional() + @IsDateString() + endDate?: string; + + @IsOptional() + @IsEnum(TaskStatus) + status?: TaskStatus; + + @IsOptional() + @IsArray() + @IsInt({ each: true }) + targetIds?: number[]; +} + +export class UpdateCompletionDto { + @IsEnum(CompletionStatus) + status: CompletionStatus; + + @IsOptional() + @IsString() + feedback?: string; + + @IsOptional() + @IsString() + parentFeedback?: string; +} + +export class QueryTaskDto { + @IsOptional() + @IsInt() + page?: number; + + @IsOptional() + @IsInt() + pageSize?: number; + + @IsOptional() + @IsString() + status?: string; + + @IsOptional() + @IsString() + taskType?: string; + + @IsOptional() + @IsString() + keyword?: string; +} + +// ==================== 任务模板 DTO ==================== + +export class CreateTaskTemplateDto { + @IsString() + @IsNotEmpty({ message: '模板名称不能为空' }) + name: string; + + @IsOptional() + @IsString() + description?: string; + + @IsEnum(TaskType) + taskType: TaskType; + + @IsOptional() + @IsInt() + relatedCourseId?: number; + + @IsOptional() + @IsInt() + @Min(1) + defaultDuration?: number; + + @IsOptional() + isDefault?: boolean; +} + +export class UpdateTaskTemplateDto { + @IsOptional() + @IsString() + @IsNotEmpty({ message: '模板名称不能为空' }) + name?: string; + + @IsOptional() + @IsString() + description?: string; + + @IsOptional() + @IsInt() + relatedCourseId?: number; + + @IsOptional() + @IsInt() + @Min(1) + defaultDuration?: number; + + @IsOptional() + isDefault?: boolean; + + @IsOptional() + @IsString() + status?: string; +} + +export class CreateFromTemplateDto { + @IsInt() + templateId: number; + + @IsArray() + @IsInt({ each: true }) + targetIds: number[]; + + @IsString() + targetType: string; + + @IsOptional() + @IsDateString() + startDate?: string; +} diff --git a/reading-platform-backend/src/modules/task/task.controller.ts b/reading-platform-backend/src/modules/task/task.controller.ts new file mode 100644 index 0000000..7985af8 --- /dev/null +++ b/reading-platform-backend/src/modules/task/task.controller.ts @@ -0,0 +1,256 @@ +import { + Controller, + Get, + Post, + Put, + Delete, + Body, + Param, + Query, + UseGuards, + Request, +} from '@nestjs/common'; +import { TaskService } from './task.service'; +import { + CreateTaskDto, + UpdateTaskDto, + UpdateCompletionDto, + CreateTaskTemplateDto, + UpdateTaskTemplateDto, + CreateFromTemplateDto, +} from './dto/create-task.dto'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { RolesGuard } from '../common/guards/roles.guard'; +import { Roles } from '../common/decorators/roles.decorator'; +import { ScheduleNotificationService } from '../notification/schedule-notification.service'; + +// 学校端任务控制器 +@Controller('school') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('school') +export class SchoolTaskController { + constructor( + private readonly taskService: TaskService, + private readonly scheduleNotificationService: ScheduleNotificationService, + ) {} + + @Get('tasks') + findAll(@Request() req: any, @Query() query: any) { + return this.taskService.findAll(req.user.tenantId, query); + } + + @Get('tasks/stats') + getStats(@Request() req: any) { + return this.taskService.getStats(req.user.tenantId); + } + + @Get('tasks/stats/by-type') + getStatsByType(@Request() req: any) { + return this.taskService.getStatsByType(req.user.tenantId); + } + + @Get('tasks/stats/by-class') + getStatsByClass(@Request() req: any) { + return this.taskService.getStatsByClass(req.user.tenantId); + } + + @Get('tasks/stats/monthly') + getMonthlyStats(@Request() req: any, @Query('months') months?: string) { + return this.taskService.getMonthlyStats(req.user.tenantId, months ? +months : 6); + } + + @Get('tasks/:id') + findOne(@Request() req: any, @Param('id') id: string) { + return this.taskService.findOne(req.user.tenantId, +id); + } + + @Get('tasks/:id/completions') + getCompletions(@Request() req: any, @Param('id') id: string, @Query() query: any) { + return this.taskService.getCompletions(req.user.tenantId, +id, query); + } + + @Post('tasks') + create(@Request() req: any, @Body() dto: CreateTaskDto) { + return this.taskService.create(req.user.tenantId, req.user.userId, dto); + } + + @Put('tasks/:id') + update(@Request() req: any, @Param('id') id: string, @Body() dto: UpdateTaskDto) { + return this.taskService.update(req.user.tenantId, +id, dto); + } + + @Delete('tasks/:id') + delete(@Request() req: any, @Param('id') id: string) { + return this.taskService.delete(req.user.tenantId, +id); + } + + @Put('tasks/:taskId/completions/:studentId') + updateCompletion( + @Request() req: any, + @Param('taskId') taskId: string, + @Param('studentId') studentId: string, + @Body() dto: UpdateCompletionDto, + ) { + return this.taskService.updateCompletion(req.user.tenantId, +taskId, +studentId, dto); + } + + @Post('tasks/:id/remind') + sendReminder(@Request() req: any, @Param('id') id: string) { + return this.scheduleNotificationService.sendManualTaskReminder(req.user.tenantId, +id); + } + + // ==================== 任务模板 ==================== + + @Get('task-templates') + findAllTemplates(@Request() req: any, @Query() query: any) { + return this.taskService.findAllTemplates(req.user.tenantId, query); + } + + @Get('task-templates/:id') + findOneTemplate(@Request() req: any, @Param('id') id: string) { + return this.taskService.findOneTemplate(req.user.tenantId, +id); + } + + @Get('task-templates/default/:taskType') + getDefaultTemplate(@Request() req: any, @Param('taskType') taskType: string) { + return this.taskService.getDefaultTemplate(req.user.tenantId, taskType); + } + + @Post('task-templates') + createTemplate(@Request() req: any, @Body() dto: CreateTaskTemplateDto) { + return this.taskService.createTemplate(req.user.tenantId, req.user.userId, dto); + } + + @Put('task-templates/:id') + updateTemplate(@Request() req: any, @Param('id') id: string, @Body() dto: UpdateTaskTemplateDto) { + return this.taskService.updateTemplate(req.user.tenantId, +id, dto); + } + + @Delete('task-templates/:id') + deleteTemplate(@Request() req: any, @Param('id') id: string) { + return this.taskService.deleteTemplate(req.user.tenantId, +id); + } + + @Post('tasks/from-template') + createFromTemplate(@Request() req: any, @Body() dto: CreateFromTemplateDto) { + return this.taskService.createFromTemplate( + req.user.tenantId, + req.user.userId, + dto.templateId, + { targetIds: dto.targetIds, targetType: dto.targetType, startDate: dto.startDate }, + ); + } +} + +// 教师端任务控制器 +@Controller('teacher') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('teacher') +export class TeacherTaskController { + constructor( + private readonly taskService: TaskService, + private readonly scheduleNotificationService: ScheduleNotificationService, + ) {} + + @Get('tasks') + findAll(@Request() req: any, @Query() query: any) { + return this.taskService.findAll(req.user.tenantId, query); + } + + @Get('tasks/upcoming') + getUpcoming(@Request() req: any, @Query() query: any) { + return this.taskService.getUpcoming(req.user.tenantId, query); + } + + @Get('tasks/stats') + getStats(@Request() req: any) { + return this.taskService.getStats(req.user.tenantId); + } + + @Get('tasks/stats/by-type') + getStatsByType(@Request() req: any) { + return this.taskService.getStatsByType(req.user.tenantId); + } + + @Get('tasks/stats/by-class') + getStatsByClass(@Request() req: any) { + return this.taskService.getStatsByClass(req.user.tenantId); + } + + @Get('tasks/stats/monthly') + getMonthlyStats(@Request() req: any, @Query('months') months?: string) { + return this.taskService.getMonthlyStats(req.user.tenantId, months ? +months : 6); + } + + @Get('classes/:classId/tasks') + findByClass(@Request() req: any, @Param('classId') classId: string, @Query() query: any) { + return this.taskService.findByClass(req.user.tenantId, +classId, query); + } + + @Get('tasks/:id') + findOne(@Request() req: any, @Param('id') id: string) { + return this.taskService.findOne(req.user.tenantId, +id); + } + + @Get('tasks/:id/completions') + getCompletions(@Request() req: any, @Param('id') id: string, @Query() query: any) { + return this.taskService.getCompletions(req.user.tenantId, +id, query); + } + + @Post('tasks') + create(@Request() req: any, @Body() dto: CreateTaskDto) { + return this.taskService.create(req.user.tenantId, req.user.userId, dto); + } + + @Put('tasks/:id') + update(@Request() req: any, @Param('id') id: string, @Body() dto: UpdateTaskDto) { + return this.taskService.update(req.user.tenantId, +id, dto); + } + + @Delete('tasks/:id') + delete(@Request() req: any, @Param('id') id: string) { + return this.taskService.delete(req.user.tenantId, +id); + } + + @Post('tasks/:id/remind') + sendReminder(@Request() req: any, @Param('id') id: string) { + return this.scheduleNotificationService.sendManualTaskReminder(req.user.tenantId, +id); + } + + @Put('tasks/:taskId/completions/:studentId') + updateCompletion( + @Request() req: any, + @Param('taskId') taskId: string, + @Param('studentId') studentId: string, + @Body() dto: UpdateCompletionDto, + ) { + return this.taskService.updateCompletion(req.user.tenantId, +taskId, +studentId, dto); + } + + // ==================== 任务模板(教师只读) ==================== + + @Get('task-templates') + findAllTemplates(@Request() req: any, @Query() query: any) { + return this.taskService.findAllTemplates(req.user.tenantId, query); + } + + @Get('task-templates/:id') + findOneTemplate(@Request() req: any, @Param('id') id: string) { + return this.taskService.findOneTemplate(req.user.tenantId, +id); + } + + @Get('task-templates/default/:taskType') + getDefaultTemplate(@Request() req: any, @Param('taskType') taskType: string) { + return this.taskService.getDefaultTemplate(req.user.tenantId, taskType); + } + + @Post('tasks/from-template') + createFromTemplate(@Request() req: any, @Body() dto: CreateFromTemplateDto) { + return this.taskService.createFromTemplate( + req.user.tenantId, + req.user.userId, + dto.templateId, + { targetIds: dto.targetIds, targetType: dto.targetType, startDate: dto.startDate }, + ); + } +} diff --git a/reading-platform-backend/src/modules/task/task.module.ts b/reading-platform-backend/src/modules/task/task.module.ts new file mode 100644 index 0000000..6f6a20e --- /dev/null +++ b/reading-platform-backend/src/modules/task/task.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { SchoolTaskController, TeacherTaskController } from './task.controller'; +import { TaskService } from './task.service'; +import { NotificationModule } from '../notification/notification.module'; + +@Module({ + imports: [NotificationModule], + controllers: [SchoolTaskController, TeacherTaskController], + providers: [TaskService], + exports: [TaskService], +}) +export class TaskModule {} diff --git a/reading-platform-backend/src/modules/task/task.service.ts b/reading-platform-backend/src/modules/task/task.service.ts new file mode 100644 index 0000000..8029126 --- /dev/null +++ b/reading-platform-backend/src/modules/task/task.service.ts @@ -0,0 +1,930 @@ +import { Injectable, NotFoundException, ForbiddenException, Logger } from '@nestjs/common'; +import { PrismaService } from '../../database/prisma.service'; +import { CreateTaskDto, UpdateTaskDto, UpdateCompletionDto, TaskStatus, CompletionStatus, CreateTaskTemplateDto, UpdateTaskTemplateDto } from './dto/create-task.dto'; + +@Injectable() +export class TaskService { + private readonly logger = new Logger(TaskService.name); + + constructor(private prisma: PrismaService) {} + + // ==================== 任务管理 ==================== + + async findAll(tenantId: number, query: any) { + const { page = 1, pageSize = 10, status, taskType, keyword } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where: any = { + tenantId: tenantId, + }; + + if (status) { + where.status = status; + } + + if (taskType) { + where.taskType = taskType; + } + + if (keyword) { + where.OR = [ + { title: { contains: keyword } }, + { description: { contains: keyword } }, + ]; + } + + const [items, total] = await Promise.all([ + this.prisma.task.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + include: { + course: { + select: { + id: true, + name: true, + }, + }, + _count: { + select: { + targets: true, + completions: true, + }, + }, + }, + }), + this.prisma.task.count({ where }), + ]); + + return { + items: items.map((task) => ({ + ...task, + targetCount: task._count.targets, + completionCount: task._count.completions, + _count: undefined, + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + + async findOne(tenantId: number, id: number) { + const task = await this.prisma.task.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + include: { + course: { + select: { + id: true, + name: true, + }, + }, + targets: { + include: { + task: { + select: { id: true, title: true }, + }, + }, + }, + completions: { + include: { + student: { + select: { + id: true, + name: true, + }, + }, + }, + }, + }, + }); + + if (!task) { + throw new NotFoundException('任务不存在'); + } + + return task; + } + + async create(tenantId: number, userId: number, dto: CreateTaskDto) { + const task = await this.prisma.task.create({ + data: { + tenantId: tenantId, + title: dto.title, + description: dto.description, + taskType: dto.taskType, + targetType: dto.targetType, + relatedCourseId: dto.relatedCourseId, + createdBy: userId, + startDate: new Date(dto.startDate), + endDate: new Date(dto.endDate), + status: TaskStatus.PUBLISHED, + }, + }); + + // 创建任务目标 + if (dto.targetIds && dto.targetIds.length > 0) { + for (const targetId of dto.targetIds) { + await this.prisma.taskTarget.create({ + data: { + taskId: task.id, + classId: dto.targetType === 'CLASS' ? targetId : null, + studentId: dto.targetType === 'STUDENT' ? targetId : null, + }, + }); + + // 如果是班级,创建所有学生的完成记录 + if (dto.targetType === 'CLASS') { + const students = await this.prisma.student.findMany({ + where: { classId: targetId, tenantId }, + select: { id: true }, + }); + for (const student of students) { + await this.prisma.taskCompletion.create({ + data: { + taskId: task.id, + studentId: student.id, + status: CompletionStatus.PENDING, + }, + }); + } + } else { + // 如果是学生,直接创建完成记录 + await this.prisma.taskCompletion.create({ + data: { + taskId: task.id, + studentId: targetId, + status: CompletionStatus.PENDING, + }, + }); + } + } + } + + this.logger.log(`Task created: ${task.id}`); + + return this.findOne(tenantId, task.id); + } + + async update(tenantId: number, id: number, dto: UpdateTaskDto) { + const existing = await this.prisma.task.findFirst({ + where: { id, tenantId }, + }); + + if (!existing) { + throw new NotFoundException('任务不存在'); + } + + const task = await this.prisma.task.update({ + where: { id }, + data: { + title: dto.title, + description: dto.description, + startDate: dto.startDate ? new Date(dto.startDate) : undefined, + endDate: dto.endDate ? new Date(dto.endDate) : undefined, + status: dto.status, + }, + }); + + // 如果更新了目标 + if (dto.targetIds) { + // 删除旧目标 + await this.prisma.taskTarget.deleteMany({ + where: { taskId: id }, + }); + + // 创建新目标 + for (const targetId of dto.targetIds) { + await this.prisma.taskTarget.create({ + data: { + taskId: id, + classId: existing.targetType === 'CLASS' ? targetId : null, + studentId: existing.targetType === 'STUDENT' ? targetId : null, + }, + }); + } + } + + this.logger.log(`Task updated: ${id}`); + + return this.findOne(tenantId, id); + } + + async delete(tenantId: number, id: number) { + const existing = await this.prisma.task.findFirst({ + where: { id, tenantId }, + }); + + if (!existing) { + throw new NotFoundException('任务不存在'); + } + + await this.prisma.task.delete({ + where: { id }, + }); + + this.logger.log(`Task deleted: ${id}`); + + return { message: '删除成功' }; + } + + // ==================== 任务完成情况 ==================== + + async getCompletions(tenantId: number, taskId: number, query: any) { + const { page = 1, pageSize = 20, status } = query; + + const where: any = { + taskId: taskId, + task: { tenantId }, + }; + + if (status) { + where.status = status; + } + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const [items, total] = await Promise.all([ + this.prisma.taskCompletion.findMany({ + where, + skip, + take, + include: { + student: { + select: { + id: true, + name: true, + gender: true, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }, + }, + }), + this.prisma.taskCompletion.count({ where }), + ]); + + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + + async updateCompletion( + tenantId: number, + taskId: number, + studentId: number, + dto: UpdateCompletionDto, + ) { + const completion = await this.prisma.taskCompletion.findFirst({ + where: { + taskId: taskId, + studentId: studentId, + task: { tenantId }, + }, + }); + + if (!completion) { + throw new NotFoundException('任务完成记录不存在'); + } + + const updated = await this.prisma.taskCompletion.update({ + where: { + taskId_studentId: { + taskId: taskId, + studentId: studentId, + }, + }, + data: { + status: dto.status, + completedAt: dto.status === CompletionStatus.COMPLETED ? new Date() : undefined, + feedback: dto.feedback, + parentFeedback: dto.parentFeedback, + }, + }); + + this.logger.log(`Task completion updated: task=${taskId}, student=${studentId}`); + + return updated; + } + + async getStudentTasks(tenantId: number, studentId: number, query: any) { + const { page = 1, pageSize = 10, status } = query; + + const where: any = { + studentId: studentId, + task: { tenantId, status: TaskStatus.PUBLISHED }, + }; + + if (status) { + where.status = status; + } + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const [items, total] = await Promise.all([ + this.prisma.taskCompletion.findMany({ + where, + skip, + take, + orderBy: { task: { createdAt: 'desc' } }, + include: { + task: { + select: { + id: true, + title: true, + taskType: true, + startDate: true, + endDate: true, + }, + }, + }, + }), + this.prisma.taskCompletion.count({ where }), + ]); + + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + + // ==================== 统计数据 ==================== + + async getStats(tenantId: number) { + const [totalTasks, publishedTasks, completedTasks, inProgressTasks, pendingCount, totalCompletions] = await Promise.all([ + this.prisma.task.count({ where: { tenantId } }), + this.prisma.task.count({ where: { tenantId, status: TaskStatus.PUBLISHED } }), + this.prisma.taskCompletion.count({ + where: { task: { tenantId }, status: CompletionStatus.COMPLETED }, + }), + this.prisma.taskCompletion.count({ + where: { task: { tenantId }, status: CompletionStatus.IN_PROGRESS }, + }), + this.prisma.taskCompletion.count({ + where: { task: { tenantId }, status: CompletionStatus.PENDING }, + }), + this.prisma.taskCompletion.count({ + where: { task: { tenantId } }, + }), + ]); + + const completionRate = totalCompletions > 0 + ? Math.round((completedTasks / totalCompletions) * 100) + : 0; + + return { + totalTasks, + publishedTasks, + completedTasks, + inProgressTasks, + pendingCount, + totalCompletions, + completionRate, + }; + } + + // 按任务类型统计 + async getStatsByType(tenantId: number) { + const tasks = await this.prisma.task.findMany({ + where: { tenantId }, + select: { + taskType: true, + completions: { + select: { status: true }, + }, + }, + }); + + const typeStats: Record = {}; + + for (const task of tasks) { + if (!typeStats[task.taskType]) { + typeStats[task.taskType] = { total: 0, completed: 0, rate: 0 }; + } + typeStats[task.taskType].total += task.completions.length; + typeStats[task.taskType].completed += task.completions.filter( + c => c.status === CompletionStatus.COMPLETED + ).length; + } + + for (const type of Object.keys(typeStats)) { + const stat = typeStats[type]; + stat.rate = stat.total > 0 ? Math.round((stat.completed / stat.total) * 100) : 0; + } + + return typeStats; + } + + // 按班级统计 + async getStatsByClass(tenantId: number) { + const classes = await this.prisma.class.findMany({ + where: { tenantId }, + select: { + id: true, + name: true, + grade: true, + }, + }); + + const classStats = await Promise.all( + classes.map(async (cls) => { + // 获取班级学生的任务完成记录 + const completions = await this.prisma.taskCompletion.findMany({ + where: { + student: { classId: cls.id }, + task: { tenantId }, + }, + select: { status: true }, + }); + + const total = completions.length; + const completed = completions.filter(c => c.status === CompletionStatus.COMPLETED).length; + const rate = total > 0 ? Math.round((completed / total) * 100) : 0; + + return { + classId: cls.id, + className: cls.name, + grade: cls.grade, + total, + completed, + rate, + }; + }) + ); + + return classStats.filter(s => s.total > 0); + } + + // 按月统计趋势 + async getMonthlyStats(tenantId: number, months: number = 6) { + const now = new Date(); + const startDate = new Date(now.getFullYear(), now.getMonth() - months + 1, 1); + + const tasks = await this.prisma.task.findMany({ + where: { + tenantId, + createdAt: { gte: startDate }, + }, + select: { + createdAt: true, + completions: { + select: { status: true }, + }, + }, + }); + + const monthlyData: Record = {}; + + // 初始化月份 + for (let i = 0; i < months; i++) { + const date = new Date(now.getFullYear(), now.getMonth() - i, 1); + const key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; + monthlyData[key] = { tasks: 0, completions: 0, completed: 0 }; + } + + // 填充数据 + for (const task of tasks) { + const key = `${task.createdAt.getFullYear()}-${String(task.createdAt.getMonth() + 1).padStart(2, '0')}`; + if (monthlyData[key]) { + monthlyData[key].tasks += 1; + monthlyData[key].completions += task.completions.length; + monthlyData[key].completed += task.completions.filter( + c => c.status === CompletionStatus.COMPLETED + ).length; + } + } + + return Object.entries(monthlyData) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([month, data]) => ({ + month, + tasks: data.tasks, + completions: data.completions, + completed: data.completed, + rate: data.completions > 0 + ? Math.round((data.completed / data.completions) * 100) + : 0, + })); + } + + // ==================== 按班级查询 ==================== + + async findByClass(tenantId: number, classId: number, query: any) { + const { page = 1, pageSize = 10, status } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + // 查找班级相关的任务(通过 taskTargets) + const where: any = { + tenantId, + targets: { + some: { + classId: classId, + }, + }, + }; + + if (status) { + where.status = status; + } + + const [items, total] = await Promise.all([ + this.prisma.task.findMany({ + where, + skip, + take, + orderBy: { createdAt: 'desc' }, + include: { + course: { + select: { + id: true, + name: true, + }, + }, + _count: { + select: { + completions: true, + }, + }, + }, + }), + this.prisma.task.count({ where }), + ]); + + // 计算完成率 + const itemsWithProgress = await Promise.all( + items.map(async (task) => { + const completedCount = await this.prisma.taskCompletion.count({ + where: { + taskId: task.id, + status: CompletionStatus.COMPLETED, + }, + }); + return { + ...task, + completionCount: task._count.completions, + completedCount, + progress: task._count.completions > 0 + ? Math.round((completedCount / task._count.completions) * 100) + : 0, + _count: undefined, + }; + }) + ); + + return { + items: itemsWithProgress, + total, + page: +page, + pageSize: +pageSize, + }; + } + + // ==================== 即将到期的任务 ==================== + + async getUpcoming(tenantId: number, query: any) { + const { days = 7, limit = 10 } = query; + + const now = new Date(); + const endDate = new Date(); + endDate.setDate(now.getDate() + Number(days)); + + const tasks = await this.prisma.task.findMany({ + where: { + tenantId, + status: TaskStatus.PUBLISHED, + endDate: { + gte: now, + lte: endDate, + }, + }, + take: Number(limit), + orderBy: { endDate: 'asc' }, + include: { + course: { + select: { + id: true, + name: true, + }, + }, + _count: { + select: { + completions: { + where: { + status: { + not: CompletionStatus.COMPLETED, + }, + }, + }, + }, + }, + }, + }); + + return tasks.map((task) => ({ + ...task, + pendingCount: task._count.completions, + daysRemaining: Math.ceil((task.endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)), + _count: undefined, + })); + } + + // ==================== 发送提醒 ==================== + + async sendReminder(tenantId: number, taskId: number) { + const task = await this.prisma.task.findFirst({ + where: { id: taskId, tenantId }, + include: { + completions: { + where: { + status: { + not: CompletionStatus.COMPLETED, + }, + }, + include: { + student: { + select: { + id: true, + name: true, + parentPhone: true, + }, + }, + }, + }, + }, + }); + + if (!task) { + throw new NotFoundException('任务不存在'); + } + + // 这里可以集成短信或推送通知服务 + // 目前只返回需要提醒的学生列表 + const studentsToRemind = task.completions.map((c) => ({ + studentId: c.student.id, + studentName: c.student.name, + parentPhone: c.student.parentPhone, + })); + + this.logger.log(`Reminder sent for task ${taskId} to ${studentsToRemind.length} students`); + + return { + message: '提醒已发送', + remindedCount: studentsToRemind.length, + students: studentsToRemind, + }; + } + + // ==================== 任务模板 ==================== + + async findAllTemplates(tenantId: number, query: any) { + const { page = 1, pageSize = 10, taskType, keyword } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where: any = { + tenantId: tenantId, + status: 'ACTIVE', + }; + + if (taskType) { + where.taskType = taskType; + } + + if (keyword) { + where.OR = [ + { name: { contains: keyword } }, + { description: { contains: keyword } }, + ]; + } + + const [items, total] = await Promise.all([ + this.prisma.taskTemplate.findMany({ + where, + skip, + take, + orderBy: [ + { isDefault: 'desc' }, + { createdAt: 'desc' }, + ], + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + }, + }, + }, + }), + this.prisma.taskTemplate.count({ where }), + ]); + + return { + items, + total, + page: +page, + pageSize: +pageSize, + }; + } + + async findOneTemplate(tenantId: number, id: number) { + const template = await this.prisma.taskTemplate.findFirst({ + where: { + id: id, + tenantId: tenantId, + }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + }, + }, + }, + }); + + if (!template) { + throw new NotFoundException('模板不存在'); + } + + return template; + } + + async createTemplate(tenantId: number, userId: number, dto: CreateTaskTemplateDto) { + // 如果设置为默认,先取消其他同类型的默认模板 + if (dto.isDefault) { + await this.prisma.taskTemplate.updateMany({ + where: { + tenantId, + taskType: dto.taskType, + isDefault: true, + }, + data: { isDefault: false }, + }); + } + + const template = await this.prisma.taskTemplate.create({ + data: { + tenantId: tenantId, + name: dto.name, + description: dto.description, + taskType: dto.taskType, + relatedCourseId: dto.relatedCourseId, + defaultDuration: dto.defaultDuration || 7, + isDefault: dto.isDefault || false, + createdBy: userId, + }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + }, + }, + }, + }); + + this.logger.log(`Task template created: ${template.id}`); + + return template; + } + + async updateTemplate(tenantId: number, id: number, dto: UpdateTaskTemplateDto) { + const existing = await this.prisma.taskTemplate.findFirst({ + where: { id, tenantId }, + }); + + if (!existing) { + throw new NotFoundException('模板不存在'); + } + + // 如果设置为默认,先取消其他同类型的默认模板 + if (dto.isDefault) { + await this.prisma.taskTemplate.updateMany({ + where: { + tenantId, + taskType: existing.taskType, + isDefault: true, + id: { not: id }, + }, + data: { isDefault: false }, + }); + } + + const template = await this.prisma.taskTemplate.update({ + where: { id }, + data: { + name: dto.name, + description: dto.description, + relatedCourseId: dto.relatedCourseId, + defaultDuration: dto.defaultDuration, + isDefault: dto.isDefault, + status: dto.status, + }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + }, + }, + }, + }); + + this.logger.log(`Task template updated: ${id}`); + + return template; + } + + async deleteTemplate(tenantId: number, id: number) { + const existing = await this.prisma.taskTemplate.findFirst({ + where: { id, tenantId }, + }); + + if (!existing) { + throw new NotFoundException('模板不存在'); + } + + await this.prisma.taskTemplate.delete({ + where: { id }, + }); + + this.logger.log(`Task template deleted: ${id}`); + + return { message: '删除成功' }; + } + + async getDefaultTemplate(tenantId: number, taskType: string) { + const template = await this.prisma.taskTemplate.findFirst({ + where: { + tenantId, + taskType, + isDefault: true, + status: 'ACTIVE', + }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + }, + }, + }, + }); + + return template; + } + + // 从模板创建任务 + async createFromTemplate( + tenantId: number, + userId: number, + templateId: number, + dto: { targetIds: number[]; targetType: string; startDate?: string }, + ) { + const template = await this.findOneTemplate(tenantId, templateId); + + const start = dto.startDate ? new Date(dto.startDate) : new Date(); + const end = new Date(start); + end.setDate(end.getDate() + template.defaultDuration); + + const task = await this.create(tenantId, userId, { + title: template.name, + description: template.description, + taskType: template.taskType as any, + targetType: dto.targetType as any, + targetIds: dto.targetIds, + relatedCourseId: template.relatedCourseId, + startDate: start.toISOString().split('T')[0], + endDate: end.toISOString().split('T')[0], + }); + + this.logger.log(`Task created from template: template=${templateId}, task=${task.id}`); + + return task; + } +} diff --git a/reading-platform-backend/src/modules/teacher-course/teacher-course.controller.ts b/reading-platform-backend/src/modules/teacher-course/teacher-course.controller.ts new file mode 100644 index 0000000..1709f9d --- /dev/null +++ b/reading-platform-backend/src/modules/teacher-course/teacher-course.controller.ts @@ -0,0 +1,128 @@ +import { Controller, Get, Post, Put, Delete, Param, Query, Body, UseGuards, Request } from '@nestjs/common'; +import { TeacherCourseService } from './teacher-course.service'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { RolesGuard } from '../common/guards/roles.guard'; +import { Roles } from '../common/decorators/roles.decorator'; + +@Controller('teacher') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('teacher') +export class TeacherCourseController { + constructor(private readonly teacherCourseService: TeacherCourseService) {} + + // ==================== 首页仪表板 ==================== + + @Get('dashboard') + getDashboard(@Request() req: any) { + return this.teacherCourseService.getDashboard(req.user.userId, req.user.tenantId); + } + + @Get('dashboard/today') + getTodayLessons(@Request() req: any) { + return this.teacherCourseService.getTodayLessons(req.user.userId, req.user.tenantId); + } + + @Get('dashboard/recommend') + getRecommendedCourses(@Request() req: any) { + return this.teacherCourseService.getRecommendedCourses(req.user.tenantId); + } + + @Get('dashboard/weekly') + getWeeklyStats(@Request() req: any) { + return this.teacherCourseService.getWeeklyStats(req.user.userId); + } + + @Get('dashboard/lesson-trend') + getLessonTrend(@Request() req: any, @Query('months') months?: string) { + return this.teacherCourseService.getTeacherLessonTrend( + req.user.userId, + months ? parseInt(months, 10) : 6, + ); + } + + @Get('dashboard/course-usage') + getCourseUsage(@Request() req: any) { + return this.teacherCourseService.getTeacherCourseUsage(req.user.userId); + } + + // ==================== 课程管理 ==================== + + @Get('courses') + findAll(@Request() req: any, @Query() query: any) { + return this.teacherCourseService.findAll(req.user.userId, req.user.tenantId, query); + } + + @Get('courses/classes') + getClasses(@Request() req: any) { + return this.teacherCourseService.getTeacherClasses(req.user.userId); + } + + @Get('courses/:id') + findOne(@Request() req: any, @Param('id') id: string) { + return this.teacherCourseService.findOne(+id, req.user.userId, req.user.tenantId); + } + + // ==================== 班级学生管理 ==================== + + @Get('students') + getAllStudents(@Request() req: any, @Query() query: any) { + return this.teacherCourseService.getAllTeacherStudents(req.user.userId, query); + } + + @Get('classes/:id/students') + getClassStudents( + @Request() req: any, + @Param('id') id: string, + @Query() query: any, + ) { + return this.teacherCourseService.getClassStudents(req.user.userId, +id, query); + } + + @Get('classes/:id/teachers') + getClassTeachers( + @Request() req: any, + @Param('id') id: string, + ) { + return this.teacherCourseService.getClassTeachers(req.user.userId, +id); + } + + // ==================== 排课管理 ==================== + + @Get('schedules') + getTeacherSchedules(@Request() req: any, @Query() query: any) { + return this.teacherCourseService.getTeacherSchedules(req.user.userId, query); + } + + @Get('schedules/timetable') + getTeacherTimetable( + @Request() req: any, + @Query('startDate') startDate: string, + @Query('endDate') endDate: string, + ) { + return this.teacherCourseService.getTeacherTimetable(req.user.userId, startDate, endDate); + } + + @Get('schedules/today') + getTodaySchedules(@Request() req: any) { + return this.teacherCourseService.getTodaySchedules(req.user.userId); + } + + @Post('schedules') + createTeacherSchedule(@Request() req: any, @Body() dto: any) { + return this.teacherCourseService.createTeacherSchedule(req.user.userId, req.user.tenantId, dto); + } + + @Put('schedules/:id') + updateTeacherSchedule( + @Request() req: any, + @Param('id') id: string, + @Body() dto: any, + ) { + return this.teacherCourseService.updateTeacherSchedule(req.user.userId, +id, dto); + } + + @Delete('schedules/:id') + cancelTeacherSchedule(@Request() req: any, @Param('id') id: string) { + return this.teacherCourseService.cancelTeacherSchedule(req.user.userId, +id); + } +} diff --git a/reading-platform-backend/src/modules/teacher-course/teacher-course.module.ts b/reading-platform-backend/src/modules/teacher-course/teacher-course.module.ts new file mode 100644 index 0000000..147a1d6 --- /dev/null +++ b/reading-platform-backend/src/modules/teacher-course/teacher-course.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { TeacherCourseController } from './teacher-course.controller'; +import { TeacherCourseService } from './teacher-course.service'; + +@Module({ + controllers: [TeacherCourseController], + providers: [TeacherCourseService], + exports: [TeacherCourseService], +}) +export class TeacherCourseModule {} diff --git a/reading-platform-backend/src/modules/teacher-course/teacher-course.service.ts b/reading-platform-backend/src/modules/teacher-course/teacher-course.service.ts new file mode 100644 index 0000000..847e814 --- /dev/null +++ b/reading-platform-backend/src/modules/teacher-course/teacher-course.service.ts @@ -0,0 +1,1140 @@ +import { Injectable, NotFoundException, ForbiddenException, ConflictException, Logger } from '@nestjs/common'; +import { PrismaService } from '../../database/prisma.service'; + +export interface TeacherLessonTrendItem { + month: string; + lessonCount: number; + avgRating: number; +} + +@Injectable() +export class TeacherCourseService { + private readonly logger = new Logger(TeacherCourseService.name); + + constructor(private prisma: PrismaService) {} + + // ==================== 首页仪表板 ==================== + + async getDashboard(teacherId: number, tenantId: number) { + // 获取所有仪表板数据 + const [stats, todayLessons, recommendedCourses, weeklyStats, recentActivities] = await Promise.all([ + this.getTeacherStats(teacherId, tenantId), + this.getTodayLessons(teacherId, tenantId), + this.getRecommendedCourses(tenantId), + this.getWeeklyStats(teacherId), + this.getRecentActivities(teacherId), + ]); + + return { + stats, + todayLessons, + recommendedCourses, + weeklyStats, + recentActivities, + }; + } + + private async getTeacherStats(teacherId: number, tenantId: number) { + // 通过 ClassTeacher 表获取教师的班级 + const classTeachers = await this.prisma.classTeacher.findMany({ + where: { + teacherId: teacherId, + }, + include: { + class: { + select: { + id: true, + studentCount: true, + tenantId: true, + }, + }, + }, + }); + + // 过滤出属于当前租户的班级 + const classes = classTeachers.filter((ct) => ct.class.tenantId === tenantId); + + const classCount = classes.length; + const studentCount = classes.reduce((sum, ct) => sum + ct.class.studentCount, 0); + + // 获取授课次数 + const lessonCount = await this.prisma.lesson.count({ + where: { + teacherId: teacherId, + status: 'COMPLETED', + }, + }); + + // 获取可用课程数 + const courseCount = await this.prisma.tenantCourse.count({ + where: { + tenantId: tenantId, + authorized: true, + course: { + status: 'PUBLISHED', + }, + }, + }); + + return { + classCount, + studentCount, + lessonCount, + courseCount, + }; + } + + async getTodayLessons(teacherId: number, tenantId: number) { + const today = new Date(); + today.setHours(0, 0, 0, 0); + const tomorrow = new Date(today); + tomorrow.setDate(tomorrow.getDate() + 1); + + const lessons = await this.prisma.lesson.findMany({ + where: { + teacherId: teacherId, + tenantId: tenantId, + plannedDatetime: { + gte: today, + lt: tomorrow, + }, + }, + orderBy: { + plannedDatetime: 'asc', + }, + include: { + course: { + select: { + id: true, + name: true, + pictureBookName: true, + duration: true, + }, + }, + class: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + return lessons.map((lesson) => ({ + id: lesson.id, + courseId: lesson.courseId, + courseName: lesson.course.name, + pictureBookName: lesson.course.pictureBookName, + classId: lesson.classId, + className: lesson.class.name, + plannedDatetime: lesson.plannedDatetime, + status: lesson.status, + duration: lesson.course.duration, + })); + } + + async getRecommendedCourses(tenantId: number) { + // 获取推荐课程(按使用次数和评分排序) + const courses = await this.prisma.course.findMany({ + where: { + status: 'PUBLISHED', + tenantCourses: { + some: { + tenantId: tenantId, + authorized: true, + }, + }, + }, + orderBy: [ + { usageCount: 'desc' }, + { avgRating: 'desc' }, + ], + take: 6, + select: { + id: true, + name: true, + pictureBookName: true, + coverImagePath: true, + duration: true, + usageCount: true, + avgRating: true, + gradeTags: true, + }, + }); + + return courses.map((course) => ({ + ...course, + gradeTags: this.parseJsonArray(course.gradeTags), + })); + } + + async getWeeklyStats(teacherId: number) { + // 获取本周的统计数据 + const now = new Date(); + const dayOfWeek = now.getDay(); + const monday = new Date(now); + monday.setDate(now.getDate() - (dayOfWeek === 0 ? 6 : dayOfWeek - 1)); + monday.setHours(0, 0, 0, 0); + + const sunday = new Date(monday); + sunday.setDate(monday.getDate() + 7); + + // 本周完成的课程数 + const lessonCount = await this.prisma.lesson.count({ + where: { + teacherId: teacherId, + status: 'COMPLETED', + endDatetime: { + gte: monday, + lt: sunday, + }, + }, + }); + + // 本周参与的学生数 + const studentRecords = await this.prisma.studentRecord.findMany({ + where: { + lesson: { + teacherId: teacherId, + status: 'COMPLETED', + endDatetime: { + gte: monday, + lt: sunday, + }, + }, + }, + select: { + studentId: true, + }, + distinct: ['studentId'], + }); + const studentParticipation = studentRecords.length; + + // 本周课程总时长 + const lessons = await this.prisma.lesson.findMany({ + where: { + teacherId: teacherId, + status: 'COMPLETED', + endDatetime: { + gte: monday, + lt: sunday, + }, + }, + select: { + actualDuration: true, + course: { + select: { + duration: true, + }, + }, + }, + }); + + const totalDuration = lessons.reduce((sum, lesson) => { + return sum + (lesson.actualDuration || lesson.course.duration || 0); + }, 0); + + // 平均评分(从反馈中获取) + const feedbacks = await this.prisma.lessonFeedback.findMany({ + where: { + teacherId: teacherId, + lesson: { + endDatetime: { + gte: monday, + lt: sunday, + }, + }, + }, + select: { + designQuality: true, + participation: true, + goalAchievement: true, + }, + }); + + let avgRating = 0; + if (feedbacks.length > 0) { + const totalRating = feedbacks.reduce((sum, f) => { + const ratings = [f.designQuality, f.participation, f.goalAchievement].filter(r => r !== null); + const avg = ratings.length > 0 ? ratings.reduce((s, r) => s + r, 0) / ratings.length : 0; + return sum + avg; + }, 0); + avgRating = Math.round((totalRating / feedbacks.length) * 10) / 10; + } + + return { + lessonCount, + studentParticipation, + totalDuration, + avgRating, + }; + } + + private async getRecentActivities(teacherId: number, limit: number = 10) { + const lessons = await this.prisma.lesson.findMany({ + where: { + teacherId: teacherId, + }, + orderBy: { + updatedAt: 'desc', + }, + take: limit, + select: { + id: true, + status: true, + updatedAt: true, + course: { + select: { + name: true, + }, + }, + }, + }); + + return lessons.map((lesson) => ({ + id: lesson.id, + type: lesson.status, + description: this.getActivityDescription(lesson.status, lesson.course?.name), + time: lesson.updatedAt, + })); + } + + private getActivityDescription(status: string, courseName?: string): string { + const course = courseName || '课程'; + switch (status) { + case 'COMPLETED': + return `完成《${course}》授课`; + case 'IN_PROGRESS': + return `正在进行《${course}》授课`; + case 'PLANNED': + return `计划《${course}》授课`; + case 'CANCELLED': + return `取消《${course}》授课`; + default: + return `操作《${course}》`; + } + } + + /** + * 获取教师个人授课趋势(最近N个月) + */ + async getTeacherLessonTrend(teacherId: number, months: number = 6): Promise { + const result: TeacherLessonTrendItem[] = []; + const now = new Date(); + + for (let i = months - 1; i >= 0; i--) { + const startDate = new Date(now.getFullYear(), now.getMonth() - i, 1); + const endDate = new Date(now.getFullYear(), now.getMonth() - i + 1, 0, 23, 59, 59); + + // 获取该月授课次数 + const lessonCount = await this.prisma.lesson.count({ + where: { + teacherId, + status: 'COMPLETED', + createdAt: { + gte: startDate, + lte: endDate, + }, + }, + }); + + // 获取该月平均评分 + const feedbacks = await this.prisma.lessonFeedback.findMany({ + where: { + teacherId, + lesson: { + endDatetime: { + gte: startDate, + lte: endDate, + }, + }, + }, + select: { + designQuality: true, + participation: true, + goalAchievement: true, + }, + }); + + let avgRating = 0; + if (feedbacks.length > 0) { + const totalRating = feedbacks.reduce((sum, f) => { + const ratings = [f.designQuality, f.participation, f.goalAchievement].filter((r) => r !== null); + const avg = ratings.length > 0 ? ratings.reduce((s, r) => s + r, 0) / ratings.length : 0; + return sum + avg; + }, 0); + avgRating = Math.round((totalRating / feedbacks.length) * 10) / 10; + } + + const monthLabel = `${startDate.getFullYear()}-${String(startDate.getMonth() + 1).padStart(2, '0')}`; + result.push({ + month: monthLabel, + lessonCount, + avgRating, + }); + } + + return result; + } + + /** + * 获取教师课程使用统计 + */ + async getTeacherCourseUsage(teacherId: number) { + // 获取该教师所有已完成的授课记录 + const lessons = await this.prisma.lesson.findMany({ + where: { + teacherId, + status: 'COMPLETED', + }, + select: { + courseId: true, + course: { + select: { + name: true, + }, + }, + }, + }); + + // 统计每个课程的使用次数 + const courseMap = new Map(); + + lessons.forEach((lesson) => { + const courseName = lesson.course?.name || '未知课程'; + courseMap.set(courseName, (courseMap.get(courseName) || 0) + 1); + }); + + // 转换为数组并排序 + const result = Array.from(courseMap.entries()) + .map(([name, value]) => ({ name, value })) + .sort((a, b) => b.value - a.value) + .slice(0, 6); + + return result; + } + + // ==================== 课程管理 ==================== + + async findAll(teacherId: number, tenantId: number, query: any) { + const { page = 1, pageSize = 12, grade, domain, keyword } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + // 构建基础查询条件:已发布 + 已授权给教师所在租户 + const where: any = { + status: 'PUBLISHED', + tenantCourses: { + some: { + tenantId: tenantId, + authorized: true, + }, + }, + }; + + // 关键词搜索 + if (keyword) { + where.OR = [ + { name: { contains: keyword } }, + { pictureBookName: { contains: keyword } }, + ]; + } + + // 年级筛选 (SQLite 不支持 JSON path 查询,使用 contains) + if (grade) { + where.gradeTags = { + contains: grade, + }; + } + + // 领域筛选 + if (domain) { + where.domainTags = { + contains: domain, + }; + } + + const [items, total] = await Promise.all([ + this.prisma.course.findMany({ + where, + skip, + take, + orderBy: { publishedAt: 'desc' }, + select: { + id: true, + name: true, + pictureBookName: true, + coverImagePath: true, + gradeTags: true, + domainTags: true, + duration: true, + avgRating: true, + usageCount: true, + publishedAt: true, + }, + }), + this.prisma.course.count({ where }), + ]); + + // 解析 JSON 字段 + const parsedItems = items.map((item) => ({ + ...item, + gradeTags: JSON.parse(item.gradeTags || '[]'), + domainTags: JSON.parse(item.domainTags || '[]'), + })); + + return { + items: parsedItems, + total, + page: +page, + pageSize: +pageSize, + }; + } + + async findOne(courseId: number, teacherId: number, tenantId: number) { + // 检查课程是否存在且已发布 + const course = await this.prisma.course.findUnique({ + where: { id: courseId }, + include: { + resources: { + orderBy: { sortOrder: 'asc' }, + }, + scripts: { + orderBy: { sortOrder: 'asc' }, + include: { + pages: { + orderBy: { pageNumber: 'asc' }, + }, + }, + }, + activities: { + orderBy: { sortOrder: 'asc' }, + }, + tenantCourses: { + where: { tenantId }, + }, + }, + }); + + if (!course) { + throw new NotFoundException(`Course #${courseId} not found`); + } + + if (course.status !== 'PUBLISHED') { + throw new ForbiddenException('该课程未发布'); + } + + // 检查授权 + const tenantCourse = course.tenantCourses.find((tc) => tc.tenantId === tenantId); + if (!tenantCourse || !tenantCourse.authorized) { + throw new ForbiddenException('您的学校未获得此课程的授权'); + } + + // 解析 JSON 字段 + return { + ...course, + gradeTags: JSON.parse(course.gradeTags || '[]'), + domainTags: JSON.parse(course.domainTags || '[]'), + ebookPaths: course.ebookPaths ? JSON.parse(course.ebookPaths) : null, + audioPaths: course.audioPaths ? JSON.parse(course.audioPaths) : null, + videoPaths: course.videoPaths ? JSON.parse(course.videoPaths) : null, + otherResources: course.otherResources ? JSON.parse(course.otherResources) : null, + posterPaths: course.posterPaths ? JSON.parse(course.posterPaths) : null, + tenantCourses: undefined, // 移除敏感信息 + scripts: course.scripts.map((script) => ({ + ...script, + interactionPoints: script.interactionPoints ? JSON.parse(script.interactionPoints) : null, + resourceIds: script.resourceIds ? JSON.parse(script.resourceIds) : null, + pages: script.pages?.map((page) => ({ + ...page, + resourceIds: page.resourceIds ? JSON.parse(page.resourceIds) : null, + })), + })), + activities: course.activities.map((activity) => ({ + ...activity, + onlineMaterials: activity.onlineMaterials ? JSON.parse(activity.onlineMaterials) : null, + objectives: activity.objectives ? JSON.parse(activity.objectives) : null, + })), + }; + } + + async getTeacherClasses(teacherId: number) { + // 通过 ClassTeacher 表查询教师关联的班级 + const classTeachers = await this.prisma.classTeacher.findMany({ + where: { teacherId }, + include: { + class: { + select: { + id: true, + name: true, + grade: true, + studentCount: true, + lessonCount: true, + }, + }, + }, + orderBy: [ + { isPrimary: 'desc' }, + { createdAt: 'asc' }, + ], + }); + + return classTeachers.map((ct) => ({ + id: ct.class.id, + name: ct.class.name, + grade: ct.class.grade, + studentCount: ct.class.studentCount, + lessonCount: ct.class.lessonCount, + myRole: ct.role, // 我在该班级的角色 + isPrimary: ct.isPrimary, // 是否班主任 + })); + } + + async getClassTeachers(teacherId: number, classId: number) { + // 验证教师是否有权限查看该班级 + const teacherClass = await this.prisma.classTeacher.findFirst({ + where: { teacherId, classId }, + }); + + if (!teacherClass) { + throw new ForbiddenException('您没有权限查看该班级'); + } + + const classTeachers = await this.prisma.classTeacher.findMany({ + where: { classId }, + include: { + teacher: { + select: { + id: true, + name: true, + phone: true, + }, + }, + }, + orderBy: [ + { isPrimary: 'desc' }, + { createdAt: 'asc' }, + ], + }); + + return classTeachers.map((ct) => ({ + teacherId: ct.teacher.id, + teacherName: ct.teacher.name, + teacherPhone: ct.teacher.phone, + role: ct.role, + isPrimary: ct.isPrimary, + })); + } + + /** + * 获取教师所有班级的学生列表(跨班级) + */ + async getAllTeacherStudents(teacherId: number, query: any) { + const { page = 1, pageSize = 20, keyword } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + + // 获取教师关联的所有班级ID + const classTeachers = await this.prisma.classTeacher.findMany({ + where: { teacherId }, + select: { classId: true }, + }); + + const classIds = classTeachers.map((ct) => ct.classId); + + if (classIds.length === 0) { + return { + items: [], + total: 0, + page: +page, + pageSize: +pageSize, + }; + } + + // 构建搜索条件 + const where: any = { + classId: { in: classIds }, + }; + + if (keyword) { + where.name = { contains: keyword }; + } + + // 查询学生列表 + const [students, total] = await Promise.all([ + this.prisma.student.findMany({ + where, + skip, + take, + select: { + id: true, + name: true, + gender: true, + birthDate: true, + classId: true, + parentName: true, + parentPhone: true, + createdAt: true, + class: { + select: { + id: true, + name: true, + grade: true, + }, + }, + }, + orderBy: { createdAt: 'desc' }, + }), + this.prisma.student.count({ where }), + ]); + + return { + items: students.map((student) => ({ + id: student.id, + name: student.name, + gender: student.gender, + birthDate: student.birthDate, + classId: student.classId, + parentName: student.parentName, + parentPhone: student.parentPhone, + createdAt: student.createdAt, + class: student.class, + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + + /** + * 获取教师关联的所有班级ID + */ + async getTeacherClassIds(teacherId: number): Promise { + const classTeachers = await this.prisma.classTeacher.findMany({ + where: { teacherId }, + select: { classId: true }, + }); + return classTeachers.map((ct) => ct.classId); + } + + async getClassStudents(teacherId: number, classId: number, query: any) { + const { page = 1, pageSize = 20, keyword } = query; + const skip = (page - 1) * pageSize; + const take = +pageSize; + + // 验证教师是否关联该班级(通过 ClassTeacher 表) + const teacherClass = await this.prisma.classTeacher.findFirst({ + where: { + teacherId, + classId, + }, + include: { + class: { + select: { + id: true, + name: true, + grade: true, + studentCount: true, + lessonCount: true, + }, + }, + }, + }); + + if (!teacherClass) { + throw new ForbiddenException('您没有权限查看该班级或班级不存在'); + } + + const classEntity = teacherClass.class; + + // 构建搜索条件 + const where: any = { + classId: classId, + }; + + if (keyword) { + where.name = { contains: keyword }; + } + + // 查询学生列表 + const [students, total] = await Promise.all([ + this.prisma.student.findMany({ + where, + skip, + take, + select: { + id: true, + name: true, + gender: true, + birthDate: true, + parentName: true, + parentPhone: true, + lessonCount: true, + readingCount: true, + createdAt: true, + }, + orderBy: { createdAt: 'desc' }, + }), + this.prisma.student.count({ where }), + ]); + + return { + items: students.map(student => ({ + id: student.id, + name: student.name, + gender: student.gender, + birthDate: student.birthDate, + parentName: student.parentName, + parentPhone: student.parentPhone, + lessonCount: student.lessonCount, + readingCount: student.readingCount, + createdAt: student.createdAt, + })), + total, + page: +page, + pageSize: +pageSize, + class: classEntity, + }; + } + + // ==================== 工具方法 ==================== + + private parseJsonArray(value: any): any[] { + if (!value) return []; + if (typeof value === 'string') { + try { + return JSON.parse(value); + } catch { + return []; + } + } + return Array.isArray(value) ? value : []; + } + + // ==================== 排课管理 ==================== + + async getTeacherSchedules(teacherId: number, query: any) { + const { page = 1, pageSize = 20, startDate, endDate, status } = query; + + const skip = (page - 1) * pageSize; + const take = +pageSize; + + const where: any = { + teacherId, + status: status || 'ACTIVE', + }; + + if (startDate || endDate) { + where.scheduledDate = {}; + if (startDate) where.scheduledDate.gte = new Date(startDate); + if (endDate) where.scheduledDate.lte = new Date(endDate); + } + + const [items, total] = await Promise.all([ + this.prisma.schedulePlan.findMany({ + where, + skip, + take, + orderBy: { scheduledDate: 'asc' }, + include: { + class: { select: { id: true, name: true, grade: true } }, + course: { select: { id: true, name: true, pictureBookName: true, duration: true } }, + lessons: { + where: { teacherId }, + select: { id: true, status: true }, + take: 1, + }, + }, + }), + this.prisma.schedulePlan.count({ where }), + ]); + + return { + items: items.map((item) => ({ + ...item, + className: item.class.name, + courseName: item.course.name, + hasLesson: item.lessons.length > 0, + lessonId: item.lessons[0]?.id, + lessonStatus: item.lessons[0]?.status, + })), + total, + page: +page, + pageSize: +pageSize, + }; + } + + async getTeacherTimetable(teacherId: number, startDate: string, endDate: string) { + const schedules = await this.prisma.schedulePlan.findMany({ + where: { + teacherId, + status: 'ACTIVE', + scheduledDate: { + gte: new Date(startDate), + lte: new Date(endDate), + }, + }, + orderBy: [{ scheduledDate: 'asc' }, { scheduledTime: 'asc' }], + include: { + class: { select: { id: true, name: true, grade: true } }, + course: { select: { id: true, name: true, pictureBookName: true, duration: true } }, + lessons: { + where: { teacherId }, + select: { id: true, status: true }, + take: 1, + }, + }, + }); + + // 按日期分组 + const timetable: Record = {}; + const start = new Date(startDate); + const end = new Date(endDate); + + // 初始化所有日期 + for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) { + const dateStr = d.toISOString().split('T')[0]; + timetable[dateStr] = []; + } + + // 填充排课数据 + schedules.forEach((schedule) => { + const dateStr = schedule.scheduledDate!.toISOString().split('T')[0]; + if (timetable[dateStr]) { + timetable[dateStr].push({ + ...schedule, + className: schedule.class.name, + courseName: schedule.course.name, + hasLesson: schedule.lessons.length > 0, + lessonId: schedule.lessons[0]?.id, + lessonStatus: schedule.lessons[0]?.status, + }); + } + }); + + return Object.entries(timetable).map(([date, items]) => ({ + date, + weekDay: new Date(date).getDay(), + schedules: items, + })); + } + + async getTodaySchedules(teacherId: number) { + const today = new Date(); + today.setHours(0, 0, 0, 0); + const tomorrow = new Date(today); + tomorrow.setDate(tomorrow.getDate() + 1); + + const schedules = await this.prisma.schedulePlan.findMany({ + where: { + teacherId, + status: 'ACTIVE', + scheduledDate: { + gte: today, + lt: tomorrow, + }, + }, + orderBy: { scheduledTime: 'asc' }, + include: { + class: { select: { id: true, name: true } }, + course: { select: { id: true, name: true, pictureBookName: true, duration: true } }, + lessons: { + where: { teacherId }, + select: { id: true, status: true }, + take: 1, + }, + }, + }); + + return schedules.map((schedule) => ({ + ...schedule, + className: schedule.class.name, + courseName: schedule.course.name, + hasLesson: schedule.lessons.length > 0, + lessonId: schedule.lessons[0]?.id, + lessonStatus: schedule.lessons[0]?.status, + })); + } + + /** + * 解析时间字符串为分钟数 + * @param timeStr 格式: "HH:mm" + * @returns 从午夜开始的分钟数 + */ + private parseTimeToMinutes(timeStr: string): number { + const [hours, minutes] = timeStr.split(':').map(Number); + return hours * 60 + minutes; + } + + /** + * 检查两个时间段是否重叠 + * @param time1 格式: "HH:mm-HH:mm" + * @param time2 格式: "HH:mm-HH:mm" + */ + private isTimeOverlapping(time1: string, time2: string): boolean { + const [start1, end1] = time1.split('-').map(t => this.parseTimeToMinutes(t.trim())); + const [start2, end2] = time2.split('-').map(t => this.parseTimeToMinutes(t.trim())); + + // 两个时间段重叠的条件: start1 < end2 AND start2 < end1 + return start1 < end2 && start2 < end1; + } + + /** + * 检查教师排课时间冲突 + */ + private async checkScheduleConflict( + teacherId: number, + scheduledDate: string, + scheduledTime: string, + excludeScheduleId?: number, + ): Promise<{ courseName: string; className: string; scheduledTime: string } | null> { + // 使用日期范围查询,避免时区问题 + const dateStart = new Date(scheduledDate + 'T00:00:00.000Z'); + const dateEnd = new Date(scheduledDate + 'T23:59:59.999Z'); + + // 获取该教师当天的所有排课 + const existingSchedules = await this.prisma.schedulePlan.findMany({ + where: { + teacherId, + scheduledDate: { + gte: dateStart, + lte: dateEnd, + }, + status: 'ACTIVE', + ...(excludeScheduleId && { id: { not: excludeScheduleId } }), + }, + include: { + class: { select: { name: true } }, + course: { select: { name: true } }, + }, + }); + + // 检查是否存在时间重叠 + for (const schedule of existingSchedules) { + if (schedule.scheduledTime && this.isTimeOverlapping(scheduledTime, schedule.scheduledTime)) { + return { + courseName: schedule.course.name, + className: schedule.class.name, + scheduledTime: schedule.scheduledTime, + }; + } + } + + return null; + } + + async createTeacherSchedule(teacherId: number, tenantId: number, dto: any) { + // 验证班级存在且教师有权限 + const classTeacher = await this.prisma.classTeacher.findFirst({ + where: { teacherId, classId: dto.classId }, + }); + + if (!classTeacher) { + throw new ForbiddenException('您没有权限在此班级排课'); + } + + // 验证课程存在且已授权 + const tenantCourse = await this.prisma.tenantCourse.findFirst({ + where: { tenantId, courseId: dto.courseId, authorized: true }, + }); + + if (!tenantCourse) { + throw new ForbiddenException('该课程未授权或不存在'); + } + + // 检查教师时间冲突 + if (dto.scheduledDate && dto.scheduledTime) { + const conflict = await this.checkScheduleConflict(teacherId, dto.scheduledDate, dto.scheduledTime); + if (conflict) { + throw new ConflictException( + `时间冲突:您在 ${conflict.scheduledTime} 已有排课「${conflict.courseName}」(${conflict.className}),请选择其他时间段` + ); + } + } + + const schedule = await this.prisma.schedulePlan.create({ + data: { + tenantId, + classId: dto.classId, + courseId: dto.courseId, + teacherId, + scheduledDate: dto.scheduledDate ? new Date(dto.scheduledDate) : null, + scheduledTime: dto.scheduledTime, + weekDay: dto.weekDay, + repeatType: dto.repeatType || 'NONE', + repeatEndDate: dto.repeatEndDate ? new Date(dto.repeatEndDate) : null, + source: 'TEACHER', + createdBy: teacherId, + note: dto.note, + status: 'ACTIVE', + }, + include: { + class: { select: { id: true, name: true } }, + course: { select: { id: true, name: true } }, + }, + }); + + this.logger.log(`Teacher schedule created: ${schedule.id} by teacher ${teacherId}`); + + return { + ...schedule, + className: schedule.class.name, + courseName: schedule.course.name, + }; + } + + async updateTeacherSchedule(teacherId: number, id: number, dto: any) { + const schedule = await this.prisma.schedulePlan.findFirst({ + where: { id, teacherId }, + }); + + if (!schedule) { + throw new NotFoundException('排课计划不存在或无权限'); + } + + const updated = await this.prisma.schedulePlan.update({ + where: { id }, + data: { + scheduledDate: dto.scheduledDate ? new Date(dto.scheduledDate) : undefined, + scheduledTime: dto.scheduledTime, + weekDay: dto.weekDay, + repeatType: dto.repeatType, + repeatEndDate: dto.repeatEndDate ? new Date(dto.repeatEndDate) : undefined, + note: dto.note, + status: dto.status, + }, + include: { + class: { select: { id: true, name: true } }, + course: { select: { id: true, name: true } }, + }, + }); + + this.logger.log(`Teacher schedule updated: ${id}`); + + return { + ...updated, + className: updated.class.name, + courseName: updated.course.name, + }; + } + + async cancelTeacherSchedule(teacherId: number, id: number) { + const schedule = await this.prisma.schedulePlan.findFirst({ + where: { id, teacherId }, + }); + + if (!schedule) { + throw new NotFoundException('排课计划不存在或无权限'); + } + + await this.prisma.schedulePlan.update({ + where: { id }, + data: { status: 'CANCELLED' }, + }); + + this.logger.log(`Teacher schedule cancelled: ${id}`); + + return { message: '取消成功' }; + } +} diff --git a/reading-platform-backend/src/modules/tenant/dto/tenant.dto.ts b/reading-platform-backend/src/modules/tenant/dto/tenant.dto.ts new file mode 100644 index 0000000..4072c77 --- /dev/null +++ b/reading-platform-backend/src/modules/tenant/dto/tenant.dto.ts @@ -0,0 +1,158 @@ +import { + IsString, + IsNotEmpty, + IsOptional, + IsInt, + IsDateString, + Matches, + Min, + Max, +} from 'class-validator'; + +export class TenantQueryDto { + @IsOptional() + @IsInt() + @Min(1) + page?: number = 1; + + @IsOptional() + @IsInt() + @Min(1) + @Max(100) + pageSize?: number = 10; + + @IsOptional() + @IsString() + keyword?: string; + + @IsOptional() + @IsString() + status?: string; + + @IsOptional() + @IsString() + packageType?: string; +} + +export class CreateTenantDto { + @IsString() + @IsNotEmpty({ message: '学校名称不能为空' }) + name: string; + + @IsString() + @IsNotEmpty({ message: '登录账号不能为空' }) + @Matches(/^[a-zA-Z][a-zA-Z0-9_]{3,19}$/, { + message: '登录账号必须以字母开头,4-20位字母、数字或下划线', + }) + loginAccount: string; + + @IsOptional() + @IsString() + @Matches(/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{6,20}$/, { + message: '密码至少6位,需包含字母和数字', + }) + password?: string; + + @IsOptional() + @IsString() + address?: string; + + @IsOptional() + @IsString() + contactPerson?: string; + + @IsOptional() + @IsString() + @Matches(/^1[3-9]\d{9}$/, { message: '请输入正确的手机号' }) + contactPhone?: string; + + @IsOptional() + @IsString() + packageType?: string = 'STANDARD'; + + @IsOptional() + @IsInt() + @Min(1) + teacherQuota?: number = 20; + + @IsOptional() + @IsInt() + @Min(1) + studentQuota?: number = 200; + + @IsOptional() + @IsDateString() + startDate?: string; + + @IsOptional() + @IsDateString() + expireDate?: string; +} + +export class UpdateTenantDto { + @IsOptional() + @IsString() + @IsNotEmpty({ message: '学校名称不能为空' }) + name?: string; + + @IsOptional() + @IsString() + address?: string; + + @IsOptional() + @IsString() + contactPerson?: string; + + @IsOptional() + @IsString() + @Matches(/^1[3-9]\d{9}$/, { message: '请输入正确的手机号' }) + contactPhone?: string; + + @IsOptional() + @IsString() + packageType?: string; + + @IsOptional() + @IsInt() + @Min(1) + teacherQuota?: number; + + @IsOptional() + @IsInt() + @Min(1) + studentQuota?: number; + + @IsOptional() + @IsDateString() + startDate?: string; + + @IsOptional() + @IsDateString() + expireDate?: string; + + @IsOptional() + @IsString() + status?: string; +} + +export class UpdateTenantQuotaDto { + @IsOptional() + @IsString() + packageType?: string; + + @IsOptional() + @IsInt() + @Min(1) + teacherQuota?: number; + + @IsOptional() + @IsInt() + @Min(1) + studentQuota?: number; +} + +export class UpdateTenantStatusDto { + @IsString() + @IsNotEmpty({ message: '状态不能为空' }) + status: string; +} diff --git a/reading-platform-backend/src/modules/tenant/tenant.controller.js b/reading-platform-backend/src/modules/tenant/tenant.controller.js new file mode 100644 index 0000000..a2519d9 --- /dev/null +++ b/reading-platform-backend/src/modules/tenant/tenant.controller.js @@ -0,0 +1,96 @@ +"use strict"; +var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { + var useValue = arguments.length > 2; + for (var i = 0; i < initializers.length; i++) { + value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); + } + return useValue ? value : void 0; +}; +var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { + function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } + var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; + var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; + var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); + var _, done = false; + for (var i = decorators.length - 1; i >= 0; i--) { + var context = {}; + for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; + for (var p in contextIn.access) context.access[p] = contextIn.access[p]; + context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; + var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); + if (kind === "accessor") { + if (result === void 0) continue; + if (result === null || typeof result !== "object") throw new TypeError("Object expected"); + if (_ = accept(result.get)) descriptor.get = _; + if (_ = accept(result.set)) descriptor.set = _; + if (_ = accept(result.init)) initializers.unshift(_); + } + else if (_ = accept(result)) { + if (kind === "field") initializers.unshift(_); + else descriptor[key] = _; + } + } + if (target) Object.defineProperty(target, contextIn.name, descriptor); + done = true; +}; +var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) { + if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : ""; + return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TenantController = void 0; +var common_1 = require("@nestjs/common"); +var jwt_auth_guard_1 = require("../common/guards/jwt-auth.guard"); +var TenantController = function () { + var _classDecorators = [(0, common_1.Controller)('tenants'), (0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard)]; + var _classDescriptor; + var _classExtraInitializers = []; + var _classThis; + var _instanceExtraInitializers = []; + var _findAll_decorators; + var _findOne_decorators; + var _create_decorators; + var _update_decorators; + var _remove_decorators; + var TenantController = _classThis = /** @class */ (function () { + function TenantController_1(tenantService) { + this.tenantService = (__runInitializers(this, _instanceExtraInitializers), tenantService); + } + TenantController_1.prototype.findAll = function () { + return this.tenantService.findAll(); + }; + TenantController_1.prototype.findOne = function (id) { + return this.tenantService.findOne(+id); + }; + TenantController_1.prototype.create = function (createTenantDto) { + return this.tenantService.create(createTenantDto); + }; + TenantController_1.prototype.update = function (id, updateTenantDto) { + return this.tenantService.update(+id, updateTenantDto); + }; + TenantController_1.prototype.remove = function (id) { + return this.tenantService.remove(+id); + }; + return TenantController_1; + }()); + __setFunctionName(_classThis, "TenantController"); + (function () { + var _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0; + _findAll_decorators = [(0, common_1.Get)()]; + _findOne_decorators = [(0, common_1.Get)(':id')]; + _create_decorators = [(0, common_1.Post)()]; + _update_decorators = [(0, common_1.Put)(':id')]; + _remove_decorators = [(0, common_1.Delete)(':id')]; + __esDecorate(_classThis, null, _findAll_decorators, { kind: "method", name: "findAll", static: false, private: false, access: { has: function (obj) { return "findAll" in obj; }, get: function (obj) { return obj.findAll; } }, metadata: _metadata }, null, _instanceExtraInitializers); + __esDecorate(_classThis, null, _findOne_decorators, { kind: "method", name: "findOne", static: false, private: false, access: { has: function (obj) { return "findOne" in obj; }, get: function (obj) { return obj.findOne; } }, metadata: _metadata }, null, _instanceExtraInitializers); + __esDecorate(_classThis, null, _create_decorators, { kind: "method", name: "create", static: false, private: false, access: { has: function (obj) { return "create" in obj; }, get: function (obj) { return obj.create; } }, metadata: _metadata }, null, _instanceExtraInitializers); + __esDecorate(_classThis, null, _update_decorators, { kind: "method", name: "update", static: false, private: false, access: { has: function (obj) { return "update" in obj; }, get: function (obj) { return obj.update; } }, metadata: _metadata }, null, _instanceExtraInitializers); + __esDecorate(_classThis, null, _remove_decorators, { kind: "method", name: "remove", static: false, private: false, access: { has: function (obj) { return "remove" in obj; }, get: function (obj) { return obj.remove; } }, metadata: _metadata }, null, _instanceExtraInitializers); + __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers); + TenantController = _classThis = _classDescriptor.value; + if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); + __runInitializers(_classThis, _classExtraInitializers); + })(); + return TenantController = _classThis; +}(); +exports.TenantController = TenantController; diff --git a/reading-platform-backend/src/modules/tenant/tenant.controller.ts b/reading-platform-backend/src/modules/tenant/tenant.controller.ts new file mode 100644 index 0000000..77b3745 --- /dev/null +++ b/reading-platform-backend/src/modules/tenant/tenant.controller.ts @@ -0,0 +1,74 @@ +import { + Controller, + Get, + Post, + Put, + Delete, + Param, + Body, + UseGuards, + Query, +} from '@nestjs/common'; +import { TenantService } from './tenant.service'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { RolesGuard } from '../common/guards/roles.guard'; +import { Roles } from '../common/decorators/roles.decorator'; +import { + TenantQueryDto, + CreateTenantDto, + UpdateTenantDto, + UpdateTenantQuotaDto, + UpdateTenantStatusDto, +} from './dto/tenant.dto'; + +@Controller('admin/tenants') +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('admin') +export class TenantController { + constructor(private readonly tenantService: TenantService) {} + + @Get() + findAll(@Query() query: TenantQueryDto) { + return this.tenantService.findAllPaginated(query); + } + + @Get('stats') + getStats() { + return this.tenantService.getStats(); + } + + @Get(':id') + findOne(@Param('id') id: string) { + return this.tenantService.findOne(+id); + } + + @Post() + create(@Body() createTenantDto: CreateTenantDto) { + return this.tenantService.create(createTenantDto); + } + + @Put(':id') + update(@Param('id') id: string, @Body() updateTenantDto: UpdateTenantDto) { + return this.tenantService.update(+id, updateTenantDto); + } + + @Put(':id/quota') + updateQuota(@Param('id') id: string, @Body() dto: UpdateTenantQuotaDto) { + return this.tenantService.updateQuota(+id, dto); + } + + @Put(':id/status') + updateStatus(@Param('id') id: string, @Body() dto: UpdateTenantStatusDto) { + return this.tenantService.updateStatus(+id, dto); + } + + @Post(':id/reset-password') + resetPassword(@Param('id') id: string) { + return this.tenantService.resetPassword(+id); + } + + @Delete(':id') + remove(@Param('id') id: string) { + return this.tenantService.remove(+id); + } +} diff --git a/reading-platform-backend/src/modules/tenant/tenant.module.js b/reading-platform-backend/src/modules/tenant/tenant.module.js new file mode 100644 index 0000000..ffd58b6 --- /dev/null +++ b/reading-platform-backend/src/modules/tenant/tenant.module.js @@ -0,0 +1,71 @@ +"use strict"; +var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { + function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } + var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; + var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; + var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); + var _, done = false; + for (var i = decorators.length - 1; i >= 0; i--) { + var context = {}; + for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; + for (var p in contextIn.access) context.access[p] = contextIn.access[p]; + context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; + var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); + if (kind === "accessor") { + if (result === void 0) continue; + if (result === null || typeof result !== "object") throw new TypeError("Object expected"); + if (_ = accept(result.get)) descriptor.get = _; + if (_ = accept(result.set)) descriptor.set = _; + if (_ = accept(result.init)) initializers.unshift(_); + } + else if (_ = accept(result)) { + if (kind === "field") initializers.unshift(_); + else descriptor[key] = _; + } + } + if (target) Object.defineProperty(target, contextIn.name, descriptor); + done = true; +}; +var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { + var useValue = arguments.length > 2; + for (var i = 0; i < initializers.length; i++) { + value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); + } + return useValue ? value : void 0; +}; +var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) { + if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : ""; + return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TenantModule = void 0; +var common_1 = require("@nestjs/common"); +var tenant_service_1 = require("./tenant.service"); +var tenant_controller_1 = require("./tenant.controller"); +var prisma_module_1 = require("../../database/prisma.module"); +var TenantModule = function () { + var _classDecorators = [(0, common_1.Module)({ + imports: [prisma_module_1.PrismaModule], + controllers: [tenant_controller_1.TenantController], + providers: [tenant_service_1.TenantService], + exports: [tenant_service_1.TenantService], + })]; + var _classDescriptor; + var _classExtraInitializers = []; + var _classThis; + var TenantModule = _classThis = /** @class */ (function () { + function TenantModule_1() { + } + return TenantModule_1; + }()); + __setFunctionName(_classThis, "TenantModule"); + (function () { + var _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0; + __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers); + TenantModule = _classThis = _classDescriptor.value; + if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); + __runInitializers(_classThis, _classExtraInitializers); + })(); + return TenantModule = _classThis; +}(); +exports.TenantModule = TenantModule; diff --git a/reading-platform-backend/src/modules/tenant/tenant.module.ts b/reading-platform-backend/src/modules/tenant/tenant.module.ts new file mode 100644 index 0000000..499b7e0 --- /dev/null +++ b/reading-platform-backend/src/modules/tenant/tenant.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TenantService } from './tenant.service'; +import { TenantController } from './tenant.controller'; +import { PrismaModule } from '../../database/prisma.module'; + +@Module({ + imports: [PrismaModule], + controllers: [TenantController], + providers: [TenantService], + exports: [TenantService], +}) +export class TenantModule {} diff --git a/reading-platform-backend/src/modules/tenant/tenant.service.js b/reading-platform-backend/src/modules/tenant/tenant.service.js new file mode 100644 index 0000000..dacd70a --- /dev/null +++ b/reading-platform-backend/src/modules/tenant/tenant.service.js @@ -0,0 +1,192 @@ +"use strict"; +var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { + function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } + var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; + var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; + var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); + var _, done = false; + for (var i = decorators.length - 1; i >= 0; i--) { + var context = {}; + for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; + for (var p in contextIn.access) context.access[p] = contextIn.access[p]; + context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; + var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); + if (kind === "accessor") { + if (result === void 0) continue; + if (result === null || typeof result !== "object") throw new TypeError("Object expected"); + if (_ = accept(result.get)) descriptor.get = _; + if (_ = accept(result.set)) descriptor.set = _; + if (_ = accept(result.init)) initializers.unshift(_); + } + else if (_ = accept(result)) { + if (kind === "field") initializers.unshift(_); + else descriptor[key] = _; + } + } + if (target) Object.defineProperty(target, contextIn.name, descriptor); + done = true; +}; +var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { + var useValue = arguments.length > 2; + for (var i = 0; i < initializers.length; i++) { + value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); + } + return useValue ? value : void 0; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); + return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) { + if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : ""; + return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TenantService = void 0; +var common_1 = require("@nestjs/common"); +var TenantService = function () { + var _classDecorators = [(0, common_1.Injectable)()]; + var _classDescriptor; + var _classExtraInitializers = []; + var _classThis; + var TenantService = _classThis = /** @class */ (function () { + function TenantService_1(prisma) { + this.prisma = prisma; + } + TenantService_1.prototype.findAll = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, this.prisma.tenant.findMany({ + select: { + id: true, + name: true, + address: true, + contactPerson: true, + contactPhone: true, + packageType: true, + teacherQuota: true, + studentQuota: true, + teacherCount: true, + studentCount: true, + startDate: true, + expireDate: true, + status: true, + createdAt: true, + }, + orderBy: { createdAt: 'desc' }, + })]; + }); + }); + }; + TenantService_1.prototype.findOne = function (id) { + return __awaiter(this, void 0, void 0, function () { + var tenant; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.prisma.tenant.findUnique({ + where: { id: id }, + include: { + teachers: { + select: { + id: true, + name: true, + phone: true, + email: true, + status: true, + lessonCount: true, + }, + }, + students: { + select: { + id: true, + name: true, + classId: true, + gender: true, + readingCount: true, + }, + }, + }, + })]; + case 1: + tenant = _a.sent(); + if (!tenant) { + throw new common_1.NotFoundException("Tenant #".concat(id, " not found")); + } + return [2 /*return*/, tenant]; + } + }); + }); + }; + TenantService_1.prototype.create = function (createTenantDto) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, this.prisma.tenant.create({ + data: createTenantDto, + })]; + }); + }); + }; + TenantService_1.prototype.update = function (id, updateTenantDto) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, this.prisma.tenant.update({ + where: { id: id }, + data: updateTenantDto, + })]; + }); + }); + }; + TenantService_1.prototype.remove = function (id) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + return [2 /*return*/, this.prisma.tenant.delete({ + where: { id: id }, + })]; + }); + }); + }; + return TenantService_1; + }()); + __setFunctionName(_classThis, "TenantService"); + (function () { + var _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0; + __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers); + TenantService = _classThis = _classDescriptor.value; + if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); + __runInitializers(_classThis, _classExtraInitializers); + })(); + return TenantService = _classThis; +}(); +exports.TenantService = TenantService; diff --git a/reading-platform-backend/src/modules/tenant/tenant.service.ts b/reading-platform-backend/src/modules/tenant/tenant.service.ts new file mode 100644 index 0000000..1d5006d --- /dev/null +++ b/reading-platform-backend/src/modules/tenant/tenant.service.ts @@ -0,0 +1,422 @@ +import { + Injectable, + NotFoundException, + ConflictException, + BadRequestException, +} from '@nestjs/common'; +import { PrismaService } from '../../database/prisma.service'; +import * as bcrypt from 'bcrypt'; +import { + TenantQueryDto, + CreateTenantDto, + UpdateTenantDto, + UpdateTenantQuotaDto, + UpdateTenantStatusDto, +} from './dto/tenant.dto'; + +@Injectable() +export class TenantService { + constructor(private prisma: PrismaService) {} + + async findAllPaginated(query: TenantQueryDto) { + const { page = 1, pageSize = 10, keyword, status, packageType } = query; + const skip = (page - 1) * pageSize; + + const where: any = {}; + + if (keyword) { + where.OR = [ + { name: { contains: keyword } }, + { loginAccount: { contains: keyword } }, + { contactPerson: { contains: keyword } }, + { contactPhone: { contains: keyword } }, + ]; + } + + if (status) { + where.status = status; + } + + if (packageType) { + where.packageType = packageType; + } + + const [items, total] = await Promise.all([ + this.prisma.tenant.findMany({ + where, + select: { + id: true, + name: true, + loginAccount: true, + address: true, + contactPerson: true, + contactPhone: true, + packageType: true, + teacherQuota: true, + studentQuota: true, + teacherCount: true, + studentCount: true, + startDate: true, + expireDate: true, + status: true, + createdAt: true, + }, + orderBy: { createdAt: 'desc' }, + skip, + take: pageSize, + }), + this.prisma.tenant.count({ where }), + ]); + + return { + items, + total, + page, + pageSize, + totalPages: Math.ceil(total / pageSize), + }; + } + + async findAll() { + return this.prisma.tenant.findMany({ + select: { + id: true, + name: true, + address: true, + contactPerson: true, + contactPhone: true, + packageType: true, + teacherQuota: true, + studentQuota: true, + teacherCount: true, + studentCount: true, + startDate: true, + expireDate: true, + status: true, + createdAt: true, + }, + orderBy: { createdAt: 'desc' }, + }); + } + + async findOne(id: number) { + const tenant = await this.prisma.tenant.findUnique({ + where: { id }, + select: { + id: true, + name: true, + loginAccount: true, + address: true, + contactPerson: true, + contactPhone: true, + logoUrl: true, + packageType: true, + teacherQuota: true, + studentQuota: true, + storageQuota: true, + teacherCount: true, + studentCount: true, + storageUsed: true, + startDate: true, + expireDate: true, + status: true, + createdAt: true, + updatedAt: true, + teachers: { + select: { + id: true, + name: true, + phone: true, + email: true, + status: true, + lessonCount: true, + }, + take: 10, + orderBy: { createdAt: 'desc' }, + }, + students: { + select: { + id: true, + name: true, + classId: true, + gender: true, + readingCount: true, + }, + take: 10, + orderBy: { createdAt: 'desc' }, + }, + classes: { + select: { + id: true, + name: true, + grade: true, + studentCount: true, + }, + take: 10, + }, + _count: { + select: { + teachers: true, + students: true, + classes: true, + lessons: true, + }, + }, + }, + }); + + if (!tenant) { + throw new NotFoundException(`租户 #${id} 不存在`); + } + + // Convert BigInt to string for JSON serialization + return { + ...tenant, + storageQuota: tenant.storageQuota?.toString() || '0', + storageUsed: tenant.storageUsed?.toString() || '0', + }; + } + + async create(dto: CreateTenantDto) { + // Check if login account already exists + const existing = await this.prisma.tenant.findUnique({ + where: { loginAccount: dto.loginAccount }, + }); + + if (existing) { + throw new ConflictException('登录账号已存在'); + } + + // Check if teacher login account exists too + const existingTeacher = await this.prisma.teacher.findUnique({ + where: { loginAccount: dto.loginAccount }, + }); + + if (existingTeacher) { + throw new ConflictException('该账号已被教师使用'); + } + + // Hash password + const defaultPassword = dto.password || '123456'; + const passwordHash = await bcrypt.hash(defaultPassword, 10); + + // Set default dates if not provided + const startDate = dto.startDate || new Date().toISOString().split('T')[0]; + const expireDate = + dto.expireDate || + new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; + + const tenant = await this.prisma.tenant.create({ + data: { + name: dto.name, + loginAccount: dto.loginAccount, + passwordHash, + address: dto.address, + contactPerson: dto.contactPerson, + contactPhone: dto.contactPhone, + packageType: dto.packageType || 'STANDARD', + teacherQuota: dto.teacherQuota || 20, + studentQuota: dto.studentQuota || 200, + startDate, + expireDate, + status: 'ACTIVE', + }, + select: { + id: true, + name: true, + loginAccount: true, + address: true, + contactPerson: true, + contactPhone: true, + packageType: true, + teacherQuota: true, + studentQuota: true, + startDate: true, + expireDate: true, + status: true, + createdAt: true, + }, + }); + + return { + ...tenant, + tempPassword: dto.password || '123456', + }; + } + + async update(id: number, dto: UpdateTenantDto) { + // Check if tenant exists + const existing = await this.prisma.tenant.findUnique({ + where: { id }, + }); + + if (!existing) { + throw new NotFoundException(`租户 #${id} 不存在`); + } + + return this.prisma.tenant.update({ + where: { id }, + data: { + name: dto.name, + address: dto.address, + contactPerson: dto.contactPerson, + contactPhone: dto.contactPhone, + packageType: dto.packageType, + teacherQuota: dto.teacherQuota, + studentQuota: dto.studentQuota, + startDate: dto.startDate, + expireDate: dto.expireDate, + status: dto.status, + }, + select: { + id: true, + name: true, + loginAccount: true, + address: true, + contactPerson: true, + contactPhone: true, + packageType: true, + teacherQuota: true, + studentQuota: true, + startDate: true, + expireDate: true, + status: true, + createdAt: true, + updatedAt: true, + }, + }); + } + + async updateQuota(id: number, dto: UpdateTenantQuotaDto) { + // Check if tenant exists + const existing = await this.prisma.tenant.findUnique({ + where: { id }, + }); + + if (!existing) { + throw new NotFoundException(`租户 #${id} 不存在`); + } + + // Validate quota not less than current usage + if (dto.teacherQuota !== undefined && dto.teacherQuota < existing.teacherCount) { + throw new BadRequestException( + `教师配额不能小于当前已用数量 (${existing.teacherCount})`, + ); + } + + if (dto.studentQuota !== undefined && dto.studentQuota < existing.studentCount) { + throw new BadRequestException( + `学生配额不能小于当前已用数量 (${existing.studentCount})`, + ); + } + + return this.prisma.tenant.update({ + where: { id }, + data: { + packageType: dto.packageType, + teacherQuota: dto.teacherQuota, + studentQuota: dto.studentQuota, + }, + select: { + id: true, + name: true, + packageType: true, + teacherQuota: true, + studentQuota: true, + teacherCount: true, + studentCount: true, + }, + }); + } + + async updateStatus(id: number, dto: UpdateTenantStatusDto) { + // Check if tenant exists + const existing = await this.prisma.tenant.findUnique({ + where: { id }, + }); + + if (!existing) { + throw new NotFoundException(`租户 #${id} 不存在`); + } + + return this.prisma.tenant.update({ + where: { id }, + data: { + status: dto.status, + }, + select: { + id: true, + name: true, + status: true, + }, + }); + } + + async resetPassword(id: number) { + // Check if tenant exists + const existing = await this.prisma.tenant.findUnique({ + where: { id }, + }); + + if (!existing) { + throw new NotFoundException(`租户 #${id} 不存在`); + } + + // Generate random password + const tempPassword = Math.random().toString(36).slice(-8); + const passwordHash = await bcrypt.hash(tempPassword, 10); + + await this.prisma.tenant.update({ + where: { id }, + data: { + passwordHash, + }, + }); + + return { + tempPassword, + }; + } + + async remove(id: number) { + // Check if tenant exists + const existing = await this.prisma.tenant.findUnique({ + where: { id }, + }); + + if (!existing) { + throw new NotFoundException(`租户 #${id} 不存在`); + } + + await this.prisma.tenant.delete({ + where: { id }, + }); + + return { success: true }; + } + + async getStats() { + const [totalCount, activeCount, expiredCount] = await Promise.all([ + this.prisma.tenant.count(), + this.prisma.tenant.count({ where: { status: 'ACTIVE' } }), + this.prisma.tenant.count({ where: { status: { not: 'ACTIVE' } } }), + ]); + + const packageDistribution = await this.prisma.tenant.groupBy({ + by: ['packageType'], + _count: { + id: true, + }, + }); + + return { + totalCount, + activeCount, + expiredCount, + packageDistribution: packageDistribution.map((item) => ({ + packageType: item.packageType, + count: item._count.id, + })), + }; + } +} diff --git a/reading-platform-backend/start-backend.sh b/reading-platform-backend/start-backend.sh new file mode 100644 index 0000000..21ef2f8 --- /dev/null +++ b/reading-platform-backend/start-backend.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# 后端启动脚本 +# 确保从正确目录启动 + +cd "$(dirname "$0")" + +echo "🚀 正在启动后端服务..." +echo "📂 工作目录: $(pwd)" + +# 检查 node_modules 是否存在 +if [ ! -d "node_modules" ]; then + echo "📦 正在安装依赖..." + npm install +fi + +# 启动后端 +npm run start:dev diff --git a/reading-platform-backend/tsconfig.build.json b/reading-platform-backend/tsconfig.build.json new file mode 100644 index 0000000..02070b3 --- /dev/null +++ b/reading-platform-backend/tsconfig.build.json @@ -0,0 +1,10 @@ +# 后端 NestJS 配置 +module.exports = { + extends: '../tsconfig.json', + compilerOptions: { + declaration: false, + sourceMap: true, + }, + include: ['src/**/*'], + exclude: ['node_modules', 'test', 'dist'], +}; diff --git a/reading-platform-backend/tsconfig.json b/reading-platform-backend/tsconfig.json new file mode 100644 index 0000000..5607d84 --- /dev/null +++ b/reading-platform-backend/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false, + "esModuleInterop": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "useDefineForClassFields": false + }, + "include": ["src/**/*", "prisma/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/reading-platform-frontend/.DS_Store b/reading-platform-frontend/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..362df26bc5b0fb79ced4a6db8e192814a85bfdbe GIT binary patch literal 6148 zcmeHKO-=$a6n=$?8PNstCmL6C1H%Q(h`3`zSh>RR6D4670=oL`?VC?6Tv78;@WW{sz zu1fV*91Sv&G9U_w0{>9~{ozxax-_Dj)c0HcxBy}A%NNC!O0yR?G3MXSo~DJzm(#Sr zz0yCpcJ_waJyL0#`V?dS4mBv!tugh`8@W98`UjI$u`aLI>dBdJcrBefisw9k6Yugq zdS=?RK?5376I{F0a=B7&hc!$2U4D(t_q%AKbz6v;b6OVT@}OM6Jo0#F!vzMr<=Wii zu01?{n{^)a+BVNsBI`}9w?F0e#MVmzoY`#tmO-gS0Z~8{_*Q`ThX92!w3rywTL&tA z1ps;&)`m9!QgBXSF|?Q%ga@WfD$t}Vd&N*D9pizG3oRxFO*$!i`A~LcWp5}-ua5Zx z9Zo7VD77dc3Zxatn{JWM|MTVd|8$b{L;+D?trSpRrCurHk?h%;c{o061C&b?HjYaS l>J(J=IMxk5iVsn=!544?7+OpWVgx2X0$K*CM1en5;1dy`kOcq$ literal 0 HcmV?d00001 diff --git a/reading-platform-frontend/.env.development b/reading-platform-frontend/.env.development new file mode 100644 index 0000000..41071c4 --- /dev/null +++ b/reading-platform-frontend/.env.development @@ -0,0 +1,3 @@ +VITE_API_BASE_URL=http://localhost:3000/api/v1 +VITE_APP_TITLE=幼儿阅读教学服务平台 +VITE_SERVER_BASE_URL=http://localhost:3000 diff --git a/reading-platform-frontend/dev.db b/reading-platform-frontend/dev.db new file mode 100644 index 0000000..e69de29 diff --git a/reading-platform-frontend/index.html b/reading-platform-frontend/index.html new file mode 100644 index 0000000..eb354c7 --- /dev/null +++ b/reading-platform-frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + 幼儿阅读教学服务平台 + + +
+ + + diff --git a/reading-platform-frontend/package-lock.json b/reading-platform-frontend/package-lock.json new file mode 100644 index 0000000..4acc361 --- /dev/null +++ b/reading-platform-frontend/package-lock.json @@ -0,0 +1,3912 @@ +{ + "name": "reading-platform-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "reading-platform-frontend", + "version": "1.0.0", + "dependencies": { + "@ant-design/icons-vue": "^7.0.1", + "@fullcalendar/core": "^6.1.20", + "@fullcalendar/daygrid": "^6.1.20", + "@fullcalendar/interaction": "^6.1.20", + "@fullcalendar/timegrid": "^6.1.20", + "@fullcalendar/vue3": "^6.1.20", + "ant-design-vue": "^4.1.2", + "axios": "^1.6.7", + "dayjs": "^1.11.10", + "echarts": "^6.0.0", + "lodash-es": "^4.17.21", + "lucide-vue-next": "^0.575.0", + "pinia": "^2.1.7", + "vue": "^3.4.21", + "vue-router": "^4.3.0" + }, + "devDependencies": { + "@types/lodash-es": "^4.17.12", + "@types/node": "^20.11.28", + "@vitejs/plugin-vue": "^5.0.4", + "@vue/tsconfig": "^0.5.1", + "sass-embedded": "^1.97.3", + "typescript": "~5.4.0", + "unplugin-auto-import": "^0.17.5", + "unplugin-vue-components": "^0.26.0", + "vite": "^5.1.6", + "vite-plugin-compression": "^0.5.1", + "vue-tsc": "^2.0.6" + } + }, + "node_modules/@ant-design/colors": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz", + "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT" + }, + "node_modules/@ant-design/icons-vue": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons-vue/-/icons-vue-7.0.1.tgz", + "integrity": "sha512-eCqY2unfZK6Fe02AwFlDHLfoyEFreP6rBwAZMIJ1LugmfMiVgwWDYlp1YsRugaPtICYOabV1iWxXdP12u9U43Q==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-svg": "^4.2.1" + }, + "peerDependencies": { + "vue": ">=3.0.3" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz", + "integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==", + "dev": true, + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@fullcalendar/core": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.20.tgz", + "integrity": "sha512-1cukXLlePFiJ8YKXn/4tMKsy0etxYLCkXk8nUCFi11nRONF2Ba2CD5b21/ovtOO2tL6afTJfwmc1ed3HG7eB1g==", + "license": "MIT", + "peer": true, + "dependencies": { + "preact": "~10.12.1" + } + }, + "node_modules/@fullcalendar/daygrid": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.20.tgz", + "integrity": "sha512-AO9vqhkLP77EesmJzuU+IGXgxNulsA8mgQHynclJ8U70vSwAVnbcLG9qftiTAFSlZjiY/NvhE7sflve6cJelyQ==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.20" + } + }, + "node_modules/@fullcalendar/interaction": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.20.tgz", + "integrity": "sha512-p6txmc5txL0bMiPaJxe2ip6o0T384TyoD2KGdsU6UjZ5yoBlaY+dg7kxfnYKpYMzEJLG58n+URrHr2PgNL2fyA==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.20" + } + }, + "node_modules/@fullcalendar/timegrid": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.20.tgz", + "integrity": "sha512-4H+/MWbz3ntA50lrPif+7TsvMeX3R1GSYjiLULz0+zEJ7/Yfd9pupZmAwUs/PBpA6aAcFmeRr0laWfcz1a9V1A==", + "license": "MIT", + "dependencies": { + "@fullcalendar/daygrid": "~6.1.20" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.20" + } + }, + "node_modules/@fullcalendar/vue3": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/vue3/-/vue3-6.1.20.tgz", + "integrity": "sha512-8qg6pS27II9QBwFkkJC+7SfflMpWqOe7i3ii5ODq9KpLAjwQAd/zjfq8RvKR1Yryoh5UmMCmvRbMB7i4RGtqog==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.20", + "vue": "^3.0.11" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@simonwep/pickr": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@simonwep/pickr/-/pickr-1.8.2.tgz", + "integrity": "sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==", + "license": "MIT", + "dependencies": { + "core-js": "^3.15.1", + "nanopop": "^2.1.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz", + "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.15" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.15.tgz", + "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.15.tgz", + "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.28.tgz", + "integrity": "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/shared": "3.5.28", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.28.tgz", + "integrity": "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.28", + "@vue/shared": "3.5.28" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.28.tgz", + "integrity": "sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/compiler-core": "3.5.28", + "@vue/compiler-dom": "3.5.28", + "@vue/compiler-ssr": "3.5.28", + "@vue/shared": "3.5.28", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.28.tgz", + "integrity": "sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.28", + "@vue/shared": "3.5.28" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/language-core": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.12.tgz", + "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.28.tgz", + "integrity": "sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.28" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.28.tgz", + "integrity": "sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.28", + "@vue/shared": "3.5.28" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.28.tgz", + "integrity": "sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.28", + "@vue/runtime-core": "3.5.28", + "@vue/shared": "3.5.28", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.28.tgz", + "integrity": "sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.28", + "@vue/shared": "3.5.28" + }, + "peerDependencies": { + "vue": "3.5.28" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.28.tgz", + "integrity": "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.5.1.tgz", + "integrity": "sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/alien-signals": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz", + "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ant-design-vue": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-4.2.6.tgz", + "integrity": "sha512-t7eX13Yj3i9+i5g9lqFyYneoIb3OzTvQjq9Tts1i+eiOd3Eva/6GagxBSXM1fOCjqemIu0FYVE1ByZ/38epR3Q==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-vue": "^7.0.0", + "@babel/runtime": "^7.10.5", + "@ctrl/tinycolor": "^3.5.0", + "@emotion/hash": "^0.9.0", + "@emotion/unitless": "^0.8.0", + "@simonwep/pickr": "~1.8.0", + "array-tree-filter": "^2.1.0", + "async-validator": "^4.0.0", + "csstype": "^3.1.1", + "dayjs": "^1.10.5", + "dom-align": "^1.12.1", + "dom-scroll-into-view": "^2.0.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.15", + "resize-observer-polyfill": "^1.5.1", + "scroll-into-view-if-needed": "^2.2.25", + "shallow-equal": "^1.0.0", + "stylis": "^4.1.3", + "throttle-debounce": "^5.0.0", + "vue-types": "^3.0.0", + "warning": "^4.0.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design-vue" + }, + "peerDependencies": { + "vue": ">=3.2.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/array-tree-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz", + "integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==", + "license": "MIT" + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorjs.io": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==", + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-js": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-align": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.4.tgz", + "integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==", + "license": "MIT" + }, + "node_modules/dom-scroll-into-view": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz", + "integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==", + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/echarts": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz", + "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "6.0.0" + } + }, + "node_modules/echarts/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loose-envify/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/lucide-vue-next": { + "version": "0.575.0", + "resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-0.575.0.tgz", + "integrity": "sha512-UHzA3cYMCgBLyGay5R9IQaidwV0NLocx7cIBnFt8vJ9Xhl6IM/oKD0fUhoCUuouFta15SX1rLXVoko9s3TzWMA==", + "license": "ISC", + "peerDependencies": { + "vue": ">=3.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanopop": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/nanopop/-/nanopop-2.4.2.tgz", + "integrity": "sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==", + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", + "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.12.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz", + "integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/sass": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz", + "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-embedded": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.97.3.tgz", + "integrity": "sha512-eKzFy13Nk+IRHhlAwP3sfuv+PzOrvzUkwJK2hdoCKYcWGSdmwFpeGpWmyewdw8EgBnsKaSBtgf/0b2K635ecSA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@bufbuild/protobuf": "^2.5.0", + "colorjs.io": "^0.5.0", + "immutable": "^5.0.2", + "rxjs": "^7.4.0", + "supports-color": "^8.1.1", + "sync-child-process": "^1.0.2", + "varint": "^6.0.0" + }, + "bin": { + "sass": "dist/bin/sass.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "sass-embedded-all-unknown": "1.97.3", + "sass-embedded-android-arm": "1.97.3", + "sass-embedded-android-arm64": "1.97.3", + "sass-embedded-android-riscv64": "1.97.3", + "sass-embedded-android-x64": "1.97.3", + "sass-embedded-darwin-arm64": "1.97.3", + "sass-embedded-darwin-x64": "1.97.3", + "sass-embedded-linux-arm": "1.97.3", + "sass-embedded-linux-arm64": "1.97.3", + "sass-embedded-linux-musl-arm": "1.97.3", + "sass-embedded-linux-musl-arm64": "1.97.3", + "sass-embedded-linux-musl-riscv64": "1.97.3", + "sass-embedded-linux-musl-x64": "1.97.3", + "sass-embedded-linux-riscv64": "1.97.3", + "sass-embedded-linux-x64": "1.97.3", + "sass-embedded-unknown-all": "1.97.3", + "sass-embedded-win32-arm64": "1.97.3", + "sass-embedded-win32-x64": "1.97.3" + } + }, + "node_modules/sass-embedded-all-unknown": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.97.3.tgz", + "integrity": "sha512-t6N46NlPuXiY3rlmG6/+1nwebOBOaLFOOVqNQOC2cJhghOD4hh2kHNQQTorCsbY9S1Kir2la1/XLBwOJfui0xg==", + "cpu": [ + "!arm", + "!arm64", + "!riscv64", + "!x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "sass": "1.97.3" + } + }, + "node_modules/sass-embedded-android-arm": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.97.3.tgz", + "integrity": "sha512-cRTtf/KV/q0nzGZoUzVkeIVVFv3L/tS1w4WnlHapphsjTXF/duTxI8JOU1c/9GhRPiMdfeXH7vYNcMmtjwX7jg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.97.3.tgz", + "integrity": "sha512-aiZ6iqiHsUsaDx0EFbbmmA0QgxicSxVVN3lnJJ0f1RStY0DthUkquGT5RJ4TPdaZ6ebeJWkboV4bra+CP766eA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-riscv64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.97.3.tgz", + "integrity": "sha512-zVEDgl9JJodofGHobaM/q6pNETG69uuBIGQHRo789jloESxxZe82lI3AWJQuPmYCOG5ElfRthqgv89h3gTeLYA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.97.3.tgz", + "integrity": "sha512-3ke0le7ZKepyXn/dKKspYkpBC0zUk/BMciyP5ajQUDy4qJwobd8zXdAq6kOkdiMB+d9UFJOmEkvgFJHl3lqwcw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.97.3.tgz", + "integrity": "sha512-fuqMTqO4gbOmA/kC5b9y9xxNYw6zDEyfOtMgabS7Mz93wimSk2M1quQaTJnL98Mkcsl2j+7shNHxIS/qpcIDDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.97.3.tgz", + "integrity": "sha512-b/2RBs/2bZpP8lMkyZ0Px0vkVkT8uBd0YXpOwK7iOwYkAT8SsO4+WdVwErsqC65vI5e1e5p1bb20tuwsoQBMVA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.97.3.tgz", + "integrity": "sha512-2lPQ7HQQg4CKsH18FTsj2hbw5GJa6sBQgDsls+cV7buXlHjqF8iTKhAQViT6nrpLK/e8nFCoaRgSqEC8xMnXuA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.97.3.tgz", + "integrity": "sha512-IP1+2otCT3DuV46ooxPaOKV1oL5rLjteRzf8ldZtfIEcwhSgSsHgA71CbjYgLEwMY9h4jeal8Jfv3QnedPvSjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.97.3.tgz", + "integrity": "sha512-cBTMU68X2opBpoYsSZnI321gnoaiMBEtc+60CKCclN6PCL3W3uXm8g4TLoil1hDD6mqU9YYNlVG6sJ+ZNef6Lg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.97.3.tgz", + "integrity": "sha512-Lij0SdZCsr+mNRSyDZ7XtJpXEITrYsaGbOTz5e6uFLJ9bmzUbV7M8BXz2/cA7bhfpRPT7/lwRKPdV4+aR9Ozcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-riscv64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.97.3.tgz", + "integrity": "sha512-sBeLFIzMGshR4WmHAD4oIM7WJVkSoCIEwutzptFtGlSlwfNiijULp+J5hA2KteGvI6Gji35apR5aWj66wEn/iA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.97.3.tgz", + "integrity": "sha512-/oWJ+OVrDg7ADDQxRLC/4g1+Nsz1g4mkYS2t6XmyMJKFTFK50FVI2t5sOdFH+zmMp+nXHKM036W94y9m4jjEcw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-riscv64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.97.3.tgz", + "integrity": "sha512-l3IfySApLVYdNx0Kjm7Zehte1CDPZVcldma3dZt+TfzvlAEerM6YDgsk5XEj3L8eHBCgHgF4A0MJspHEo2WNfA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.97.3.tgz", + "integrity": "sha512-Kwqwc/jSSlcpRjULAOVbndqEy2GBzo6OBmmuBVINWUaJLJ8Kczz3vIsDUWLfWz/kTEw9FHBSiL0WCtYLVAXSLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-unknown-all": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.97.3.tgz", + "integrity": "sha512-/GHajyYJmvb0IABUQHbVHf1nuHPtIDo/ClMZ81IDr59wT5CNcMe7/dMNujXwWugtQVGI5UGmqXWZQCeoGnct8Q==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "!android", + "!darwin", + "!linux", + "!win32" + ], + "dependencies": { + "sass": "1.97.3" + } + }, + "node_modules/sass-embedded-win32-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.97.3.tgz", + "integrity": "sha512-RDGtRS1GVvQfMGAmVXNxYiUOvPzn9oO1zYB/XUM9fudDRnieYTcUytpNTQZLs6Y1KfJxgt5Y+giRceC92fT8Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.97.3.tgz", + "integrity": "sha512-SFRa2lED9UEwV6vIGeBXeBOLKF+rowF3WmNfb/BzhxmdAsKofCXrJ8ePW7OcDVrvNEbTOGwhsReIsF5sH8fVaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "2.2.31", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", + "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^1.0.20" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/shallow-equal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sync-child-process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", + "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sync-message-port": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/sync-message-port": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.2.0.tgz", + "integrity": "sha512-gAQ9qrUN/UCypHtGFbbe7Rc/f9bzO88IwrG8TDo/aMKAApKyD6E3W4Cm0EfhfBb6Z6SKt59tTCTfD+n1xmAvMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unimport": { + "version": "3.14.6", + "resolved": "https://registry.npmjs.org/unimport/-/unimport-3.14.6.tgz", + "integrity": "sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.4", + "acorn": "^8.14.0", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "fast-glob": "^3.3.3", + "local-pkg": "^1.0.0", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "pathe": "^2.0.1", + "picomatch": "^4.0.2", + "pkg-types": "^1.3.0", + "scule": "^1.3.0", + "strip-literal": "^2.1.1", + "unplugin": "^1.16.1" + } + }, + "node_modules/unimport/node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unimport/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/unimport/node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unimport/node_modules/local-pkg/node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unplugin": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz", + "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/unplugin-auto-import": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-0.17.8.tgz", + "integrity": "sha512-CHryj6HzJ+n4ASjzwHruD8arhbdl+UXvhuAIlHDs15Y/IMecG3wrf7FVg4pVH/DIysbq/n0phIjNHAjl7TG7Iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.0", + "fast-glob": "^3.3.2", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.10", + "minimatch": "^9.0.4", + "unimport": "^3.7.2", + "unplugin": "^1.11.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@nuxt/kit": "^3.2.2", + "@vueuse/core": "*" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@vueuse/core": { + "optional": true + } + } + }, + "node_modules/unplugin-vue-components": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-0.26.0.tgz", + "integrity": "sha512-s7IdPDlnOvPamjunVxw8kNgKNK8A5KM1YpK5j/p97jEKTjlPNrA0nZBiSfAKKlK1gWZuyWXlKL5dk3EDw874LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.6", + "@rollup/pluginutils": "^5.0.4", + "chokidar": "^3.5.3", + "debug": "^4.3.4", + "fast-glob": "^3.3.1", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.3", + "minimatch": "^9.0.3", + "resolve": "^1.22.4", + "unplugin": "^1.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/unplugin-vue-components/node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-compression": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/vite-plugin-compression/-/vite-plugin-compression-0.5.1.tgz", + "integrity": "sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "debug": "^4.3.3", + "fs-extra": "^10.0.0" + }, + "peerDependencies": { + "vite": ">=2.0.0" + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.28.tgz", + "integrity": "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.5.28", + "@vue/compiler-sfc": "3.5.28", + "@vue/runtime-dom": "3.5.28", + "@vue/server-renderer": "3.5.28", + "@vue/shared": "3.5.28" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-tsc": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz", + "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.15", + "@vue/language-core": "2.2.12" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/vue-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/vue-types/-/vue-types-3.0.2.tgz", + "integrity": "sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==", + "license": "MIT", + "dependencies": { + "is-plain-object": "3.0.1" + }, + "engines": { + "node": ">=10.15.0" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/zrender": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz", + "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + }, + "node_modules/zrender/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + } + } +} diff --git a/reading-platform-frontend/package.json b/reading-platform-frontend/package.json new file mode 100644 index 0000000..d490b31 --- /dev/null +++ b/reading-platform-frontend/package.json @@ -0,0 +1,42 @@ +{ + "name": "reading-platform-frontend", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc && vite build", + "preview": "vite preview", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" + }, + "dependencies": { + "@ant-design/icons-vue": "^7.0.1", + "@fullcalendar/core": "^6.1.20", + "@fullcalendar/daygrid": "^6.1.20", + "@fullcalendar/interaction": "^6.1.20", + "@fullcalendar/timegrid": "^6.1.20", + "@fullcalendar/vue3": "^6.1.20", + "ant-design-vue": "^4.1.2", + "axios": "^1.6.7", + "dayjs": "^1.11.10", + "echarts": "^6.0.0", + "lodash-es": "^4.17.21", + "lucide-vue-next": "^0.575.0", + "pinia": "^2.1.7", + "vue": "^3.4.21", + "vue-router": "^4.3.0" + }, + "devDependencies": { + "@types/lodash-es": "^4.17.12", + "@types/node": "^20.11.28", + "@vitejs/plugin-vue": "^5.0.4", + "@vue/tsconfig": "^0.5.1", + "sass-embedded": "^1.97.3", + "typescript": "~5.4.0", + "unplugin-auto-import": "^0.17.5", + "unplugin-vue-components": "^0.26.0", + "vite": "^5.1.6", + "vite-plugin-compression": "^0.5.1", + "vue-tsc": "^2.0.6" + } +} diff --git a/reading-platform-frontend/public/logo.png b/reading-platform-frontend/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..456046e315f931694d091a8ad2f1ad7d4cf2337a GIT binary patch literal 135769 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%0A?Mf4>vT`63y<{>ix&7`aNQ{Dvp|^ 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 >n#HP$tKL|x>Xl&uxJCsQ-(RsM+p4` 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%0DDOx=OittZ{vRzuV-b z-A{Gha|nSKxg%qdO1PLo<65rV{qvCKeGXv462pf2h3pKLpW&KLJjYC{bFPMqk5;Ew zuc(^lT&->TbmrOb>CRvczQwYk1(Fq|bg!u5f;$YiaR5N73RLIF8>TFPk@gzQn-tKz zV^cbW^Nv7=6l*Z{Mhwm1XHj9VKbyqsqk5>L63snF&aUAxdtG)w@qFdImK2kl-T6P{ zk4I-XIQi0b_$Rc@d}P-4e^0n+6D=Mz_2ppp+0qUp7Xka#Fo-*UBJ1^TUsq?_%>DFj z_R_Uqi@g@-;mIGTvvFC*PK@Lm)D$T^P;qSpUg;_$)d_OF8r8garSOIA)W`LtFM^y<^GztWAgtHL{xm4Eg zkCu5L_iL%yI1{YhC7D)T6>#2;Cb@iK!(3*UMh)Vz@0tL#z++%>OHZ)=W;}IyF!tp+l>jJndHojE#m# zw=204vB`XLsi))&SNPC_hZY{kfg=9L z$aMHQRC}Ldyjja|hi}1`g`3FV1#1(BKBE?pTAJn!xLN@ zSj^l0Vgd$X_JzY_aiR+`VL+?1DD$&VSl=t zK03>m*B(R9)}}kZ%1h=N0w0zN(2Mr|*FVc>RI&d4jMzc%gp>5%f?LTNI!;o^3ks{l z$;26a5K8}jW5Q+VGjYU9JCeRf*a@U2U})de_=*so(R~JqWvd%5TF&V@j^_eW#TlKi$Tm*c^75T8JY%;zQweR$8eZCFfGV zt#(Fa#l3+fz#SZ4aKLZg`wvm!dBuAF_yby#>eR34j{GjGsfLQw=g}vG{k2v|@(CET z`J^Ia*Xc-B$hYD`~y9Q_(74wSm8;&@8W8XYHl~Y)OKD-GsJTJ zJy#O1`&Q(P)0HLkhv*O1!JBt!i@~=v0{*VyB}7N_fBnoqcnqJZ_1b^P6Sn+Zrhgz7 ze#p${?^`2k!B&mG7yAD%^Zz&P|IkXi^?MCRgw3F;_QnD_{MoO0XkVtrNw5C_J|$*p literal 0 HcmV?d00001 diff --git a/reading-platform-frontend/src/.DS_Store b/reading-platform-frontend/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..fbc0610b2a018226fa1dce1eb7b5f38ff5539113 GIT binary patch literal 6148 zcmeHLyGjE=6un~-iLr^;2!d{FBS|BO%_hWB5%K|Q9%vwOLt?bB*~UV!w-z*CVQ(Q= zXr*FpEfxwQc;?PvX7bQN5D9Z(=Iq|b+_Q&dClewP*;Xk{lq8}!hQw$BQy-IYIeKhF zd!~Rwu940cgIb-vxOe6ha0>i$1>|=(LYq{lN_ETp{!Tx2BDElwD^!C5Jn@t4^XWtT zGGj(d-$d)%=BoU1NKlais-b5?<2~`SIr9vMuger&M(|NweP@yU(!JV;= z?kw~PMR0fcJx(X#D|E9{z$suW5K*^Dx&JQ@KL6WA?#?OT6c{Q6L^PMrW${RQZ|!+F xxz~Ca8yM1LUZ{{uFzM}>FXUD%|5so`%##;DUt_2c9+>+fz%sbWDe$KXd; + + + + + + + + diff --git a/reading-platform-frontend/src/api/admin.ts b/reading-platform-frontend/src/api/admin.ts new file mode 100644 index 0000000..32ec169 --- /dev/null +++ b/reading-platform-frontend/src/api/admin.ts @@ -0,0 +1,215 @@ +import { http } from './index'; + +// ==================== 类型定义 ==================== + +export interface TenantQueryParams { + page?: number; + pageSize?: number; + keyword?: string; + status?: string; + packageType?: string; +} + +export interface Tenant { + id: number; + name: string; + loginAccount: string; + address?: string; + contactPerson?: string; + contactPhone?: string; + logoUrl?: string; + packageType: string; + teacherQuota: number; + studentQuota: number; + storageQuota?: number; + teacherCount: number; + studentCount: number; + storageUsed?: number; + startDate: string; + expireDate: string; + status: string; + createdAt: string; + updatedAt?: string; +} + +export interface TenantDetail extends Tenant { + teachers?: Array<{ + id: number; + name: string; + phone: string; + email?: string; + status: string; + lessonCount: number; + }>; + students?: Array<{ + id: number; + name: string; + classId: number; + gender?: string; + readingCount: number; + }>; + classes?: Array<{ + id: number; + name: string; + grade: string; + studentCount: number; + }>; + _count?: { + teachers: number; + students: number; + classes: number; + lessons: number; + }; +} + +export interface CreateTenantDto { + name: string; + loginAccount: string; + password?: string; + address?: string; + contactPerson?: string; + contactPhone?: string; + packageType?: string; + teacherQuota?: number; + studentQuota?: number; + startDate?: string; + expireDate?: string; +} + +export interface UpdateTenantDto { + name?: string; + address?: string; + contactPerson?: string; + contactPhone?: string; + packageType?: string; + teacherQuota?: number; + studentQuota?: number; + startDate?: string; + expireDate?: string; + status?: string; +} + +export interface UpdateTenantQuotaDto { + packageType?: string; + teacherQuota?: number; + studentQuota?: number; +} + +export interface AdminStats { + tenantCount: number; + activeTenantCount: number; + courseCount: number; + publishedCourseCount: number; + studentCount: number; + teacherCount: number; + lessonCount: number; + monthlyLessons: number; +} + +export interface TrendData { + month: string; + tenantCount: number; + lessonCount: number; + studentCount: number; +} + +export interface ActiveTenant { + id: number; + name: string; + lessonCount: number; + teacherCount: number; + studentCount: number; +} + +export interface PopularCourse { + id: number; + name: string; + usageCount: number; + teacherCount: number; +} + +export interface AdminSettings { + // Basic settings + systemName?: string; + systemDesc?: string; + contactPhone?: string; + contactEmail?: string; + systemLogo?: string; + + // Security settings + passwordStrength?: string; + maxLoginAttempts?: number; + tokenExpire?: string; + forceHttps?: boolean; + + // Notification settings + emailEnabled?: boolean; + smtpHost?: string; + smtpPort?: number; + smtpUser?: string; + smtpPassword?: string; + fromEmail?: string; + smsEnabled?: boolean; + + // Storage settings + storageType?: string; + maxFileSize?: number; + allowedTypes?: string; + + // Tenant defaults + defaultTeacherQuota?: number; + defaultStudentQuota?: number; + enableAutoExpire?: boolean; + notifyBeforeDays?: number; +} + +// ==================== 租户管理 ==================== + +export const getTenants = (params: TenantQueryParams) => + http.get<{ items: Tenant[]; total: number; page: number; pageSize: number; totalPages: number }>( + '/admin/tenants', + { params } + ); + +export const getTenant = (id: number) => + http.get(`/admin/tenants/${id}`); + +export const createTenant = (data: CreateTenantDto) => + http.post('/admin/tenants', data); + +export const updateTenant = (id: number, data: UpdateTenantDto) => + http.put(`/admin/tenants/${id}`, data); + +export const updateTenantQuota = (id: number, data: UpdateTenantQuotaDto) => + http.put(`/admin/tenants/${id}/quota`, data); + +export const updateTenantStatus = (id: number, status: string) => + http.put<{ id: number; name: string; status: string }>(`/admin/tenants/${id}/status`, { status }); + +export const resetTenantPassword = (id: number) => + http.post<{ tempPassword: string }>(`/admin/tenants/${id}/reset-password`); + +export const deleteTenant = (id: number) => + http.delete<{ success: boolean }>(`/admin/tenants/${id}`); + +// ==================== 统计数据 ==================== + +export const getAdminStats = () => + http.get('/admin/stats'); + +export const getTrendData = () => + http.get('/admin/stats/trend'); + +export const getActiveTenants = (limit?: number) => + http.get('/admin/stats/tenants/active', { params: { limit } }); + +export const getPopularCourses = (limit?: number) => + http.get('/admin/stats/courses/popular', { params: { limit } }); + +// ==================== 系统设置 ==================== + +export const getAdminSettings = () => + http.get('/admin/settings'); + +export const updateAdminSettings = (data: Record) => + http.put('/admin/settings', data); diff --git a/reading-platform-frontend/src/api/auth.ts b/reading-platform-frontend/src/api/auth.ts new file mode 100644 index 0000000..9f2c983 --- /dev/null +++ b/reading-platform-frontend/src/api/auth.ts @@ -0,0 +1,51 @@ +import { http } from './index'; + +export interface LoginParams { + account: string; + password: string; + role: string; +} + +export interface LoginResponse { + token: string; + user: { + id: number; + name: string; + role: 'admin' | 'school' | 'teacher'; + tenantId?: number; + tenantName?: string; + email?: string; + phone?: string; + }; +} + +export interface UserProfile { + id: number; + name: string; + role: 'admin' | 'school' | 'teacher'; + tenantId?: number; + tenantName?: string; + email?: string; + phone?: string; + avatar?: string; +} + +// 登录 +export function login(params: LoginParams): Promise { + return http.post('/auth/login', params); +} + +// 登出 +export function logout(): Promise { + return http.post('/auth/logout'); +} + +// 刷新Token +export function refreshToken(): Promise<{ token: string }> { + return http.post('/auth/refresh'); +} + +// 获取当前用户信息 +export function getProfile(): Promise { + return http.get('/auth/profile'); +} diff --git a/reading-platform-frontend/src/api/course.ts b/reading-platform-frontend/src/api/course.ts new file mode 100644 index 0000000..bb0b189 --- /dev/null +++ b/reading-platform-frontend/src/api/course.ts @@ -0,0 +1,153 @@ +import { http } from './index'; + +export interface CourseQueryParams { + page?: number; + pageSize?: number; + grade?: string; + status?: string; + keyword?: string; +} + +export interface Course { + id: number; + name: string; + description?: string; + pictureBookName?: string; + grades: string[]; + status: string; + version: string; + usageCount: number; + teacherCount: number; + avgRating: number; + createdAt: Date; + submittedAt?: Date; + reviewedAt?: Date; + reviewComment?: string; +} + +export interface ValidationResult { + valid: boolean; + errors: ValidationError[]; + warnings: ValidationWarning[]; +} + +export interface ValidationError { + field: string; + message: string; + code: string; +} + +export interface ValidationWarning { + field: string; + message: string; + code: string; +} + +// 获取课程包列表 +export function getCourses(params: CourseQueryParams): Promise<{ + items: Course[]; + total: number; + page: number; + pageSize: number; +}> { + return http.get('/courses', { params }); +} + +// 获取审核列表 +export function getReviewList(params: CourseQueryParams): Promise<{ + items: Course[]; + total: number; + page: number; + pageSize: number; +}> { + return http.get('/courses/review', { params }); +} + +// 获取课程包详情 +export function getCourse(id: number): Promise { + return http.get(`/courses/${id}`); +} + +// 创建课程包 +export function createCourse(data: any): Promise { + return http.post('/courses', data); +} + +// 更新课程包 +export function updateCourse(id: number, data: any): Promise { + return http.put(`/courses/${id}`, data); +} + +// 删除课程包 +export function deleteCourse(id: number): Promise { + return http.delete(`/courses/${id}`); +} + +// 验证课程完整性 +export function validateCourse(id: number): Promise { + return http.get(`/courses/${id}/validate`); +} + +// 提交审核 +export function submitCourse(id: number, copyrightConfirmed: boolean): Promise { + return http.post(`/courses/${id}/submit`, { copyrightConfirmed }); +} + +// 撤销审核 +export function withdrawCourse(id: number): Promise { + return http.post(`/courses/${id}/withdraw`); +} + +// 审核通过 +export function approveCourse(id: number, data: { checklist?: any; comment?: string }): Promise { + return http.post(`/courses/${id}/approve`, data); +} + +// 审核驳回 +export function rejectCourse(id: number, data: { checklist?: any; comment: string }): Promise { + return http.post(`/courses/${id}/reject`, data); +} + +// 直接发布(超级管理员) +export function directPublishCourse(id: number, skipValidation?: boolean): Promise { + return http.post(`/courses/${id}/direct-publish`, { skipValidation }); +} + +// 发布课程包(兼容旧API) +export function publishCourse(id: number): Promise { + return http.post(`/courses/${id}/publish`); +} + +// 下架课程包 +export function unpublishCourse(id: number): Promise { + return http.post(`/courses/${id}/unpublish`); +} + +// 重新发布 +export function republishCourse(id: number): Promise { + return http.post(`/courses/${id}/republish`); +} + +// 获取课程包统计数据 +export function getCourseStats(id: number): Promise { + return http.get(`/courses/${id}/stats`); +} + +// 获取版本历史 +export function getCourseVersions(id: number): Promise { + return http.get(`/courses/${id}/versions`); +} + +// 课程状态映射 +export const COURSE_STATUS_MAP: Record = { + DRAFT: { label: '草稿', color: 'default' }, + PENDING: { label: '审核中', color: 'processing' }, + REJECTED: { label: '已驳回', color: 'error' }, + PUBLISHED: { label: '已发布', color: 'success' }, + ARCHIVED: { label: '已下架', color: 'warning' }, +}; + +// 获取状态显示信息 +export function getCourseStatusInfo(status: string) { + return COURSE_STATUS_MAP[status] || { label: status, color: 'default' }; +} diff --git a/reading-platform-frontend/src/api/file.ts b/reading-platform-frontend/src/api/file.ts new file mode 100644 index 0000000..1a4a156 --- /dev/null +++ b/reading-platform-frontend/src/api/file.ts @@ -0,0 +1,127 @@ +import axios from 'axios'; + +const API_BASE = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api/v1'; + +export interface UploadResult { + success: boolean; + filePath: string; + fileName: string; + originalName: string; + fileSize: number; + mimeType: string; +} + +export interface DeleteResult { + success: boolean; + message: string; +} + +/** + * 文件上传 API + */ +export const fileApi = { + /** + * 上传文件 + */ + uploadFile: async ( + file: File, + type: 'cover' | 'ebook' | 'audio' | 'video' | 'ppt' | 'poster' | 'other', + courseId?: number, + ): Promise => { + const formData = new FormData(); + formData.append('file', file); + formData.append('type', type); + if (courseId) { + formData.append('courseId', courseId.toString()); + } + + const response = await axios.post( + `${API_BASE}/files/upload`, + formData, + { + headers: { + 'Content-Type': 'multipart/form-data', + }, + // 添加上传进度回调 + onUploadProgress: (progressEvent) => { + if (progressEvent.total) { + const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total); + console.log(`Upload progress: ${percentCompleted}%`); + } + }, + }, + ); + + return response.data; + }, + + /** + * 删除文件 + */ + deleteFile: async (filePath: string): Promise => { + const response = await axios.delete(`${API_BASE}/files/delete`, { + data: { filePath }, + }); + return response.data; + }, + + /** + * 获取文件URL + */ + getFileUrl: (filePath: string): string => { + // filePath 格式: /uploads/courses/covers/xxx.png + // 直接返回相对路径,由 nginx 或后端静态服务处理 + return filePath; + }, +}; + +/** + * 文件类型常量 + */ +export const FILE_TYPES = { + COVER: 'cover', + EBOOK: 'ebook', + AUDIO: 'audio', + VIDEO: 'video', + PPT: 'ppt', + POSTER: 'poster', + OTHER: 'other', +} as const; + +/** + * 文件大小限制(字节) + */ +export const FILE_SIZE_LIMITS = { + COVER: 10 * 1024 * 1024, // 10MB + EBOOK: 300 * 1024 * 1024, // 300MB + AUDIO: 300 * 1024 * 1024, // 300MB + VIDEO: 300 * 1024 * 1024, // 300MB + PPT: 300 * 1024 * 1024, // 300MB + POSTER: 10 * 1024 * 1024, // 10MB + OTHER: 300 * 1024 * 1024, // 300MB +} as const; + +/** + * 每个分类最大文件数量 + */ +export const MAX_FILE_COUNT = 15; + +/** + * 文件类型验证 + */ +export const validateFileType = ( + file: File, + type: keyof typeof FILE_SIZE_LIMITS, +): { valid: boolean; error?: string } => { + const maxSize = FILE_SIZE_LIMITS[type]; + + if (file.size > maxSize) { + const maxSizeMB = (maxSize / (1024 * 1024)).toFixed(0); + return { + valid: false, + error: `文件大小超过限制,最大允许 ${maxSizeMB}MB`, + }; + } + + return { valid: true }; +}; diff --git a/reading-platform-frontend/src/api/growth.ts b/reading-platform-frontend/src/api/growth.ts new file mode 100644 index 0000000..c506b18 --- /dev/null +++ b/reading-platform-frontend/src/api/growth.ts @@ -0,0 +1,130 @@ +import { http } from './index'; + +// ==================== 类型定义 ==================== + +export type RecordType = 'STUDENT' | 'CLASS'; + +export interface GrowthRecord { + id: number; + tenantId: number; + studentId: number; + classId?: number; + recordType: RecordType; + title: string; + content?: string; + images: string[]; + recordDate: string; + createdBy: number; + createdAt: string; + updatedAt: string; + student?: { + id: number; + name: string; + gender?: string; + }; + class?: { + id: number; + name: string; + grade?: string; + }; +} + +export interface CreateGrowthRecordDto { + studentId: number; + classId?: number; + recordType: RecordType; + title: string; + content?: string; + images?: string[]; + recordDate: string; +} + +export interface UpdateGrowthRecordDto { + title?: string; + content?: string; + images?: string[]; + recordDate?: string; +} + +// ==================== 学校端 API ==================== + +export const getGrowthRecords = (params?: { + page?: number; + pageSize?: number; + studentId?: number; + classId?: number; + recordType?: RecordType; + keyword?: string; +}) => + http.get<{ items: GrowthRecord[]; total: number; page: number; pageSize: number }>( + '/school/growth-records', + { params } + ); + +export const getGrowthRecord = (id: number) => + http.get(`/school/growth-records/${id}`); + +export const createGrowthRecord = (data: CreateGrowthRecordDto) => + http.post('/school/growth-records', data); + +export const updateGrowthRecord = (id: number, data: UpdateGrowthRecordDto) => + http.put(`/school/growth-records/${id}`, data); + +export const deleteGrowthRecord = (id: number) => + http.delete(`/school/growth-records/${id}`); + +export const getStudentGrowthRecords = (studentId: number, params?: { + page?: number; + pageSize?: number; +}) => + http.get<{ items: GrowthRecord[]; total: number; page: number; pageSize: number }>( + `/school/students/${studentId}/growth-records`, + { params } + ); + +export const getClassGrowthRecords = (classId: number, params?: { + page?: number; + pageSize?: number; + recordDate?: string; +}) => + http.get<{ items: GrowthRecord[]; total: number; page: number; pageSize: number }>( + `/school/classes/${classId}/growth-records`, + { params } + ); + +// ==================== 教师端 API ==================== + +export const getTeacherGrowthRecords = (params?: { + page?: number; + pageSize?: number; + studentId?: number; + classId?: number; + recordType?: RecordType; + keyword?: string; +}) => + http.get<{ items: GrowthRecord[]; total: number; page: number; pageSize: number }>( + '/teacher/growth-records', + { params } + ); + +export const getTeacherGrowthRecord = (id: number) => + http.get(`/teacher/growth-records/${id}`); + +export const createTeacherGrowthRecord = (data: CreateGrowthRecordDto) => + http.post('/teacher/growth-records', data); + +export const updateTeacherGrowthRecord = (id: number, data: UpdateGrowthRecordDto) => + http.put(`/teacher/growth-records/${id}`, data); + +export const deleteTeacherGrowthRecord = (id: number) => + http.delete(`/teacher/growth-records/${id}`); + +export const getTeacherClassGrowthRecords = (classId: number, params?: { + page?: number; + pageSize?: number; + recordDate?: string; +}) => + http.get<{ items: GrowthRecord[]; total: number; page: number; pageSize: number }>( + `/teacher/classes/${classId}/growth-records`, + { params } + ); diff --git a/reading-platform-frontend/src/api/index.ts b/reading-platform-frontend/src/api/index.ts new file mode 100644 index 0000000..badbbc9 --- /dev/null +++ b/reading-platform-frontend/src/api/index.ts @@ -0,0 +1,94 @@ +import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; +import { message } from 'ant-design-vue'; + +// 创建axios实例 +const request: AxiosInstance = axios.create({ + baseURL: '/api/v1', // 使用 /api/v1,代理会保留完整路径 + timeout: 30000, + headers: { + 'Content-Type': 'application/json', + }, +}); + +// 请求拦截器 +request.interceptors.request.use( + (config) => { + const token = localStorage.getItem('token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +// 响应拦截器 +request.interceptors.response.use( + (response: AxiosResponse) => { + // 直接返回响应数据 + return response.data; + }, + (error) => { + const { response } = error; + + if (response) { + const { status, data } = response; + + switch (status) { + case 401: + message.error('登录已过期,请重新登录'); + localStorage.removeItem('token'); + localStorage.removeItem('user'); + localStorage.removeItem('role'); + localStorage.removeItem('name'); + window.location.href = '/login'; + break; + case 403: + message.error('没有权限访问'); + break; + case 404: + message.error('请求的资源不存在'); + break; + case 500: + message.error('服务器错误'); + break; + default: + message.error(data?.message || '请求失败'); + } + } else if (error.code === 'ECONNABORTED') { + message.error('请求超时'); + } else { + message.error('网络错误'); + } + + return Promise.reject(error); + } +); + +// 导出请求方法 +export default request; + +// 通用请求方法 +export const http = { + get(url: string, config?: AxiosRequestConfig): Promise { + return request.get(url, config); + }, + + post(url: string, data?: any, config?: AxiosRequestConfig): Promise { + return request.post(url, data, config); + }, + + put(url: string, data?: any, config?: AxiosRequestConfig): Promise { + return request.put(url, data, config); + }, + + delete(url: string, config?: AxiosRequestConfig): Promise { + return request.delete(url, config); + }, + + patch(url: string, data?: any, config?: AxiosRequestConfig): Promise { + return request.patch(url, data, config); + }, +}; diff --git a/reading-platform-frontend/src/api/parent.ts b/reading-platform-frontend/src/api/parent.ts new file mode 100644 index 0000000..c261006 --- /dev/null +++ b/reading-platform-frontend/src/api/parent.ts @@ -0,0 +1,152 @@ +import { http } from './index'; + +// ==================== 类型定义 ==================== + +export interface ChildInfo { + id: number; + name: string; + gender?: string; + birthDate?: string; + relationship: string; + class: { + id: number; + name: string; + grade: string; + }; + readingCount: number; + lessonCount: number; +} + +export interface ChildProfile extends ChildInfo { + stats: { + lessonRecords: number; + growthRecords: number; + taskCompletions: number; + }; +} + +export interface LessonRecord { + id: number; + lesson: { + id: number; + startDatetime: string; + endDatetime?: string; + actualDuration?: number; + course: { + id: number; + name: string; + pictureBookName?: string; + }; + }; + focus?: number; + participation?: number; + interest?: number; + understanding?: number; + notes?: string; + createdAt: string; +} + +export interface TaskWithCompletion { + id: number; + status: string; + completedAt?: string; + feedback?: string; + parentFeedback?: string; + task: { + id: number; + title: string; + description?: string; + taskType: string; + startDate: string; + endDate: string; + course?: { + id: number; + name: string; + }; + }; +} + +export interface GrowthRecord { + id: number; + title: string; + content?: string; + images: string[]; + recordDate: string; + recordType: string; + class?: { + id: number; + name: string; + }; + createdAt: string; +} + +export interface Notification { + id: number; + title: string; + content: string; + notificationType: string; + isRead: boolean; + readAt?: string; + createdAt: string; +} + +// ==================== 孩子信息 API ==================== + +export const getChildren = (): Promise => + http.get('/parent/children'); + +export const getChildProfile = (childId: number): Promise => + http.get(`/parent/children/${childId}`); + +// ==================== 阅读记录 API ==================== + +export const getChildLessons = ( + childId: number, + params?: { page?: number; pageSize?: number } +): Promise<{ items: LessonRecord[]; total: number; page: number; pageSize: number }> => + http.get(`/parent/children/${childId}/lessons`, { params }); + +// ==================== 任务 API ==================== + +export const getChildTasks = ( + childId: number, + params?: { page?: number; pageSize?: number; status?: string } +): Promise<{ items: TaskWithCompletion[]; total: number; page: number; pageSize: number }> => + http.get(`/parent/children/${childId}/tasks`, { params }); + +export const submitTaskFeedback = ( + childId: number, + taskId: number, + feedback: string +): Promise => + http.put(`/parent/children/${childId}/tasks/${taskId}/feedback`, { feedback }); + +// ==================== 成长档案 API ==================== + +export const getChildGrowthRecords = ( + childId: number, + params?: { page?: number; pageSize?: number } +): Promise<{ items: GrowthRecord[]; total: number; page: number; pageSize: number }> => + http.get(`/parent/children/${childId}/growth-records`, { params }); + +// ==================== 通知 API ==================== + +export const getNotifications = ( + params?: { page?: number; pageSize?: number; isRead?: boolean; notificationType?: string } +): Promise<{ + items: Notification[]; + total: number; + unreadCount: number; + page: number; + pageSize: number; +}> => + http.get('/parent/notifications', { params }); + +export const getUnreadCount = (): Promise => + http.get('/parent/notifications/unread-count'); + +export const markNotificationAsRead = (id: number): Promise => + http.put(`/parent/notifications/${id}/read`); + +export const markAllNotificationsAsRead = (): Promise => + http.put('/parent/notifications/read-all'); diff --git a/reading-platform-frontend/src/api/resource.ts b/reading-platform-frontend/src/api/resource.ts new file mode 100644 index 0000000..b1fd971 --- /dev/null +++ b/reading-platform-frontend/src/api/resource.ts @@ -0,0 +1,135 @@ +import { http } from './index'; + +// ==================== 类型定义 ==================== + +export type LibraryType = 'PICTURE_BOOK' | 'MATERIAL' | 'TEMPLATE'; +export type FileType = 'IMAGE' | 'PDF' | 'VIDEO' | 'AUDIO' | 'PPT' | 'OTHER'; + +export interface ResourceLibrary { + id: number; + name: string; + libraryType: LibraryType; + description?: string; + coverImage?: string; + createdBy: number; + status: string; + sortOrder: number; + itemCount: number; + createdAt: string; + updatedAt: string; +} + +export interface ResourceItem { + id: number; + libraryId: number; + title: string; + description?: string; + fileType: FileType; + filePath: string; + fileSize?: number; + tags: string[]; + sortOrder: number; + createdAt: string; + library?: { + id: number; + name: string; + libraryType: LibraryType; + }; +} + +export interface CreateLibraryDto { + name: string; + libraryType: LibraryType; + description?: string; + coverImage?: string; +} + +export interface UpdateLibraryDto { + name?: string; + description?: string; + coverImage?: string; + sortOrder?: number; +} + +export interface CreateResourceItemDto { + libraryId: number; + title: string; + description?: string; + fileType: FileType; + filePath: string; + fileSize?: number; + tags?: string[]; +} + +export interface UpdateResourceItemDto { + title?: string; + description?: string; + tags?: string[]; + sortOrder?: number; +} + +export interface ResourceStats { + totalLibraries: number; + totalItems: number; + itemsByType: Record; + itemsByLibraryType: Record; +} + +// ==================== 资源库管理 ==================== + +export const getLibraries = (params?: { + page?: number; + pageSize?: number; + libraryType?: LibraryType; + keyword?: string; +}) => + http.get<{ items: ResourceLibrary[]; total: number; page: number; pageSize: number }>( + '/admin/resources/libraries', + { params } + ); + +export const getLibrary = (id: number) => + http.get(`/admin/resources/libraries/${id}`); + +export const createLibrary = (data: CreateLibraryDto) => + http.post('/admin/resources/libraries', data); + +export const updateLibrary = (id: number, data: UpdateLibraryDto) => + http.put(`/admin/resources/libraries/${id}`, data); + +export const deleteLibrary = (id: number) => + http.delete(`/admin/resources/libraries/${id}`); + +// ==================== 资源项目管理 ==================== + +export const getResourceItems = (params?: { + page?: number; + pageSize?: number; + libraryId?: number; + fileType?: FileType; + keyword?: string; +}) => + http.get<{ items: ResourceItem[]; total: number; page: number; pageSize: number }>( + '/admin/resources/items', + { params } + ); + +export const getResourceItem = (id: number) => + http.get(`/admin/resources/items/${id}`); + +export const createResourceItem = (data: CreateResourceItemDto) => + http.post('/admin/resources/items', data); + +export const updateResourceItem = (id: number, data: UpdateResourceItemDto) => + http.put(`/admin/resources/items/${id}`, data); + +export const deleteResourceItem = (id: number) => + http.delete(`/admin/resources/items/${id}`); + +export const batchDeleteResourceItems = (ids: number[]) => + http.post<{ message: string }>('/admin/resources/items/batch-delete', { ids }); + +// ==================== 统计数据 ==================== + +export const getResourceStats = () => + http.get('/admin/resources/stats'); diff --git a/reading-platform-frontend/src/api/school.ts b/reading-platform-frontend/src/api/school.ts new file mode 100644 index 0000000..f60f048 --- /dev/null +++ b/reading-platform-frontend/src/api/school.ts @@ -0,0 +1,958 @@ +import { http } from './index'; + +// ==================== 类型定义 ==================== + +export interface TeacherQueryParams { + page?: number; + pageSize?: number; + keyword?: string; + status?: string; +} + +export interface Teacher { + id: number; + name: string; + phone: string; + email?: string; + loginAccount: string; + status: string; + classIds: number[]; + classNames?: string | string[]; + lessonCount?: number; + createdAt: string; +} + +export interface CreateTeacherDto { + name: string; + phone: string; + email?: string; + loginAccount: string; + password?: string; + classIds?: number[]; +} + +export interface StudentQueryParams { + page?: number; + pageSize?: number; + classId?: number; + keyword?: string; +} + +export interface Student { + id: number; + name: string; + gender?: string; + birthDate?: string; + classId: number; + className?: string; + parentName?: string; + parentPhone?: string; + lessonCount?: number; + readingCount?: number; + avgScore?: number; + createdAt: string; +} + +export interface CreateStudentDto { + name: string; + gender?: string; + birthDate?: string; + classId: number; + parentName?: string; + parentPhone?: string; +} + +export interface ClassInfo { + id: number; + name: string; + grade: string; + teacherId?: number; + teacherName?: string; + studentCount: number; + lessonCount: number; + createdAt?: string; + teachers?: ClassTeacher[]; // 新增:教师团队 +} + +export interface ClassTeacher { + id: number; + teacherId: number; + teacherName: string; + teacherPhone?: string; + teacherEmail?: string; + role: 'MAIN' | 'ASSIST' | 'CARE'; + isPrimary: boolean; + createdAt?: string; +} + +export interface AddClassTeacherDto { + teacherId: number; + role: 'MAIN' | 'ASSIST' | 'CARE'; + isPrimary?: boolean; +} + +export interface UpdateClassTeacherDto { + role?: 'MAIN' | 'ASSIST' | 'CARE'; + isPrimary?: boolean; +} + +export interface TransferStudentDto { + toClassId: number; + reason?: string; +} + +export interface StudentClassHistory { + id: number; + fromClass: { id: number; name: string; grade: string } | null; + toClass: { id: number; name: string; grade: string }; + reason?: string; + operatedBy?: number; + createdAt: string; +} + +export interface CreateClassDto { + name: string; + grade: string; + teacherId?: number; +} + +export interface SchoolStats { + teacherCount: number; + studentCount: number; + classCount: number; + lessonCount: number; +} + +export interface PackageInfo { + packageType: string; + teacherQuota: number; + studentQuota: number; + storageQuota: number; + teacherCount: number; + studentCount: number; + storageUsed: number; + startDate: string; + expireDate: string; + status: string; +} + +export interface PackageUsage { + teacher: { + used: number; + quota: number; + percentage: number; + }; + student: { + used: number; + quota: number; + percentage: number; + }; + storage: { + used: number; + quota: number; + percentage: number; + }; +} + +// ==================== 教师管理 ==================== + +export const getTeachers = (params: TeacherQueryParams) => + http.get<{ items: Teacher[]; total: number; page: number; pageSize: number }>('/school/teachers', { params }); + +export const getTeacher = (id: number) => + http.get(`/school/teachers/${id}`); + +export const createTeacher = (data: CreateTeacherDto) => + http.post('/school/teachers', data); + +export const updateTeacher = (id: number, data: Partial) => + http.put(`/school/teachers/${id}`, data); + +export const deleteTeacher = (id: number) => + http.delete(`/school/teachers/${id}`); + +export const resetTeacherPassword = (id: number) => + http.post<{ tempPassword: string }>(`/school/teachers/${id}/reset-password`); + +// ==================== 学生管理 ==================== + +export const getStudents = (params: StudentQueryParams) => + http.get<{ items: Student[]; total: number; page: number; pageSize: number }>('/school/students', { params }); + +export const getStudent = (id: number) => + http.get(`/school/students/${id}`); + +export const createStudent = (data: CreateStudentDto) => + http.post('/school/students', data); + +export const updateStudent = (id: number, data: Partial) => + http.put(`/school/students/${id}`, data); + +export const deleteStudent = (id: number) => + http.delete(`/school/students/${id}`); + +// ==================== 学生批量导入 ==================== + +export interface ImportResult { + success: number; + failed: number; + errors: Array<{ row: number; message: string }>; +} + +export interface ImportTemplate { + headers: string[]; + example: string[]; + notes: string[]; +} + +export const getStudentImportTemplate = () => + http.get('/school/students/import/template'); + +export const importStudents = (file: File, defaultClassId?: number): Promise => { + const formData = new FormData(); + formData.append('file', file); + + const params = defaultClassId ? { defaultClassId } : {}; + return http.post('/school/students/import', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + params, + }); +}; + +// ==================== 班级管理 ==================== + +export const getClasses = () => + http.get('/school/classes'); + +export const getClass = (id: number) => + http.get(`/school/classes/${id}`); + +export const createClass = (data: CreateClassDto) => + http.post('/school/classes', data); + +export const updateClass = (id: number, data: Partial) => + http.put(`/school/classes/${id}`, data); + +export const deleteClass = (id: number) => + http.delete(`/school/classes/${id}`); + +export const getClassStudents = (classId: number, params?: { page?: number; pageSize?: number; keyword?: string }) => + http.get<{ items: Student[]; total: number; page: number; pageSize: number; class?: ClassInfo }>(`/school/classes/${classId}/students`, { params }); + +// ==================== 统计数据 ==================== + +export const getSchoolStats = () => + http.get('/school/stats'); + +export const getActiveTeachers = (limit?: number) => + http.get>('/school/stats/teachers', { params: { limit } }); + +export const getCourseUsageStats = () => + http.get>('/school/stats/courses'); + +export const getRecentActivities = (limit?: number) => + http.get>('/school/stats/activities', { params: { limit } }); + +// ==================== 套餐信息 ==================== + +export const getPackageInfo = () => + http.get('/school/package'); + +export const getPackageUsage = () => + http.get('/school/package/usage'); + +// ==================== 系统设置 ==================== + +export interface SystemSettings { + id: number; + tenantId: number; + schoolName?: string; + schoolLogo?: string; + address?: string; + notifyOnLesson: boolean; + notifyOnTask: boolean; + notifyOnGrowth: boolean; + createdAt: string; + updatedAt: string; +} + +export interface UpdateSettingsDto { + schoolName?: string; + schoolLogo?: string; + address?: string; + notifyOnLesson?: boolean; + notifyOnTask?: boolean; + notifyOnGrowth?: boolean; +} + +export const getSettings = () => + http.get('/school/settings'); + +export const updateSettings = (data: UpdateSettingsDto) => + http.put('/school/settings', data); + +// ==================== 课程管理 ==================== + +export const getSchoolCourses = () => + http.get('/school/courses'); + +export const getSchoolCourse = (id: number) => + http.get(`/school/courses/${id}`); + +// ==================== 班级教师管理 ==================== + +export const getClassTeachers = (classId: number) => + http.get(`/school/classes/${classId}/teachers`); + +export const addClassTeacher = (classId: number, data: AddClassTeacherDto) => + http.post(`/school/classes/${classId}/teachers`, data); + +export const updateClassTeacher = (classId: number, teacherId: number, data: UpdateClassTeacherDto) => + http.put(`/school/classes/${classId}/teachers/${teacherId}`, data); + +export const removeClassTeacher = (classId: number, teacherId: number) => + http.delete<{ message: string }>(`/school/classes/${classId}/teachers/${teacherId}`); + +// ==================== 学生调班 ==================== + +export const transferStudent = (studentId: number, data: TransferStudentDto) => + http.post<{ message: string }>(`/school/students/${studentId}/transfer`, data); + +export const getStudentClassHistory = (studentId: number) => + http.get(`/school/students/${studentId}/history`); + +// ==================== 排课管理 ==================== + +export interface SchedulePlan { + id: number; + tenantId: number; + classId: number; + className: string; + courseId: number; + courseName: string; + teacherId?: number; + teacherName?: string; + scheduledDate?: string; + scheduledTime?: string; + weekDay?: number; + repeatType: 'NONE' | 'DAILY' | 'WEEKLY'; + repeatEndDate?: string; + source: 'SCHOOL' | 'TEACHER'; + status: 'ACTIVE' | 'CANCELLED'; + note?: string; + createdBy: number; + createdAt: string; + updatedAt: string; +} + +export interface CreateScheduleDto { + classId: number; + courseId: number; + teacherId?: number; + scheduledDate?: string; + scheduledTime?: string; + weekDay?: number; + repeatType: 'NONE' | 'DAILY' | 'WEEKLY'; + repeatEndDate?: string; + note?: string; +} + +export interface UpdateScheduleDto { + teacherId?: number; + scheduledDate?: string; + scheduledTime?: string; + weekDay?: number; + repeatType?: 'NONE' | 'DAILY' | 'WEEKLY'; + repeatEndDate?: string; + note?: string; + status?: string; +} + +export interface ScheduleQueryParams { + classId?: number; + teacherId?: number; + courseId?: number; + startDate?: string; + endDate?: string; + status?: string; + source?: string; + page?: number; + pageSize?: number; +} + +export interface TimetableItem { + date: string; + weekDay: number; + schedules: SchedulePlan[]; +} + +export interface TimetableQueryParams { + startDate: string; + endDate: string; + classId?: number; + teacherId?: number; +} + +export const getSchedules = (params?: ScheduleQueryParams) => + http.get<{ items: SchedulePlan[]; total: number; page: number; pageSize: number }>('/school/schedules', { params }); + +export const getSchedule = (id: number) => + http.get(`/school/schedules/${id}`); + +export const createSchedule = (data: CreateScheduleDto) => + http.post('/school/schedules', data); + +export const updateSchedule = (id: number, data: UpdateScheduleDto) => + http.put(`/school/schedules/${id}`, data); + +export const cancelSchedule = (id: number) => + http.delete<{ message: string }>(`/school/schedules/${id}`); + +export const getTimetable = (params: TimetableQueryParams) => + http.get('/school/schedules/timetable', { params }); + +export interface BatchScheduleItem { + classId: number; + courseId: number; + teacherId?: number; + scheduledDate: string; + scheduledTime?: string; + note?: string; +} + +export interface BatchCreateResult { + success: number; + failed: number; + results: SchedulePlan[]; + errors: Array<{ index: number; message: string }>; +} + +export const batchCreateSchedules = (schedules: BatchScheduleItem[]) => + http.post('/school/schedules/batch', { schedules }); + +// ==================== 趋势与分布统计 ==================== + +export interface LessonTrendItem { + month: string; + lessonCount: number; + studentCount: number; +} + +export interface CourseDistributionItem { + name: string; + value: number; +} + +export const getLessonTrend = (months?: number) => + http.get('/school/stats/lesson-trend', { params: { months } }); + +export const getCourseDistribution = () => + http.get('/school/stats/course-distribution'); + +// ==================== 数据导出 ==================== + +export const exportLessons = (startDate?: string, endDate?: string) => { + const params = new URLSearchParams(); + if (startDate) params.append('startDate', startDate); + if (endDate) params.append('endDate', endDate); + + const token = localStorage.getItem('token'); + const url = `/api/v1/school/export/lessons?${params.toString()}`; + + return fetch(url, { + headers: { + Authorization: `Bearer ${token}`, + }, + }).then((res) => { + if (!res.ok) throw new Error('导出失败'); + return res.blob(); + }).then((blob) => { + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `授课记录_${startDate || ''}_${endDate || ''}.xlsx`; + a.click(); + window.URL.revokeObjectURL(url); + }); +}; + +export const exportTeacherStats = (startDate?: string, endDate?: string) => { + const params = new URLSearchParams(); + if (startDate) params.append('startDate', startDate); + if (endDate) params.append('endDate', endDate); + + const token = localStorage.getItem('token'); + const url = `/api/v1/school/export/teacher-stats?${params.toString()}`; + + return fetch(url, { + headers: { + Authorization: `Bearer ${token}`, + }, + }).then((res) => { + if (!res.ok) throw new Error('导出失败'); + return res.blob(); + }).then((blob) => { + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `教师绩效统计_${startDate || ''}_${endDate || ''}.xlsx`; + a.click(); + window.URL.revokeObjectURL(url); + }); +}; + +export const exportStudentStats = (classId?: number) => { + const params = new URLSearchParams(); + if (classId) params.append('classId', String(classId)); + + const token = localStorage.getItem('token'); + const url = `/api/v1/school/export/student-stats?${params.toString()}`; + + return fetch(url, { + headers: { + Authorization: `Bearer ${token}`, + }, + }).then((res) => { + if (!res.ok) throw new Error('导出失败'); + return res.blob(); + }).then((blob) => { + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `学生统计.xlsx`; + a.click(); + window.URL.revokeObjectURL(url); + }); +}; + +// ==================== 排课模板 ==================== + +export interface ScheduleTemplate { + id: number; + tenantId: number; + name: string; + courseId: number; + courseName?: string; + classId?: number; + className?: string; + teacherId?: number; + teacherName?: string; + scheduledTime?: string; + weekDay?: number; + duration: number; + isDefault: boolean; + createdAt: string; + updatedAt: string; +} + +export interface CreateScheduleTemplateDto { + name: string; + courseId: number; + classId?: number; + teacherId?: number; + scheduledTime?: string; + weekDay?: number; + duration?: number; + isDefault?: boolean; +} + +export interface UpdateScheduleTemplateDto { + name?: string; + classId?: number; + teacherId?: number; + scheduledTime?: string; + weekDay?: number; + duration?: number; + isDefault?: boolean; +} + +export interface ApplyTemplateDto { + scheduledDate: string; + classId?: number; + teacherId?: number; +} + +export const getScheduleTemplates = (params?: { classId?: number; courseId?: number }) => + http.get('/school/schedule-templates', { params }); + +export const getScheduleTemplate = (id: number) => + http.get(`/school/schedule-templates/${id}`); + +export const createScheduleTemplate = (data: CreateScheduleTemplateDto) => + http.post('/school/schedule-templates', data); + +export const updateScheduleTemplate = (id: number, data: UpdateScheduleTemplateDto) => + http.put(`/school/schedule-templates/${id}`, data); + +export const deleteScheduleTemplate = (id: number) => + http.delete<{ message: string }>(`/school/schedule-templates/${id}`); + +export const applyScheduleTemplate = (id: number, data: ApplyTemplateDto) => + http.post(`/school/schedule-templates/${id}/apply`, data); + +// ==================== 操作日志 ==================== + +export interface OperationLog { + id: number; + tenantId: number; + userId: number; + userType: string; + action: string; + module: string; + description: string; + targetId: number | null; + oldValue: string | null; + newValue: string | null; + ipAddress: string | null; + createdAt: string; +} + +export interface OperationLogStats { + total: number; + modules: { name: string; count: number }[]; + actions: { name: string; count: number }[]; +} + +export const getOperationLogs = (params?: { + page?: number; + pageSize?: number; + module?: string; + action?: string; + startDate?: string; + endDate?: string; +}) => http.get<{ items: OperationLog[]; total: number; page: number; pageSize: number }>( + '/school/operation-logs', + { params } +); + +export const getOperationLogStats = (startDate?: string, endDate?: string) => + http.get('/school/operation-logs/stats', { + params: { startDate, endDate }, + }); + +export const getOperationLogById = (id: number) => + http.get(`/school/operation-logs/${id}`); + +// ==================== 任务模板 API ==================== + +export interface TaskTemplate { + id: number; + tenantId: number; + name: string; + description?: string; + taskType: 'READING' | 'ACTIVITY' | 'HOMEWORK'; + relatedCourseId?: number; + defaultDuration: number; + isDefault: boolean; + status: string; + createdBy: number; + createdAt: string; + updatedAt: string; + course?: { + id: number; + name: string; + pictureBookName?: string; + }; +} + +export interface CreateTaskTemplateDto { + name: string; + description?: string; + taskType: 'READING' | 'ACTIVITY' | 'HOMEWORK'; + relatedCourseId?: number; + defaultDuration?: number; + isDefault?: boolean; +} + +export interface UpdateTaskTemplateDto { + name?: string; + description?: string; + relatedCourseId?: number; + defaultDuration?: number; + isDefault?: boolean; + status?: string; +} + +export const getTaskTemplates = (params?: { + page?: number; + pageSize?: number; + taskType?: string; + keyword?: string; +}) => http.get<{ items: TaskTemplate[]; total: number; page: number; pageSize: number }>('/school/task-templates', { params }); + +export const getTaskTemplate = (id: number) => + http.get(`/school/task-templates/${id}`); + +export const getDefaultTaskTemplate = (taskType: string) => + http.get(`/school/task-templates/default/${taskType}`); + +export const createTaskTemplate = (data: CreateTaskTemplateDto) => + http.post('/school/task-templates', data); + +export const updateTaskTemplate = (id: number, data: UpdateTaskTemplateDto) => + http.put(`/school/task-templates/${id}`, data); + +export const deleteTaskTemplate = (id: number) => + http.delete<{ message: string }>(`/school/task-templates/${id}`); + +// ==================== 任务统计 API ==================== + +export interface TaskStats { + totalTasks: number; + publishedTasks: number; + completedTasks: number; + inProgressTasks: number; + pendingCount: number; + totalCompletions: number; + completionRate: number; +} + +export interface TaskStatsByType { + [key: string]: { + total: number; + completed: number; + rate: number; + }; +} + +export interface TaskStatsByClass { + classId: number; + className: string; + grade: string; + total: number; + completed: number; + rate: number; +} + +export interface MonthlyTaskStats { + month: string; + tasks: number; + completions: number; + completed: number; + rate: number; +} + +export const getTaskStats = () => + http.get('/school/tasks/stats'); + +export const getTaskStatsByType = () => + http.get('/school/tasks/stats/by-type'); + +export const getTaskStatsByClass = () => + http.get('/school/tasks/stats/by-class'); + +export const getMonthlyTaskStats = (months?: number) => + http.get('/school/tasks/stats/monthly', { params: { months } }); + +// ==================== 任务管理 API ==================== + +export interface SchoolTask { + id: number; + tenantId: number; + title: string; + description?: string; + taskType: 'READING' | 'ACTIVITY' | 'HOMEWORK'; + targetType: 'CLASS' | 'STUDENT'; + relatedCourseId?: number; + course?: { + id: number; + name: string; + }; + startDate: string; + endDate: string; + status: 'DRAFT' | 'PUBLISHED' | 'ARCHIVED'; + createdBy: number; + targetCount?: number; + completionCount?: number; + createdAt: string; + updatedAt: string; +} + +export interface TaskCompletion { + id: number; + taskId: number; + studentId: number; + studentName: string; + className: string; + status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED'; + completedAt?: string; + parentFeedback?: string; + rating?: number; +} + +export interface CreateSchoolTaskDto { + title: string; + description?: string; + taskType: 'READING' | 'ACTIVITY' | 'HOMEWORK'; + targetType: 'CLASS' | 'STUDENT'; + targetIds: number[]; + relatedCourseId?: number; + startDate: string; + endDate: string; +} + +export interface UpdateSchoolTaskDto { + title?: string; + description?: string; + taskType?: 'READING' | 'ACTIVITY' | 'HOMEWORK'; + relatedCourseId?: number; + startDate?: string; + endDate?: string; + status?: 'DRAFT' | 'PUBLISHED' | 'ARCHIVED'; +} + +export const getSchoolTasks = (params?: { + page?: number; + pageSize?: number; + status?: string; + taskType?: string; + keyword?: string; +}) => http.get<{ items: SchoolTask[]; total: number; page: number; pageSize: number }>('/school/tasks', { params }); + +export const getSchoolTask = (id: number) => + http.get(`/school/tasks/${id}`); + +export const createSchoolTask = (data: CreateSchoolTaskDto) => + http.post('/school/tasks', data); + +export const updateSchoolTask = (id: number, data: UpdateSchoolTaskDto) => + http.put(`/school/tasks/${id}`, data); + +export const deleteSchoolTask = (id: number) => + http.delete<{ message: string }>(`/school/tasks/${id}`); + +export const getSchoolTaskCompletions = (taskId: number) => + http.get(`/school/tasks/${taskId}/completions`); + +export const getSchoolClasses = () => + http.get('/school/classes'); + +// ==================== 数据报告 API ==================== + +export interface ReportOverview { + totalLessons: number; + activeTeacherCount: number; + usedCourseCount: number; + avgRating: number; +} + +export interface TeacherReport { + id: number; + name: string; + lessonCount: number; + courseCount: number; + feedbackCount: number; + avgRating: number; +} + +export interface CourseReport { + id: number; + name: string; + lessonCount: number; + teacherCount: number; + studentCount: number; + avgRating: number; +} + +export interface StudentReport { + id: number; + name: string; + className: string; + lessonCount: number; + avgFocus: number; + avgParticipation: number; +} + +export const getReportOverview = () => + http.get('/school/reports/overview'); + +export const getTeacherReports = () => + http.get('/school/reports/teachers'); + +export const getCourseReports = () => + http.get('/school/reports/courses'); + +export const getStudentReports = () => + http.get('/school/reports/students'); + +// ==================== 家长管理 ==================== + +export interface ParentQueryParams { + page?: number; + pageSize?: number; + keyword?: string; + status?: string; +} + +export interface ParentChild { + id: number; + name: string; + relationship: string; + class?: { + id: number; + name: string; + }; +} + +export interface Parent { + id: number; + name: string; + phone: string; + email?: string; + loginAccount: string; + status: string; + tenantId: number; + childrenCount: number; + children?: ParentChild[]; + createdAt: string; +} + +export interface CreateParentDto { + name: string; + phone: string; + email?: string; + loginAccount: string; + password?: string; +} + +export interface UpdateParentDto { + name?: string; + phone?: string; + email?: string; +} + +export interface AddChildDto { + studentId: number; + relationship: string; +} + +export const getParents = (params?: ParentQueryParams) => + http.get<{ items: Parent[]; total: number; page: number; pageSize: number }>('/school/parents', { params }); + +export const getParent = (id: number) => + http.get(`/school/parents/${id}`); + +export const createParent = (data: CreateParentDto) => + http.post('/school/parents', data); + +export const updateParent = (id: number, data: UpdateParentDto) => + http.put(`/school/parents/${id}`, data); + +export const deleteParent = (id: number) => + http.delete<{ message: string }>(`/school/parents/${id}`); + +export const resetParentPassword = (id: number) => + http.post<{ tempPassword: string }>(`/school/parents/${id}/reset-password`); + +export const getParentChildren = async (parentId: number): Promise => { + const parent = await http.get(`/school/parents/${parentId}`); + return parent.children || []; +}; + +export const addChildToParent = (parentId: number, data: AddChildDto) => + http.post(`/school/parents/${parentId}/children/${data.studentId}`, { relationship: data.relationship }); + +export const removeChildFromParent = (parentId: number, studentId: number) => + http.delete<{ message: string }>(`/school/parents/${parentId}/children/${studentId}`); diff --git a/reading-platform-frontend/src/api/task.ts b/reading-platform-frontend/src/api/task.ts new file mode 100644 index 0000000..78b8c2b --- /dev/null +++ b/reading-platform-frontend/src/api/task.ts @@ -0,0 +1,175 @@ +import { http } from './index'; + +// ==================== 类型定义 ==================== + +export type TaskType = 'READING' | 'ACTIVITY' | 'HOMEWORK'; +export type TargetType = 'CLASS' | 'STUDENT'; +export type TaskStatus = 'DRAFT' | 'PUBLISHED' | 'ARCHIVED'; +export type CompletionStatus = 'PENDING' | 'IN_PROGRESS' | 'COMPLETED'; + +export interface Task { + id: number; + tenantId: number; + title: string; + description?: string; + taskType: TaskType; + targetType: TargetType; + relatedCourseId?: number; + createdBy: number; + startDate: string; + endDate: string; + status: TaskStatus; + createdAt: string; + updatedAt: string; + course?: { + id: number; + name: string; + }; + targetCount?: number; + completionCount?: number; +} + +export interface TaskCompletion { + id: number; + taskId: number; + studentId: number; + status: CompletionStatus; + completedAt?: string; + feedback?: string; + parentFeedback?: string; + createdAt: string; + student?: { + id: number; + name: string; + gender?: string; + class?: { + id: number; + name: string; + }; + }; +} + +export interface CreateTaskDto { + title: string; + description?: string; + taskType: TaskType; + targetType: TargetType; + relatedCourseId?: number; + startDate: string; + endDate: string; + targetIds: number[]; +} + +export interface UpdateTaskDto { + title?: string; + description?: string; + startDate?: string; + endDate?: string; + status?: TaskStatus; + targetIds?: number[]; +} + +export interface UpdateCompletionDto { + status: CompletionStatus; + feedback?: string; + parentFeedback?: string; +} + +export interface TaskStats { + totalTasks: number; + publishedTasks: number; + completedTasks: number; + inProgressTasks: number; +} + +// ==================== 学校端 API ==================== + +export const getTasks = (params?: { + page?: number; + pageSize?: number; + status?: TaskStatus; + taskType?: TaskType; + keyword?: string; +}) => + http.get<{ items: Task[]; total: number; page: number; pageSize: number }>( + '/school/tasks', + { params } + ); + +export const getTask = (id: number) => + http.get(`/school/tasks/${id}`); + +export const getTaskCompletions = (taskId: number, params?: { + page?: number; + pageSize?: number; + status?: CompletionStatus; +}) => + http.get<{ items: TaskCompletion[]; total: number; page: number; pageSize: number }>( + `/school/tasks/${taskId}/completions`, + { params } + ); + +export const createTask = (data: CreateTaskDto) => + http.post('/school/tasks', data); + +export const updateTask = (id: number, data: UpdateTaskDto) => + http.put(`/school/tasks/${id}`, data); + +export const deleteTask = (id: number) => + http.delete(`/school/tasks/${id}`); + +export const updateTaskCompletion = ( + taskId: number, + studentId: number, + data: UpdateCompletionDto +) => + http.put(`/school/tasks/${taskId}/completions/${studentId}`, data); + +export const getTaskStats = () => + http.get('/school/tasks/stats'); + +// ==================== 教师端 API ==================== + +export const getTeacherTasks = (params?: { + page?: number; + pageSize?: number; + status?: TaskStatus; + taskType?: TaskType; + keyword?: string; +}) => + http.get<{ items: Task[]; total: number; page: number; pageSize: number }>( + '/teacher/tasks', + { params } + ); + +export const getTeacherTask = (id: number) => + http.get(`/teacher/tasks/${id}`); + +export const getTeacherTaskCompletions = (taskId: number, params?: { + page?: number; + pageSize?: number; + status?: CompletionStatus; +}) => + http.get<{ items: TaskCompletion[]; total: number; page: number; pageSize: number }>( + `/teacher/tasks/${taskId}/completions`, + { params } + ); + +export const createTeacherTask = (data: CreateTaskDto) => + http.post('/teacher/tasks', data); + +export const updateTeacherTask = (id: number, data: UpdateTaskDto) => + http.put(`/teacher/tasks/${id}`, data); + +export const deleteTeacherTask = (id: number) => + http.delete(`/teacher/tasks/${id}`); + +export const updateTeacherTaskCompletion = ( + taskId: number, + studentId: number, + data: UpdateCompletionDto +) => + http.put(`/teacher/tasks/${taskId}/completions/${studentId}`, data); + +export const getTeacherTaskStats = () => + http.get('/teacher/tasks/stats'); diff --git a/reading-platform-frontend/src/api/teacher.ts b/reading-platform-frontend/src/api/teacher.ts new file mode 100644 index 0000000..1d6edeb --- /dev/null +++ b/reading-platform-frontend/src/api/teacher.ts @@ -0,0 +1,660 @@ +import { http } from './index'; + +// ==================== 教师课程 API ==================== + +export interface TeacherCourseQueryParams { + page?: number; + pageSize?: number; + grade?: string; + keyword?: string; +} + +export interface TeacherCourse { + id: number; + name: string; + pictureBookName?: string; + coverImagePath?: string; + gradeTags: string[]; + domainTags: string[]; + duration: number; + avgRating: number; + usageCount: number; + publishedAt: string; +} + +// 教师班级信息(更新:新增角色字段) +export interface TeacherClass { + id: number; + name: string; + grade: string; + studentCount: number; + lessonCount: number; + myRole: 'MAIN' | 'ASSIST' | 'CARE'; // 我在该班级的角色 + isPrimary: boolean; // 是否班主任 +} + +// 班级教师信息 +export interface TeacherClassTeacher { + teacherId: number; + teacherName: string; + teacherPhone?: string; + role: 'MAIN' | 'ASSIST' | 'CARE'; + isPrimary: boolean; +} + +// 获取教师可用的课程列表 +export function getTeacherCourses(params: TeacherCourseQueryParams): Promise<{ + items: TeacherCourse[]; + total: number; + page: number; + pageSize: number; +}> { + return http.get('/teacher/courses', { params }); +} + +// 获取课程详情 +export function getTeacherCourse(id: number): Promise { + return http.get(`/teacher/courses/${id}`); +} + +// 获取教师的班级列表 +export function getTeacherClasses(): Promise { + return http.get('/teacher/courses/classes'); +} + +// 获取教师所有学生列表(跨班级) +export function getTeacherStudents(params?: { page?: number; pageSize?: number; keyword?: string }): Promise<{ + items: Array<{ + id: number; + name: string; + gender?: string; + birthDate?: string; + classId: number; + class?: { + id: number; + name: string; + grade: string; + }; + parentName?: string; + parentPhone?: string; + createdAt: string; + }>; + total: number; + page: number; + pageSize: number; +}> { + return http.get('/teacher/students', { params }); +} + +// 获取班级学生列表 +export function getTeacherClassStudents(classId: number, params?: { page?: number; pageSize?: number; keyword?: string }): Promise<{ + items: Array<{ + id: number; + name: string; + gender?: string; + birthDate?: string; + parentName?: string; + parentPhone?: string; + lessonCount?: number; + readingCount?: number; + createdAt: string; + }>; + total: number; + page: number; + pageSize: number; + class?: { + id: number; + name: string; + grade: string; + studentCount: number; + lessonCount: number; + }; +}> { + return http.get(`/teacher/classes/${classId}/students`, { params }); +} + +// 获取班级教师列表 +export function getClassTeachers(classId: number): Promise { + return http.get(`/teacher/classes/${classId}/teachers`); +} + +// ==================== 授课记录 API ==================== + +export interface CreateLessonDto { + courseId: number; + classId: number; + plannedDatetime?: string; +} + +export interface FinishLessonDto { + overallRating?: string; + participationRating?: string; + completionNote?: string; + actualDuration?: number; +} + +export interface StudentRecordDto { + focus?: number; + participation?: number; + interest?: number; + understanding?: number; + notes?: string; +} + +// 获取授课记录列表 +export function getLessons(params?: { + page?: number; + pageSize?: number; + status?: string; + courseId?: number; +}): Promise<{ + items: any[]; + total: number; + page: number; + pageSize: number; +}> { + return http.get('/teacher/lessons', { params }); +} + +// 获取单个授课记录详情 +export function getLesson(id: number): Promise { + return http.get(`/teacher/lessons/${id}`); +} + +// 创建授课记录(备课) +export function createLesson(data: CreateLessonDto): Promise { + return http.post('/teacher/lessons', data); +} + +// 开始上课 +export function startLesson(id: number): Promise { + return http.post(`/teacher/lessons/${id}/start`); +} + +// 结束上课 +export function finishLesson(id: number, data: FinishLessonDto): Promise { + return http.post(`/teacher/lessons/${id}/finish`, data); +} + +// 取消课程 +export function cancelLesson(id: number): Promise { + return http.post(`/teacher/lessons/${id}/cancel`); +} + +// 保存学生评价记录 +export function saveStudentRecord( + lessonId: number, + studentId: number, + data: StudentRecordDto +): Promise { + return http.post(`/teacher/lessons/${lessonId}/students/${studentId}/record`, data); +} + +// 获取课程所有学生记录 +export interface StudentWithRecord { + id: number; + name: string; + gender?: string; + record: { + id: number; + focus?: number; + participation?: number; + interest?: number; + understanding?: number; + notes?: string; + } | null; +} + +export interface StudentRecordsResponse { + lesson: { + id: number; + status: string; + className: string; + }; + students: StudentWithRecord[]; +} + +export function getStudentRecords(lessonId: number): Promise { + return http.get(`/teacher/lessons/${lessonId}/student-records`); +} + +// 批量保存学生评价记录 +export function batchSaveStudentRecords( + lessonId: number, + records: Array<{ studentId: number } & StudentRecordDto> +): Promise<{ count: number; records: any[] }> { + return http.post(`/teacher/lessons/${lessonId}/student-records/batch`, { records }); +} + +// ==================== 教师首页 API ==================== + +export interface DashboardData { + stats: { + classCount: number; + studentCount: number; + lessonCount: number; + courseCount: number; + }; + todayLessons: Array<{ + id: number; + courseId: number; + courseName: string; + pictureBookName?: string; + classId: number; + className: string; + plannedDatetime: string; + status: string; + duration: number; + }>; + recommendedCourses: Array<{ + id: number; + name: string; + pictureBookName?: string; + coverImagePath?: string; + duration: number; + usageCount: number; + avgRating: number; + gradeTags: string[]; + }>; + weeklyStats: { + lessonCount: number; + studentParticipation: number; + avgRating: number; + totalDuration: number; + }; + recentActivities: Array<{ + id: number; + type: string; + description: string; + time: string; + }>; +} + +export const getTeacherDashboard = () => + http.get('/teacher/dashboard'); + +export const getTodayLessons = () => + http.get('/teacher/dashboard/today'); + +export const getRecommendedCourses = () => + http.get('/teacher/dashboard/recommend'); + +export const getWeeklyStats = () => + http.get('/teacher/dashboard/weekly'); + +// ==================== 教师统计趋势 ==================== + +export interface TeacherLessonTrendItem { + month: string; + lessonCount: number; + avgRating: number; +} + +export interface TeacherCourseUsageItem { + name: string; + value: number; +} + +export const getTeacherLessonTrend = (months?: number) => + http.get('/teacher/dashboard/lesson-trend', { params: { months } }); + +export const getTeacherCourseUsage = () => + http.get('/teacher/dashboard/course-usage'); + +// ==================== 课程反馈 API ==================== + +export interface FeedbackDto { + designQuality?: number; + participation?: number; + goalAchievement?: number; + stepFeedbacks?: any; + pros?: string; + suggestions?: string; + activitiesDone?: any; +} + +export interface LessonFeedback { + id: number; + lessonId: number; + teacherId: number; + designQuality?: number; + participation?: number; + goalAchievement?: number; + stepFeedbacks?: any; + pros?: string; + suggestions?: string; + activitiesDone?: any; + createdAt: string; + updatedAt: string; + teacher?: { + id: number; + name: string; + }; + lesson?: { + id: number; + startDatetime?: string; + course: { + id: number; + name: string; + pictureBookName?: string; + }; + class: { + id: number; + name: string; + }; + }; +} + +// 提交课程反馈 +export function submitFeedback(lessonId: number, data: FeedbackDto): Promise { + return http.post(`/teacher/lessons/${lessonId}/feedback`, data); +} + +// 获取课程反馈 +export function getFeedback(lessonId: number): Promise { + return http.get(`/teacher/lessons/${lessonId}/feedback`); +} + +// ==================== 学校端反馈 API ==================== + +export interface FeedbackQueryParams { + page?: number; + pageSize?: number; + teacherId?: number; + courseId?: number; +} + +export interface FeedbackStats { + totalFeedbacks: number; + avgDesignQuality: number; + avgParticipation: number; + avgGoalAchievement: number; + courseStats: Record; +} + +// 获取学校端反馈列表 +export function getSchoolFeedbacks(params: FeedbackQueryParams): Promise<{ + items: LessonFeedback[]; + total: number; + page: number; + pageSize: number; +}> { + return http.get('/school/feedbacks', { params }); +} + +// 获取反馈统计 +export function getFeedbackStats(): Promise { + return http.get('/school/feedbacks/stats'); +} + +// 获取教师自己的反馈列表 +export function getTeacherFeedbacks(params: FeedbackQueryParams): Promise<{ + items: LessonFeedback[]; + total: number; + page: number; + pageSize: number; +}> { + return http.get('/teacher/feedbacks', { params }); +} + +// 获取教师自己的反馈统计 +export function getTeacherFeedbackStats(): Promise { + return http.get('/teacher/feedbacks/stats'); +} + +// ==================== 排课管理 API ==================== + +export interface TeacherSchedule { + id: number; + classId: number; + className: string; + courseId: number; + courseName: string; + teacherId?: number; + scheduledDate?: string; + scheduledTime?: string; + weekDay?: number; + repeatType: 'NONE' | 'DAILY' | 'WEEKLY'; + source: 'SCHOOL' | 'TEACHER'; + status: 'ACTIVE' | 'CANCELLED'; + note?: string; + hasLesson: boolean; + lessonId?: number; + lessonStatus?: string; + createdAt: string; +} + +export interface CreateTeacherScheduleDto { + classId: number; + courseId: number; + scheduledDate?: string; + scheduledTime?: string; + weekDay?: number; + repeatType?: 'NONE' | 'DAILY' | 'WEEKLY'; + repeatEndDate?: string; + note?: string; +} + +export interface TeacherTimetableItem { + date: string; + weekDay: number; + schedules: TeacherSchedule[]; +} + +export const getTeacherSchedules = (params?: { + startDate?: string; + endDate?: string; + status?: string; + page?: number; + pageSize?: number; +}) => http.get<{ items: TeacherSchedule[]; total: number; page: number; pageSize: number }>('/teacher/schedules', { params }); + +export const getTeacherTimetable = (params: { startDate: string; endDate: string }) => + http.get('/teacher/schedules/timetable', { params }); + +export const getTodayTeacherSchedules = () => + http.get('/teacher/schedules/today'); + +export const createTeacherSchedule = (data: CreateTeacherScheduleDto) => + http.post('/teacher/schedules', data); + +export const updateTeacherSchedule = (id: number, data: Partial & { status?: string }) => + http.put(`/teacher/schedules/${id}`, data); + +export const cancelTeacherSchedule = (id: number) => + http.delete<{ message: string }>(`/teacher/schedules/${id}`); + +// ==================== 阅读任务 API ==================== + +export interface TeacherTask { + id: number; + tenantId: number; + title: string; + description?: string; + taskType: 'READING' | 'ACTIVITY' | 'HOMEWORK'; + targetType: 'CLASS' | 'STUDENT'; + status: 'DRAFT' | 'PUBLISHED' | 'ARCHIVED'; + relatedCourseId?: number; + startDate: string; + endDate: string; + createdBy: number; + createdAt: string; + updatedAt: string; + course?: { + id: number; + name: string; + }; + targetCount?: number; + completionCount?: number; +} + +export interface TaskCompletion { + id: number; + taskId: number; + studentId: number; + status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED'; + completedAt?: string; + feedback?: string; + parentFeedback?: string; + createdAt: string; + student: { + id: number; + name: string; + gender?: string; + class?: { + id: number; + name: string; + }; + }; +} + +export interface CreateTeacherTaskDto { + title: string; + description?: string; + taskType: 'READING' | 'ACTIVITY' | 'HOMEWORK'; + targetType: 'CLASS' | 'STUDENT'; + targetIds: number[]; + relatedCourseId?: number; + startDate: string; + endDate: string; +} + +export interface UpdateTaskCompletionDto { + status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED'; + feedback?: string; +} + +export const getTeacherTasks = (params?: { + page?: number; + pageSize?: number; + status?: string; + taskType?: string; + keyword?: string; +}) => http.get<{ items: TeacherTask[]; total: number; page: number; pageSize: number }>('/teacher/tasks', { params }); + +export const getTeacherTask = (id: number) => + http.get(`/teacher/tasks/${id}`); + +export const getTeacherTaskCompletions = (taskId: number, params?: { + page?: number; + pageSize?: number; + status?: string; +}) => http.get<{ items: TaskCompletion[]; total: number; page: number; pageSize: number }>(`/teacher/tasks/${taskId}/completions`, { params }); + +export const createTeacherTask = (data: CreateTeacherTaskDto) => + http.post('/teacher/tasks', data); + +export const updateTeacherTask = (id: number, data: Partial & { status?: string }) => + http.put(`/teacher/tasks/${id}`, data); + +export const deleteTeacherTask = (id: number) => + http.delete<{ message: string }>(`/teacher/tasks/${id}`); + +export const updateTaskCompletion = (taskId: number, studentId: number, data: UpdateTaskCompletionDto) => + http.put(`/teacher/tasks/${taskId}/completions/${studentId}`, data); + +export const sendTaskReminder = (taskId: number) => + http.post<{ message: string }>(`/teacher/tasks/${taskId}/remind`); + +// ==================== 任务模板 API ==================== + +export interface TaskTemplate { + id: number; + tenantId: number; + name: string; + description?: string; + taskType: 'READING' | 'ACTIVITY' | 'HOMEWORK'; + relatedCourseId?: number; + defaultDuration: number; + isDefault: boolean; + status: string; + createdBy: number; + createdAt: string; + updatedAt: string; + course?: { + id: number; + name: string; + pictureBookName?: string; + }; +} + +export interface CreateTaskTemplateDto { + name: string; + description?: string; + taskType: 'READING' | 'ACTIVITY' | 'HOMEWORK'; + relatedCourseId?: number; + defaultDuration?: number; + isDefault?: boolean; +} + +export interface CreateTaskFromTemplateDto { + templateId: number; + targetIds: number[]; + targetType: 'CLASS' | 'STUDENT'; + startDate?: string; +} + +export const getTaskTemplates = (params?: { + page?: number; + pageSize?: number; + taskType?: string; + keyword?: string; +}) => http.get<{ items: TaskTemplate[]; total: number; page: number; pageSize: number }>('/teacher/task-templates', { params }); + +export const getTaskTemplate = (id: number) => + http.get(`/teacher/task-templates/${id}`); + +export const getDefaultTaskTemplate = (taskType: string) => + http.get(`/teacher/task-templates/default/${taskType}`); + +export const createTaskFromTemplate = (data: CreateTaskFromTemplateDto) => + http.post('/teacher/tasks/from-template', data); + +// ==================== 任务统计 API ==================== + +export interface TaskStats { + totalTasks: number; + publishedTasks: number; + completedTasks: number; + inProgressTasks: number; + pendingCount: number; + totalCompletions: number; + completionRate: number; +} + +export interface TaskStatsByType { + [key: string]: { + total: number; + completed: number; + rate: number; + }; +} + +export interface TaskStatsByClass { + classId: number; + className: string; + grade: string; + total: number; + completed: number; + rate: number; +} + +export interface MonthlyTaskStats { + month: string; + tasks: number; + completions: number; + completed: number; + rate: number; +} + +export const getTaskStats = () => + http.get('/teacher/tasks/stats'); + +export const getTaskStatsByType = () => + http.get('/teacher/tasks/stats/by-type'); + +export const getTaskStatsByClass = () => + http.get('/teacher/tasks/stats/by-class'); + +export const getMonthlyTaskStats = (months?: number) => + http.get('/teacher/tasks/stats/monthly', { params: { months } }); diff --git a/reading-platform-frontend/src/auto-imports.d.ts b/reading-platform-frontend/src/auto-imports.d.ts new file mode 100644 index 0000000..0ca6bef --- /dev/null +++ b/reading-platform-frontend/src/auto-imports.d.ts @@ -0,0 +1,90 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +export {} +declare global { + const EffectScope: typeof import('vue')['EffectScope'] + const Modal: typeof import('ant-design-vue')['Modal'] + const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] + const computed: typeof import('vue')['computed'] + const createApp: typeof import('vue')['createApp'] + const createPinia: typeof import('pinia')['createPinia'] + const customRef: typeof import('vue')['customRef'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const defineStore: typeof import('pinia')['defineStore'] + const effectScope: typeof import('vue')['effectScope'] + const getActivePinia: typeof import('pinia')['getActivePinia'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const h: typeof import('vue')['h'] + const inject: typeof import('vue')['inject'] + const isProxy: typeof import('vue')['isProxy'] + const isReactive: typeof import('vue')['isReactive'] + const isReadonly: typeof import('vue')['isReadonly'] + const isRef: typeof import('vue')['isRef'] + const mapActions: typeof import('pinia')['mapActions'] + const mapGetters: typeof import('pinia')['mapGetters'] + const mapState: typeof import('pinia')['mapState'] + const mapStores: typeof import('pinia')['mapStores'] + const mapWritableState: typeof import('pinia')['mapWritableState'] + const markRaw: typeof import('vue')['markRaw'] + const message: typeof import('ant-design-vue')['message'] + const nextTick: typeof import('vue')['nextTick'] + const notification: typeof import('ant-design-vue')['notification'] + const onActivated: typeof import('vue')['onActivated'] + const onBeforeMount: typeof import('vue')['onBeforeMount'] + const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] + const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] + const onScopeDispose: typeof import('vue')['onScopeDispose'] + const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const onWatcherCleanup: typeof import('vue')['onWatcherCleanup'] + const provide: typeof import('vue')['provide'] + const reactive: typeof import('vue')['reactive'] + const readonly: typeof import('vue')['readonly'] + const ref: typeof import('vue')['ref'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const setActivePinia: typeof import('pinia')['setActivePinia'] + const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + const storeToRefs: typeof import('pinia')['storeToRefs'] + const toRaw: typeof import('vue')['toRaw'] + const toRef: typeof import('vue')['toRef'] + const toRefs: typeof import('vue')['toRefs'] + const toValue: typeof import('vue')['toValue'] + const triggerRef: typeof import('vue')['triggerRef'] + const unref: typeof import('vue')['unref'] + const useAttrs: typeof import('vue')['useAttrs'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVars: typeof import('vue')['useCssVars'] + const useId: typeof import('vue')['useId'] + const useLink: typeof import('vue-router')['useLink'] + const useModel: typeof import('vue')['useModel'] + const useRoute: typeof import('vue-router')['useRoute'] + const useRouter: typeof import('vue-router')['useRouter'] + const useSlots: typeof import('vue')['useSlots'] + const useTemplateRef: typeof import('vue')['useTemplateRef'] + const watch: typeof import('vue')['watch'] + const watchEffect: typeof import('vue')['watchEffect'] + const watchPostEffect: typeof import('vue')['watchPostEffect'] + const watchSyncEffect: typeof import('vue')['watchSyncEffect'] +} +// for type re-export +declare global { + // @ts-ignore + export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' + import('vue') +} diff --git a/reading-platform-frontend/src/components.d.ts b/reading-platform-frontend/src/components.d.ts new file mode 100644 index 0000000..49eaa1b --- /dev/null +++ b/reading-platform-frontend/src/components.d.ts @@ -0,0 +1,86 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +export {} + +declare module 'vue' { + export interface GlobalComponents { + AAlert: typeof import('ant-design-vue/es')['Alert'] + AAvatar: typeof import('ant-design-vue/es')['Avatar'] + ABadge: typeof import('ant-design-vue/es')['Badge'] + AButton: typeof import('ant-design-vue/es')['Button'] + AButtonGroup: typeof import('ant-design-vue/es')['ButtonGroup'] + ACard: typeof import('ant-design-vue/es')['Card'] + ACheckbox: typeof import('ant-design-vue/es')['Checkbox'] + ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup'] + ACol: typeof import('ant-design-vue/es')['Col'] + ACollapse: typeof import('ant-design-vue/es')['Collapse'] + ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel'] + ADatePicker: typeof import('ant-design-vue/es')['DatePicker'] + ADescriptions: typeof import('ant-design-vue/es')['Descriptions'] + ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem'] + ADivider: typeof import('ant-design-vue/es')['Divider'] + ADrawer: typeof import('ant-design-vue/es')['Drawer'] + ADropdown: typeof import('ant-design-vue/es')['Dropdown'] + AEmpty: typeof import('ant-design-vue/es')['Empty'] + AForm: typeof import('ant-design-vue/es')['Form'] + AFormItem: typeof import('ant-design-vue/es')['FormItem'] + AImage: typeof import('ant-design-vue/es')['Image'] + AImagePreviewGroup: typeof import('ant-design-vue/es')['ImagePreviewGroup'] + AInput: typeof import('ant-design-vue/es')['Input'] + AInputNumber: typeof import('ant-design-vue/es')['InputNumber'] + AInputPassword: typeof import('ant-design-vue/es')['InputPassword'] + AInputSearch: typeof import('ant-design-vue/es')['InputSearch'] + ALayout: typeof import('ant-design-vue/es')['Layout'] + ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent'] + ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader'] + ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider'] + AList: typeof import('ant-design-vue/es')['List'] + AListItem: typeof import('ant-design-vue/es')['ListItem'] + AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta'] + AMenu: typeof import('ant-design-vue/es')['Menu'] + AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider'] + AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] + AModal: typeof import('ant-design-vue/es')['Modal'] + APageHeader: typeof import('ant-design-vue/es')['PageHeader'] + APagination: typeof import('ant-design-vue/es')['Pagination'] + APopconfirm: typeof import('ant-design-vue/es')['Popconfirm'] + AProgress: typeof import('ant-design-vue/es')['Progress'] + ARadio: typeof import('ant-design-vue/es')['Radio'] + ARadioButton: typeof import('ant-design-vue/es')['RadioButton'] + ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] + ARangePicker: typeof import('ant-design-vue/es')['RangePicker'] + ARate: typeof import('ant-design-vue/es')['Rate'] + AResult: typeof import('ant-design-vue/es')['Result'] + ARow: typeof import('ant-design-vue/es')['Row'] + ASelect: typeof import('ant-design-vue/es')['Select'] + ASelectOptGroup: typeof import('ant-design-vue/es')['SelectOptGroup'] + ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] + ASkeleton: typeof import('ant-design-vue/es')['Skeleton'] + ASpace: typeof import('ant-design-vue/es')['Space'] + ASpin: typeof import('ant-design-vue/es')['Spin'] + AStatistic: typeof import('ant-design-vue/es')['Statistic'] + AStep: typeof import('ant-design-vue/es')['Step'] + ASteps: typeof import('ant-design-vue/es')['Steps'] + ASubMenu: typeof import('ant-design-vue/es')['SubMenu'] + ASwitch: typeof import('ant-design-vue/es')['Switch'] + ATable: typeof import('ant-design-vue/es')['Table'] + ATabPane: typeof import('ant-design-vue/es')['TabPane'] + ATabs: typeof import('ant-design-vue/es')['Tabs'] + ATag: typeof import('ant-design-vue/es')['Tag'] + ATextarea: typeof import('ant-design-vue/es')['Textarea'] + ATimeline: typeof import('ant-design-vue/es')['Timeline'] + ATimelineItem: typeof import('ant-design-vue/es')['TimelineItem'] + ATimeRangePicker: typeof import('ant-design-vue/es')['TimeRangePicker'] + ATooltip: typeof import('ant-design-vue/es')['Tooltip'] + ATypographyText: typeof import('ant-design-vue/es')['TypographyText'] + AUpload: typeof import('ant-design-vue/es')['Upload'] + AUploadDragger: typeof import('ant-design-vue/es')['UploadDragger'] + FilePreviewModal: typeof import('./components/FilePreviewModal.vue')['default'] + NotificationBell: typeof import('./components/NotificationBell.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + } +} diff --git a/reading-platform-frontend/src/components/FilePreviewModal.vue b/reading-platform-frontend/src/components/FilePreviewModal.vue new file mode 100644 index 0000000..2e3c0af --- /dev/null +++ b/reading-platform-frontend/src/components/FilePreviewModal.vue @@ -0,0 +1,379 @@ + + + + + diff --git a/reading-platform-frontend/src/components/NotificationBell.vue b/reading-platform-frontend/src/components/NotificationBell.vue new file mode 100644 index 0000000..36ec186 --- /dev/null +++ b/reading-platform-frontend/src/components/NotificationBell.vue @@ -0,0 +1,260 @@ + + + + + diff --git a/reading-platform-frontend/src/main.ts b/reading-platform-frontend/src/main.ts new file mode 100644 index 0000000..7565559 --- /dev/null +++ b/reading-platform-frontend/src/main.ts @@ -0,0 +1,15 @@ +import { createApp } from 'vue'; +import { createPinia } from 'pinia'; +import Antd from 'ant-design-vue'; +import 'ant-design-vue/dist/reset.css'; +import App from './App.vue'; +import router from './router'; + +const app = createApp(App); +const pinia = createPinia(); + +app.use(pinia); +app.use(router); +app.use(Antd); + +app.mount('#app'); diff --git a/reading-platform-frontend/src/router/index.ts b/reading-platform-frontend/src/router/index.ts new file mode 100644 index 0000000..c5381c5 --- /dev/null +++ b/reading-platform-frontend/src/router/index.ts @@ -0,0 +1,399 @@ +import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'; +import { message } from 'ant-design-vue'; + +const routes: RouteRecordRaw[] = [ + { + path: '/', + redirect: '/login', + }, + { + path: '/login', + name: 'Login', + component: () => import('@/views/auth/LoginView.vue'), + meta: { requiresAuth: false, title: '登录' }, + }, + { + path: '/admin', + component: () => import('@/views/admin/LayoutView.vue'), + meta: { requiresAuth: true, role: 'admin', title: '超管端' }, + children: [ + { + path: '', + redirect: '/admin/dashboard', + }, + { + path: 'dashboard', + name: 'AdminDashboard', + component: () => import('@/views/admin/DashboardView.vue'), + meta: { title: '数据看板' }, + }, + { + path: 'courses', + name: 'AdminCourses', + component: () => import('@/views/admin/courses/CourseListView.vue'), + meta: { title: '课程包管理' }, + }, + { + path: 'courses/review', + name: 'AdminCourseReview', + component: () => import('@/views/admin/courses/CourseReviewView.vue'), + meta: { title: '审核管理' }, + }, + { + path: 'courses/:id', + name: 'AdminCourseDetail', + component: () => import('@/views/admin/courses/CourseDetailView.vue'), + meta: { title: '课程包详情' }, + }, + { + path: 'courses/create', + name: 'AdminCourseCreate', + component: () => import('@/views/admin/courses/CourseEditView.vue'), + meta: { title: '创建课程包' }, + }, + { + path: 'courses/:id/edit', + name: 'AdminCourseEdit', + component: () => import('@/views/admin/courses/CourseEditView.vue'), + meta: { title: '编辑课程包' }, + }, + { + path: 'courses/:id/stats', + name: 'AdminCourseStats', + component: () => import('@/views/admin/courses/CourseStatsView.vue'), + meta: { title: '课程数据统计' }, + }, + { + path: 'tenants', + name: 'AdminTenants', + component: () => import('@/views/admin/tenants/TenantListView.vue'), + meta: { title: '租户管理' }, + }, + { + path: 'resources', + name: 'AdminResources', + component: () => import('@/views/admin/resources/ResourceListView.vue'), + meta: { title: '资源库管理' }, + }, + { + path: 'settings', + name: 'AdminSettings', + component: () => import('@/views/admin/SettingsView.vue'), + meta: { title: '系统设置' }, + }, + ], + }, + { + path: '/school', + component: () => import('@/views/school/LayoutView.vue'), + meta: { requiresAuth: true, role: 'school', title: '学校端' }, + children: [ + { + path: '', + redirect: '/school/dashboard', + }, + { + path: 'dashboard', + name: 'SchoolDashboard', + component: () => import('@/views/school/DashboardView.vue'), + meta: { title: '数据看板' }, + }, + { + path: 'teachers', + name: 'SchoolTeachers', + component: () => import('@/views/school/teachers/TeacherListView.vue'), + meta: { title: '教师管理' }, + }, + { + path: 'students', + name: 'SchoolStudents', + component: () => import('@/views/school/students/StudentListView.vue'), + meta: { title: '学生管理' }, + }, + { + path: 'parents', + name: 'SchoolParents', + component: () => import('@/views/school/parents/ParentListView.vue'), + meta: { title: '家长管理' }, + }, + { + path: 'classes', + name: 'SchoolClasses', + component: () => import('@/views/school/classes/ClassListView.vue'), + meta: { title: '班级管理' }, + }, + { + path: 'courses', + name: 'SchoolCourses', + component: () => import('@/views/school/courses/CourseListView.vue'), + meta: { title: '课程管理' }, + }, + { + path: 'courses/:id', + name: 'SchoolCourseDetail', + component: () => import('@/views/school/courses/CourseDetailView.vue'), + meta: { title: '课程详情' }, + }, + { + path: 'package', + name: 'SchoolPackage', + component: () => import('@/views/school/PackageView.vue'), + meta: { title: '套餐管理' }, + }, + { + path: 'reports', + name: 'SchoolReports', + component: () => import('@/views/school/ReportView.vue'), + meta: { title: '数据报表' }, + }, + { + path: 'growth', + name: 'SchoolGrowth', + component: () => import('@/views/school/growth/GrowthRecordView.vue'), + meta: { title: '成长档案' }, + }, + { + path: 'tasks', + name: 'SchoolTasks', + component: () => import('@/views/school/tasks/TaskListView.vue'), + meta: { title: '阅读任务' }, + }, + { + path: 'task-templates', + name: 'SchoolTaskTemplates', + component: () => import('@/views/school/tasks/TaskTemplateView.vue'), + meta: { title: '任务模板' }, + }, + { + path: 'feedback', + name: 'SchoolFeedback', + component: () => import('@/views/school/feedback/FeedbackView.vue'), + meta: { title: '课程反馈' }, + }, + { + path: 'schedule', + name: 'SchoolSchedule', + component: () => import('@/views/school/schedule/ScheduleView.vue'), + meta: { title: '课程排期' }, + }, + { + path: 'schedule/timetable', + name: 'SchoolTimetable', + component: () => import('@/views/school/schedule/TimetableView.vue'), + meta: { title: '课表视图' }, + }, + { + path: 'schedule/calendar', + name: 'SchoolCalendar', + component: () => import('@/views/school/schedule/CalendarView.vue'), + meta: { title: '日历视图' }, + }, + { + path: 'operation-logs', + name: 'SchoolOperationLogs', + component: () => import('@/views/school/settings/OperationLogView.vue'), + meta: { title: '操作日志' }, + }, + { + path: 'settings', + name: 'SchoolSettings', + component: () => import('@/views/school/settings/SettingsView.vue'), + meta: { title: '系统设置' }, + }, + ], + }, + { + path: '/teacher', + component: () => import('@/views/teacher/LayoutView.vue'), + meta: { requiresAuth: true, role: 'teacher', title: '教师端' }, + children: [ + { + path: '', + redirect: '/teacher/dashboard', + }, + { + path: 'dashboard', + name: 'TeacherDashboard', + component: () => import('@/views/teacher/DashboardView.vue'), + meta: { title: '首页' }, + }, + { + path: 'courses', + name: 'TeacherCourses', + component: () => import('@/views/teacher/courses/CourseListView.vue'), + meta: { title: '课程中心' }, + }, + { + path: 'courses/:id', + name: 'TeacherCourseDetail', + component: () => import('@/views/teacher/courses/CourseDetailView.vue'), + meta: { title: '课程详情' }, + }, + { + path: 'courses/:id/prepare', + name: 'TeacherCoursePrepare', + component: () => import('@/views/teacher/courses/PrepareModeView.vue'), + meta: { title: '备课模式' }, + }, + { + path: 'lessons', + name: 'TeacherLessons', + component: () => import('@/views/teacher/lessons/LessonListView.vue'), + meta: { title: '上课记录' }, + }, + { + path: 'lessons/:id', + name: 'TeacherLesson', + component: () => import('@/views/teacher/lessons/LessonView.vue'), + meta: { title: '上课模式' }, + }, + { + path: 'lessons/:id/records', + name: 'TeacherLessonRecords', + component: () => import('@/views/teacher/lessons/LessonRecordsView.vue'), + meta: { title: '课后记录' }, + }, + { + path: 'broadcast/:id', + name: 'TeacherBroadcast', + component: () => import('@/views/teacher/lessons/BroadcastView.vue'), + meta: { title: '展播模式' }, + }, + { + path: 'classes', + name: 'TeacherClasses', + component: () => import('@/views/teacher/classes/ClassListView.vue'), + meta: { title: '我的班级' }, + }, + { + path: 'classes/:id/students', + name: 'TeacherClassStudents', + component: () => import('@/views/teacher/classes/ClassStudentsView.vue'), + meta: { title: '班级学生' }, + }, + { + path: 'tasks', + name: 'TeacherTasks', + component: () => import('@/views/teacher/tasks/TaskListView.vue'), + meta: { title: '阅读任务' }, + }, + { + path: 'feedback', + name: 'TeacherFeedback', + component: () => import('@/views/teacher/feedback/FeedbackView.vue'), + meta: { title: '课程反馈' }, + }, + { + path: 'schedule', + name: 'TeacherSchedule', + component: () => import('@/views/teacher/schedule/ScheduleView.vue'), + meta: { title: '我的课表' }, + }, + { + path: 'growth', + name: 'TeacherGrowth', + component: () => import('@/views/teacher/growth/GrowthRecordView.vue'), + meta: { title: '成长档案' }, + }, + ], + }, + { + path: '/parent', + component: () => import('@/views/parent/LayoutView.vue'), + meta: { requiresAuth: true, role: 'parent', title: '家长端' }, + children: [ + { + path: '', + redirect: '/parent/dashboard', + }, + { + path: 'dashboard', + name: 'ParentDashboard', + component: () => import('@/views/parent/DashboardView.vue'), + meta: { title: '首页' }, + }, + { + path: 'children', + name: 'ParentChildren', + component: () => import('@/views/parent/children/ChildrenView.vue'), + meta: { title: '我的孩子' }, + }, + { + path: 'children/:id', + name: 'ParentChildDetail', + component: () => import('@/views/parent/children/ChildProfileView.vue'), + meta: { title: '孩子详情' }, + }, + { + path: 'lessons', + name: 'ParentLessons', + component: () => import('@/views/parent/lessons/LessonHistoryView.vue'), + meta: { title: '阅读记录' }, + }, + { + path: 'tasks', + name: 'ParentTasks', + component: () => import('@/views/parent/tasks/TaskListView.vue'), + meta: { title: '阅读任务' }, + }, + { + path: 'growth', + name: 'ParentGrowth', + component: () => import('@/views/parent/growth/GrowthRecordView.vue'), + meta: { title: '成长档案' }, + }, + ], + }, + { + path: '/404', + name: 'NotFound', + component: () => import('@/views/NotFoundView.vue'), + meta: { title: '页面不存在' }, + }, + { + path: '/:pathMatch(.*)*', + redirect: '/404', + }, +]; + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes, +}); + +// 路由守卫 +router.beforeEach((to, from, next) => { + const token = localStorage.getItem('token'); + const userRole = localStorage.getItem('role'); + + // 设置页面标题 + if (to.meta.title) { + document.title = `${to.meta.title} - 幼儿阅读教学服务平台`; + } + + // 需要认证但未登录 + if (to.meta.requiresAuth && !token) { + message.warning('请先登录'); + next('/login'); + return; + } + + // 已登录用户访问登录页,跳转到对应首页 + if (to.path === '/login' && token) { + const defaultRoute = userRole ? `/${userRole}/dashboard` : '/admin/dashboard'; + next(defaultRoute); + return; + } + + // 角色权限检查 + if (to.meta.role && userRole !== to.meta.role) { + message.error('没有权限访问该页面'); + next(`/${userRole}/dashboard`); + return; + } + + next(); +}); + +export { router }; +export default router; diff --git a/reading-platform-frontend/src/stores/user.ts b/reading-platform-frontend/src/stores/user.ts new file mode 100644 index 0000000..fb0c6d9 --- /dev/null +++ b/reading-platform-frontend/src/stores/user.ts @@ -0,0 +1,92 @@ +import { defineStore } from 'pinia'; +import { ref, computed } from 'vue'; +import { message } from 'ant-design-vue'; +import { router } from '@/router'; +import * as authApi from '@/api/auth'; + +export interface User { + id: number; + name: string; + role: 'admin' | 'school' | 'teacher' | 'parent'; + tenantId?: number; + tenantName?: string; + email?: string; + phone?: string; + children?: Array<{ id: number; name: string; relationship: string }>; +} + +export const useUserStore = defineStore('user', () => { + const token = ref(localStorage.getItem('token') || ''); + const user = ref( + localStorage.getItem('user') + ? JSON.parse(localStorage.getItem('user')!) + : null + ); + + const isLoggedIn = computed(() => !!token.value); + const userRole = computed(() => user.value?.role || null); + + // 登录 + async function login(account: string, password: string, role: string) { + try { + const data = await authApi.login({ account, password, role }); + token.value = data.token; + user.value = data.user; + + localStorage.setItem('token', data.token); + localStorage.setItem('user', JSON.stringify(data.user)); + localStorage.setItem('role', data.user.role); + + message.success('登录成功'); + + // 跳转到对应首页 + const defaultRoute = `/${data.user.role}/dashboard`; + router.push(defaultRoute); + + return data; + } catch (error: any) { + message.error(error.message || '登录失败'); + throw error; + } + } + + // 登出 + async function logout() { + try { + await authApi.logout(); + } catch (error) { + console.error('Logout error:', error); + } finally { + token.value = ''; + user.value = null; + localStorage.removeItem('token'); + localStorage.removeItem('user'); + localStorage.removeItem('role'); + router.push('/login'); + message.success('已退出登录'); + } + } + + // 获取用户信息 + async function fetchUserInfo() { + try { + const data = await authApi.getProfile(); + user.value = data; + localStorage.setItem('user', JSON.stringify(data)); + return data; + } catch (error: any) { + message.error('获取用户信息失败'); + throw error; + } + } + + return { + token, + user, + isLoggedIn, + userRole, + login, + logout, + fetchUserInfo, + }; +}); diff --git a/reading-platform-frontend/src/utils/tagMaps.ts b/reading-platform-frontend/src/utils/tagMaps.ts new file mode 100644 index 0000000..af5f394 --- /dev/null +++ b/reading-platform-frontend/src/utils/tagMaps.ts @@ -0,0 +1,389 @@ +/** + * 统一的标签映射工具 + * 用于课程包的年级标签和领域标签的中英文转换 + */ + +// 年级标签映射(英文 → 中文) +export const GRADE_TAG_MAP: Record = { + // 大写格式 + SMALL: '小班', + MIDDLE: '中班', + BIG: '大班', + // 小写格式 + small: '小班', + middle: '中班', + big: '大班', +}; + +// 领域标签映射(英文 → 中文)- 导出供其他模块使用 +export const DOMAIN_TAG_MAP: Record = { + // 旧格式(大写) + LANGUAGE: '语言', + SCIENCE: '科学', + SOCIAL: '社会', + ART: '艺术', + HEALTH: '健康', + MATH: '数学', + + // 活动类型作为领域的兼容映射(历史数据兼容) + family: '亲子活动', + FAMILY: '亲子活动', + class: '课堂活动', + CLASS: '课堂活动', + outdoor: '户外活动', + OUTDOOR: '户外活动', + handicraft: '手工活动', + HANDICRAFT: '手工活动', + game: '游戏活动', + GAME: '游戏活动', + music: '音乐活动', + MUSIC: '音乐活动', + exploration: '探索活动', + EXPLORATION: '探索活动', + sports: '运动活动', + SPORTS: '运动活动', + art: '艺术活动', + + // 新格式(细分领域) + // 健康领域 + health_motor: '身体动作发展', + health_hygiene: '生活习惯与能力', + HEALTH_MOTOR: '身体动作发展', + HEALTH_HYGIENE: '生活习惯与能力', + + // 语言领域 + lang_listen: '倾听与表达', + lang_read: '早期阅读', + LANG_LISTEN: '倾听与表达', + LANG_READ: '早期阅读', + language_communication: '语言', + LANGUAGE_COMMUNICATION: '语言', + + // 社会领域 + social_interact: '人际交往', + social_adapt: '社会适应', + SOCIAL_INTERACT: '人际交往', + SOCIAL_ADAPT: '社会适应', + social_emotional: '社会', + SOCIAL_EMOTIONAL: '社会', + + // 科学领域 + science_explore: '科学探究', + math_cog: '数学认知', + SCIENCE_EXPLORE: '科学探究', + MATH_COG: '数学认知', + science_exploration: '科学', + SCIENCE_EXPLORATION: '科学', + + // 艺术领域 + art_music: '音乐表现', + art_create: '美术创作', + ART_MUSIC: '音乐表现', + ART_CREATE: '美术创作', + art_creativity: '艺术', + ART_CREATIVITY: '艺术', +}; + +// 年级标签颜色配置 +export const GRADE_TAG_COLORS: Record = { + '小班': { bg: '#FFE4E8', text: '#E85A71' }, + '中班': { bg: '#E3F2FD', text: '#1976D2' }, + '大班': { bg: '#FFF8E1', text: '#F9A825' }, +}; + +// 领域标签颜色配置 +export const DOMAIN_TAG_COLORS: Record = { + '语言': { bg: '#F3E5F5', text: '#8E24AA' }, + '倾听与表达': { bg: '#F3E5F5', text: '#8E24AA' }, + '早期阅读': { bg: '#EDE7F6', text: '#7B1FA2' }, + + '科学': { bg: '#E8F5E9', text: '#43A047' }, + '科学探究': { bg: '#E8F5E9', text: '#43A047' }, + '数学认知': { bg: '#F1F8E9', text: '#558B2F' }, + + '社会': { bg: '#E0F7FA', text: '#0097A7' }, + '人际交往': { bg: '#E0F7FA', text: '#0097A7' }, + '社会适应': { bg: '#E0F2F1', text: '#00695C' }, + + '艺术': { bg: '#FFF3E0', text: '#FB8C00' }, + '音乐表现': { bg: '#FFF3E0', text: '#FB8C00' }, + '美术创作': { bg: '#FBE9E7', text: '#E64A19' }, + + '健康': { bg: '#FFEBEE', text: '#E53935' }, + '身体动作发展': { bg: '#FFEBEE', text: '#E53935' }, + '生活习惯与能力': { bg: '#FFCDD2', text: '#C62828' }, + + '数学': { bg: '#FFF8E1', text: '#F9A825' }, +}; + +/** + * 转换年级标签为中文 + */ +export function translateGradeTag(tag: string): string { + return GRADE_TAG_MAP[tag] || tag; +} + +/** + * 转换领域标签为中文 + */ +export function translateDomainTag(tag: string): string { + return DOMAIN_TAG_MAP[tag] || tag; +} + +/** + * 批量转换年级标签 + */ +export function translateGradeTags(tags: string[]): string[] { + return (tags || []).map(translateGradeTag); +} + +/** + * 批量转换领域标签 + */ +export function translateDomainTags(tags: string[]): string[] { + return (tags || []).map(translateDomainTag); +} + +/** + * 获取年级标签样式 + */ +export function getGradeTagStyle(tag: string): { background: string; color: string; border: string } { + const colors = GRADE_TAG_COLORS[tag] || { bg: '#F0F0F0', text: '#666' }; + return { + background: colors.bg, + color: colors.text, + border: 'none', + }; +} + +/** + * 获取领域标签样式 + */ +export function getDomainTagStyle(tag: string): { background: string; color: string; border: string } { + const colors = DOMAIN_TAG_COLORS[tag] || { bg: '#F0F0F0', text: '#666' }; + return { + background: colors.bg, + color: colors.text, + border: 'none', + }; +} + +// ==================== 活动相关映射 ==================== + +// 活动类型映射(英文 → 中文) +export const ACTIVITY_TYPE_MAP: Record = { + // 大写格式 + HANDICRAFT: '手工活动', + GAME: '游戏活动', + MUSIC: '音乐活动', + EXPLORATION: '探索活动', + SPORTS: '运动活动', + OUTDOOR: '户外活动', + FAMILY: '家庭延伸', + ART: '美工活动', + OTHER: '其他', + + // 小写格式 + handicraft: '手工活动', + game: '游戏活动', + music: '音乐活动', + exploration: '探索活动', + sports: '运动活动', + outdoor: '户外活动', + family: '家庭延伸', + art: '美工活动', + other: '其他', + + // 其他格式 + class: '课堂活动', +}; + +// 活动类型颜色配置 +export const ACTIVITY_TYPE_COLORS: Record = { + '手工活动': { bg: '#F3E5F5', text: '#8E24AA' }, + '美工活动': { bg: '#F3E5F5', text: '#8E24AA' }, + '游戏活动': { bg: '#FFF3E0', text: '#FB8C00' }, + '音乐活动': { bg: '#E3F2FD', text: '#1976D2' }, + '运动活动': { bg: '#E8F5E9', text: '#43A047' }, + '探索活动': { bg: '#E0F7FA', text: '#0097A7' }, + '户外活动': { bg: '#F1F8E9', text: '#558B2F' }, + '亲子活动': { bg: '#FCE4EC', text: '#C2185B' }, + '家庭延伸': { bg: '#E3F2FD', text: '#1976D2' }, + '课堂活动': { bg: '#FFF8E1', text: '#F9A825' }, + '艺术活动': { bg: '#FBE9E7', text: '#E64A19' }, + '其他': { bg: '#F5F5F5', text: '#666666' }, +}; + +/** + * 转换活动类型为中文 + */ +export function translateActivityType(type: string): string { + return ACTIVITY_TYPE_MAP[type] || type; +} + +/** + * 获取活动类型样式 + */ +export function getActivityTypeStyle(type: string): { background: string; color: string; border: string } { + const colors = ACTIVITY_TYPE_COLORS[type] || { bg: '#F0F0F0', text: '#666' }; + return { + background: colors.bg, + color: colors.text, + border: 'none', + }; +} + +/** + * 转换活动领域为中文(使用领域标签映射) + */ +export function translateActivityDomain(domain: string): string { + return DOMAIN_TAG_MAP[domain] || domain; +} + +// ==================== 教学流程步骤类型映射 ==================== + +// 步骤类型映射(英文 → 中文) +export const STEP_TYPE_MAP: Record = { + // 大写格式 + TEACHING: '教学', + READING: '共读', + DISCUSSION: '讨论', + ACTIVITY: '活动', + GAME: '游戏', + SUMMARY: '总结', + WARMUP: '热身', + PRACTICE: '练习', + INTERACTION: '互动', + // 新增环节类型 + INTRODUCTION: '导入', + CREATIVE: '创作', + CUSTOM: '自定义', + + // 小写格式 + teaching: '教学', + reading: '共读', + discussion: '讨论', + activity: '活动', + game: '游戏', + summary: '总结', + warmup: '热身', + practice: '练习', + interaction: '互动', + introduction: '导入', + creative: '创作', + custom: '自定义', +}; + +// 步骤类型颜色配置 +export const STEP_TYPE_COLORS: Record = { + '教学': { bg: '#E3F2FD', text: '#1976D2' }, + '共读': { bg: '#F3E5F5', text: '#8E24AA' }, + '阅读': { bg: '#F3E5F5', text: '#8E24AA' }, + '讨论': { bg: '#E0F7FA', text: '#0097A7' }, + '活动': { bg: '#FFF3E0', text: '#FB8C00' }, + '游戏': { bg: '#FCE4EC', text: '#C2185B' }, + '总结': { bg: '#E8F5E9', text: '#43A047' }, + '热身': { bg: '#FFF8E1', text: '#F9A825' }, + '练习': { bg: '#EDE7F6', text: '#673AB7' }, + '互动': { bg: '#FBE9E7', text: '#E64A19' }, + '导入': { bg: '#E8F5E9', text: '#4CAF50' }, + '创作': { bg: '#FCE4EC', text: '#E91E63' }, + '自定义': { bg: '#ECEFF1', text: '#607D8B' }, +}; + +/** + * 转换步骤类型为中文 + */ +export function translateStepType(type: string): string { + return STEP_TYPE_MAP[type] || type; +} + +/** + * 获取步骤类型样式 + */ +export function getStepTypeStyle(type: string): { background: string; color: string; border: string } { + const colors = STEP_TYPE_COLORS[type] || { bg: '#F0F0F0', text: '#666' }; + return { + background: colors.bg, + color: colors.text, + border: 'none', + }; +} + +// ==================== 课程状态映射 ==================== + +// 课程状态映射(英文 → 中文) +export const COURSE_STATUS_MAP: Record = { + DRAFT: '草稿', + PENDING: '审核中', + REJECTED: '已驳回', + PUBLISHED: '已发布', + ARCHIVED: '已下架', + REVIEWING: '审核中', + + // 小写格式 + draft: '草稿', + pending: '审核中', + rejected: '已驳回', + published: '已发布', + archived: '已下架', + reviewing: '审核中', +}; + +// 课程状态颜色配置 +export const COURSE_STATUS_COLORS: Record = { + '草稿': { bg: '#F5F5F5', text: '#666666' }, + '审核中': { bg: '#E3F2FD', text: '#1976D2' }, + '已驳回': { bg: '#FFEBEE', text: '#E53935' }, + '已发布': { bg: '#E8F5E9', text: '#43A047' }, + '已下架': { bg: '#FFF8E1', text: '#F9A825' }, +}; + +/** + * 转换课程状态为中文 + */ +export function translateCourseStatus(status: string): string { + return COURSE_STATUS_MAP[status] || status; +} + +/** + * 获取课程状态样式 + */ +export function getCourseStatusStyle(status: string): { background: string; color: string; border: string } { + const chineseStatus = COURSE_STATUS_MAP[status] || status; + const colors = COURSE_STATUS_COLORS[chineseStatus] || { bg: '#F0F0F0', text: '#666' }; + return { + background: colors.bg, + color: colors.text, + border: 'none', + }; +} + +// ==================== 资源类型映射 ==================== + +// 资源类型映射 +export const RESOURCE_TYPE_MAP: Record = { + EBOOK: '电子绘本', + AUDIO: '音频', + VIDEO: '视频', + PPT: 'PPT课件', + POSTER: '教学挂图', + OTHER: '其他资源', + IMAGE: '图片', + + // 已中文的不转换 + '电子绘本': '电子绘本', + '音频': '音频', + '视频': '视频', + 'PPT课件': 'PPT课件', + '教学挂图': '教学挂图', + '其他资源': '其他资源', + '图片': '图片', +}; + +/** + * 转换资源类型为中文 + */ +export function translateResourceType(type: string): string { + return RESOURCE_TYPE_MAP[type] || type; +} diff --git a/reading-platform-frontend/src/views/NotFoundView.vue b/reading-platform-frontend/src/views/NotFoundView.vue new file mode 100644 index 0000000..1fb02ba --- /dev/null +++ b/reading-platform-frontend/src/views/NotFoundView.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/reading-platform-frontend/src/views/admin/DashboardView.vue b/reading-platform-frontend/src/views/admin/DashboardView.vue new file mode 100644 index 0000000..cbc8298 --- /dev/null +++ b/reading-platform-frontend/src/views/admin/DashboardView.vue @@ -0,0 +1,697 @@ + + + + + diff --git a/reading-platform-frontend/src/views/admin/LayoutView.vue b/reading-platform-frontend/src/views/admin/LayoutView.vue new file mode 100644 index 0000000..bd89aee --- /dev/null +++ b/reading-platform-frontend/src/views/admin/LayoutView.vue @@ -0,0 +1,342 @@ + + + + + diff --git a/reading-platform-frontend/src/views/admin/SettingsView.vue b/reading-platform-frontend/src/views/admin/SettingsView.vue new file mode 100644 index 0000000..ecb07cb --- /dev/null +++ b/reading-platform-frontend/src/views/admin/SettingsView.vue @@ -0,0 +1,303 @@ + + + + + diff --git a/reading-platform-frontend/src/views/admin/courses/CourseDetailView.vue b/reading-platform-frontend/src/views/admin/courses/CourseDetailView.vue new file mode 100644 index 0000000..9cbb76b --- /dev/null +++ b/reading-platform-frontend/src/views/admin/courses/CourseDetailView.vue @@ -0,0 +1,956 @@ + + + + + diff --git a/reading-platform-frontend/src/views/admin/courses/CourseEditView.vue b/reading-platform-frontend/src/views/admin/courses/CourseEditView.vue new file mode 100644 index 0000000..3847d8f --- /dev/null +++ b/reading-platform-frontend/src/views/admin/courses/CourseEditView.vue @@ -0,0 +1,2654 @@ + + + + + diff --git a/reading-platform-frontend/src/views/admin/courses/CourseListView.vue b/reading-platform-frontend/src/views/admin/courses/CourseListView.vue new file mode 100644 index 0000000..c037f83 --- /dev/null +++ b/reading-platform-frontend/src/views/admin/courses/CourseListView.vue @@ -0,0 +1,538 @@ + + + + + diff --git a/reading-platform-frontend/src/views/admin/courses/CourseReviewView.vue b/reading-platform-frontend/src/views/admin/courses/CourseReviewView.vue new file mode 100644 index 0000000..b3b8069 --- /dev/null +++ b/reading-platform-frontend/src/views/admin/courses/CourseReviewView.vue @@ -0,0 +1,387 @@ + + + + + diff --git a/reading-platform-frontend/src/views/admin/courses/CourseStatsView.vue b/reading-platform-frontend/src/views/admin/courses/CourseStatsView.vue new file mode 100644 index 0000000..81adb6d --- /dev/null +++ b/reading-platform-frontend/src/views/admin/courses/CourseStatsView.vue @@ -0,0 +1,318 @@ + + + + + diff --git a/reading-platform-frontend/src/views/admin/resources/ResourceListView.vue b/reading-platform-frontend/src/views/admin/resources/ResourceListView.vue new file mode 100644 index 0000000..4c6bcce --- /dev/null +++ b/reading-platform-frontend/src/views/admin/resources/ResourceListView.vue @@ -0,0 +1,734 @@ + + + + + diff --git a/reading-platform-frontend/src/views/admin/tenants/TenantListView.vue b/reading-platform-frontend/src/views/admin/tenants/TenantListView.vue new file mode 100644 index 0000000..7076165 --- /dev/null +++ b/reading-platform-frontend/src/views/admin/tenants/TenantListView.vue @@ -0,0 +1,707 @@ + + + + + diff --git a/reading-platform-frontend/src/views/auth/LoginView.vue b/reading-platform-frontend/src/views/auth/LoginView.vue new file mode 100644 index 0000000..dd3a2c9 --- /dev/null +++ b/reading-platform-frontend/src/views/auth/LoginView.vue @@ -0,0 +1,428 @@ + + + + + diff --git a/reading-platform-frontend/src/views/parent/DashboardView.vue b/reading-platform-frontend/src/views/parent/DashboardView.vue new file mode 100644 index 0000000..75d0cde --- /dev/null +++ b/reading-platform-frontend/src/views/parent/DashboardView.vue @@ -0,0 +1,592 @@ + + + + + diff --git a/reading-platform-frontend/src/views/parent/LayoutView.vue b/reading-platform-frontend/src/views/parent/LayoutView.vue new file mode 100644 index 0000000..b3b6d3a --- /dev/null +++ b/reading-platform-frontend/src/views/parent/LayoutView.vue @@ -0,0 +1,640 @@ + + + + + diff --git a/reading-platform-frontend/src/views/parent/children/ChildProfileView.vue b/reading-platform-frontend/src/views/parent/children/ChildProfileView.vue new file mode 100644 index 0000000..53d53fb --- /dev/null +++ b/reading-platform-frontend/src/views/parent/children/ChildProfileView.vue @@ -0,0 +1,387 @@ + + + + + diff --git a/reading-platform-frontend/src/views/parent/children/ChildrenView.vue b/reading-platform-frontend/src/views/parent/children/ChildrenView.vue new file mode 100644 index 0000000..352b4f2 --- /dev/null +++ b/reading-platform-frontend/src/views/parent/children/ChildrenView.vue @@ -0,0 +1,407 @@ + + + + + diff --git a/reading-platform-frontend/src/views/parent/growth/GrowthRecordView.vue b/reading-platform-frontend/src/views/parent/growth/GrowthRecordView.vue new file mode 100644 index 0000000..ec44a39 --- /dev/null +++ b/reading-platform-frontend/src/views/parent/growth/GrowthRecordView.vue @@ -0,0 +1,330 @@ + + + + + diff --git a/reading-platform-frontend/src/views/parent/lessons/LessonHistoryView.vue b/reading-platform-frontend/src/views/parent/lessons/LessonHistoryView.vue new file mode 100644 index 0000000..1c13ae8 --- /dev/null +++ b/reading-platform-frontend/src/views/parent/lessons/LessonHistoryView.vue @@ -0,0 +1,353 @@ + + + + + diff --git a/reading-platform-frontend/src/views/parent/tasks/TaskListView.vue b/reading-platform-frontend/src/views/parent/tasks/TaskListView.vue new file mode 100644 index 0000000..72f6ae7 --- /dev/null +++ b/reading-platform-frontend/src/views/parent/tasks/TaskListView.vue @@ -0,0 +1,362 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/DashboardView.vue b/reading-platform-frontend/src/views/school/DashboardView.vue new file mode 100644 index 0000000..f7903be --- /dev/null +++ b/reading-platform-frontend/src/views/school/DashboardView.vue @@ -0,0 +1,1177 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/LayoutView.vue b/reading-platform-frontend/src/views/school/LayoutView.vue new file mode 100644 index 0000000..1dd4dea --- /dev/null +++ b/reading-platform-frontend/src/views/school/LayoutView.vue @@ -0,0 +1,462 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/PackageView.vue b/reading-platform-frontend/src/views/school/PackageView.vue new file mode 100644 index 0000000..9385681 --- /dev/null +++ b/reading-platform-frontend/src/views/school/PackageView.vue @@ -0,0 +1,772 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/ReportView.vue b/reading-platform-frontend/src/views/school/ReportView.vue new file mode 100644 index 0000000..169c15a --- /dev/null +++ b/reading-platform-frontend/src/views/school/ReportView.vue @@ -0,0 +1,1127 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/classes/ClassListView.vue b/reading-platform-frontend/src/views/school/classes/ClassListView.vue new file mode 100644 index 0000000..10d34aa --- /dev/null +++ b/reading-platform-frontend/src/views/school/classes/ClassListView.vue @@ -0,0 +1,1352 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/courses/CourseDetailView.vue b/reading-platform-frontend/src/views/school/courses/CourseDetailView.vue new file mode 100644 index 0000000..8526c76 --- /dev/null +++ b/reading-platform-frontend/src/views/school/courses/CourseDetailView.vue @@ -0,0 +1,1204 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/courses/CourseListView.vue b/reading-platform-frontend/src/views/school/courses/CourseListView.vue new file mode 100644 index 0000000..ed4e091 --- /dev/null +++ b/reading-platform-frontend/src/views/school/courses/CourseListView.vue @@ -0,0 +1,896 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/feedback/FeedbackView.vue b/reading-platform-frontend/src/views/school/feedback/FeedbackView.vue new file mode 100644 index 0000000..2a9f763 --- /dev/null +++ b/reading-platform-frontend/src/views/school/feedback/FeedbackView.vue @@ -0,0 +1,940 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/growth/GrowthRecordView.vue b/reading-platform-frontend/src/views/school/growth/GrowthRecordView.vue new file mode 100644 index 0000000..9ec4edb --- /dev/null +++ b/reading-platform-frontend/src/views/school/growth/GrowthRecordView.vue @@ -0,0 +1,812 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/parents/ParentListView.vue b/reading-platform-frontend/src/views/school/parents/ParentListView.vue new file mode 100644 index 0000000..c701c0f --- /dev/null +++ b/reading-platform-frontend/src/views/school/parents/ParentListView.vue @@ -0,0 +1,1248 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/schedule/CalendarView.vue b/reading-platform-frontend/src/views/school/schedule/CalendarView.vue new file mode 100644 index 0000000..63c0255 --- /dev/null +++ b/reading-platform-frontend/src/views/school/schedule/CalendarView.vue @@ -0,0 +1,417 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/schedule/ScheduleView.vue b/reading-platform-frontend/src/views/school/schedule/ScheduleView.vue new file mode 100644 index 0000000..4273659 --- /dev/null +++ b/reading-platform-frontend/src/views/school/schedule/ScheduleView.vue @@ -0,0 +1,964 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/schedule/TimetableView.vue b/reading-platform-frontend/src/views/school/schedule/TimetableView.vue new file mode 100644 index 0000000..58da0e4 --- /dev/null +++ b/reading-platform-frontend/src/views/school/schedule/TimetableView.vue @@ -0,0 +1,739 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/settings/OperationLogView.vue b/reading-platform-frontend/src/views/school/settings/OperationLogView.vue new file mode 100644 index 0000000..2a72ac4 --- /dev/null +++ b/reading-platform-frontend/src/views/school/settings/OperationLogView.vue @@ -0,0 +1,328 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/settings/SettingsView.vue b/reading-platform-frontend/src/views/school/settings/SettingsView.vue new file mode 100644 index 0000000..5b1b4cc --- /dev/null +++ b/reading-platform-frontend/src/views/school/settings/SettingsView.vue @@ -0,0 +1,329 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/students/StudentListView.vue b/reading-platform-frontend/src/views/school/students/StudentListView.vue new file mode 100644 index 0000000..745ecb0 --- /dev/null +++ b/reading-platform-frontend/src/views/school/students/StudentListView.vue @@ -0,0 +1,1473 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/tasks/TaskListView.vue b/reading-platform-frontend/src/views/school/tasks/TaskListView.vue new file mode 100644 index 0000000..041f0bd --- /dev/null +++ b/reading-platform-frontend/src/views/school/tasks/TaskListView.vue @@ -0,0 +1,743 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/tasks/TaskTemplateView.vue b/reading-platform-frontend/src/views/school/tasks/TaskTemplateView.vue new file mode 100644 index 0000000..9d9a040 --- /dev/null +++ b/reading-platform-frontend/src/views/school/tasks/TaskTemplateView.vue @@ -0,0 +1,474 @@ + + + + + diff --git a/reading-platform-frontend/src/views/school/teachers/TeacherListView.vue b/reading-platform-frontend/src/views/school/teachers/TeacherListView.vue new file mode 100644 index 0000000..a0f1f9f --- /dev/null +++ b/reading-platform-frontend/src/views/school/teachers/TeacherListView.vue @@ -0,0 +1,841 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/DashboardView.vue b/reading-platform-frontend/src/views/teacher/DashboardView.vue new file mode 100644 index 0000000..063ed7c --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/DashboardView.vue @@ -0,0 +1,1331 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/LayoutView.vue b/reading-platform-frontend/src/views/teacher/LayoutView.vue new file mode 100644 index 0000000..a30bc61 --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/LayoutView.vue @@ -0,0 +1,341 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/classes/ClassListView.vue b/reading-platform-frontend/src/views/teacher/classes/ClassListView.vue new file mode 100644 index 0000000..8f40e12 --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/classes/ClassListView.vue @@ -0,0 +1,839 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/classes/ClassStudentsView.vue b/reading-platform-frontend/src/views/teacher/classes/ClassStudentsView.vue new file mode 100644 index 0000000..5f2c0bd --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/classes/ClassStudentsView.vue @@ -0,0 +1,473 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/courses/CourseDetailView.vue b/reading-platform-frontend/src/views/teacher/courses/CourseDetailView.vue new file mode 100644 index 0000000..c78106a --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/courses/CourseDetailView.vue @@ -0,0 +1,1219 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/courses/CourseListView.vue b/reading-platform-frontend/src/views/teacher/courses/CourseListView.vue new file mode 100644 index 0000000..c184fe7 --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/courses/CourseListView.vue @@ -0,0 +1,688 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/courses/PrepareModeView.vue b/reading-platform-frontend/src/views/teacher/courses/PrepareModeView.vue new file mode 100644 index 0000000..f4585ec --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/courses/PrepareModeView.vue @@ -0,0 +1,1426 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/feedback/FeedbackView.vue b/reading-platform-frontend/src/views/teacher/feedback/FeedbackView.vue new file mode 100644 index 0000000..d8442fc --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/feedback/FeedbackView.vue @@ -0,0 +1,850 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/growth/GrowthRecordView.vue b/reading-platform-frontend/src/views/teacher/growth/GrowthRecordView.vue new file mode 100644 index 0000000..118115a --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/growth/GrowthRecordView.vue @@ -0,0 +1,886 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/lessons/BroadcastView.vue b/reading-platform-frontend/src/views/teacher/lessons/BroadcastView.vue new file mode 100644 index 0000000..6e75b35 --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/lessons/BroadcastView.vue @@ -0,0 +1,234 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/lessons/LessonListView.vue b/reading-platform-frontend/src/views/teacher/lessons/LessonListView.vue new file mode 100644 index 0000000..e74dc9b --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/lessons/LessonListView.vue @@ -0,0 +1,615 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/lessons/LessonRecordsView.vue b/reading-platform-frontend/src/views/teacher/lessons/LessonRecordsView.vue new file mode 100644 index 0000000..82b719c --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/lessons/LessonRecordsView.vue @@ -0,0 +1,622 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/lessons/LessonView.vue b/reading-platform-frontend/src/views/teacher/lessons/LessonView.vue new file mode 100644 index 0000000..8604416 --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/lessons/LessonView.vue @@ -0,0 +1,1462 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/lessons/components/KidsMode.vue b/reading-platform-frontend/src/views/teacher/lessons/components/KidsMode.vue new file mode 100644 index 0000000..c1d8114 --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/lessons/components/KidsMode.vue @@ -0,0 +1,1065 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/lessons/components/viewers/AudioPlayer.vue b/reading-platform-frontend/src/views/teacher/lessons/components/viewers/AudioPlayer.vue new file mode 100644 index 0000000..577f8bf --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/lessons/components/viewers/AudioPlayer.vue @@ -0,0 +1,559 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/lessons/components/viewers/EbookViewer.vue b/reading-platform-frontend/src/views/teacher/lessons/components/viewers/EbookViewer.vue new file mode 100644 index 0000000..470f692 --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/lessons/components/viewers/EbookViewer.vue @@ -0,0 +1,684 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/lessons/components/viewers/SlidesViewer.vue b/reading-platform-frontend/src/views/teacher/lessons/components/viewers/SlidesViewer.vue new file mode 100644 index 0000000..de33b01 --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/lessons/components/viewers/SlidesViewer.vue @@ -0,0 +1,624 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/lessons/components/viewers/VideoPlayer.vue b/reading-platform-frontend/src/views/teacher/lessons/components/viewers/VideoPlayer.vue new file mode 100644 index 0000000..246f1d2 --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/lessons/components/viewers/VideoPlayer.vue @@ -0,0 +1,811 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/schedule/ScheduleView.vue b/reading-platform-frontend/src/views/teacher/schedule/ScheduleView.vue new file mode 100644 index 0000000..b4b4e03 --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/schedule/ScheduleView.vue @@ -0,0 +1,684 @@ + + + + + diff --git a/reading-platform-frontend/src/views/teacher/tasks/TaskListView.vue b/reading-platform-frontend/src/views/teacher/tasks/TaskListView.vue new file mode 100644 index 0000000..e3aaf62 --- /dev/null +++ b/reading-platform-frontend/src/views/teacher/tasks/TaskListView.vue @@ -0,0 +1,1023 @@ + + + + + diff --git a/reading-platform-frontend/src/vite-env.d.ts b/reading-platform-frontend/src/vite-env.d.ts new file mode 100644 index 0000000..614ceda --- /dev/null +++ b/reading-platform-frontend/src/vite-env.d.ts @@ -0,0 +1,11 @@ +/// + +interface ImportMetaEnv { + readonly VITE_API_URL: string + readonly VITE_SERVER_BASE_URL: string + // 添加更多环境变量类型... +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/reading-platform-frontend/start-frontend.sh b/reading-platform-frontend/start-frontend.sh new file mode 100644 index 0000000..bdb39f4 --- /dev/null +++ b/reading-platform-frontend/start-frontend.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# 前端启动脚本 +# 确保从正确目录启动 + +cd "$(dirname "$0")" + +echo "🚀 正在启动前端服务..." +echo "📂 工作目录: $(pwd)" + +# 检查 node_modules 是否存在 +if [ ! -d "node_modules" ]; then + echo "📦 正在安装依赖..." + npm install +fi + +# 启动前端 +npm run dev diff --git a/reading-platform-frontend/tsconfig.json b/reading-platform-frontend/tsconfig.json new file mode 100644 index 0000000..7a2eeb3 --- /dev/null +++ b/reading-platform-frontend/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + /* Path mapping */ + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/reading-platform-frontend/tsconfig.node.json b/reading-platform-frontend/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/reading-platform-frontend/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/reading-platform-frontend/vite.config.ts b/reading-platform-frontend/vite.config.ts new file mode 100644 index 0000000..91bfe75 --- /dev/null +++ b/reading-platform-frontend/vite.config.ts @@ -0,0 +1,72 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import { resolve } from 'path'; +import AutoImport from 'unplugin-auto-import/vite'; +import Components from 'unplugin-vue-components/vite'; +import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'; +import viteCompression from 'vite-plugin-compression'; + +export default defineConfig({ + plugins: [ + vue(), + AutoImport({ + imports: [ + 'vue', + 'vue-router', + 'pinia', + { + 'ant-design-vue': ['message', 'notification', 'Modal'], + }, + ], + dts: 'src/auto-imports.d.ts', + }), + Components({ + resolvers: [ + AntDesignVueResolver({ + importStyle: false, + }), + ], + dts: 'src/components.d.ts', + }), + viteCompression({ + verbose: true, + disable: false, + threshold: 10240, + algorithm: 'gzip', + ext: '.gz', + }), + ], + resolve: { + alias: { + '@': resolve(__dirname, 'src'), + }, + }, + server: { + port: 5173, + host: true, + proxy: { + '/api': { + target: 'http://localhost:3000', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '/api'), + }, + '/uploads': { + target: 'http://localhost:3000', + changeOrigin: true, + }, + }, + }, + build: { + rollupOptions: { + output: { + manualChunks: { + 'ant-design-vue': ['ant-design-vue', '@ant-design/icons-vue'], + 'echarts': ['echarts'], + 'fullcalendar': ['@fullcalendar/vue3', '@fullcalendar/core', '@fullcalendar/daygrid', '@fullcalendar/timegrid', '@fullcalendar/interaction'], + 'dayjs': ['dayjs'], + }, + }, + }, + chunkSizeWarningLimit: 1000, + }, +});