mysqladmin may not be in CI runner PATH. Use docker exec mysql
mysqladmin ping instead, which works from the Gitea Actions runner.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Deploy zhixi-nest-worker.service after API restart
- Run test/run-integration-ci.sh with real MySQL/Redis/BullMQ
- Integration test failure blocks deployment (exit 1)
- timeout-minutes: 10 to prevent hung tests
Line: .gitea/workflows/deploy.yml:99-113
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- C端测试账号与Token.md: permanent test user c-test-001 + 24h JWT
- Fix Active Recall 404 test assertion
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- NEW test/m-ai-01-09-api-e2e.worker-int-spec.ts: real HTTP endpoints
with JWT auth → Controller → BullMQ → Worker → MySQL
- Feynman: POST /api/ai-analysis/feynman (200/401/400)
- Active Recall: POST /api/active-recalls/:id/submit (200/401/404)
- Document Import: POST /api/imports (200/401)
- generateJWT() in integration-harness.ts (TokenService format)
- httpPost() supports optional Bearer token
- Save test credentials to devops-projects/凭据配置/测试API凭据.md
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- SIGKILL: enqueue job BEFORE kill, wait for pickup, verify recovery
- Document Import: track before/after KnowledgeItem IDs to prove
Worker created them (not direct DB INSERT)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Worker consumes from BullMQ Redis, not MySQL polling.
Replace all dbExec(INSERT INTO AiAnalysisJob) with enqueueJob()
which uses real BullMQ Queue.add() to the test Redis.
Add setRedisConfig() + enqueueJob() to integration harness.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- User INSERT: add updatedAt=NOW() (NOT NULL, no default)
- AiAnalysisJob: retryCount→progress (column doesn't exist)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- NEW test/helpers/mock-ai-provider.ts: Mock AI HTTP server with
normal/timeout/429/500/invalid-json behavior controls
- NEW test/helpers/integration-harness.ts: spawn real API/Worker
processes, HTTP helpers, PrismaClient-based DB helpers
- NEW test/m-ai-01-09.worker-int-spec.ts: real integration test
using actual MySQL/Redis/BullMQ/HTTP (Active Recall, Feynman,
Document Import, SIGKILL recovery, provider faults, idempotency)
- NEW test/jest-worker-integration.json: Jest config with NO mocks
- DELETE test/m-ai-01-09.e2e-spec.ts: removed mock-based pseudo-e2e
- UPDATE test/run-integration-ci.sh: uses real integration config
- UPDATE package.json: test:integration:worker uses real config
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- TestingModule.compile() returns an application context directly
- Use $executeRaw tagged template instead of $executeRawUnsafe
- Add jest.setTimeout(30000) for integration tests
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- test/m-ai-01-09-integration.spec.ts: Active Recall/Feynman/Document Import
E2E tests with real MySQL/Redis/BullMQ
- test/run-integration-ci.sh: CI script (MySQL→Prisma→Build→API→Worker→Test)
- npm run test:integration:worker: one-command integration test
- Includes Active Recall chain audit (actual code paths with file:line)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Notification jobs are triggered implicitly by notifyJobComplete() during
Active Recall analysis, not by a dedicated test endpoint. Script now
checks Worker logs and DB records instead.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ProtectSystem=strict requires /run/systemd/unit-root paths to exist
which fails on production. Worker only needs network access (Redis/MySQL).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Extract queue name constants to queue.constants.ts to resolve
the require cycle that caused 'Cannot read properties of undefined'
at startup. Both queue-definitions.ts and queue.service.ts now
import from the shared constants file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Freeze the architecture decision for 8-batch unified AI migration.
Covers current/target topology, API/Worker process boundaries,
5 Processors, Heavy Runtime disposition, phase-1 non-goals,
rollback principles, and cross-batch constraints.
Includes Appendix A: worker dependency closure audit.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- New shared helper: src/common/helpers/name-resolver.ts
- Applied to: reviews, users/members, files, knowledge-bases, imports
- Learning service refactored to use shared helper
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
All list endpoints now return userName, kbName, materialName
resolved from User, KnowledgeBase, KnowledgeSource tables.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Prevents 500 from crashing the entire batch. Logs the actual error
to server log for debugging.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
iOS sends knowledgeBaseId in batch upload but DTO didn't have it,
causing forbidNonWhitelisted to reject with 400.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
iOS ImportStatusResponse expects 'id' field. The mismatch caused
DecodingError.keyNotFound on the client side.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add timestamp to lock key to prevent blocking retries
- Wrap create logic in try-finally to always release lock
- Reduce lock TTL from 30min to 5min
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DocumentImport.userId is a required foreign key, but the iOS app
doesn't send it. Extract from JWT via @CurrentUser() decorator
and pass to service.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Allows users to manually retry source parsing by creating a new
DocumentImport job and enqueuing it. Returns jobId for polling.
Route: POST /knowledge-bases/:kbId/sources/:id/parse
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
KnowledgeSourceService.addSource() was creating Source + DocumentImport
records but never enqueuing the job for processing. Also the worker
wasn't linking created KnowledgeItems back to their source.
Changes:
- Inject QueueService + RedisService into KnowledgeSourceService
- Enqueue document-import job after creating Source + DocumentImport
- Set Redis status keys (matching DocumentImportService pattern)
- Worker: resolve sourceId from job data or DocumentImport record
- Worker: pass sourceRef when creating KnowledgeItems
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Query string params (page=1&limit=5) arrive as strings but DTOs expect
@IsInt(). enableImplicitConversion tells class-transformer to auto-cast
types based on the DTO decorators.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Move AdminLearningService + DTOs to learning-session module
- Merge 21 new endpoints into existing admin-api/learning controller
- Add analysis and ai-usage methods to unified service
- Delete admin-learning module (no longer needed)
- Revert JwtAuthGuard /api/admin bypass (was breaking isolation)
- Fix: /api/* now exclusively serves user/iOS traffic again
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
request.path includes the global 'api' prefix, so /admin/learning
routes appear as /api/admin/learning. Bypass /api/admin in addition
to existing /admin-api and /internal paths.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Global JwtAuthGuard was blocking /admin/learning/* requests before
AdminAuthGuard could process x-api-key. Now bypasses all /admin/*
paths (not just /admin-api/*), letting controller-level admin auth
handle those routes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add AdminApiKey model (keyHash, expiresAt nullable for permanent)
- Extend AdminAuthGuard to accept x-api-key header as fallback auth
- Seed creates test-admin@zhixi.com with permanent SUPER_ADMIN API key
- Key format: zxat_<64 hex chars>, stored as SHA-256 hash
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Dashboard filter dropdown calls this to populate KB selector.
Returns knowledge bases that have reading event data with real titles.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>