feat: UltiCode Platform Enhancement - CI/CD, PWA, Payment & Analytics Redesign - #4
Merged
Conversation
- Replace top tabs with sidebar navigation for better UX - Create 5 new analytics components: - AnalyticsNav: Side navigation with icons and active states - AnalyticsMetricCard: Metric cards with trend indicators - AnalyticsBarList: Horizontal bar chart ranking lists - AnalyticsTagCloud: Tag cloud with size-mapped values - AnalyticsHeatmap: 24-hour activity heatmap - Refactor AnalyticsView layout: - Sidebar navigation + content area layout - Bento grid layout for each report type - Consistent precision card styling - Update dashboard components with precision design: - AreaChart, StatCards, DashboardTimeline - Add i18n translations for new labels - Remove completed BUSINESS_IMPROVEMENT_PLAN.md Design system: silver-gray palette, ice-blue accent, monospace data fonts, subtle hover effects
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4672f26e00
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Comprehensive refactoring of management frontend with terminal-style design: - Add new terminal UI components (TerminalCard, TerminalBadge, TerminalInput, DataBlock) - Create reusable auth components (AuthCard, AuthButton, AuthInput, AuthDivider, OAuthButton) - Migrate all views to terminal precision design: - Audit module (LogsView, ReportView, DetailDrawer) - Auth views (Login, Signup with modern card layouts) - Comments, Contests, Forum, Moderation, Notifications - Problem lists, Problems, Solutions, Submissions, Users - Extract table columns into dedicated files for maintainability - Extend i18n with new terminal-themed translations - Update core UI components (Input, Select, Checkbox, Avatar, DropdownMenu) - Add 392 lines of terminal precision design CSS utilities
Remove manual `actions/cache@v4` steps that use wildcard paths (`**/node_modules`), which cause tar extraction failures in cache restore. The `actions/setup-node@v4` already has `cache: 'pnpm'` enabled, which automatically caches the pnpm store (~/.local/share/pnpm/store). Since pnpm uses hard links from the store, installation remains fast without manual node_modules caching.
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
…_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
…00 on bad JSON + mask SMTP password
Closes all 5 P0/P1 bugs from docs/SETTINGS_API_TEST_REPORT_2026-06-09.md
plus 3 review findings (M1/L2/L3/L4) from .claude/reviews/settings-fix-review.md.
Bugs fixed
- §5.1 PATCH/POST endpoints were no-op stubs (echoed input, never wrote
to DB). All 14 endpoints now persist via SystemSettingsService backed by
the existing `system_settings` table (one JSON-serialized row per
category: general/email/rate-limits/uploads/features).
- §5.2 Maintenance toggle set `maintenanceMode` on MaintenanceModeVO only
— GET /admin/settings still returned false. Now toggles the same row
read by the general endpoint, so a single source of truth.
- §5.3 Malformed JSON / type mismatches returned 500 'Unknown error'.
Added handlers for HttpMessageNotReadableException and
MethodArgumentTypeMismatchException in GlobalExceptionHandler; both
return 400 with descriptive messages.
- §5.4 Empty body `{}` silently overwrote all fields with defaults.
MaintenanceModeRequest.enabled is now @NotNull Boolean (boxed) so
missing field is rejected; PATCH features with all 8 flags at JSON
default (false) is now rejected by the service as an accidental
empty-PATCH safety guard (M1).
- §8.1 SMTP password returned in cleartext. PASSWORD_MASK = '***' is
returned on GET; PATCH with mask preserves existing password;
PATCH with new value overwrites.
Review findings applied
- M1: service rejects 'PATCH features with all 8 flags false' as
BusinessException(SETTING_INVALID_VALUE, 200002) — empty body
detection. Frontend should always send all 8 flags; to truly take
the platform offline, use POST /admin/settings/maintenance.
- L2: removed unreachable null check in toggleMaintenance —
@NotNull on Boolean + Spring's body binding already reject null
with 400 via the global handler.
- L3: resetToDefaults and toggleMaintenance now log an 'AUDIT'
line with the Spring Security principal from SecurityContextHolder,
so destructive operations are traceable.
- L4: getAllSettings now uses one selectBatchIds query (5 keys)
instead of 5 sequential selectById round-trips.
Security: `key` is a MySQL reserved word; entity uses @TableId("`key`")
to force backtick quoting. Verified via Testcontainers IT.
BREAKING CHANGE
- PATCH /admin/settings body shape: was `AllSettingsVO` (28 fields),
now `GeneralSettingsVO` (6 fields). The parent path now manages the
general category. If a client used the 28-field payload, Jackson
silently ignores unknown fields — the persisted state is the
intersection. To patch all 28 fields, use PATCH on each of
/admin/settings/{email,rate-limits,uploads,features} and PATCH /admin/settings
for general. Documented in docs/SETTINGS_API_FIX_REPORT_2026-06-09.md
§6 #4.
- PATCH /admin/settings/features rejects all-defaults (see M1).
Clients that need 'disable every feature' must use maintenance mode
instead.
Tests
- AdminSettingsControllerTest: 20 cases (14 happy + 4 negative + 2 routing),
all green.
- SystemSettingsServiceImplIT: 11 cases (Testcontainers MySQL), including
P0 regressions for §5.1, §5.2, §8.1, plus the new M1 'all defaults
rejected' and 'one flag true accepted' assertions.
Files
- +12 new: 8 DTOs, SystemSetting entity, mapper, service interface,
service impl, controller test, IT.
- ~3 modified: AdminSettingsController (387 -> 150 lines), ErrorCode
(+4 SETTING_* codes), GlobalExceptionHandler (+2 400 handlers).
- +2 docs: original test report and the regression report.
Reported-by: docs/SETTINGS_API_TEST_REPORT_2026-06-09.md
Reviewed-by: .claude/reviews/settings-fix-review.md (APPROVE with comments)
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 10, 2026
- UserStatsDTO$DifficultyStats.getCount() NoSuchMethodError
on /users/{id}/stats, /users/{id}/profile, /users/by-username/{u}/profile
- /v3/api-docs 500 due to missing RunResultDTO$RunResultDTOBuilder
Root cause: target/classes/ had stale Lombok-failed class files.
PM2 uses spring-boot:run which reads target/classes/ directly, not
the packaged app.jar. The mismatch was introduced when service code
(UserServiceImpl.getUserStatsById) was updated to call getCount()
but the DTO recompile skipped the inner class regeneration.
Fix: mvnw clean install -DskipTests + pm2 restart ulticode-9001.
No source changes. Verified via javap -p that Lombok methods are
regenerated (getCount/getTotal/setCount/setTotal/equals/hashCode/toString
on DifficultyStats; RunResultDTOBuilder inner class on RunResultDTO).
All 4 originally-500 endpoints now return 200:
- GET /users/{id}/stats
- GET /users/{id}/profile
- GET /users/by-username/{u}/profile
- GET /v3/api-docs
Verified via curl + the report docs/api-test-report-user-userstats.md.
Refs: docs/api-test-report-user-userstats.md (Defect #1 + #4)
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.
This was referenced Jun 13, 2026
DavidHLP
pushed a commit
that referenced
this pull request
Jun 13, 2026
…-claim 漏洞) Review finding #4: tryClaim uses ON DUPLICATE KEY UPDATE id=id, so any existing row (including a stuck CLAIMED from a crashed dispatcher or channel JVM) returns 0 on retry → dispatcher treats 0 as 'already delivered, skip' → user permanently misses that channel's delivery. No reaper was in the M4a-d implementation; the DeliveryState javadoc explicitly deferred this to 'a future reaper'. New NotificationLedgerReaper: - @scheduled(fixedDelay=5min, initialDelay=1min) — interval is overridable via ulticode.notification.ledger.reaper-interval-ms. - reapStaleClaimed() transitions CLAIMED rows older than 10 minutes to FAILED with a clear failure_reason. The 10-minute grace covers slow SMTP responses (Email is the slowest channel); stuck rows are usually fixed within 1-2 reaper cycles. - Counter notification.ledger.reaper.reaped exposes the count for ops dashboards. A non-zero value is a real signal of dispatcher/channel JVM crashes and warrants investigation. - Reaper exceptions are swallowed (logged at warn) so a single DB hiccup does not stop the scheduled task from firing every 5 minutes. This is a minimal stop-gap; the durable retry path (ADR-007) is the long-term answer. The reaper only transitions the row to FAILED — a future outbox-style retry can still re-attempt the original intent because tryClaim is keyed on (intent_id, channel_id) and the existing row's state is informational. mvn compile: green. mvn test on the notification slice: green (new mapper method not yet covered by a unit test — added in a follow-up chore alongside the reaper metrics tests). Refs: docs/adr/ADR-004-notification-intents.md (M4d-1 review finding #4); DeliveryState javadoc CLAIMED → DELIVERED/FAILED transition.
DavidHLP
pushed a commit
that referenced
this pull request
Jun 13, 2026
ADR-005 §4 #4: 5 产品 flag + 5 cutover flag 矩阵化, 用于 CI matrix. - application-features-off.yml: 全部 10 flag 显式 false/默认值, 与 FeatureFlagsProperties 默认值一致 - application-features-on.yml: 5 产品 flag 全部 true, cutover flag 保持 (走 cutover 守门, 不在 CI matrix 强开) 配合 -Dspring.profiles.active=ci,features-{off,on} 使用. T4 会加 ci.yml matrix 把默认 backend-test job 复制为 off/on 两变体. Refs: docs/adr/ADR-005-rolling-deploy-playbook.md §4 Row #4
DavidHLP
pushed a commit
that referenced
this pull request
Jun 13, 2026
…atrix) ADR-005 §4 #4: ci.yml add backend-test-features-off and backend-test-features-on jobs, same shape as backend-test (mysql/redis services + JDK 17 + mvnw test), but: - -Dspring.profiles.active appends features-{off,on} - env block explicitly overrides 10 env vars (USE_JUDGE_OUTBOX etc.) as belt-and-suspenders against profile-not-loaded silent fallback - features-on flips 5 product flags true; cutover flags stay default 3 jobs (default / off / on) let PRs run all 3 paths, catching behavior drift between "default-flag path" and "all-off / all-on paths". Refs: docs/adr/ADR-005-rolling-deploy-playbook.md Row #4
DavidHLP
pushed a commit
that referenced
this pull request
Jun 13, 2026
Code review finding R1 (HIGH): backend-test-features-off 与 -on 2 job 复制粘贴 162 行 services+env+steps, 改 backend-test 模板 (JDK 升 / mysql 镜像升 / 加 env) 容易漏改. ci.yml 已有 4 处 matrix 用法 (frontend lint/ type-check/test + docker-build), 现成模式. 修复: 合并为 1 个 backend-test-features job + matrix.features=[off,on], 服务 + 5 cutover flag env 不变, 5 产品 flag 由 step 内部根据 matrix 注入 GITHUB_ENV. 改 backend-test 模板 (JDK 17 / mysql 9.1 / redis 7 镜像升级) 只需同步改 backend-test 与 backend-test-features 2 job, 不再 3 job. 注: 原 backend-test (default profile) 保留, 不走 matrix, 避免动 PR status check 名称 (项目 owner dashboard 已记录该 job 名). Refs: docs/adr/ADR-005-rolling-deploy-playbook.md §4 Row #4
DavidHLP
added a commit
that referenced
this pull request
Jun 13, 2026
…-claim 漏洞) Review finding #4: tryClaim uses ON DUPLICATE KEY UPDATE id=id, so any existing row (including a stuck CLAIMED from a crashed dispatcher or channel JVM) returns 0 on retry → dispatcher treats 0 as 'already delivered, skip' → user permanently misses that channel's delivery. No reaper was in the M4a-d implementation; the DeliveryState javadoc explicitly deferred this to 'a future reaper'. New NotificationLedgerReaper: - @scheduled(fixedDelay=5min, initialDelay=1min) — interval is overridable via ulticode.notification.ledger.reaper-interval-ms. - reapStaleClaimed() transitions CLAIMED rows older than 10 minutes to FAILED with a clear failure_reason. The 10-minute grace covers slow SMTP responses (Email is the slowest channel); stuck rows are usually fixed within 1-2 reaper cycles. - Counter notification.ledger.reaper.reaped exposes the count for ops dashboards. A non-zero value is a real signal of dispatcher/channel JVM crashes and warrants investigation. - Reaper exceptions are swallowed (logged at warn) so a single DB hiccup does not stop the scheduled task from firing every 5 minutes. This is a minimal stop-gap; the durable retry path (ADR-007) is the long-term answer. The reaper only transitions the row to FAILED — a future outbox-style retry can still re-attempt the original intent because tryClaim is keyed on (intent_id, channel_id) and the existing row's state is informational. mvn compile: green. mvn test on the notification slice: green (new mapper method not yet covered by a unit test — added in a follow-up chore alongside the reaper metrics tests). Refs: docs/adr/ADR-004-notification-intents.md (M4d-1 review finding #4); DeliveryState javadoc CLAIMED → DELIVERED/FAILED transition.
DavidHLP
added a commit
that referenced
this pull request
Jun 13, 2026
ADR-005 §4 #4: 5 产品 flag + 5 cutover flag 矩阵化, 用于 CI matrix. - application-features-off.yml: 全部 10 flag 显式 false/默认值, 与 FeatureFlagsProperties 默认值一致 - application-features-on.yml: 5 产品 flag 全部 true, cutover flag 保持 (走 cutover 守门, 不在 CI matrix 强开) 配合 -Dspring.profiles.active=ci,features-{off,on} 使用. T4 会加 ci.yml matrix 把默认 backend-test job 复制为 off/on 两变体. Refs: docs/adr/ADR-005-rolling-deploy-playbook.md §4 Row #4
DavidHLP
added a commit
that referenced
this pull request
Jun 13, 2026
…atrix) ADR-005 §4 #4: ci.yml add backend-test-features-off and backend-test-features-on jobs, same shape as backend-test (mysql/redis services + JDK 17 + mvnw test), but: - -Dspring.profiles.active appends features-{off,on} - env block explicitly overrides 10 env vars (USE_JUDGE_OUTBOX etc.) as belt-and-suspenders against profile-not-loaded silent fallback - features-on flips 5 product flags true; cutover flags stay default 3 jobs (default / off / on) let PRs run all 3 paths, catching behavior drift between "default-flag path" and "all-off / all-on paths". Refs: docs/adr/ADR-005-rolling-deploy-playbook.md Row #4
DavidHLP
added a commit
that referenced
this pull request
Jun 13, 2026
Code review finding R1 (HIGH): backend-test-features-off 与 -on 2 job 复制粘贴 162 行 services+env+steps, 改 backend-test 模板 (JDK 升 / mysql 镜像升 / 加 env) 容易漏改. ci.yml 已有 4 处 matrix 用法 (frontend lint/ type-check/test + docker-build), 现成模式. 修复: 合并为 1 个 backend-test-features job + matrix.features=[off,on], 服务 + 5 cutover flag env 不变, 5 产品 flag 由 step 内部根据 matrix 注入 GITHUB_ENV. 改 backend-test 模板 (JDK 17 / mysql 9.1 / redis 7 镜像升级) 只需同步改 backend-test 与 backend-test-features 2 job, 不再 3 job. 注: 原 backend-test (default profile) 保留, 不走 matrix, 避免动 PR status check 名称 (项目 owner dashboard 已记录该 job 名). Refs: docs/adr/ADR-005-rolling-deploy-playbook.md §4 Row #4
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
…t mapping Add LOCALE_VARIANT_MAP for explicit handling of common locale variants: - Chinese Simplified: zh, zh-Hans, zh-Hans-CN, zh-Hans-SG, zh-SG → zh-CN - English variants: en, en-GB, en-AU, en-CA, en-IN, en-NZ, en-ZA, en-IE → en-US Rewrite matchSupportedLocale with three-tier approach: 1. Exact match against SUPPORTED_LOCALES 2. Explicit variant mapping lookup 3. Base language code fallback for unlisted variants Add comprehensive unit tests (33 tests) covering: - Exact matches, variant mappings, language code fallback - Unsupported locales (ja-JP, fr-FR, etc.) - Edge cases (empty string, whitespace, case variations) The implementation preserves existing behavior while providing: - Clear documentation for future locale additions (e.g., zh-TW) - Better maintainability through explicit mappings - Case-permissive matching for user convenience Fixes issue #4 from temporal-herding-hamster.md
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
feat: UltiCode Platform Enhancement - CI/CD, PWA, Payment & Analytics Redesign
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
…_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
…00 on bad JSON + mask SMTP password
Closes all 5 P0/P1 bugs from docs/SETTINGS_API_TEST_REPORT_2026-06-09.md
plus 3 review findings (M1/L2/L3/L4) from .claude/reviews/settings-fix-review.md.
Bugs fixed
- §5.1 PATCH/POST endpoints were no-op stubs (echoed input, never wrote
to DB). All 14 endpoints now persist via SystemSettingsService backed by
the existing `system_settings` table (one JSON-serialized row per
category: general/email/rate-limits/uploads/features).
- §5.2 Maintenance toggle set `maintenanceMode` on MaintenanceModeVO only
— GET /admin/settings still returned false. Now toggles the same row
read by the general endpoint, so a single source of truth.
- §5.3 Malformed JSON / type mismatches returned 500 'Unknown error'.
Added handlers for HttpMessageNotReadableException and
MethodArgumentTypeMismatchException in GlobalExceptionHandler; both
return 400 with descriptive messages.
- §5.4 Empty body `{}` silently overwrote all fields with defaults.
MaintenanceModeRequest.enabled is now @NotNull Boolean (boxed) so
missing field is rejected; PATCH features with all 8 flags at JSON
default (false) is now rejected by the service as an accidental
empty-PATCH safety guard (M1).
- §8.1 SMTP password returned in cleartext. PASSWORD_MASK = '***' is
returned on GET; PATCH with mask preserves existing password;
PATCH with new value overwrites.
Review findings applied
- M1: service rejects 'PATCH features with all 8 flags false' as
BusinessException(SETTING_INVALID_VALUE, 200002) — empty body
detection. Frontend should always send all 8 flags; to truly take
the platform offline, use POST /admin/settings/maintenance.
- L2: removed unreachable null check in toggleMaintenance —
@NotNull on Boolean + Spring's body binding already reject null
with 400 via the global handler.
- L3: resetToDefaults and toggleMaintenance now log an 'AUDIT'
line with the Spring Security principal from SecurityContextHolder,
so destructive operations are traceable.
- L4: getAllSettings now uses one selectBatchIds query (5 keys)
instead of 5 sequential selectById round-trips.
Security: `key` is a MySQL reserved word; entity uses @TableId("`key`")
to force backtick quoting. Verified via Testcontainers IT.
BREAKING CHANGE
- PATCH /admin/settings body shape: was `AllSettingsVO` (28 fields),
now `GeneralSettingsVO` (6 fields). The parent path now manages the
general category. If a client used the 28-field payload, Jackson
silently ignores unknown fields — the persisted state is the
intersection. To patch all 28 fields, use PATCH on each of
/admin/settings/{email,rate-limits,uploads,features} and PATCH /admin/settings
for general. Documented in docs/SETTINGS_API_FIX_REPORT_2026-06-09.md
§6 #4.
- PATCH /admin/settings/features rejects all-defaults (see M1).
Clients that need 'disable every feature' must use maintenance mode
instead.
Tests
- AdminSettingsControllerTest: 20 cases (14 happy + 4 negative + 2 routing),
all green.
- SystemSettingsServiceImplIT: 11 cases (Testcontainers MySQL), including
P0 regressions for §5.1, §5.2, §8.1, plus the new M1 'all defaults
rejected' and 'one flag true accepted' assertions.
Files
- +12 new: 8 DTOs, SystemSetting entity, mapper, service interface,
service impl, controller test, IT.
- ~3 modified: AdminSettingsController (387 -> 150 lines), ErrorCode
(+4 SETTING_* codes), GlobalExceptionHandler (+2 400 handlers).
- +2 docs: original test report and the regression report.
Reported-by: docs/SETTINGS_API_TEST_REPORT_2026-06-09.md
Reviewed-by: .claude/reviews/settings-fix-review.md (APPROVE with comments)
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
- UserStatsDTO$DifficultyStats.getCount() NoSuchMethodError
on /users/{id}/stats, /users/{id}/profile, /users/by-username/{u}/profile
- /v3/api-docs 500 due to missing RunResultDTO$RunResultDTOBuilder
Root cause: target/classes/ had stale Lombok-failed class files.
PM2 uses spring-boot:run which reads target/classes/ directly, not
the packaged app.jar. The mismatch was introduced when service code
(UserServiceImpl.getUserStatsById) was updated to call getCount()
but the DTO recompile skipped the inner class regeneration.
Fix: mvnw clean install -DskipTests + pm2 restart ulticode-9001.
No source changes. Verified via javap -p that Lombok methods are
regenerated (getCount/getTotal/setCount/setTotal/equals/hashCode/toString
on DifficultyStats; RunResultDTOBuilder inner class on RunResultDTO).
All 4 originally-500 endpoints now return 200:
- GET /users/{id}/stats
- GET /users/{id}/profile
- GET /users/by-username/{u}/profile
- GET /v3/api-docs
Verified via curl + the report docs/api-test-report-user-userstats.md.
Refs: docs/api-test-report-user-userstats.md (Defect #1 + #4)
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
…-claim 漏洞) Review finding #4: tryClaim uses ON DUPLICATE KEY UPDATE id=id, so any existing row (including a stuck CLAIMED from a crashed dispatcher or channel JVM) returns 0 on retry → dispatcher treats 0 as 'already delivered, skip' → user permanently misses that channel's delivery. No reaper was in the M4a-d implementation; the DeliveryState javadoc explicitly deferred this to 'a future reaper'. New NotificationLedgerReaper: - @scheduled(fixedDelay=5min, initialDelay=1min) — interval is overridable via ulticode.notification.ledger.reaper-interval-ms. - reapStaleClaimed() transitions CLAIMED rows older than 10 minutes to FAILED with a clear failure_reason. The 10-minute grace covers slow SMTP responses (Email is the slowest channel); stuck rows are usually fixed within 1-2 reaper cycles. - Counter notification.ledger.reaper.reaped exposes the count for ops dashboards. A non-zero value is a real signal of dispatcher/channel JVM crashes and warrants investigation. - Reaper exceptions are swallowed (logged at warn) so a single DB hiccup does not stop the scheduled task from firing every 5 minutes. This is a minimal stop-gap; the durable retry path (ADR-007) is the long-term answer. The reaper only transitions the row to FAILED — a future outbox-style retry can still re-attempt the original intent because tryClaim is keyed on (intent_id, channel_id) and the existing row's state is informational. mvn compile: green. mvn test on the notification slice: green (new mapper method not yet covered by a unit test — added in a follow-up chore alongside the reaper metrics tests). Refs: docs/adr/ADR-004-notification-intents.md (M4d-1 review finding #4); DeliveryState javadoc CLAIMED → DELIVERED/FAILED transition.
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
ADR-005 §4 #4: 5 产品 flag + 5 cutover flag 矩阵化, 用于 CI matrix. - application-features-off.yml: 全部 10 flag 显式 false/默认值, 与 FeatureFlagsProperties 默认值一致 - application-features-on.yml: 5 产品 flag 全部 true, cutover flag 保持 (走 cutover 守门, 不在 CI matrix 强开) 配合 -Dspring.profiles.active=ci,features-{off,on} 使用. T4 会加 ci.yml matrix 把默认 backend-test job 复制为 off/on 两变体. Refs: docs/adr/ADR-005-rolling-deploy-playbook.md §4 Row #4
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
…atrix) ADR-005 §4 #4: ci.yml add backend-test-features-off and backend-test-features-on jobs, same shape as backend-test (mysql/redis services + JDK 17 + mvnw test), but: - -Dspring.profiles.active appends features-{off,on} - env block explicitly overrides 10 env vars (USE_JUDGE_OUTBOX etc.) as belt-and-suspenders against profile-not-loaded silent fallback - features-on flips 5 product flags true; cutover flags stay default 3 jobs (default / off / on) let PRs run all 3 paths, catching behavior drift between "default-flag path" and "all-off / all-on paths". Refs: docs/adr/ADR-005-rolling-deploy-playbook.md Row #4
DavidHLP
added a commit
that referenced
this pull request
Jul 3, 2026
Code review finding R1 (HIGH): backend-test-features-off 与 -on 2 job 复制粘贴 162 行 services+env+steps, 改 backend-test 模板 (JDK 升 / mysql 镜像升 / 加 env) 容易漏改. ci.yml 已有 4 处 matrix 用法 (frontend lint/ type-check/test + docker-build), 现成模式. 修复: 合并为 1 个 backend-test-features job + matrix.features=[off,on], 服务 + 5 cutover flag env 不变, 5 产品 flag 由 step 内部根据 matrix 注入 GITHUB_ENV. 改 backend-test 模板 (JDK 17 / mysql 9.1 / redis 7 镜像升级) 只需同步改 backend-test 与 backend-test-features 2 job, 不再 3 job. 注: 原 backend-test (default profile) 保留, 不走 matrix, 避免动 PR status check 名称 (项目 owner dashboard 已记录该 job 名). Refs: docs/adr/ADR-005-rolling-deploy-playbook.md §4 Row #4
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
Single source of truth for the inline hasPermission / hasRole / hasAnyRole logic that lived in both auth stores (arch review candidate #4). Supports *:* / action:* / *:resource / exact-match wildcards for checkPermission; case-insensitive comparison for role checks. Exports added in index.ts.
DavidHLP
added a commit
that referenced
this pull request
Jul 10, 2026
…norm rule Architecture review candidate #4 — three confused vote paths: 1. POST /vote (VoteController) → voteService.vote() — pure vote-state mutation, no side effects. Legitimate. 2. POST /edge-operations with VOTE_UP/DOWN (EdgeOperationsController) → voteService.vote() + denormalized counter projection onto solution.likes. Legitimate — owns the analytics + denorm side-effects. 3. ForumVoteServiceImpl (27 LoC) — type-alias pass-through that delegates to voteService.getVoteStatus with EdgeOperationTargetType.FORUM_POST hardcoded. ZERO callers (verified by grep across main/ and test/). Pure dead code. This commit: - Deletes ForumVoteService.java (interface, 19 LoC) - Deletes ForumVoteServiceImpl.java (impl, 27 LoC) - Updates AGENTS.md vote rule to document: * Denormalization is the caller's responsibility (not VoteService's) * The two legitimate routes (VoteController, EdgeOperationsController) * The asymmetry (only EdgeOperations projects onto solution.likes) is deliberate — coupling VoteService to Solution would be worse than the asymmetry * Future VoteResultUpdated domain-event pattern would resolve the asymmetry without coupling * ForumVoteService deletion is permanent — do not resurrect Architecture-review proposal to move updateSolutionVoteCounts INTO VoteServiceImpl is REJECTED — that would couple Vote (generic, votes on Solutions + Forum Posts + other types) to Solution (specific entity with a denormalized column). The review was right that the topology was confusing but wrong about the fix. Verified: ./mvnw compile clean. No tests referenced ForumVoteService (grep across src/test/ returned zero matches).
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
9 management views (ForumPostDetailDrawer, AuditTab, OverviewDisplay, BackupView, EmailView, AuditLogViewer, ActionHistoryTimeline, ModerationDetailDrawer, EntityPreviewCard) each defined a local `formatDate` that only forwarded to `formatDateTimeByLocale` — pure Middle Man. Remove the wrappers; templates now call `formatDateTimeByLocale` directly (already imported from @/i18n/utils). Arch review #4 scope notes: - management/lib/format/date.ts is RETAINED — it is not a duplicate of shared/datetime-utils but a locale default-injection seam (18 callers rely on its locale-less signature). Earlier CR suggestion to delete it was retracted after investigation. - Inline formatters that remain are intentionally different output semantics, kept per the review's "preserve deliberately different business display": DashboardView.formatRelativeTime (i18n keys), BackupView/MonitoringView formatBytes/formatUptime, and console's locale-specific formatDate/formatTime variants (long-month, no-year, ISO, conditional today/time). Verified: management vue-tsc type-check clean.
DavidHLP
added a commit
that referenced
this pull request
Jul 10, 2026
… color maps Arch review 2026-07-10 candidate #4: CONTEST_STATUS_COLOR_MAP held both lowercase and uppercase variants of the same status ('draft'/'DRAFT', 'upcoming'/'UPCOMING', 'finished'/'FINISHED') plus a set of management-only UI states ('published', 'registering', 'ongoing', 'freezing', 'archived') that do not exist in the backend ContestStatus enum. Grep verified zero callers pass lowercase keys. DIFFICULTY_COLOR_MAP had the same issue with easy/medium/hard fallbacks. Removed all lowercase aliasing; the maps now mirror only the backend canonical values. Added normalizeContestStatusKey() helper next to the map for future defensive callers. Management's UI-only contest states stay owned by the local ContestStatusBadge component (its own statusConfig), not the shared presentation seam.
DavidHLP
added a commit
that referenced
this pull request
Jul 10, 2026
Pull the lifecycle (claim → heartbeat → execute → verdict → contest effect → push → release) out of JudgeWorkerProcessor into a JudgeAttemptExecutor / DefaultJudgeAttemptExecutor pair: - The worker becomes a thin polling adapter: poll loop, envelope reconstruction, and the public JobProcessor interface only. It no longer holds the lease CAS, the heartbeat executor, the push port, or the contest-submission mapper. - The executor owns the legacy / fenced cutover (selected by the shared FeatureFlagsProperties.isUseGenerationFence() flag) and is the only place that calls SubmissionService, SubmissionResultPushPort, ContestSubmissionMapper, or runs the heartbeat scheduler. Tests can drive the full attempt against the leased or RQueue path without mocking the worker. - The onFailure retry-exhausted path also routes through the executor (new markExhausted entry point) so the System-Error write + push lives in one place. - enqueueId() → envelope.id() fix on the leased path. net: 544 lines of JudgeWorkerProcessor collapse to 240; the executor is 305 lines. Two source files now own what one was trying to do.
DavidHLP
added a commit
that referenced
this pull request
Jul 10, 2026
…r (arch-review #4) The concrete defect the review's Before diagram names — 'joinContest -. overwrites .-> onConnect' — joinContest reassigned client.onConnect while still connecting, destroying the singleton's connect handler (status notification + reconnect reset + the broadcast subscription) and leaving the parallel 'connected_once' callback as dead code that nothing ever fired. Concentrate room-lifecycle ownership in the singleton: the onConnect handler now fires a one-shot 'ready' event after the broadcast subscription, and joinContest registers on it instead of clobbering onConnect. Cleanup is deterministic (one-shot self-remove + timeout drop). Reconnect policy and message decoding already had one owner. Public composable API is unchanged, so ContestRankingsView and ContestAnnouncementBell need no changes. A regression test asserts joinContest never reassigns onConnect and the ready hook drives the room subscribe+publish. (The full two-adapter transport seam — STOMP + in-memory — remains the documented 'Worth exploring' follow-on; this commit ships the verified defect fix rather than an unverified realtime rewrite.) Validated: console vue-tsc 0 errors; useContestSocket spec 16/16. Co-Authored-By: Claude <noreply@anthropic.com>
DavidHLP
added a commit
that referenced
this pull request
Jul 16, 2026
C3 (STOMP factory strong) — red team CR §3.2/§A.8/§A.9 verified the premise is partially false: actual layout is one shared singleton + one thin adapter + one self-managed connection, not 'two parallel stacks'. Gating: (a) auth/topic verification of contest endpoints, (b) adapter consolidation, (c) only then the factory abstraction. C5 (Bookmark split worth exploring) — red team §7 priority #4 deferred behind a SQL reorder migration (sort_order unique index + batch CASE expression). Without that migration, the 'reorder becomes one write' claim doesn't hold. C7 (moderationStore god store worth exploring) — red team §1.8/§4 flagged that problems.ts (431 lines) is the same god-store shape and must be bundled. Bundled scope is 935 lines + consumer migration + Vitest rewrites; sequenced in independent sessions. 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 16, 2026
Complete date/time presentation deepening (arch review #4) on console side. Shared module + management adapter + contest views were already migrated; seven console personal/profile/subscription views still called raw toLocaleDateString/toLocaleString, re-learning locale + invalid + variant policy at each call site. Add console/src/utils/datetime.ts adapter (mirrors management, injects active i18n locale). Migrate UserProfileView, ProfileView, UserStatsPanel, UserProfileCard, SubscriptionView, ForumPostsView, SubmissionsView. NotificationsView's bespoke today-conditional stays local (app-specific). Type-check + eslint clean.
DavidHLP
added a commit
that referenced
this pull request
Jul 16, 2026
…xt composable Architecture-review candidate #4 (safe subset). The contest-problem context composable already owns the contest-aware state; move the repeated contest-problem navigation action behind it too, so the ?contestId= preservation lives with the rest of the contest routing invariants instead of being copied per renderer. - useContestProblemContext: add goToContestProblem(slug) owning the router.push({ name: "problem-detail", params: { slug }, query: { contestId: currentContest.slug ?? "" } }) shape. - ContestProblemShell / ContestProblemDock: goToPill delegates to ctx?.goToContestProblem(p.slug). - ContestProblemShell drops its now-unused useRouter import + router decl (router was only used by the inlined push). ContestProblemDock keeps router/route (still used by the separate submissions-tab push). Provider/inject wiring (ProblemContextKey, ContestProblemContextKey) is untouched. pnpm type-check + ESLint pass. Co-Authored-By: Claude <noreply@anthropic.com>
DavidHLP
added a commit
that referenced
this pull request
Jul 16, 2026
…osable useProblemLayout now receives the contestId ref as a parameter (threaded from ProblemDetailView's single route.query.contestId derivation) instead of re-reading the route, so the desktop layout, mobile layout, and contest dock all consume one contest-mode signal. Adds a behavioural test for the mid-session contest-entry reactivity and updates the contest spec to the threaded seam. Advances candidate #4 of the 2026-07-15 architecture review.
DavidHLP
pushed a commit
that referenced
this pull request
Jul 19, 2026
…s, type-safe tests Two-axis code review of the 13-commit arch-review-20260718 branch emitted 6 hard + 2 judgement Standards findings and 6 missing/partial + 5 scope-creep + 4 looks-wrong Spec findings. This commit closes all hard Standards findings, 1 missing Spec, and the volatile-comment documentation hygiene; defers 1 missing (Cand1 SCAN aggregation) and 1 judgement (settings emit helper) to keep the fix-up focused. Standards (6 hard): - useSolutionAuthoring.publish: add isPublishing ref + re-entry guard, wire SolutionsEditView so the submit button is disabled and shows 'Publishing...' while a create/update is in flight. i18n key 'solution.editor.publishing' added in both locales. - CommunityMembershipServiceImpl.joinCommunity: log loud if incrementMembers matched no row, so a deleted-in-flight community cannot silently leave the new membership without a counter bump. Leave path already checks affected rows. - settings.ts FieldMapping: type the wireKey / dtoKey constraint to keyof W / keyof D, so a misspelled mapping entry fails to compile rather than silently dropping the field at runtime. The inner as Partial<W> / as D casts remain — TypeScript cannot prove value compatibility, but every key was already validated by the generic constraint. - useSolutionAuthoring.spec.ts: type the mockRoute as a real RouteLocationNormalized (not 'as never'); useContestAuthoring.test.ts: type the createContest / addProblem mock returns as Contest / ContestProblem. Both are now real typed mocks at the public boundary. - CommunityMembershipServiceImplTest: pin the Clock to a fixed Instant + ZoneOffset.UTC and assert joinedAt deterministically. Replaces the previous Instant.now() / ZoneId.systemDefault() which drifted between CI machines. - Volatile review references removed from comments: 'Architecture review candidate #3' (system-settings.ts:47), 'residual-risk note in the task report' (useContestAuthoring.ts:272), 'CreateContestDTO.java ~line 55' (contests.ts:88 + :229), 'arch review 2026-07-10' (separator/index.ts in both apps), 'arch review Card 7' (i18n/utils.ts), 'arch review #3' (date.ts), 'arch review #4' (datetime.ts), 'arch review candidate #3' (storage.ts). All replaced with self-contained descriptions. Spec (1 missing addressed + 1 documented): - Cand3 clearCache: replace untyped Map<String,Object> with a typed ClearCacheResponseVO(List<String> clearedScopes, String timestamp) on backend (impl + service + controller), and update the two pre-existing tests to match. The frontend type was already correct; the contract is now typed at the API boundary. - Cand2 StepScoringRule: comment updated to document that the Selector owns the on-mount default-pick (which needs the rules endpoint) rather than duplicating the fetch in the authoring module. Verified: - backend-spring mvn -Dtest=... test: 54/54 green (forum 33 + admin 21) - console pnpm vitest useSolutionAuthoring.spec.ts: 9/9 - management pnpm vitest useContestAuthoring.test.ts: 16/16 - pnpm type-check clean on both frontends Deferred: - Cand1 QueueHealthSnapshot SCAN aggregation for failedCount / completedCount (still hardcoded 0L with TODO). The inFlight field is also missing. Both are still acknowledged in the plan; deferred to keep this fix-up focused. - J1 settings emit helper extraction (judgement smell): 5 settings components repeat emit('update:settings', { [key]: value }); left in place; refactor would touch all 5 view files for marginal DRY. 24 files / +224 / -82. No pre-existing dirty worktree files modified.
DavidHLP
added a commit
that referenced
this pull request
Jul 20, 2026
Architecture-review 2026-07-19 candidate #4 noted both apps hard-coded 5 * 60 * 1000 ms as a local STALE_SESSION_MS constant, duplicating the stale-session revalidation policy across console and management routers. - Adds DEFAULT_STALE_SESSION_MS (5 minutes) to shared/auth-core/navigation and makes NavigationPolicyOptions.staleSessionMs optional. Callers that omit it now inherit the shared default automatically. - Drops the local STALE_SESSION_MS constants and the explicit policy assignments in console/src/router/index.ts and management/src/router/index.ts. - Adds three regression tests pinning the default value, the behavior when the option is omitted (revalidation still fires after 5 minutes, never silently degrades), and the override path. The pre-edit comparison 'clock.now() - last > policy.staleSessionMs' would have silently evaluated against undefined when a caller omitted the option, disabling revalidation entirely. The local 'staleSessionMs' variable in createNavigationPolicy closes that hole.
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
Analytics Dashboard Redesign: Complete UI overhaul with precision dashboard design
PWA Offline Support: Full offline functionality for console app
Payment Integration: Complete Stripe implementation
OAuth Enhancement: GitHub + Google login support
CI/CD Workflow: Build, lint, type-check automation
New Components
AnalyticsNavAnalyticsMetricCardAnalyticsBarListAnalyticsTagCloudAnalyticsHeatmapTest plan