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>
- 3-stage: validate → dry-run → deploy-migration
- validate: prisma format check, client generation verification
- dry-run: MySQL service container, db push + migrate deploy + status
- deploy-migration: manual trigger, SSH migrate deploy with pre/post status
- Triggered on schema.prisma or migrations/** changes
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Record previous docker images before deploy
- Health check loop: 12 attempts × 5s = 60s total
- On failure: warning + previous images printed for manual rollback
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add health checks: api (GET /health), worker, heavy-runtime, nginx
- Add resource limits (deploy.resources) for all 6 services
- Add heavy-runtime service (Rust AI Worker)
- Add INTERNAL_API_KEY + CREDENTIAL_ENCRYPTION_KEY env vars
- nginx health check via GET /health
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Complete error code enumeration: Internal API + User API + Runtime errors
- Rate limiting: user quota, platform circuit breaker, global throttle
- Changelog: v1.0.0 initial → v1.1.0 current with all changes
- Integration examples: heartbeat cancel detection, job creation idempotency
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Replace hardcoded 'https://api.deepseek.com/v1/models' with config lookup
- Single source of truth via ai.deepseek.baseUrl (DEEPSEEK_BASE_URL env)
- Add ConfigService dep to UserAiService constructor + test mock
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Replace single findMany(take:500) with cursor-based while loop
- REAP_BATCH_SIZE=500 constant; processes all stuck running + expired jobs
- Prevents missing jobs when >500 are stuck simultaneously
- Update tests: reset mocks before custom chains, explicit call ordering
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Simulates two concurrent getSnapshot calls with no valid snapshot
- Documents the known race: both trigger buildSnapshot, second update overwrites first
- Verifies both calls complete without error (accepted-risk behavior)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- convertQuizCandidates: batch findMany all stems before loop, dedup in-memory
- convertFlashcardCandidates: batch findMany all fronts before loop, dedup in-memory
- 50 items now = 1 query instead of 50
- Update tests: mock findMany instead of per-item findFirst
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add static counters: persistResultFailures, notifyFailures
- Replace .catch(() => {}) with logger.error + counter increment
- Add error-path unit tests for both counter increments
- Reset counters in beforeEach for test isolation
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Rename checkAndReserve→checkQuota (method only reads, does not reserve)
- Add doc comment: incrementJobCount is the actual quota reservation
- Update user-ai.service.spec.ts references
- Add comments for apiKeyMode/credentialId reassignment in budget fallback
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1. heartbeat: locked→running transition on first heartbeat
2. submitResult: validate job is locked/running before accepting
3. submitFailure: validate job is locked/running before accepting
4. resolveCredentialForJob: update lastUsedAt on credential
5. pollJobs: filter by capabilities (snapshotVersion + outputSchemaVersion)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Prisma validation error P1012: User.aiAnalysisResultsNew missing
opposite relation on AiLearningAnalysis.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>