- 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>
- 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>
- Fix AdminJwtGuard → AdminAuthGuard (guard file was admin-auth.guard.ts)
- Add AdminAuthModule import to provide guard dependencies
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Fix PrismaService/PrismaModule import paths (infrastructure/database/)
- Remove dead upsertFromReadingEvent wrappers in learning-activity/learning-session services
- Export ProcessResult interface for controller return type
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>