From 44fe108bac1f47dcc3792a355bd75eb52465cee0 Mon Sep 17 00:00:00 2001 From: TianKai Ma Date: Sat, 18 Jul 2026 20:59:22 +0800 Subject: [PATCH] fix: close infrastructure reliability gaps --- .github/workflows/ci.yml | 43 ++---- .github/workflows/db-backed-bun-job.yml | 19 +-- .github/workflows/static-sync.yml | 2 +- README.md | 1 + docs/observability.md | 4 +- examples/catalog_exam.json | 71 ---------- examples/catalog_lesson.json | 77 ----------- examples/catalog_semester.json | 10 -- examples/jw_datum.json | 170 ------------------------ 9 files changed, 21 insertions(+), 376 deletions(-) delete mode 100644 examples/catalog_exam.json delete mode 100644 examples/catalog_lesson.json delete mode 100644 examples/catalog_semester.json delete mode 100644 examples/jw_datum.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c22c0db49..63cb6509e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,7 +132,7 @@ jobs: ' test-e2e: - name: E2E Test (${{ matrix.scope }}) + name: E2E Test (${{ matrix.shard }}) needs: - check - build-e2e-artifacts @@ -141,35 +141,14 @@ jobs: fail-fast: false matrix: include: - - scope: api-mcp - paths: tests/e2e/src/app/api - - scope: public-web - paths: | - tests/e2e/src/app/test.ts - tests/e2e/src/app/bus - tests/e2e/src/app/bus-map - tests/e2e/src/app/courses - tests/e2e/src/app/guides - tests/e2e/src/app/mobile-app - tests/e2e/src/app/privacy - tests/e2e/src/app/sections - tests/e2e/src/app/signin - tests/e2e/src/app/teachers - tests/e2e/src/app/terms - tests/e2e/src/app/u - tests/e2e/src/app/welcome - tests/e2e/mobile-screenshots - - scope: signed-in - paths: | - tests/e2e/src/app/dashboard - tests/e2e/src/app/e2e - tests/e2e/src/app/oauth - tests/e2e/src/app/settings - - scope: admin-comments - paths: | - tests/e2e/src/app/admin - tests/e2e/src/app/comments - tests/e2e/src/app/error + - shard: 1/4 + artifact: shard-1 + - shard: 2/4 + artifact: shard-2 + - shard: 3/4 + artifact: shard-3 + - shard: 4/4 + artifact: shard-4 permissions: contents: read uses: ./.github/workflows/db-backed-bun-job.yml @@ -183,8 +162,8 @@ jobs: download-artifact-name: e2e-svelte-kit download-artifact-path: .svelte-kit job-phase: ci:e2e:test - e2e-test-paths: ${{ matrix.paths }} - upload-artifact-name: playwright-report-${{ matrix.scope }} + e2e-shard: ${{ matrix.shard }} + upload-artifact-name: playwright-report-${{ matrix.artifact }} upload-artifact-path: playwright-report/ upload-artifact-no-files-found: ignore upload-artifact-always: true diff --git a/.github/workflows/db-backed-bun-job.yml b/.github/workflows/db-backed-bun-job.yml index 69a26330a..5f55d7ff3 100644 --- a/.github/workflows/db-backed-bun-job.yml +++ b/.github/workflows/db-backed-bun-job.yml @@ -13,7 +13,7 @@ on: job-phase: required: true type: string - e2e-test-paths: + e2e-shard: required: false type: string default: "" @@ -127,28 +127,19 @@ jobs: - name: Run job phase shell: bash env: - E2E_TEST_PATHS: ${{ inputs.e2e-test-paths }} + E2E_SHARD: ${{ inputs.e2e-shard }} JOB_PHASE: ${{ inputs.job-phase }} run: | run_e2e_test_phase() { - local -a paths=() - local path - - while IFS= read -r path; do - if [ -n "$path" ]; then - paths+=("$path") - fi - done <<< "$E2E_TEST_PATHS" - - if [ "${#paths[@]}" -eq 0 ]; then - echo "::error::ci:e2e:test requires e2e-test-paths." + if [[ ! "$E2E_SHARD" =~ ^[1-9][0-9]*/[1-9][0-9]*$ ]]; then + echo "::error::ci:e2e:test requires e2e-shard in current/total format." exit 1 fi bun run app:prepare bun run db:migrate:deploy bunx prisma db seed - bunx playwright test "${paths[@]}" + bunx playwright test --shard="$E2E_SHARD" } case "$JOB_PHASE" in diff --git a/.github/workflows/static-sync.yml b/.github/workflows/static-sync.yml index 984bd2b4b..58e3f1f8f 100644 --- a/.github/workflows/static-sync.yml +++ b/.github/workflows/static-sync.yml @@ -72,7 +72,7 @@ jobs: DATABASE_URL: ${{ secrets.DATABASE_URL }} STATIC_SNAPSHOT_URL: ${{ vars.STATIC_SNAPSHOT_URL || 'https://static.life-ustc.tiankaima.dev/life-ustc-static.sqlite' }} STATIC_LOADER_MIN_SEMESTER: ${{ vars.STATIC_LOADER_MIN_SEMESTER || '401' }} - STATIC_LOADER_DRY_RUN: ${{ inputs.dry-run }} + STATIC_LOADER_DRY_RUN: ${{ inputs.dry-run || 'false' }} STATIC_LOADER_BOOTSTRAP_IMPORT_STATE: ${{ github.event_name == 'workflow_dispatch' && inputs.bootstrap_import_state || false }} STATIC_LOADER_RETIRE_MISSING_SECTIONS: ${{ github.event_name == 'workflow_dispatch' && inputs.retire_missing_sections || false }} STATIC_LOADER_EXPECTED_SNAPSHOT_SHA256: ${{ github.event_name == 'workflow_dispatch' && inputs.expected_snapshot_sha256 || '' }} diff --git a/README.md b/README.md index b24de6c5b..3678d0f28 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ contracts live in [docs/contracts/](./docs/contracts/). ```bash bun install --frozen-lockfile +bun run hooks:install cp .env.example .env docker compose -f docker-compose.dev.yml up -d bun run app:prepare diff --git a/docs/observability.md b/docs/observability.md index 87cd1e6f0..9d78c723c 100644 --- a/docs/observability.md +++ b/docs/observability.md @@ -14,7 +14,9 @@ high-cardinality resource IDs. ## Endpoints -No internal operational metrics/readiness endpoints are exposed by the app. +`GET /api/health` is a public, unauthenticated process-liveness endpoint that +returns `ok`. No internal metrics or dependency-readiness endpoints are exposed +by the app. ## Alerts diff --git a/examples/catalog_exam.json b/examples/catalog_exam.json deleted file mode 100644 index ddf8eb786..000000000 --- a/examples/catalog_exam.json +++ /dev/null @@ -1,71 +0,0 @@ -[ - { - "id": 11130, - "examType": 1, - "startTime": 930, - "endTime": 1130, - "examBatch": { - "id": 1137, - "name": "期中考试" - }, - "examDate": "2025-11-16", - "examRooms": [ - { - "room": "G3-109", - "count": 64 - } - ], - "examTakeCount": 126, - "examMode": "笔试(半开卷)", - "lesson": { - "actualPeriods": 58, - "classType": { - "en": "理论实验课", - "cn": "计划内与自由选修" - }, - "code": "011144.01", - "compulsory": true, - "course": { - "id": 16095, - "code": "011144", - "credits": 3.5, - "en": "Computer Networks", - "cn": "计算机网络" - }, - "courseCategory": { - "en": null, - "cn": "本科计划内课程" - }, - "courseGradation": { - "en": "Professional Basic Courses", - "cn": "专业基础" - }, - "courseType": { - "en": "理论实验课", - "cn": "理论实验课" - }, - "education": { - "en": "Undergraduate", - "cn": "本科" - }, - "graduateAndPostgraduate": false, - "id": 166896, - "openDepartment": { - "code": "011", - "en": "Department of Computer Science and Technology", - "cn": "计算机科学与技术系" - }, - "teacherAssignmentList": [ - { - "id": 2308, - "en": "Hua Bei", - "cn": "华蓓" - } - ] - }, - "monitors": [], - "spvisors": [], - "grades": "2023", - "adminclasseNames": "23计算机科学与技术*(245)" - } -] diff --git a/examples/catalog_lesson.json b/examples/catalog_lesson.json deleted file mode 100644 index 2c6c78724..000000000 --- a/examples/catalog_lesson.json +++ /dev/null @@ -1,77 +0,0 @@ -[ - { - "id": 165185, - "code": "IS4007.01", - "period": 30, - "periodsPerWeek": 5, - "credits": 1, - "dateTimePlaceText": "GT-B110: 2(3,4);GT-B110: 4(3,4,5)", - "dateTimePlacePersonText": { - "cn": "2~3周 GT-B110 :2(3,4) 苗付友\n2~3周 GT-B110 :4(3,4,5) 苗付友" - }, - "stdCount": 51, - "limitCount": 75, - "graduateAndPostgraduate": false, - "course": { - "id": 145426, - "code": "IS4007", - "cn": "鸿蒙生态学堂", - "en": "HarmonyOS" - }, - "courseType": { - "cn": "理论实验课", - "en": "理论实验课" - }, - "courseGradation": { - "cn": "专业选修", - "en": "Major Selectives" - }, - "courseCategory": { - "cn": "本科计划内课程", - "en": null - }, - "courseClassify": { - "cn": null, - "en": null - }, - "openDepartment": { - "college": true, - "code": "221", - "cn": "网络空间安全学院", - "en": "School of Cyber Science and Technology" - }, - "campus": { - "cn": "高新区", - "en": "high and new campus" - }, - "examMode": { - "cn": "大作业(论文、报告、项目或作品等)", - "en": "Course Assignment(Term Paper, Report, Project, Works Etc)" - }, - "teachLang": { - "cn": "中文", - "en": "1" - }, - "education": { - "cn": "本科", - "en": "Undergraduate" - }, - "classType": { - "cn": "计划内与自由选修", - "en": "Major Courses" - }, - "teacherAssignmentList": [ - { - "cn": "苗付友", - "en": "Miao Fuyou", - "departmentCode": "215" - } - ], - "adminClasses": [ - { - "cn": "22信息安全*", - "en": "22 Information Security*" - } - ] - } -] diff --git a/examples/catalog_semester.json b/examples/catalog_semester.json deleted file mode 100644 index d55a0fc64..000000000 --- a/examples/catalog_semester.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "id": 1, - "nameZh": "2002年夏季学期", - "code": "20013", - "start": "2002-07-01", - "end": "2002-07-31", - "isLast": false - } -] diff --git a/examples/jw_datum.json b/examples/jw_datum.json deleted file mode 100644 index ff2821266..000000000 --- a/examples/jw_datum.json +++ /dev/null @@ -1,170 +0,0 @@ -{ - "result": { - "lessonList": [ - { - "id": 133830, - "code": "MATH1001.01", - "name": "21数学学院", - "courseId": 9205, - "bizTypeId": 2, - "courseName": "数学分析(A1)", - "courseTypeName": "理论课", - "teacherAssignmentList": [ - { - "role": "MAJOR", - "code": "00353", - "teacherId": 1760, - "personId": 4369, - "name": "任广斌", - "age": 60, - "titleName": "教授", - "title": { - "nameZh": "教授", - "nameEn": "js", - "id": 69, - "code": "18", - "enabled": true, - "bizTypeAssocs": [1], - "specialistPositionLevelAssoc": 1, - "bizTypeIds": [1], - "transient": false, - "name": "教授" - }, - "period": 120, - "teacherLessonType": { - "id": 1, - "nameZh": "主讲", - "nameEn": null, - "code": "01", - "role": "MAJOR", - "enabled": true - }, - "contactInfo": { - "email": null, - "telephone": null, - "mobile": null, - "address": null, - "postcode": null, - "qq": null, - "wechat": null - }, - "weekIndices": [], - "weekIndicesMsg": null - } - ], - "requiredPeriodInfo": { - "total": 120.0, - "weeks": 17, - "theory": 120.0, - "theoryUnit": null, - "requireTheory": null, - "practice": 0.0, - "practiceUnit": null, - "requirePractice": null, - "test": null, - "testUnit": null, - "requireTest": null, - "experiment": 0.0, - "experimentUnit": null, - "requireExperiment": null, - "machine": null, - "machineUnit": null, - "requireMachine": null, - "design": null, - "designUnit": null, - "requireDesign": null, - "periodsPerWeek": 6.0, - "timesPerWeek": 3 - }, - "actualPeriods": 102, - "scheduleState": "NOT_FULLY_SCHEDULED", - "limitCount": 90, - "stdCount": 98, - "suggestScheduleWeeks": [2], - "suggestScheduleWeekInfo": "2-18", - "campusId": 9, - "roomTypeId": null, - "adminclassIds": [20425], - "remark": null, - "scheduleRemark": null, - "adminclasses": [ - { - "id": 20425, - "nameZh": "21数学学院", - "nameEn": "21 Maths", - "code": "21DL001", - "grade": "2021", - "stdCount": 171, - "planCount": 0, - "enabled": true, - "abbrZh": "21数院", - "abbrEn": "21DL001" - } - ], - "scheduleJsonParams": [], - "selectedStdCount": 0 - } - ], - "scheduleList": [ - { - "lessonId": 133830, - "scheduleGroupId": 144758, - "periods": 2, - "date": "2021-09-13", - "room": { - "id": 91, - "nameZh": "5102", - "nameEn": "5102", - "code": "5102", - "building": { - "id": 3, - "nameZh": "第五教学楼", - "nameEn": "NO.5", - "code": "5", - "campus": { - "id": 9, - "nameZh": "东区", - "nameEn": "east campus", - "code": "1" - } - }, - "roomType": { - "id": 24, - "nameZh": "多媒体教室", - "nameEn": "Multimedia Classroom", - "code": "2" - }, - "floor": 1, - "virtual": false, - "seatsForLesson": 208, - "remark": "四块黑板", - "seats": 0 - }, - "weekday": 1, - "startTime": 945, - "endTime": 1120, - "teacherId": 1760, - "personId": 4369, - "personName": "任广斌", - "experiment": null, - "customPlace": null, - "lessonType": null, - "weekIndex": 2, - "exerciseClass": null, - "startUnit": 0, - "endUnit": 0 - } - ], - "scheduleGroupList": [ - { - "id": 144758, - "lessonId": 133830, - "no": 0, - "limitCount": 90, - "stdCount": 99, - "actualPeriods": 102, - "default": true - } - ] - } -}