feat(ci): add GitHub Actions CI/CD workflow and Dockerfiles - #2
Merged
Conversation
- Add CI workflow with lint, type-check, test, build, and docker jobs - Add multi-stage Dockerfiles for backend (NestJS), console, and management (Vue + nginx) - Add nginx configs for SPA routing with security headers - Add .dockerignore for optimized Docker builds - Remove obsolete .husky/pre-commit hook CI runs on push/PR to main with MySQL and Redis services for backend tests. Docker images pushed to ghcr.io on main branch merges.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
## Phase 1: Critical Security Fixes - Docker sandbox for secure code execution with resource limits - Test case management system with hidden test cases - Rate limiting with tiered limits per endpoint type ## Phase 2: Core Features - Global search with MeiliSearch integration - Real-time notifications via Socket.IO with Redis adapter - Achievement system with user progress tracking - Multi-language support (JS, TS, Python, Java, C++, Go) ## Phase 3.1: Code Editor Enhancement - Theme switching with localStorage persistence - Font size adjustment (10-24px) - Code templates for 7 languages (16 templates) - Keyboard shortcuts reference modal ## Phase 3.2: Loading States & Error Handling - Global loading overlay with spinner - Error boundary component for graceful error handling - Retry button with exponential backoff - Offline indicator with network status detection - Global error handler plugin with toast notifications
- Add Skill Radar Chart to PersonalView showing problem-solving strengths by tag - Add GET /users/:id/skills endpoint to aggregate solved problems by tags - Implement virtual scrolling in DataTable component using @tanstack/vue-virtual - Add database indexes for Problem (difficulty, is_published, is_deleted) and Submission (problem_id, created_at) - Add @tanstack/vue-virtual package for efficient rendering of large lists - Add i18n translations for skills section in English and Chinese
Backend features: - Add backup/recovery system with scheduling and restore capabilities - Add email management service with template support - Add system monitoring with real-time metrics - Implement cache interceptor for response optimization - Enhance code sandbox with Docker and VM support - Add rate limiting with custom throttle guard - Improve test case management with hidden test support Console features: - Add loading overlay, error boundary, and skeleton components - Enhance virtual scrolling in DataTable component - Improve error handling and user feedback Management features: - Add system views (Backup, Email, Monitoring) - Add hidden test cases editor component - Improve router configuration with lazy loading Cleanup: - Remove eslintcache files from version control
…nd new features - Fix backend throttle guard dependency injection using getOptionsToken/getStorageToken - Add session flag to prevent unnecessary /auth/me API calls for unauthenticated users - Integrate socket.io-client for real-time WebSocket communication - Improve loading states, error boundaries, and retry mechanisms - Enhance editor components (keyboard shortcuts, code templates, toolbar) - Add new features: achievements system, user dashboard, search functionality - Improve notification store with real-time updates - Enhance management views (backup, email, monitoring, test cases)
Owner
Author
New Changes AddedThis PR now includes additional enhancements beyond the original CI/CD workflow: Backend Fixes
Auth Optimization
New Features
Component Improvements
Files Changed
|
DavidHLP
added a commit
that referenced
this pull request
Apr 5, 2026
管理后台鉴权初始化(CRITICAL #1): - management/src/main.ts: 新增完整 auth bootstrap 流程 - management/src/stores/auth.ts: 新增 setupSessionExpiredCallback + clearUser WebSocket Token 安全泄露(CRITICAL #2): - console/src/lib/socket.ts: 移除 URL ?token= 参数,token 改走 connectHeaders - console/src/composables/contest/useContestSocket.ts: 同上 OAuth 重定向 URL 统一(CRITICAL #4): - management/src/views/auth/components/OAuthButton.vue: 相对路径→绝对路径 Router Guard 错误处理(HIGH #5): - console/src/router/index.ts: catch 块增加 ApiError instanceof 判断 WebSocket Token 传递优化(HIGH #6): - useContestSocket.ts: 已正确从 socket.ts 导入 getTokenFromCookie verifyAuth 绕过 axios 拦截器(HIGH #7): - console/src/utils/auth.ts: fetch() → apiGet() 性能优化(HIGH #9): - console/src/contexts/AuthContext.ts: setInterval(1000) → watch(isAuthenticated) 代码质量提升(MEDIUM): - console/src/stores/auth.ts: 移除 LOGOUT_KEY localStorage 追踪 - 新增 hasAuthCookie() 提前检查,173 行净减少 - clearUser() 重置 status="idle" 允许重新初始化 - 移除所有调试 console.log Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DavidHLP
added a commit
that referenced
this pull request
Jun 8, 2026
Three defects from docs/comments-api-test-report.md (curl test report): #1 GET /admin/comments/{type}/{id} with invalid 'type' returned HTTP 500 instead of 400. Root cause: `@PathVariable @Pattern` on `@Validated` controllers throws ConstraintViolationException which had no `@ExceptionHandler` in GlobalExceptionHandler, falling through to handleGenericException. Fix: add handleConstraintViolation mirroring the response shape of the existing handleValidationException (@Valid @RequestBody). #2 unflagComment left flagged_reason / flagged_at stale in the row even though the service code called setFlaggedReason(null) / setFlaggedAt(null). Root cause: MyBatis-Plus FieldStrategy.NOT_NULL (the default) silently drops null columns in updateById, and the entity-level @TableField updateStrategy may apply similar rules. The setter was a no-op at the SQL layer. Fix: switch unflagComment to mapper.update(null, LambdaUpdateWrapper) with explicit .set(field, null) clauses. #3 deleteComment set only the audit columns (deleted_at, deleted_by) but is_deleted remained 0, so GET endpoints happily returned already-soft-deleted comments. Same root cause as #2 plus the Boolean wrapper for is_deleted. Fix: switch to LambdaUpdateWrapper with explicit .set(IsDeleted, true). bulkCommentAction automatically benefits since it dispatches to the patched deleteComment / unflagComment methods. Verification: real curl regression against ulticode-9001 (PM2, dev profile) — three defects each return the expected 2xx/4xx and the DB columns reflect the intended final state. Tests: AdminCommentServiceImplTest covers 3 smoke cases (construct, invalid-type throw, deleteComment unknown-id throw). Full mutator SQL inspection requires `@SpringBootTest` because MyBatis-Plus LambdaUpdateWrapper relies on a Spring-initialized lambda-method cache (AbstractLambdaWrapper.tryInitCache) that is absent under plain Mockito; integration coverage is recorded in docs/comments-api-test-report.md §9. Audit: AuditContext.setUserId / setOldValues / setNewValues calls are preserved unchanged before each mapper.update invocation so the @Audited behavior on the three methods is identical to before. Out of scope: getComment / getComments queries do not yet filter `is_deleted=1` (GET still returns 200 for a deleted row). Tracked in a separate follow-up plan: .claude/PRPs/plans/admin-comments-get-filter-soft-deleted.plan.md
DavidHLP
added a commit
that referenced
this pull request
Jun 8, 2026
Fixes three defects from docs/comments-api-test-report.md: #1 GET /admin/comments/{type}/{id} invalid 'type' (500 → 400) #2 unflagComment left flagged_reason / flagged_at stale (null 字段未被 MyBatis-Plus 持久化) #3 deleteComment only set audit columns, is_deleted remained 0 Plus unit test scaffolding and follow-up plan for getComment soft-delete filtering.
DavidHLP
added a commit
that referenced
this pull request
Jun 8, 2026
…_ADMIN + rankings 404 + is_deleted filter + Unicode title + @Audited + new ErrorCode + controller XSS guard + mutator tests) 修复 docs/contests-api-test-report.md 报告的 7 处缺陷 (1 HIGH + 6 MEDIUM/LOW): 缺陷 #1 (HIGH) — DELETE 软删除 is_deleted 字段未持久化 - 根因:Contest.isDeleted 字段 @TableLogic + mapper.updateById(entity) 自动忽略逻辑删除字段 - 修复:改用 mapper.update(null, LambdaUpdateWrapper.set(...)) 显式写入 缺陷 #2 — PATCH on FINISHED/RUNNING 比赛无状态守卫 - 修复:service 层加 status guard,仅允许 UPCOMING 修改 - 新增 ErrorCode CONTEST_ONLY_UPDATE_UPCOMING (70006) 区分注册与修改语义 缺陷 #3 — service 层 hasRole("ADMIN") 拒绝 SUPER_ADMIN,与控制器 @PreAuthorize 不一致 - 修复:SecurityUtil 新增 hasAnyRole(String...) 工具方法,ContestServiceImpl 7 处 mutator 替换 缺陷 #4 — CreateContestDTO/UpdateContestDTO title @pattern 拒绝 CJK 字符 - 修复:正则放宽为 [\p{L}\p{N}\s\p{P}]+ 接受 Unicode 字母 - AdminContestController 新增 rejectUnsafeTitleChars helper 显式排除 < 和 > 缺陷 #5 — GET /admin/contest/{id}/rankings 对不存在 contest 返 200+空 - 修复:getAdminContestRanking 入口加 contest 存在性 + is_deleted 校验 缺陷 #6 — addProblem/removeProblem/startContest/endContest 未校验 is_deleted - 修复:4 个 mutator 入口加 .filter(c -> !isDeleted).orElseThrow(COMPETITION_NOT_FOUND) 缺陷 #7 — @Audited 注解缺失导致 audit_logs 无 contest 记录 - 修复:7 个 mutator 方法加 @Audited + AuditContext.setOldValues/setNewValues/setUserId - 复用 AuditActionUtil.CREATE_CONTEST/UPDATE_CONTEST/DELETE_CONTEST 常量 新增测试 ContestServiceImplMutatorTest:5 case 覆盖缺陷 #2/#5/#6 的状态守卫 + 软删除过滤 + 404 路径。 缺陷 #1 的 LambdaUpdateWrapper.getSqlSet() 断言需 @SpringBootTest 上下文,保留为 follow-up, 运行时 curl 验证已确认三列同改 (docs/contests-api-test-report.md §3.5)。 验证: - mvnw compile -B:0 错误 - mvnw test -Dtest=ContestServiceImplMutatorTest:5/5 通过,2.0s - 运行时 curl 5/5 critical 缺陷修复确认 (PM2 重启 ulticode-9001) Artifacts: - Plan: .claude/PRPs/plans/completed/admin-contests-mutator-fixes.plan.md - Report: .claude/PRPs/reports/admin-contests-mutator-fixes-report.md - Review v1: .claude/reviews/admin-contests-mutator-review.md - Review v2: .claude/reviews/admin-contests-mutator-review-v2.md - Test report: docs/contests-api-test-report.md
DavidHLP
added a commit
that referenced
this pull request
Jun 9, 2026
…ject empty PATCH fields Fixes4 defects in AdminTagController discovered via docs/admin-tags-test-plan.md: - Bug #1: GET/DELETE /admin/tags/{id} without ?type= returned500. Added MissingServletRequestParameterException handler in GlobalExceptionHandler returning400 with field-level error map (mirrors handleConstraintViolation). - Bug #2: ?type=GARBAGE silently fell back to PROBLEM. Added @validated + @pattern on controller query params; added @pattern on TagQueryDTO.type, UpdateTagDTO.type, CreateTagDTO.type. New dto.tag.TagTypes class is the single source of truth for the whitelist (PROBLEM|FORUM). Service-layer normalizeType() helper enforces the same whitelist for direct callers. - Bug #3: PROBLEM tag list ignored sortBy. getProblemTags now dispatches on usageCount / createdAt / slug / label, mirroring getForumTags. - Bug #4: PATCH with empty name silently ignored. Added @SiZe(min=1) on UpdateTagDTO.name and slug; @NotNull already covered type. Adds39 regression tests (15 Mockito service +24 WebMvcTest controller) with exact code=40000 + data.<field> assertions. Co-Authored-By: Claude Opus4.8 <noreply@anthropic.com>
DavidHLP
pushed a commit
that referenced
this pull request
Jun 11, 2026
… handler, compute real progress Fixes 6 issues from docs/achievement-api-test-report-2026-06-11.md plus 2 newly-discovered typeHandler bugs (Bug #7, Bug #8) surfaced during validation. CRITICAL #1 — escape `key` column via @TableField(value = "`key`") - Achievement.java:19 — affects MyBatis-Plus auto-generated column lists in selectById / selectPage / selectBatchIds. - Without this, every SQL that lists the achievements columns fails with 'You have an error in your SQL syntax ... near key,...'. CRITICAL #2 — escape `key` in @select SQL - AchievementMapper.findByKey:24 — WHERE clause now backticks. - Fixes admin POST /achievements which called findByKey for dedup. HIGH #3 — T2b /achievements/{invalid} now returns 404 (was 500) - Follows from CRITICAL #1+#2: the BadSqlGrammarException no longer fires before the business-level ACHIEVEMENT_NOT_FOUND can be raised. MEDIUM #4 — BadSqlGrammarException → DATABASE_ERROR (50001) - GlobalExceptionHandler:227 — new handler preserves root cause in server log while returning generic 'Database error' to clients. - Pairs with existing MyBatisSystemException / BindingException / DataIntegrityViolationException handlers (no fallback to 50000 'Unknown error'). LOW #5 — getUserAchievements progress is no longer hard-coded to 0 - AchievementServiceImpl:280 — mirrors getUserProgress logic, reads criteria.type from JSON Map, dispatches to SubmissionMapper counter. LOW #6 — re-scoped Javadoc: getUserAchievements and getUserProgress are NOT duplicates (verified UserController /me/achievements/progress uses the latter). Both serve different endpoints with different DTO shapes. Bug #7 — findAllActive missing JacksonTypeHandler - AchievementMapper:46 — @select bypassed @TableField(typeHandler= JacksonTypeHandler.class), returning criteria=null. - Converted to default method via BaseMapper.selectList. Bug #8 — findByKey same typeHandler bypass - Discovered by AchievementMapperIT (L2) on first run; findByKey_criteriaIsDeserializedAsMap failed. - AchievementMapper:24 — also converted to default method. - Latent: only caller (create) never read criteria. Refactors - M1 (review): getUserAchievements extracted buildProgressDTO helper (52 → 16 lines). - L1 (review): findAllActive uses LambdaQueryWrapper<Achievement> method refs instead of string column names. - L3 (review): Javadoc clarified the two service methods. Tests + AchievementMapperSQLGuardTest: 3 reflection-guard tests (CRITICAL #1, CRITICAL #2, no @select on findAllActive/findByKey). + AchievementMapperIT: 5 Testcontainers MySQL 8.0 IT tests verifying JacksonTypeHandler runtime behavior + Bug #7/#8 regression guards. + AchievementServiceTest: 3 progress tests + 2 new mock fields (SubmissionMapper, ContestParticipantMapper). + GlobalExceptionHandlerTest: 2 BadSqlGrammarException tests. Validation - compile / package: BUILD SUCCESS - Unit tests: 36/36 pass - Testcontainers IT: 5/5 pass - 8 curl integration tests on worktree:9091: all match expected HTTP codes (200/400/404/409) and real progress values (admin 6 accepted problems → progress=6 for all 3 rows). Refs: docs/achievement-api-test-report-2026-06-11.md, .claude/PRPs/plans/completed/achievement-api-fixes.plan.md, .claude/PRPs/reports/achievement-api-fixes-report.md, .claude/reviews/achievement-api-fixes-review.md
DavidHLP
pushed a commit
that referenced
this pull request
Jun 11, 2026
…dation Merges the fix/achievement-api worktree changes: - CRITICAL #1, #2 (MySQL reserved word `key`) - HIGH #3 (T2b 404) - MEDIUM #4 (BadSqlGrammarException handler) - LOW #5 (real progress computation) - LOW #6 (Javadoc re-scoped) - Bug #7 (findAllActive typeHandler) - Bug #8 (findByKey typeHandler, IT-discovered) M1/L1/L2/L3/L4 from .claude/reviews/achievement-api-fixes-review.md all addressed. Test coverage: 36 unit + 5 Testcontainers IT.
DavidHLP
pushed a commit
that referenced
this pull request
Jun 13, 2026
… / reaper reclaim codex 6-commit 对抗审查发现 3 个 P1 真缺陷,本 commit 全修。ADR-003 Status 保持 Accepted(修复等价于补完 F12 验证)。 P1 #1: 真 cutover 不发生 (SubmissionServiceImpl) - 问题: use-judge-outbox=true 时 submit 写 is_shadow=true,但 claimRealDispatch 只选 is_shadow=0 → dispatcher 永远不接收 新行,旧 RQueue 仍是唯一 active producer - 修复: is_shadow = !judgeQueueUsePort (切流时写 is_shadow=false); portActive=true 时**不**调 enqueueJudgeJob (避免双投递); portActive=false 时调 RQueue (M3a 影子 + legacy 真投递) - 范围: 仅改 SubmissionServiceImpl.submit 主路径; AdminSubmissionServiceImpl.rejudge + JudgingLeaseReaper.afterCommit 两条次路径 commit message 标记 follow-up,影响低(rejudge 与 lease 恢复频次远低于 submit) P1 #2: stream.add 失败时静默丢消息 (RedissonStreamsJudgeQueueAdapter) - 问题: SETNX 成功但 stream.add 抛异常时,dedup key 未清除, dispatcher retry 时 enqueue 误判已投递,outbox 标 SENT 但 stream 无 entry → 消息永久丢失 - 修复: try/catch 包裹 stream.add,失败时 delete bucket (dedup key rollback) 后 rethrow。原 JSON 序列化失败时 delete 已存在,新 路径覆盖同样的回滚契约 P1 #3: reaper reclaim 路径无效 (UnackedStreamEntriesReaper + JudgeWorkerProcessor) - 问题: claimIdle 返回 reclaimed handle 但 reaper 只 log 不消费; worker poll 用 neverDelivered() 不会读 PEL,reclaimed entry 永远不被消费 - 修复: reaper 注入 ObjectProvider<JudgeWorkerProcessor>(provider 模式让无 worker bean 时仍可编译),reclaim 后调 worker.processReclaimedHandle(port, handle); worker 加 processReclaimedHandle public 入口复用 processJobFromPort (fenced 核心) 未动: AdminSubmissionServiceImpl 3 处 rejudge 路径 + JudgingLeaseReaper 2 处 afterCommit 路径 (commit message 标记 follow-up;主路径修了 80% cutover 行为) ADR-003 Status: Accepted (保持)— 修复等价于补完 F12 验证,README 状态转换规则的"M3c merged + F12 验证"门禁现满足 验证: ./mvnw compile 通过, ./mvnw test 1091 tests / 8 失败 + 2 错误 与 M3a+M3b+M3c-1+M3c-2+M3c-3a+M3c-3b 预存基线一致, 本 commit 零回归(失败数未变)。
DavidHLP
pushed a commit
that referenced
this pull request
Jun 13, 2026
… perf + per-channel tests New test files (5): - NotificationDispatcherTest: 8 tests covering ADR-004 §4 #2 (channel failure isolation), #3 (intentId idempotency), #6 (latency < 50ms), category preference suppression, ledger short-circuit, failure_reason truncation, ledger state sanity. - NotificationChannelContractTest: ADR-004 §4 #1 — every intent has at least 1 supporting channel; matrix sanity for InApp/Email/WebSocket. - InAppNotificationChannelTest: channelId, supports, send → row-only path with type=recordSimpleName, metadata includes isAccepted, follow link + title. - EmailNotificationChannelTest: channelId, supports matrix, in-flight rejection, missing-email BusinessException, send routes to EmailService.sendEmail with templateId. - WebSocketNotificationChannelTest: channelId, achievement emits BadgeEarnedPayload (badgeTier slug), submission emits NotificationPayload with isAccepted, contest reminder type tag. EmailNotificationChannel.userMapper: package-private (was private) so unit tests in the same package can inject a mock directly. Production wiring is still via @Autowired(required=false). mvn test on the 5 new test classes + the 4 existing legacy tests: - 5 new classes: 28 tests, 0 failures - 4 existing tests: 30 tests, 0 failures (legacy flag-off path verified) Pre-existing ContestPublicControllerTest failure (ApplicationContext load issue, unrelated to ADR-004) confirmed to fail on main without my changes. Refs: docs/adr/ADR-004-notification-intents.md §4.
DavidHLP
pushed a commit
that referenced
this pull request
Jun 13, 2026
…t-skip 3 review findings addressed in 1 commit because they are all 1-line behavior fixes (no record schema change, no test infrastructure change): - EmailTemplates.forIntent: 5 null guards added to Map.of(...) calls (achievementName/contestTitle/replierUsername/title). Map.of throws NPE on null values; legacy code coalesced similar fields to '' but missed these. Fixes finding #1. - WebSocketNotificationChannel.send: 5 NotificationPayload type strings flipped from lowercase to UPPERCASE to match the legacy wire contract (SUBMISSION / CONTEST REMINDER / FOLLOW / REPLY / SYSTEM). Frontend branches on payload.type case-sensitively; this would have been a silent frontend regression. Fixes finding #2. - EmailNotificationChannel.send: missing-email path now silently returns (log.debug) instead of throwing BusinessException. ADR-004 §2.5 says email failures are best-effort; throwing caused warn-spam + a FAILED ledger row per dispatch for every user that hasn't filled in an email. Fixes finding #3. Tests: - WebSocketNotificationChannelTest updated to expect UPPERCASE type strings. - EmailNotificationChannelTest#sendSilentlySkipsWhenUserHasNoEmail replaces the old #sendThrowsBusinessExceptionWhenUserHasNoEmail. mvn test on the notification/follow/submission/email slice: green. Refs: docs/adr/ADR-004-notification-intents.md (M4d-1 review findings #1, #2, #3).
DavidHLP
added a commit
that referenced
this pull request
Jun 13, 2026
… / reaper reclaim codex 6-commit 对抗审查发现 3 个 P1 真缺陷,本 commit 全修。ADR-003 Status 保持 Accepted(修复等价于补完 F12 验证)。 P1 #1: 真 cutover 不发生 (SubmissionServiceImpl) - 问题: use-judge-outbox=true 时 submit 写 is_shadow=true,但 claimRealDispatch 只选 is_shadow=0 → dispatcher 永远不接收 新行,旧 RQueue 仍是唯一 active producer - 修复: is_shadow = !judgeQueueUsePort (切流时写 is_shadow=false); portActive=true 时**不**调 enqueueJudgeJob (避免双投递); portActive=false 时调 RQueue (M3a 影子 + legacy 真投递) - 范围: 仅改 SubmissionServiceImpl.submit 主路径; AdminSubmissionServiceImpl.rejudge + JudgingLeaseReaper.afterCommit 两条次路径 commit message 标记 follow-up,影响低(rejudge 与 lease 恢复频次远低于 submit) P1 #2: stream.add 失败时静默丢消息 (RedissonStreamsJudgeQueueAdapter) - 问题: SETNX 成功但 stream.add 抛异常时,dedup key 未清除, dispatcher retry 时 enqueue 误判已投递,outbox 标 SENT 但 stream 无 entry → 消息永久丢失 - 修复: try/catch 包裹 stream.add,失败时 delete bucket (dedup key rollback) 后 rethrow。原 JSON 序列化失败时 delete 已存在,新 路径覆盖同样的回滚契约 P1 #3: reaper reclaim 路径无效 (UnackedStreamEntriesReaper + JudgeWorkerProcessor) - 问题: claimIdle 返回 reclaimed handle 但 reaper 只 log 不消费; worker poll 用 neverDelivered() 不会读 PEL,reclaimed entry 永远不被消费 - 修复: reaper 注入 ObjectProvider<JudgeWorkerProcessor>(provider 模式让无 worker bean 时仍可编译),reclaim 后调 worker.processReclaimedHandle(port, handle); worker 加 processReclaimedHandle public 入口复用 processJobFromPort (fenced 核心) 未动: AdminSubmissionServiceImpl 3 处 rejudge 路径 + JudgingLeaseReaper 2 处 afterCommit 路径 (commit message 标记 follow-up;主路径修了 80% cutover 行为) ADR-003 Status: Accepted (保持)— 修复等价于补完 F12 验证,README 状态转换规则的"M3c merged + F12 验证"门禁现满足 验证: ./mvnw compile 通过, ./mvnw test 1091 tests / 8 失败 + 2 错误 与 M3a+M3b+M3c-1+M3c-2+M3c-3a+M3c-3b 预存基线一致, 本 commit 零回归(失败数未变)。
DavidHLP
added a commit
that referenced
this pull request
Jun 13, 2026
… perf + per-channel tests New test files (5): - NotificationDispatcherTest: 8 tests covering ADR-004 §4 #2 (channel failure isolation), #3 (intentId idempotency), #6 (latency < 50ms), category preference suppression, ledger short-circuit, failure_reason truncation, ledger state sanity. - NotificationChannelContractTest: ADR-004 §4 #1 — every intent has at least 1 supporting channel; matrix sanity for InApp/Email/WebSocket. - InAppNotificationChannelTest: channelId, supports, send → row-only path with type=recordSimpleName, metadata includes isAccepted, follow link + title. - EmailNotificationChannelTest: channelId, supports matrix, in-flight rejection, missing-email BusinessException, send routes to EmailService.sendEmail with templateId. - WebSocketNotificationChannelTest: channelId, achievement emits BadgeEarnedPayload (badgeTier slug), submission emits NotificationPayload with isAccepted, contest reminder type tag. EmailNotificationChannel.userMapper: package-private (was private) so unit tests in the same package can inject a mock directly. Production wiring is still via @Autowired(required=false). mvn test on the 5 new test classes + the 4 existing legacy tests: - 5 new classes: 28 tests, 0 failures - 4 existing tests: 30 tests, 0 failures (legacy flag-off path verified) Pre-existing ContestPublicControllerTest failure (ApplicationContext load issue, unrelated to ADR-004) confirmed to fail on main without my changes. Refs: docs/adr/ADR-004-notification-intents.md §4.
DavidHLP
added a commit
that referenced
this pull request
Jun 13, 2026
…t-skip 3 review findings addressed in 1 commit because they are all 1-line behavior fixes (no record schema change, no test infrastructure change): - EmailTemplates.forIntent: 5 null guards added to Map.of(...) calls (achievementName/contestTitle/replierUsername/title). Map.of throws NPE on null values; legacy code coalesced similar fields to '' but missed these. Fixes finding #1. - WebSocketNotificationChannel.send: 5 NotificationPayload type strings flipped from lowercase to UPPERCASE to match the legacy wire contract (SUBMISSION / CONTEST REMINDER / FOLLOW / REPLY / SYSTEM). Frontend branches on payload.type case-sensitively; this would have been a silent frontend regression. Fixes finding #2. - EmailNotificationChannel.send: missing-email path now silently returns (log.debug) instead of throwing BusinessException. ADR-004 §2.5 says email failures are best-effort; throwing caused warn-spam + a FAILED ledger row per dispatch for every user that hasn't filled in an email. Fixes finding #3. Tests: - WebSocketNotificationChannelTest updated to expect UPPERCASE type strings. - EmailNotificationChannelTest#sendSilentlySkipsWhenUserHasNoEmail replaces the old #sendThrowsBusinessExceptionWhenUserHasNoEmail. mvn test on the notification/follow/submission/email slice: green. Refs: docs/adr/ADR-004-notification-intents.md (M4d-1 review findings #1, #2, #3).
DavidHLP
added a commit
that referenced
this pull request
Jun 14, 2026
…rationProperties) Pre-P0-2: FeatureFlagsProperties 用 flat 字段 judgeQueueUsePort/judgeQueueEnvelopeVersion, 但 application.yml 用 nested key app.features.judge-queue.use-port。Spring Binder 的 kebab→camel 转换只作用于叶节点名,不作用于路径段,导致 YAML override 永远不生效, M3c cutover flag 静默卡在 false。 Fix: 改为 @NestedConfigurationProperty JudgeQueue inner class (usePort/envelopeVersion/cutoverAt), 与 app.features 整体 prefix 一致,方便后续 FlagCombinationValidator 单 bean 聚合校验。 cutover-at (F13 watermark) 之前被 Binder 静默忽略,本轮一并补字段。 调用方更新 (2 处): - JudgeWorkerProcessor.pollAndProcessFromPort - SubmissionServiceImpl.submit (portActive) Note: JudgeOutboxDispatcher 仍用 @value 直接读 YAML key (reviewer 补充 #1), task #7 统一切到 FeatureFlagsProperties,避免 YAML 双解析。 @ConditionalOnProperty 4 处 (RedissonStreamsJudgeQueueAdapter/QueueConfig/...) 不动 (reviewer 补充 #2 — 它们直接读 YAML key,P0-2 只修 API 暴露层)。 Tests: FeatureFlagsPropertiesBindingTest (3 case, 真实 Binder 端到端验证) + 回归 41 case 全 PASS (JudgeWorkerProcessorTest + P0-1 测试集)。 Refs: task #5 (P0-2) Co-Authored-By: Claude <noreply@anthropic.com>
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
…formanceStats deep modules Address the 2026-07-02 architecture review (candidates #1 and #2) by deepening SubmissionServiceImpl two ways. 1. SubmissionPerformanceStats — moves ~130 lines of pure percentile / distribution-bin math (computePerformanceStats, buildDistributionBins, calculateBetterThanPercentile, formatDistributionBin) out of the 1008-line service into a deep module (interface + DefaultSubmissionPerformanceStats), mirroring the SubmissionProjection / ProblemProjection pattern. Pure computation, zero I/O — now unit-testable without persistence mocks. 2. ContestSubmissionPort — inverts the submission -> contest dependency. SubmissionServiceImpl no longer imports any com.ulticode.modules.contest.* type: the four contest mappers and RealtimeService move behind a port owned by submission (recordSubmissionIfNeeded, isVirtualParticipation) and implemented by ContestSubmissionAdapter in the contest module (new contest/integration/ package). Behaviour is byte-for-byte identical (same RUNNING/STARTED gating, R6.2/F-06 clock selection, first-match-break, fire-and-forget semantics). Post-commit scoring stays event-driven via SubmissionJudgedEvent; only the synchronous same-transaction recording path was coupled (see ADR-0001). Net: SubmissionServiceImpl 1008 -> ~830 lines, 17 -> 14 constructor deps, submission module zero contest imports, submission unit tests mock 1 port instead of 4 contest mappers. SubmissionServiceImplTest/IT updated to the new constructor signature (stats wired real with the mocked SubmissionMapper so the existing percentile/bin assertions exercise the extracted math end-to-end). Verification: ./mvnw compile green; ./mvnw test green for all submission tests (SubmissionServiceImplTest passes). 9 pre-existing failures elsewhere (JavaLanguageProfileTest, AdminSolutionServiceImplTest, AdminProblemListControllerTest) are unrelated to this change — confirmed identical on a clean tree with and without these changes (1207 tests, 9 failures, same set). ADR-0001 records the decoupling decision and the rejected event-driven alternative (conflicts with D-04 same-transaction invariant). CONTEXT.md adds the domain glossary the architecture-review skill expects.
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
feat(ci): add GitHub Actions CI/CD workflow and Dockerfiles
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
管理后台鉴权初始化(CRITICAL #1): - management/src/main.ts: 新增完整 auth bootstrap 流程 - management/src/stores/auth.ts: 新增 setupSessionExpiredCallback + clearUser WebSocket Token 安全泄露(CRITICAL #2): - console/src/lib/socket.ts: 移除 URL ?token= 参数,token 改走 connectHeaders - console/src/composables/contest/useContestSocket.ts: 同上 OAuth 重定向 URL 统一(CRITICAL #4): - management/src/views/auth/components/OAuthButton.vue: 相对路径→绝对路径 Router Guard 错误处理(HIGH #5): - console/src/router/index.ts: catch 块增加 ApiError instanceof 判断 WebSocket Token 传递优化(HIGH #6): - useContestSocket.ts: 已正确从 socket.ts 导入 getTokenFromCookie verifyAuth 绕过 axios 拦截器(HIGH #7): - console/src/utils/auth.ts: fetch() → apiGet() 性能优化(HIGH #9): - console/src/contexts/AuthContext.ts: setInterval(1000) → watch(isAuthenticated) 代码质量提升(MEDIUM): - console/src/stores/auth.ts: 移除 LOGOUT_KEY localStorage 追踪 - 新增 hasAuthCookie() 提前检查,173 行净减少 - clearUser() 重置 status="idle" 允许重新初始化 - 移除所有调试 console.log Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
Three defects from docs/comments-api-test-report.md (curl test report): #1 GET /admin/comments/{type}/{id} with invalid 'type' returned HTTP 500 instead of 400. Root cause: `@PathVariable @Pattern` on `@Validated` controllers throws ConstraintViolationException which had no `@ExceptionHandler` in GlobalExceptionHandler, falling through to handleGenericException. Fix: add handleConstraintViolation mirroring the response shape of the existing handleValidationException (@Valid @RequestBody). #2 unflagComment left flagged_reason / flagged_at stale in the row even though the service code called setFlaggedReason(null) / setFlaggedAt(null). Root cause: MyBatis-Plus FieldStrategy.NOT_NULL (the default) silently drops null columns in updateById, and the entity-level @TableField updateStrategy may apply similar rules. The setter was a no-op at the SQL layer. Fix: switch unflagComment to mapper.update(null, LambdaUpdateWrapper) with explicit .set(field, null) clauses. #3 deleteComment set only the audit columns (deleted_at, deleted_by) but is_deleted remained 0, so GET endpoints happily returned already-soft-deleted comments. Same root cause as #2 plus the Boolean wrapper for is_deleted. Fix: switch to LambdaUpdateWrapper with explicit .set(IsDeleted, true). bulkCommentAction automatically benefits since it dispatches to the patched deleteComment / unflagComment methods. Verification: real curl regression against ulticode-9001 (PM2, dev profile) — three defects each return the expected 2xx/4xx and the DB columns reflect the intended final state. Tests: AdminCommentServiceImplTest covers 3 smoke cases (construct, invalid-type throw, deleteComment unknown-id throw). Full mutator SQL inspection requires `@SpringBootTest` because MyBatis-Plus LambdaUpdateWrapper relies on a Spring-initialized lambda-method cache (AbstractLambdaWrapper.tryInitCache) that is absent under plain Mockito; integration coverage is recorded in docs/comments-api-test-report.md §9. Audit: AuditContext.setUserId / setOldValues / setNewValues calls are preserved unchanged before each mapper.update invocation so the @Audited behavior on the three methods is identical to before. Out of scope: getComment / getComments queries do not yet filter `is_deleted=1` (GET still returns 200 for a deleted row). Tracked in a separate follow-up plan: .claude/PRPs/plans/admin-comments-get-filter-soft-deleted.plan.md
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
Fixes three defects from docs/comments-api-test-report.md: #1 GET /admin/comments/{type}/{id} invalid 'type' (500 → 400) #2 unflagComment left flagged_reason / flagged_at stale (null 字段未被 MyBatis-Plus 持久化) #3 deleteComment only set audit columns, is_deleted remained 0 Plus unit test scaffolding and follow-up plan for getComment soft-delete filtering.
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
…_ADMIN + rankings 404 + is_deleted filter + Unicode title + @Audited + new ErrorCode + controller XSS guard + mutator tests) 修复 docs/contests-api-test-report.md 报告的 7 处缺陷 (1 HIGH + 6 MEDIUM/LOW): 缺陷 #1 (HIGH) — DELETE 软删除 is_deleted 字段未持久化 - 根因:Contest.isDeleted 字段 @TableLogic + mapper.updateById(entity) 自动忽略逻辑删除字段 - 修复:改用 mapper.update(null, LambdaUpdateWrapper.set(...)) 显式写入 缺陷 #2 — PATCH on FINISHED/RUNNING 比赛无状态守卫 - 修复:service 层加 status guard,仅允许 UPCOMING 修改 - 新增 ErrorCode CONTEST_ONLY_UPDATE_UPCOMING (70006) 区分注册与修改语义 缺陷 #3 — service 层 hasRole("ADMIN") 拒绝 SUPER_ADMIN,与控制器 @PreAuthorize 不一致 - 修复:SecurityUtil 新增 hasAnyRole(String...) 工具方法,ContestServiceImpl 7 处 mutator 替换 缺陷 #4 — CreateContestDTO/UpdateContestDTO title @pattern 拒绝 CJK 字符 - 修复:正则放宽为 [\p{L}\p{N}\s\p{P}]+ 接受 Unicode 字母 - AdminContestController 新增 rejectUnsafeTitleChars helper 显式排除 < 和 > 缺陷 #5 — GET /admin/contest/{id}/rankings 对不存在 contest 返 200+空 - 修复:getAdminContestRanking 入口加 contest 存在性 + is_deleted 校验 缺陷 #6 — addProblem/removeProblem/startContest/endContest 未校验 is_deleted - 修复:4 个 mutator 入口加 .filter(c -> !isDeleted).orElseThrow(COMPETITION_NOT_FOUND) 缺陷 #7 — @Audited 注解缺失导致 audit_logs 无 contest 记录 - 修复:7 个 mutator 方法加 @Audited + AuditContext.setOldValues/setNewValues/setUserId - 复用 AuditActionUtil.CREATE_CONTEST/UPDATE_CONTEST/DELETE_CONTEST 常量 新增测试 ContestServiceImplMutatorTest:5 case 覆盖缺陷 #2/#5/#6 的状态守卫 + 软删除过滤 + 404 路径。 缺陷 #1 的 LambdaUpdateWrapper.getSqlSet() 断言需 @SpringBootTest 上下文,保留为 follow-up, 运行时 curl 验证已确认三列同改 (docs/contests-api-test-report.md §3.5)。 验证: - mvnw compile -B:0 错误 - mvnw test -Dtest=ContestServiceImplMutatorTest:5/5 通过,2.0s - 运行时 curl 5/5 critical 缺陷修复确认 (PM2 重启 ulticode-9001) Artifacts: - Plan: .claude/PRPs/plans/completed/admin-contests-mutator-fixes.plan.md - Report: .claude/PRPs/reports/admin-contests-mutator-fixes-report.md - Review v1: .claude/reviews/admin-contests-mutator-review.md - Review v2: .claude/reviews/admin-contests-mutator-review-v2.md - Test report: docs/contests-api-test-report.md
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
…ject empty PATCH fields Fixes4 defects in AdminTagController discovered via docs/admin-tags-test-plan.md: - Bug #1: GET/DELETE /admin/tags/{id} without ?type= returned500. Added MissingServletRequestParameterException handler in GlobalExceptionHandler returning400 with field-level error map (mirrors handleConstraintViolation). - Bug #2: ?type=GARBAGE silently fell back to PROBLEM. Added @validated + @pattern on controller query params; added @pattern on TagQueryDTO.type, UpdateTagDTO.type, CreateTagDTO.type. New dto.tag.TagTypes class is the single source of truth for the whitelist (PROBLEM|FORUM). Service-layer normalizeType() helper enforces the same whitelist for direct callers. - Bug #3: PROBLEM tag list ignored sortBy. getProblemTags now dispatches on usageCount / createdAt / slug / label, mirroring getForumTags. - Bug #4: PATCH with empty name silently ignored. Added @SiZe(min=1) on UpdateTagDTO.name and slug; @NotNull already covered type. Adds39 regression tests (15 Mockito service +24 WebMvcTest controller) with exact code=40000 + data.<field> assertions. Co-Authored-By: Claude Opus4.8 <noreply@anthropic.com>
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
… handler, compute real progress Fixes 6 issues from docs/achievement-api-test-report-2026-06-11.md plus 2 newly-discovered typeHandler bugs (Bug #7, Bug #8) surfaced during validation. CRITICAL #1 — escape `key` column via @TableField(value = "`key`") - Achievement.java:19 — affects MyBatis-Plus auto-generated column lists in selectById / selectPage / selectBatchIds. - Without this, every SQL that lists the achievements columns fails with 'You have an error in your SQL syntax ... near key,...'. CRITICAL #2 — escape `key` in @select SQL - AchievementMapper.findByKey:24 — WHERE clause now backticks. - Fixes admin POST /achievements which called findByKey for dedup. HIGH #3 — T2b /achievements/{invalid} now returns 404 (was 500) - Follows from CRITICAL #1+#2: the BadSqlGrammarException no longer fires before the business-level ACHIEVEMENT_NOT_FOUND can be raised. MEDIUM #4 — BadSqlGrammarException → DATABASE_ERROR (50001) - GlobalExceptionHandler:227 — new handler preserves root cause in server log while returning generic 'Database error' to clients. - Pairs with existing MyBatisSystemException / BindingException / DataIntegrityViolationException handlers (no fallback to 50000 'Unknown error'). LOW #5 — getUserAchievements progress is no longer hard-coded to 0 - AchievementServiceImpl:280 — mirrors getUserProgress logic, reads criteria.type from JSON Map, dispatches to SubmissionMapper counter. LOW #6 — re-scoped Javadoc: getUserAchievements and getUserProgress are NOT duplicates (verified UserController /me/achievements/progress uses the latter). Both serve different endpoints with different DTO shapes. Bug #7 — findAllActive missing JacksonTypeHandler - AchievementMapper:46 — @select bypassed @TableField(typeHandler= JacksonTypeHandler.class), returning criteria=null. - Converted to default method via BaseMapper.selectList. Bug #8 — findByKey same typeHandler bypass - Discovered by AchievementMapperIT (L2) on first run; findByKey_criteriaIsDeserializedAsMap failed. - AchievementMapper:24 — also converted to default method. - Latent: only caller (create) never read criteria. Refactors - M1 (review): getUserAchievements extracted buildProgressDTO helper (52 → 16 lines). - L1 (review): findAllActive uses LambdaQueryWrapper<Achievement> method refs instead of string column names. - L3 (review): Javadoc clarified the two service methods. Tests + AchievementMapperSQLGuardTest: 3 reflection-guard tests (CRITICAL #1, CRITICAL #2, no @select on findAllActive/findByKey). + AchievementMapperIT: 5 Testcontainers MySQL 8.0 IT tests verifying JacksonTypeHandler runtime behavior + Bug #7/#8 regression guards. + AchievementServiceTest: 3 progress tests + 2 new mock fields (SubmissionMapper, ContestParticipantMapper). + GlobalExceptionHandlerTest: 2 BadSqlGrammarException tests. Validation - compile / package: BUILD SUCCESS - Unit tests: 36/36 pass - Testcontainers IT: 5/5 pass - 8 curl integration tests on worktree:9091: all match expected HTTP codes (200/400/404/409) and real progress values (admin 6 accepted problems → progress=6 for all 3 rows). Refs: docs/achievement-api-test-report-2026-06-11.md, .claude/PRPs/plans/completed/achievement-api-fixes.plan.md, .claude/PRPs/reports/achievement-api-fixes-report.md, .claude/reviews/achievement-api-fixes-review.md
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
…dation Merges the fix/achievement-api worktree changes: - CRITICAL #1, #2 (MySQL reserved word `key`) - HIGH #3 (T2b 404) - MEDIUM #4 (BadSqlGrammarException handler) - LOW #5 (real progress computation) - LOW #6 (Javadoc re-scoped) - Bug #7 (findAllActive typeHandler) - Bug #8 (findByKey typeHandler, IT-discovered) M1/L1/L2/L3/L4 from .claude/reviews/achievement-api-fixes-review.md all addressed. Test coverage: 36 unit + 5 Testcontainers IT.
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
… / reaper reclaim codex 6-commit 对抗审查发现 3 个 P1 真缺陷,本 commit 全修。ADR-003 Status 保持 Accepted(修复等价于补完 F12 验证)。 P1 #1: 真 cutover 不发生 (SubmissionServiceImpl) - 问题: use-judge-outbox=true 时 submit 写 is_shadow=true,但 claimRealDispatch 只选 is_shadow=0 → dispatcher 永远不接收 新行,旧 RQueue 仍是唯一 active producer - 修复: is_shadow = !judgeQueueUsePort (切流时写 is_shadow=false); portActive=true 时**不**调 enqueueJudgeJob (避免双投递); portActive=false 时调 RQueue (M3a 影子 + legacy 真投递) - 范围: 仅改 SubmissionServiceImpl.submit 主路径; AdminSubmissionServiceImpl.rejudge + JudgingLeaseReaper.afterCommit 两条次路径 commit message 标记 follow-up,影响低(rejudge 与 lease 恢复频次远低于 submit) P1 #2: stream.add 失败时静默丢消息 (RedissonStreamsJudgeQueueAdapter) - 问题: SETNX 成功但 stream.add 抛异常时,dedup key 未清除, dispatcher retry 时 enqueue 误判已投递,outbox 标 SENT 但 stream 无 entry → 消息永久丢失 - 修复: try/catch 包裹 stream.add,失败时 delete bucket (dedup key rollback) 后 rethrow。原 JSON 序列化失败时 delete 已存在,新 路径覆盖同样的回滚契约 P1 #3: reaper reclaim 路径无效 (UnackedStreamEntriesReaper + JudgeWorkerProcessor) - 问题: claimIdle 返回 reclaimed handle 但 reaper 只 log 不消费; worker poll 用 neverDelivered() 不会读 PEL,reclaimed entry 永远不被消费 - 修复: reaper 注入 ObjectProvider<JudgeWorkerProcessor>(provider 模式让无 worker bean 时仍可编译),reclaim 后调 worker.processReclaimedHandle(port, handle); worker 加 processReclaimedHandle public 入口复用 processJobFromPort (fenced 核心) 未动: AdminSubmissionServiceImpl 3 处 rejudge 路径 + JudgingLeaseReaper 2 处 afterCommit 路径 (commit message 标记 follow-up;主路径修了 80% cutover 行为) ADR-003 Status: Accepted (保持)— 修复等价于补完 F12 验证,README 状态转换规则的"M3c merged + F12 验证"门禁现满足 验证: ./mvnw compile 通过, ./mvnw test 1091 tests / 8 失败 + 2 错误 与 M3a+M3b+M3c-1+M3c-2+M3c-3a+M3c-3b 预存基线一致, 本 commit 零回归(失败数未变)。
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
… perf + per-channel tests New test files (5): - NotificationDispatcherTest: 8 tests covering ADR-004 §4 #2 (channel failure isolation), #3 (intentId idempotency), #6 (latency < 50ms), category preference suppression, ledger short-circuit, failure_reason truncation, ledger state sanity. - NotificationChannelContractTest: ADR-004 §4 #1 — every intent has at least 1 supporting channel; matrix sanity for InApp/Email/WebSocket. - InAppNotificationChannelTest: channelId, supports, send → row-only path with type=recordSimpleName, metadata includes isAccepted, follow link + title. - EmailNotificationChannelTest: channelId, supports matrix, in-flight rejection, missing-email BusinessException, send routes to EmailService.sendEmail with templateId. - WebSocketNotificationChannelTest: channelId, achievement emits BadgeEarnedPayload (badgeTier slug), submission emits NotificationPayload with isAccepted, contest reminder type tag. EmailNotificationChannel.userMapper: package-private (was private) so unit tests in the same package can inject a mock directly. Production wiring is still via @Autowired(required=false). mvn test on the 5 new test classes + the 4 existing legacy tests: - 5 new classes: 28 tests, 0 failures - 4 existing tests: 30 tests, 0 failures (legacy flag-off path verified) Pre-existing ContestPublicControllerTest failure (ApplicationContext load issue, unrelated to ADR-004) confirmed to fail on main without my changes. Refs: docs/adr/ADR-004-notification-intents.md §4.
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
…t-skip 3 review findings addressed in 1 commit because they are all 1-line behavior fixes (no record schema change, no test infrastructure change): - EmailTemplates.forIntent: 5 null guards added to Map.of(...) calls (achievementName/contestTitle/replierUsername/title). Map.of throws NPE on null values; legacy code coalesced similar fields to '' but missed these. Fixes finding #1. - WebSocketNotificationChannel.send: 5 NotificationPayload type strings flipped from lowercase to UPPERCASE to match the legacy wire contract (SUBMISSION / CONTEST REMINDER / FOLLOW / REPLY / SYSTEM). Frontend branches on payload.type case-sensitively; this would have been a silent frontend regression. Fixes finding #2. - EmailNotificationChannel.send: missing-email path now silently returns (log.debug) instead of throwing BusinessException. ADR-004 §2.5 says email failures are best-effort; throwing caused warn-spam + a FAILED ledger row per dispatch for every user that hasn't filled in an email. Fixes finding #3. Tests: - WebSocketNotificationChannelTest updated to expect UPPERCASE type strings. - EmailNotificationChannelTest#sendSilentlySkipsWhenUserHasNoEmail replaces the old #sendThrowsBusinessExceptionWhenUserHasNoEmail. mvn test on the notification/follow/submission/email slice: green. Refs: docs/adr/ADR-004-notification-intents.md (M4d-1 review findings #1, #2, #3).
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
…rationProperties) Pre-P0-2: FeatureFlagsProperties 用 flat 字段 judgeQueueUsePort/judgeQueueEnvelopeVersion, 但 application.yml 用 nested key app.features.judge-queue.use-port。Spring Binder 的 kebab→camel 转换只作用于叶节点名,不作用于路径段,导致 YAML override 永远不生效, M3c cutover flag 静默卡在 false。 Fix: 改为 @NestedConfigurationProperty JudgeQueue inner class (usePort/envelopeVersion/cutoverAt), 与 app.features 整体 prefix 一致,方便后续 FlagCombinationValidator 单 bean 聚合校验。 cutover-at (F13 watermark) 之前被 Binder 静默忽略,本轮一并补字段。 调用方更新 (2 处): - JudgeWorkerProcessor.pollAndProcessFromPort - SubmissionServiceImpl.submit (portActive) Note: JudgeOutboxDispatcher 仍用 @value 直接读 YAML key (reviewer 补充 #1), task #7 统一切到 FeatureFlagsProperties,避免 YAML 双解析。 @ConditionalOnProperty 4 处 (RedissonStreamsJudgeQueueAdapter/QueueConfig/...) 不动 (reviewer 补充 #2 — 它们直接读 YAML key,P0-2 只修 API 暴露层)。 Tests: FeatureFlagsPropertiesBindingTest (3 case, 真实 Binder 端到端验证) + 回归 41 case 全 PASS (JudgeWorkerProcessorTest + P0-1 测试集)。 Refs: task #5 (P0-2) Co-Authored-By: Claude <noreply@anthropic.com>
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
…formanceStats deep modules Address the 2026-07-02 architecture review (candidates #1 and #2) by deepening SubmissionServiceImpl two ways. 1. SubmissionPerformanceStats — moves ~130 lines of pure percentile / distribution-bin math (computePerformanceStats, buildDistributionBins, calculateBetterThanPercentile, formatDistributionBin) out of the 1008-line service into a deep module (interface + DefaultSubmissionPerformanceStats), mirroring the SubmissionProjection / ProblemProjection pattern. Pure computation, zero I/O — now unit-testable without persistence mocks. 2. ContestSubmissionPort — inverts the submission -> contest dependency. SubmissionServiceImpl no longer imports any com.ulticode.modules.contest.* type: the four contest mappers and RealtimeService move behind a port owned by submission (recordSubmissionIfNeeded, isVirtualParticipation) and implemented by ContestSubmissionAdapter in the contest module (new contest/integration/ package). Behaviour is byte-for-byte identical (same RUNNING/STARTED gating, R6.2/F-06 clock selection, first-match-break, fire-and-forget semantics). Post-commit scoring stays event-driven via SubmissionJudgedEvent; only the synchronous same-transaction recording path was coupled (see ADR-0001). Net: SubmissionServiceImpl 1008 -> ~830 lines, 17 -> 14 constructor deps, submission module zero contest imports, submission unit tests mock 1 port instead of 4 contest mappers. SubmissionServiceImplTest/IT updated to the new constructor signature (stats wired real with the mocked SubmissionMapper so the existing percentile/bin assertions exercise the extracted math end-to-end). Verification: ./mvnw compile green; ./mvnw test green for all submission tests (SubmissionServiceImplTest passes). 9 pre-existing failures elsewhere (JavaLanguageProfileTest, AdminSolutionServiceImplTest, AdminProblemListControllerTest) are unrelated to this change — confirmed identical on a clean tree with and without these changes (1207 tests, 9 failures, same set). ADR-0001 records the decoupling decision and the rejected event-driven alternative (conflicts with D-04 same-transaction invariant). CONTEXT.md adds the domain glossary the architecture-review skill expects.
DavidHLP
added a commit
that referenced
this pull request
Jul 10, 2026
…es to constants Architecture review candidates #2 and #4: - Delete useRetry.ts (266 LOC) and its test (213 LOC) — HTTP retry logic already lives in request.ts at the seam; the composable reimplemented calculateDelay/sleep/shouldRetryError on top. - Move static code template data from useCodeTemplates composable (527 LOC) to constants/codeTemplates.ts (508 LOC). The composable wrapped immutable data in pointless computed() calls; the constants module exports plain functions. Fixes broken call site in CodeTemplatesModal.vue where the import was updated but the composable call was not. Verified: 355 console tests pass, type-check clean.
DavidHLP
added a commit
that referenced
this pull request
Jul 10, 2026
#1 SecurityUtil → CurrentUserProvider port - New CurrentUserProvider interface in common/auth/ - SecurityCurrentUserProvider @component adapter (wraps SecurityContextHolder) - 37 files migrated from static SecurityUtil calls to injected interface - Eliminates duplicated userId==null guards across controller layer #2 DashboardMapper+Service → DashboardStatsProjection - New DashboardStatsProjection interface + DefaultDashboardStatsProjection - Absorbs 7 private sub-aggregators + 6 mapper default methods - DashboardService + DashboardServiceImpl deleted - DashboardController now injects the projection directly - Mirrors ADR-0011 *Projection pattern #3 AdminCommentService → CommentModerator polymorphic seam - New CommentModerator interface with ForumCommentModerator + SolutionCommentModerator - AdminCommentServiceImpl refactored to thin router - Eliminates 5x duplicated forum/solution type-string switch #5 SubmissionMapper.calculateStreak → SubmissionStreakCalculator - New SubmissionStreakCalculator interface + JdbcSubmissionStreakCalculator - Streak algorithm (recursive CTE) now behind a JVM-testable interface - 3 production callers redirected + 3 test classes updated #7 Frontend wrapper cleanup - 8 re-export wrapper files deleted (markdown, useTheme, useTypographyDensity, cn) - tsconfig path remaps @/lib/utils → @/shared/auth-core/src/utils - shared/sidebar-menu cn() consolidated to import from auth-core - LOCALE_HEADER_KEY duplicate declarations removed #8 OAuthService → OAuthClient port + adapters - New OAuthClient interface + OAuthTokenResponse/OAuthUserInfo DTOs - GithubOAuthClient + GoogleOAuthClient @component adapters - OAuthService refactored to thin coordinator (221→195 LoC) - 11 unit tests covering dispatch, callback, DB upsert Production code compiles (BUILD SUCCESS). 53 test compilation errors from constructor signature changes — mechanical @mock field additions. Candidates #4 (analytics projections) and #6 (contest.ts split) pending.
DavidHLP
added a commit
that referenced
this pull request
Jul 10, 2026
Arch review #2. console/public and management/public theme-bootstrap.js were byte-identical copies kept in sync by hand, plus a verify script that only checked the two copies matched each other — nothing tied them to a canonical source, so the seam could not prevent drift. Promote shared/theme to the single source of truth: - shared/theme/bootstrap.js — canonical FOUC bootstrap (plain IIFE JS, served as <script src> in <head> before the bundle; mirrors applyThemeToDOM.ts isDarkMode + reads THEME_STORAGE_KEY = 'ulticode-theme'). No TS->JS compile step introduced (that would reshape the build); the source is plain JS. - scripts/sync-theme-bootstrap.mjs — regenerates both public copies from the source (`pnpm sync:theme-bootstrap`). - scripts/verify-theme-sync.mjs — upgraded: now asserts each copy is byte-identical to the SOURCE (not just to each other) + behavior markers, so an edit to a copy, or to the source without a re-sync, fails CI. - console/management package.json: add `sync:theme-bootstrap` entry. The generated copies' IIFE is byte-identical to the previous logic, so FOUC behavior is unchanged; only the leading comment + the generation contract change. No CSP nonce added (index.html has no CSP today); the single-source contract is ready for a future CSP that adds a nonce/hash to the <script src>. Verified: verify-theme-sync OK (6 markers + copies identical to source); console vue-tsc type-check clean.
DavidHLP
added a commit
that referenced
this pull request
Jul 10, 2026
…ale-composable Arch review 2026-07-10 candidate #2: locale-composable exported `<L extends SupportedLocale, C>` where its own `SupportedLocale = string` was a meaningless alias that shadowed @ulticode/i18n-storage's tighter `SupportedLocale = 'en-US' | 'zh-CN'`. Two packages, two SupportedLocale types — a real type contract conflict. Removed the redundant export. Generic bounds are now `L extends string` directly (type-identical, since the previous bound resolved to string anyway). The canonical union lives in @ulticode/i18n-storage; each app re-exports it from its @/i18n module.
DavidHLP
added a commit
that referenced
this pull request
Jul 10, 2026
…arch-review #2) Move the static ForumPostVOAssembler into a Spring-injected ForumPostProjection / DefaultForumPostProjection pair. Both read and write callers now depend on the same small interface (3-arg or 5-arg toPostVO) instead of threading 4–6 collaborators through static methods. - New ForumPostProjection interface owns the batch (5-arg) and single-item (3-arg) entry points; the deletion test passes — removing the projection recreates the same parameter lists in both callers. - DefaultForumPostProjection is the only place that touches ObjectMapper / voteService / mappers; helpers are private to the projection (parseTagList, parseMedia, attachVoteAndStats, attachCommunity, attachAuthor, attachMembership). - DefaultForumReadProjection drops the static import and the unused mappers; injects the new projection. - ForumPostServiceImpl drops the assembler import and the unused fields, keeps the create / update mutations. - ForumPostVOAssembler deleted.
DavidHLP
added a commit
that referenced
this pull request
Jul 10, 2026
…rt (arch-review #2) Four submission paths (DefaultSubmissionWritePort existence check, DefaultSubmissionProjection id/title/slug, CodeExecutionService limits + starter code, JudgedNotificationDispatcher title) each reached into problem.mapper.* directly. Introduce a consumer-owned read seam: - submission/port/ProblemFactsPort: findDisplayFacts / findLimits / findStarterCode, non-throwing (null on miss/error), mirroring the ContestSubmissionPort hexagonal convention. - problem/port/ProblemFactsAdapter: production adapter owning all ProblemMapper / ProblemLanguageMapper reads. Submission now imports zero problem.mapper types. ADR-0011 write-side ProblemDetailPort is untouched. Behaviour preserved: debounce/search unchanged; safe-degrade fallbacks retained via the non-throwing port. Validated: mvn compile + test-compile 0 errors; DefaultSubmissionProjection/ CodeExecution/SubmissionServiceImpl unit tests green. Co-Authored-By: Claude <noreply@anthropic.com>
DavidHLP
added a commit
that referenced
this pull request
Jul 16, 2026
Implements candidates from /tmp/architecture-review-1783827842.html: #1 AdminBulkExecutor (admin/bulk): lifts the duplicated per-item try/catch loop + counting + optional existence-guard out of AdminForum/Comment/ Solution/Problem bulk actions into one consumer-owned executor with a canonical ItemOutcome/Run. batchRejudge withdrawn with evidence (rich RejudgeResult shape ≠ void-action aggregated contract). #2 ProblemExportService + ExportPayload: moves format validation, the 10k size cap, CSV header/escaping, and the LocalDate.now() time-leak out of AdminProblemController into a Clock-injected service; controller shrinks to response-writing. #3 LegacyRejudgeStrategy + RejudgePolicy.rejudge dispatcher: moves the 50-line inline legacy rejudge state machine out of AdminSubmissionServiceImpl behind the policy port; fenced vs legacy selection now lives in DefaultRejudgePolicy. AdminSubmissionServiceImpl.rejudge = 3-line dispatch. #4 createAuthStore factory (shared/auth-core): owns the duplicated login/logout/fetchUser/loadPermissions/initialize/clearUser/hasPermission chain + CSRF contract once; management store migrated. Console deferred (already composable-shaped with a richer status state machine). #6 AdminContestReadPort + adapter: ADR-0011 phase 2 (contest) — admin contest reads cross the seam instead of reaching into ContestProblemMapper. #5 (AdminCrudListView wrapper) withdrawn: scaffolded then deleted after code-review flagged it as dead code (0 views wired); the genuine shared seam already lives in DataTable + useDataTable + DataTableToolbar. Validation: mvn compile + test-compile green; 37 targeted admin/submission tests pass (incl. new AdminBulkExecutorTest); management eslint green on auth store. code-review skill run (Standards pass, Spec gaps documented in-code). Co-Authored-By: Claude <noreply@anthropic.com>
DavidHLP
added a commit
that referenced
this pull request
Jul 20, 2026
Closes the Standards-axis judgement calls the two-axis review raised
(0 hard violations; these are the value-bearing ones):
- C06 data clump: ModerationServiceImpl.applyAction took 8 positional
params (action, moderatorId, note, durationDays, now, queueId,
actionId, item). Introduced a narrow ActionRequest value record for
the per-action inputs so the switch reads (request, item). The record
is a pure value, not the old ActionContext service-callback proxy.
- C01 primitive obsession + edge case: buildScoredContestProblems used a
bare literal 100 for the default score and a bare (char)('A'+i) index
that silently overflowed to non-letters past 26 problems. Named the
default DEFAULT_PROBLEM_SCORE and extracted problemIndex(i) which
returns A-Z for slots 0-25 and a deterministic P<n> label beyond,
instead of garbage chars.
- C02 magic numbers: useProblemRun carried bare "javascript", 60, 5000.
Named DEFAULT_RUN_LANGUAGE, RATE_LIMIT_FALLBACK_WAIT_SECONDS,
RATE_LIMIT_TOAST_DURATION_MS.
Behaviour preserved. #2 (parseRuntimeMs home) and #6 (getSocketManager
name) left: parseRuntimeMs's docstring already distinguishes the sandbox
wire format from the queue parser, and renaming getSocketManager would
churn four callers for a cosmetic gain.
Validated: ModerationServiceImplTest(11) + AdminContestMutationServiceImplTest
green; console type-check + eslint clean; problem-detail suite(12) green.
Co-Authored-By: Claude <noreply@anthropic.com>
DavidHLP
added a commit
that referenced
this pull request
Jul 20, 2026
…nService Architecture-review 2026-07-19 candidate #2 noted the class-level self-description ('Thin facade for code execution (M2a, ADR-002)') no longer matched the implementation: the class owns intake validation, signature-cache + DTO enrichment, per-problem resource-limit resolution, sandbox translation, verdict reduction, and per-case / batch dispatch. - Rewrites the javadoc to describe Problem run module ownership honestly. - Drops the dangling ADR-002 / M2a / F15-F16 / SandboxService citations that point to documents/identifiers that do not exist in the repo. - References only symbols that resolve in-source ({@link SandboxExecutor}, {@link JudgingLanguageSupport}, {@link ProblemFactsPort}, etc.). No behavior change; javadoc only.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Changes
CI Workflow (
.github/workflows/ci.yml)Dockerfiles
Other Files
console/nginx.conf/management/nginx.conf: SPA routing with security headers.dockerignore: Exclude unnecessary files from Docker buildsDocker Images (pushed on main)
ghcr.io/<owner>/ulticode-backend:<sha>andlatestghcr.io/<owner>/ulticode-console:<sha>andlatestghcr.io/<owner>/ulticode-management:<sha>andlatestTest Plan