Ver Fonte

chore(package): remove redundant test script and clean up linting commands

Dave há 3 meses atrás
pai
commit
350bf8447c

+ 0 - 117
apps/box-mgnt-api/test/README.md

@@ -1,117 +0,0 @@
-# Video Cache E2E Test
-
-## Overview
-
-This test validates the Redis cache contract for video caches:
-
-- Category video lists contain ONLY video IDs (not Tag JSON)
-- Tag-filtered video lists contain ONLY video IDs
-- Tag metadata lists contain ONLY Tag JSON objects
-
-## Prerequisites
-
-Before running the test, ensure:
-
-1. **MongoDB is running** and accessible with valid credentials
-2. **Redis is running** on localhost:6379 (or configure in .env file)
-3. **MongoDB has test data** (channels, categories, videos, tags)
-
-## Environment Setup
-
-The test uses `.env.mgnt.test` for configuration. Update the following variables if needed:
-
-```bash
-# MongoDB connection (ensure user exists and has correct permissions)
-MONGO_URL="mongodb://boxuser:PASSWORD@localhost:27017/box_admin?authSource=admin"
-MONGO_STATS_URL="mongodb://boxstatuser:PASSWORD@localhost:27017/box_stats?authSource=admin"
-
-# Redis connection
-REDIS_HOST=127.0.0.1
-REDIS_PORT=6379
-REDIS_DB=0
-```
-
-### MongoDB Authentication Issues
-
-If you see `SCRAM failure: Authentication failed`:
-
-1. **Option A**: Create the MongoDB user:
-
-   ```bash
-   mongosh admin
-   db.createUser({
-     user: "boxuser",
-     pwd: "YOUR_PASSWORD",
-     roles: [{ role: "readWrite", db: "box_admin" }]
-   })
-   ```
-
-2. **Option B**: Update `.env.mgnt.test` to use your existing MongoDB credentials
-
-3. **Option C**: Use connection string without authentication:
-   ```bash
-   MONGO_URL="mongodb://localhost:27017/box_admin"
-   ```
-
-## Running the Test
-
-```bash
-# From box-nestjs-monorepo directory
-pnpm test:e2e:mgnt
-```
-
-## Test Output
-
-Successful test output includes:
-
-```
-✅ Redis connection verified
-🗑️  Deleted cache keys: { ... }
-🔨 Rebuilding video caches...
-📋 Found X category video list keys
-📋 Found X tag-filtered video list keys
-📋 Found X tag metadata list keys
-✅ All cache keys validated successfully!
-```
-
-## Validation Logic
-
-The test performs these validations:
-
-### Video List Keys
-
-- Key type is `list`
-- Each element is a 24-character hex string (MongoDB ObjectId)
-- **Fails if** Tag JSON or Category JSON is detected
-
-### Tag Metadata Keys
-
-- Key type is `list`
-- Each element is valid JSON with required fields: `id`, `name`, `seq`, `status`, `channelId`, `categoryId`
-- **Fails if** plain video IDs are detected
-
-## Troubleshooting
-
-### No Cache Keys Found
-
-If test shows `Found 0 category video list keys`:
-
-- MongoDB has no data - run seed scripts:
-  ```bash
-  pnpm prisma:seed:mongo:test
-  ```
-
-### Test Timeout
-
-If test exceeds 60 seconds:
-
-- Check MongoDB query performance
-- Ensure indexes exist on frequently queried fields
-- Reduce test data volume
-
-### Redis Connection Failed
-
-If `expect(pong).toBe('PONG')` fails:
-
-- Verify Redis is running: `redis-cli ping`
-- Check REDIS_HOST and REDIS_PORT in `.env.mgnt.test`

+ 0 - 32
apps/box-mgnt-api/test/jest-e2e.json

@@ -1,32 +0,0 @@
-{
-  "preset": "ts-jest",
-  "testEnvironment": "node",
-  "rootDir": ".",
-  "testRegex": ".e2e-spec.ts$",
-  "moduleNameMapper": {
-    "^@box/common/(.*)$": "<rootDir>/../../../libs/common/src/$1",
-    "^@box/db/(.*)$": "<rootDir>/../../../libs/db/src/$1",
-    "^@box/core/(.*)$": "<rootDir>/../../../libs/core/src/$1",
-    "^@box/mgnt/(.*)$": "<rootDir>/../../box-mgnt-api/src/$1"
-  },
-  "transform": {
-    "^.+\\.(t|j)s$": "ts-jest"
-  },
-  "collectCoverageFrom": [
-    "**/*.(t|j)s"
-  ],
-  "coverageDirectory": "../coverage",
-  "testTimeout": 60000,
-  "globals": {
-    "ts-jest": {
-      "tsconfig": {
-        "types": [
-          "jest",
-          "node"
-        ],
-        "esModuleInterop": true,
-        "allowSyntheticDefaultImports": true
-      }
-    }
-  }
-}

+ 0 - 16
apps/box-mgnt-api/test/tsconfig.json

@@ -1,16 +0,0 @@
-{
-  "extends": "../tsconfig.json",
-  "compilerOptions": {
-    "types": ["jest", "node"],
-    "esModuleInterop": true,
-    "allowSyntheticDefaultImports": true,
-    "module": "commonjs",
-    "target": "ES2021",
-    "lib": ["ES2021"],
-    "strict": true,
-    "skipLibCheck": true,
-    "resolveJsonModule": true
-  },
-  "include": ["**/*.spec.ts", "**/*.e2e-spec.ts"],
-  "exclude": ["node_modules", "dist"]
-}

+ 0 - 181
libs/common/src/cache/CACHE_KEYS_QUICK_REF.md

@@ -1,181 +0,0 @@
-# Redis Cache Keys - Quick Reference
-
-This is a quick lookup table for Redis cache key semantics. For detailed information, see `CACHE_SEMANTICS.md`.
-
-## Key Patterns & Types
-
-### Video Lists (LIST type - video IDs only)
-
-| Key Pattern                                | Purpose                     | Elements           | Order       | Operations    |
-| ------------------------------------------ | --------------------------- | ------------------ | ----------- | ------------- |
-| `box:app:video:category:list:<catId>`      | All videos in category      | Video IDs (string) | Seq/newest  | LRANGE, RPUSH |
-| `box:app:video:tag:list:<catId>:<tagId>`   | Videos with tag in category | Video IDs (string) | Seq/newest  | LRANGE, RPUSH |
-| `box:app:video:list:home:<chId>:<section>` | Home page section           | Video IDs (string) | Index order | LRANGE, RPUSH |
-
-### Video Pools (ZSET type - scored video IDs)
-
-| Key Pattern                                         | Purpose                     | Members   | Score     | Operations      |
-| --------------------------------------------------- | --------------------------- | --------- | --------- | --------------- |
-| `box:app:video:list:category:<chId>:<catId>:<sort>` | Category videos (paginated) | Video IDs | Timestamp | ZREVRANGE, ZADD |
-| `box:app:video:list:tag:<chId>:<tagId>:<sort>`      | Tag videos (paginated)      | Video IDs | Timestamp | ZREVRANGE, ZADD |
-
-### Tag Lists (LIST type - Tag JSON)
-
-| Key Pattern                | Purpose          | Elements         | Order   | Operations    |
-| -------------------------- | ---------------- | ---------------- | ------- | ------------- |
-| `box:app:tag:list:<catId>` | Tags in category | Tag JSON         | seq asc | LRANGE, RPUSH |
-| `box:app:tag:all`          | Global tag pool  | Tag JSON (array) | seq asc | GET, SET      |
-
-### Video Details
-
-| Key Pattern                    | Purpose        | Type | Operations |
-| ------------------------------ | -------------- | ---- | ---------- |
-| `box:app:video:detail:<vidId>` | Video metadata | JSON | GET, SET   |
-
----
-
-## Common Mistakes to Avoid
-
-### ❌ WRONG: Storing JSON in video lists
-
-```typescript
-// DON'T DO THIS
-await redis.rpush(
-  'box:app:video:category:list:cat-1',
-  JSON.stringify({ id: 'vid-1', title: 'Video 1' }),
-);
-```
-
-### ✅ CORRECT: Store video IDs only
-
-```typescript
-// DO THIS
-await redis.rpush(
-  'box:app:video:category:list:cat-1',
-  'vid-1',
-  'vid-2',
-  'vid-3',
-);
-```
-
-### ❌ WRONG: Confusing tag keys
-
-```typescript
-// DON'T MIX UP THESE KEYS:
-const videoIdsWithTag = await redis.lrange(
-  'box:app:video:tag:list:cat-1:tag-sports',
-  0,
-  -1,
-); // ✅ Returns video IDs
-
-const allTags = await redis.get('box:app:tag:all');
-// ✅ Returns JSON array of Tag objects
-```
-
-### ✅ CORRECT: Parse appropriately
-
-```typescript
-// For video tag list
-const videoIds = await redis.lrange(
-  'box:app:video:tag:list:cat-1:tag-sports',
-  0,
-  -1,
-);
-// videoIds = ['vid-1', 'vid-2', 'vid-3']
-
-// For tag list
-const tagsJson = await redis.get('box:app:tag:list:cat-1');
-const tags = JSON.parse(tagsJson); // Parse each element
-// tags = [{ id: 'tag-1', name: 'Sports', ... }, ...]
-```
-
----
-
-## TypeScript Usage
-
-Use `tsCacheKeys` provider for safe key generation:
-
-```typescript
-import { tsCacheKeys } from '@box/common/cache/ts-cache-key.provider';
-
-// Get video IDs for a category
-const key = tsCacheKeys.video.categoryList('channel-1');
-const videoIds = await redis.lrange(key, 0, -1);
-
-// Get tag filter options
-const tagKey = tsCacheKeys.tag.all();
-const tagsJson = await redis.get(tagKey);
-const tags = JSON.parse(tagsJson);
-
-// Paginate videos by tag
-const poolKey = tsCacheKeys.video.tagPool('channel-1', 'tag-1', 'latest');
-const page1VideoIds = await redis.zrevrange(poolKey, 0, 19);
-```
-
----
-
-## Builder Information
-
-| Key Built By              | Method                         | Triggered When         |
-| ------------------------- | ------------------------------ | ---------------------- |
-| VideoCategoryCacheBuilder | buildCategoryListForChannel()  | Category/video changes |
-| VideoCategoryCacheBuilder | buildTagListForCategory()      | Tag/video-tag changes  |
-| VideoListCacheBuilder     | buildCategoryPoolsForChannel() | Video list changes     |
-| VideoListCacheBuilder     | buildTagPoolsForChannel()      | Video list changes     |
-| VideoListCacheBuilder     | buildHomeSectionsForChannel()  | Video list changes     |
-| TagCacheBuilder           | buildAll()                     | Tag changes            |
-
----
-
-## Reading in Code
-
-### Category Videos List
-
-```typescript
-const categoryListKey = tsCacheKeys.video.categoryList(channelId);
-const videoIds = await redis.lrange(categoryListKey, 0, -1);
-// Returns: string[] of video IDs
-```
-
-### Tag-Filtered Videos
-
-```typescript
-const tagListKey = tsCacheKeys.video.tagList(channelId, categoryId);
-const videoIds = await redis.lrange(tagListKey, 0, -1);
-// Returns: string[] of video IDs
-```
-
-### Paginated Category Videos
-
-```typescript
-const poolKey = tsCacheKeys.video.categoryPool(channelId, categoryId, 'latest');
-const page = 1;
-const pageSize = 20;
-const videoIds = await redis.zrevrange(
-  poolKey,
-  (page - 1) * pageSize,
-  page * pageSize - 1,
-);
-// Returns: string[] of video IDs for this page
-```
-
-### Category Tags
-
-```typescript
-const tagListKey = tsCacheKeys.video.tagList(channelId, categoryId);
-// Wait, this is wrong! You want the TAG list, not video list.
-
-// For tags in a category:
-// Currently not implemented as LIST
-// Use tsCacheKeys.tag.all() for global tags
-```
-
----
-
-## Related Documentation
-
-- `libs/common/src/cache/cache-keys.ts` — Key definitions with JSDoc
-- `libs/common/src/cache/CACHE_SEMANTICS.md` — Detailed semantics
-- `libs/common/src/cache/ts-cache-key.provider.ts` — TypeScript interface
-- `libs/core/src/cache/video/category/video-category-cache.builder.ts` — Builder implementation
-- `libs/core/src/cache/video/list/video-list-cache.builder.ts` — Pool builder

+ 0 - 212
libs/common/src/cache/CACHE_SEMANTICS.md

@@ -1,212 +0,0 @@
-# Redis Cache Key Semantics
-
-This document defines the contract for all Redis cache keys in the Box monorepo, particularly the video/category/tag hierarchy.
-
-## Core Principles
-
-1. **Type Safety**: Each Redis key has a defined type (STRING, LIST, ZSET, SET, etc.)
-2. **Element Semantics**: For container types (LIST, ZSET, SET), the element format is strictly defined (e.g., video IDs vs. JSON objects)
-3. **Ordering**: For ordered types (LIST, ZSET), the sort order and business meaning is documented
-4. **Atomicity**: All writes use pipeline operations (DEL + command) to prevent partial states
-5. **Versioning**: No versioning suffix; cache is rebuilt atomically on changes
-
-## Video/Category/Tag Hierarchy
-
-### 1. Category Video List (NEW SEMANTICS)
-
-**Key Pattern**: `box:app:video:category:list:<categoryId>`
-
-**Redis Type**: LIST
-
-**Elements**: Video IDs (string, Mongo ObjectId format like `"64a2b3c4d5e6f7g8h9i0j1k2"`)
-
-**Ordering**: Descending by business order (e.g., sequence number → newest first)
-
-**Operations**:
-
-- Write: `DEL key` + `RPUSH key <videoId1> <videoId2> ...` (atomic pipeline)
-- Read: `LRANGE key 0 -1` to fetch all, or `LRANGE key <offset> <offset+limit-1>` for pagination
-- TTL: No expiry (persistent until rebuild)
-
-**Example Redis State**:
-
-```
-KEY: "box:app:video:category:list:cat-001"
-TYPE: LIST
-VALUES: ["video-001", "video-002", "video-003"]
-```
-
-**Business Meaning**:
-
-- Represents all videos in a category, in display order
-- Used by category-detail API to show video list
-- Rebuilt when categories or video-category mappings change
-
-**⚠️ IMPORTANT**: This key **must contain video IDs only**, not JSON objects.
-
----
-
-### 2. Category + Tag Filtered Video List (NEW SEMANTICS)
-
-**Key Pattern**: `box:app:video:tag:list:<categoryId>:<tagId>`
-
-**Redis Type**: LIST
-
-**Elements**: Video IDs (string, Mongo ObjectId format)
-
-**Ordering**: Same as category list (descending by business order)
-
-**Operations**:
-
-- Write: `DEL key` + `RPUSH key <videoId1> <videoId2> ...` (atomic pipeline)
-- Read: `LRANGE key 0 -1` to fetch all, or `LRANGE key <offset> <offset+limit-1>` for pagination
-- TTL: No expiry
-
-**Example Redis State**:
-
-```
-KEY: "box:app:video:tag:list:cat-001:tag-sports"
-TYPE: LIST
-VALUES: ["video-001", "video-003", "video-007"]
-```
-
-**Business Meaning**:
-
-- Represents videos in a category **that also have a specific tag**
-- Subset of category videos
-- Used by tag-filter API to show filtered videos
-- Rebuilt when tags or video-tag mappings change
-
-**⚠️ IMPORTANT**: This key **must contain video IDs only**, not JSON objects.
-
----
-
-### 3. Category Tags List (NEW SEMANTICS)
-
-**Key Pattern**: `box:app:tag:list:<categoryId>`
-
-**Redis Type**: LIST
-
-**Elements**: Stringified JSON objects (Tag model)
-
-**JSON Structure per Element**:
-
-```typescript
-{
-  id: string;                    // Tag ID (Mongo ObjectId)
-  name: string;                  // Tag name (e.g., "Sports", "Action")
-  subtitle?: string | null;      // Optional subtitle
-  seq: number;                   // Display sequence
-  status: number;                // 1 = enabled, 0 = disabled
-  createAt: string;              // ISO timestamp or milliseconds (as string for BigInt preservation)
-  updateAt: string;              // ISO timestamp or milliseconds (as string)
-  channelId: string;             // Channel this tag belongs to
-  categoryId: string;            // Category this tag belongs to
-}
-```
-
-**Operations**:
-
-- Write: `DEL key` + `RPUSH key <json1> <json2> ...` (atomic pipeline)
-- Read: `LRANGE key 0 -1`, then parse each element as JSON
-- TTL: No expiry
-
-**Example Redis State**:
-
-```
-KEY: "box:app:tag:list:cat-001"
-TYPE: LIST
-VALUES: [
-  "{\"id\":\"tag-001\",\"name\":\"Sports\",\"seq\":1,\"status\":1,\"createAt\":\"1700000000\",\"updateAt\":\"1700000000\",\"channelId\":\"ch-1\",\"categoryId\":\"cat-001\"}",
-  "{\"id\":\"tag-002\",\"name\":\"Action\",\"seq\":2,\"status\":1,\"createAt\":\"1700000000\",\"updateAt\":\"1700000000\",\"channelId\":\"ch-1\",\"categoryId\":\"cat-001\"}"
-]
-```
-
-**Business Meaning**:
-
-- Represents all **enabled** tags available in a category
-- Ordered by sequence (display order)
-- Used by category-detail API to show tag filter options
-- Rebuilt when categories or tag-category mappings change
-
-**✅ CORRECT**: This key **contains Tag JSON objects**, not video IDs.
-
----
-
-## Complete Key Reference Table
-
-| Key Pattern                                                   | Type         | Elements                   | Order            | Purpose                     | Built By                  |
-| ------------------------------------------------------------- | ------------ | -------------------------- | ---------------- | --------------------------- | ------------------------- |
-| `box:app:video:category:list:<categoryId>`                    | LIST         | Video IDs (string)         | Seq/newest first | All videos in category      | VideoCategoryCacheBuilder |
-| `box:app:video:tag:list:<categoryId>:<tagId>`                 | LIST         | Video IDs (string)         | Seq/newest first | Videos with tag in category | VideoCategoryCacheBuilder |
-| `box:app:tag:list:<categoryId>`                               | LIST         | Tag JSON (string)          | seq ascending    | Filter options for category | VideoCategoryCacheBuilder |
-| `box:app:tag:all`                                             | JSON (Array) | Tag JSON (array items)     | seq ascending    | Global tag pool             | TagCacheBuilder           |
-| `box:app:video:detail:<videoId>`                              | JSON         | Single video object        | N/A              | Video metadata              | VideoDetailCacheBuilder   |
-| `box:app:video:list:category:<channelId>:<categoryId>:<sort>` | ZSET         | Video IDs (member) + score | Score desc       | Paginated category videos   | VideoListCacheBuilder     |
-| `box:app:video:list:tag:<channelId>:<tagId>:<sort>`           | ZSET         | Video IDs (member) + score | Score desc       | Paginated tag videos        | VideoListCacheBuilder     |
-| `box:app:video:list:home:<channelId>:<section>`               | LIST         | Video IDs (string)         | Index order      | Home page section           | VideoListCacheBuilder     |
-
----
-
-## Key Naming Conventions
-
-### Suffix Rules
-
-- **`:list:<entityId>`** = LIST of **video IDs** (strings only)
-- **`:tag:list:<categoryId>:<tagId>`** = LIST of **video IDs** (strings only, filtered by tag)
-- **`:tag:list:<categoryId>`** = LIST of **Tag JSON objects**
-- **`:pool:<entityId>:<sort>`** = ZSET of video IDs with scoring
-- **`:detail:<id>`** = STRING, single JSON object
-
-### Atomicity Guarantees
-
-All cache writes use atomic operations:
-
-```typescript
-// Example: rpushList() ensures atomicity
-async rpushList(key: string, items: string[]): Promise<void> {
-  const pipeline = client.pipeline();
-  pipeline.del(key);           // ← Delete old key first
-  pipeline.rpush(key, ...items); // ← Push all new items
-  await pipeline.exec();         // ← Execute atomically
-}
-```
-
-This prevents readers from seeing partially-updated lists.
-
----
-
-## Migration Notes
-
-### OLD vs. NEW Semantics
-
-If you're migrating from older cache structures:
-
-- **Video IDs in lists**: Always plain strings (Mongo ObjectId), never wrapped in JSON
-- **Tag data in lists**: Always JSON objects (one JSON per list element)
-- **Never mix**: A LIST of video IDs should never contain Tag JSON, and vice versa
-
----
-
-## Developer Checklist
-
-When adding a new cache key:
-
-- [ ] Document the key pattern (with parameterization)
-- [ ] Specify Redis type (STRING, LIST, ZSET, etc.)
-- [ ] Define element format (video ID, JSON object, etc.)
-- [ ] Define ordering (if applicable)
-- [ ] Document read/write operations and TTL
-- [ ] Specify which builder creates/updates the key
-- [ ] Add example Redis state
-- [ ] Update this file and `cache-keys.ts`
-
----
-
-## Related Files
-
-- `libs/common/src/cache/cache-keys.ts` — Key definitions and helper functions
-- `libs/core/src/cache/video/category/video-category-cache.builder.ts` — Builds category/tag lists
-- `libs/core/src/cache/video/list/video-list-cache.builder.ts` — Builds pools and home sections
-- `libs/core/src/cache/tag/tag-cache.builder.ts` — Builds global tag pool
-- `apps/box-app-api/src/feature/video/video.service.ts` — Reads all cache keys

+ 0 - 292
libs/common/src/cache/DEVELOPER_CHECKLIST.md

@@ -1,292 +0,0 @@
-# Redis Cache Development Checklist
-
-Use this checklist when reading from or writing to Redis cache keys for videos, categories, and tags.
-
-## ✅ Before Writing to Cache
-
-### Understand Your Key Type
-
-- [ ] I've identified which key I'm writing to
-- [ ] I've read the JSDoc in `cache-keys.ts` for this key
-- [ ] I understand the Redis type (LIST, ZSET, STRING)
-- [ ] I know what elements should go in this key
-
-### Video ID Keys (Video Lists & Pools)
-
-- [ ] **My key contains VIDEO IDs only** (strings like `"64a2b3c4..."`)
-- [ ] **NOT JSON objects** with title, description, etc.
-- [ ] **NOT Tag data**
-- [ ] I'm using `rpushList()` for LIST keys (atomic DEL + RPUSH)
-- [ ] I'm using `zadd()` for ZSET keys (atomic DEL + ZADD)
-- [ ] Verify: Example key is `box:app:video:*` (not `box:app:tag:list:*`)
-
-### Tag JSON Keys
-
-- [ ] **My key contains TAG JSON objects** (stringified with id, name, seq, etc.)
-- [ ] **NOT video IDs**
-- [ ] Each element is valid JSON or full array for STRING type
-- [ ] I'm parsing each element if it's a LIST
-- [ ] For `box:app:tag:all`, I'm storing the full JSON array as one STRING
-
-### Atomic Operations
-
-- [ ] I'm using pipeline for all writes: `DEL` then `RPUSH`/`ZADD`/`SET`
-- [ ] I'm NOT doing multiple separate Redis commands
-- [ ] Atomicity prevents readers from seeing partial data
-
-### Key Naming
-
-- [ ] My key uses the format from `cache-keys.ts` (not custom format)
-- [ ] I'm using `tsCacheKeys` provider for generation
-- [ ] I'm including all required parameters (channelId, categoryId, etc.)
-
----
-
-## ✅ Before Reading from Cache
-
-### Understand Your Key Type
-
-- [ ] I've identified which key I'm reading from
-- [ ] I've read the JSDoc in `cache-keys.ts` for this key
-- [ ] I understand the Redis type (LIST, ZSET, STRING)
-- [ ] I know what elements to expect
-
-### Video ID Keys (Video Lists & Pools)
-
-- [ ] **I expect STRING values** (video IDs, not JSON)
-- [ ] I'm using `lrange()` for LIST keys (returns `string[]`)
-- [ ] I'm using `zrevrange()` for ZSET keys (returns `string[]`)
-- [ ] I'm NOT parsing each element as JSON
-- [ ] I'm NOT expecting object properties (id, title, etc.)
-- [ ] Verify: Example key is `box:app:video:*` (not `box:app:tag:list:*`)
-
-### Tag JSON Keys
-
-- [ ] **I expect JSON content** to parse
-- [ ] For LIST keys (`box:app:tag:list:*`), each element is JSON
-- [ ] For STRING keys (`box:app:tag:all`), the whole value is a JSON array
-- [ ] I'm calling `JSON.parse()` on the result
-- [ ] I'm extracting fields like `id`, `name`, `seq` from parsed objects
-
-### Pagination (ZSET Keys)
-
-- [ ] I'm using `ZREVRANGE` (reverse order = newest first)
-- [ ] Offset calculation: `(page - 1) * pageSize`
-- [ ] Limit calculation: `offset + pageSize - 1`
-- [ ] Example: Page 1, size 20 → ZREVRANGE key 0 19
-
-### Non-Pagination (LIST Keys)
-
-- [ ] I'm using `LRANGE` with `0 -1` to get all
-- [ ] Or specific range: `LRANGE key offset offset+limit-1`
-- [ ] Results are in insertion order, not sorted
-
----
-
-## ✅ Common Operations
-
-### Write Video IDs to Category List
-
-```typescript
-import { tsCacheKeys } from '@box/common/cache/ts-cache-key.provider';
-
-const key = tsCacheKeys.video.categoryList('channel-1');
-const videoIds = ['video-001', 'video-002', 'video-003'];
-
-// ✅ CORRECT: Use rpushList (atomic DEL + RPUSH)
-await redis.rpushList(key, videoIds);
-
-// ❌ WRONG: Direct push without clearing
-// await redis.rpush(key, ...videoIds);
-```
-
-### Read Video IDs from Category List
-
-```typescript
-const key = tsCacheKeys.video.categoryList('channel-1');
-
-// ✅ CORRECT: LRANGE returns string[]
-const videoIds = await redis.lrange(key, 0, -1);
-// Result: ['video-001', 'video-002', 'video-003']
-
-// ❌ WRONG: Parsing as JSON
-// const data = await redis.lrange(key, 0, -1);
-// const parsed = data.map(item => JSON.parse(item)); // ERROR!
-```
-
-### Write Video Pool (ZSET)
-
-```typescript
-const key = tsCacheKeys.video.categoryPool('channel-1', 'category-1', 'latest');
-const members = [
-  { member: 'video-001', score: 1700000000 },
-  { member: 'video-002', score: 1700000001 },
-];
-
-// ✅ CORRECT: Use zadd (atomic DEL + ZADD)
-await redis.zadd(key, members);
-```
-
-### Read Video Pool with Pagination
-
-```typescript
-const key = tsCacheKeys.video.categoryPool('channel-1', 'category-1', 'latest');
-const page = 1;
-const pageSize = 20;
-
-// ✅ CORRECT: ZREVRANGE for pagination
-const videoIds = await redis.zrevrange(
-  key,
-  (page - 1) * pageSize,
-  page * pageSize - 1,
-);
-// Result: ['video-001', 'video-002', ...] (20 items, newest first)
-```
-
-### Write Tags (JSON List)
-
-```typescript
-const key = tsCacheKeys.video.tagList('channel-1', 'category-1');
-// Note: This key doesn't exist in current schema
-// For global tags, use tag.all()
-
-const tagKey = tsCacheKeys.tag.all();
-const tags = [
-  { id: 'tag-1', name: 'Sports', seq: 1, status: 1, ... },
-  { id: 'tag-2', name: 'Action', seq: 2, status: 1, ... }
-];
-
-// ✅ CORRECT: Use setJson for JSON array
-await redis.setJson(tagKey, tags);
-```
-
-### Read Tags (JSON)
-
-```typescript
-const tagKey = tsCacheKeys.tag.all();
-
-// ✅ CORRECT: GET returns full JSON, parse it
-const tagsJson = await redis.get(tagKey);
-const tags = JSON.parse(tagsJson);
-// Result: [{ id: 'tag-1', name: 'Sports', ... }, ...]
-```
-
----
-
-## ✅ Mistake Prevention
-
-### ❌ Mistake 1: Storing JSON in Video Lists
-
-```typescript
-// WRONG!
-const videos = [
-  { id: 'vid-1', title: 'Video 1', description: 'Desc' },
-  { id: 'vid-2', title: 'Video 2', description: 'Desc' },
-];
-await redis.rpush(key, ...videos.map((v) => JSON.stringify(v)));
-
-// CORRECT!
-const videoIds = videos.map((v) => v.id); // Extract IDs only
-await redis.rpushList(key, videoIds); // ['vid-1', 'vid-2']
-```
-
-### ❌ Mistake 2: Parsing Video ID Lists as JSON
-
-```typescript
-// WRONG!
-const items = await redis.lrange(key, 0, -1); // ['vid-1', 'vid-2', ...]
-const parsed = items.map((item) => JSON.parse(item)); // ERROR! They're not JSON
-
-// CORRECT!
-const videoIds = await redis.lrange(key, 0, -1); // Use as-is
-// Result: ['vid-1', 'vid-2', ...]
-```
-
-### ❌ Mistake 3: Confusing Tag Keys
-
-```typescript
-// WRONG!
-const tagVideoKey = tsCacheKeys.video.tagList('channel-1', 'category-1');
-const allTagsKey = tsCacheKeys.tag.all();
-
-// These are DIFFERENT keys with DIFFERENT data!
-const videoIds = await redis.lrange(tagVideoKey, 0, -1); // Returns video IDs
-const tagsJson = await redis.get(allTagsKey); // Returns JSON array of tags
-const tags = JSON.parse(tagsJson); // Now we have tag objects
-
-// CORRECT USAGE!
-// video.tagList() → video IDs (strings only)
-// tag.all() → Tag JSON objects
-```
-
-### ❌ Mistake 4: Non-Atomic Writes
-
-```typescript
-// WRONG! Not atomic - readers might see partial state
-await redis.del(key);
-await redis.rpush(key, ...items);
-
-// CORRECT! Atomic via pipeline
-await redis.rpushList(key, items); // Internally does DEL then RPUSH
-```
-
-### ❌ Mistake 5: Using Wrong Redis Operation
-
-```typescript
-// WRONG! Using SET for LIST key
-await redis.set(videoListKey, JSON.stringify(videoIds));
-
-// CORRECT! Use appropriate operation for type
-await redis.rpushList(videoListKey, videoIds); // For LIST type
-await redis.zadd(videoPoolKey, membersWithScores); // For ZSET type
-await redis.setJson(tagKey, tagObjects); // For STRING/JSON type
-```
-
----
-
-## ✅ Testing Checklist
-
-### Before Submitting Code
-
-- [ ] I've used `tsCacheKeys` for key generation (type-safe)
-- [ ] I understand the Redis type for my key
-- [ ] I'm using correct operations (LRANGE, ZREVRANGE, GET, etc.)
-- [ ] All writes use atomic pipelines (DEL + operation)
-- [ ] Video ID keys contain strings only, not JSON
-- [ ] Tag JSON keys are parsed correctly
-- [ ] No manual JSON stringification for video lists
-- [ ] Pagination offset calculations are correct
-
-### Code Review Questions
-
-1. **Is this a video ID key or JSON key?** → Check key pattern
-2. **What Redis type is this?** → Check cache-keys.ts JSDoc
-3. **Are we writing atomically?** → Check for rpushList, zadd, setJson
-4. **Are we parsing JSON when needed?** → Check for JSON.parse on tag keys
-5. **Are we using tsCacheKeys?** → Check for type safety
-
----
-
-## ✅ Quick Reference Links
-
-- **Complete Semantics**: `libs/common/src/cache/CACHE_SEMANTICS.md`
-- **Quick Lookup**: `libs/common/src/cache/CACHE_KEYS_QUICK_REF.md`
-- **Key Definitions**: `libs/common/src/cache/cache-keys.ts`
-- **Type-Safe API**: `libs/common/src/cache/ts-cache-key.provider.ts`
-- **Type Checking**: `libs/common/src/cache/cache-semantics.constants.ts`
-- **Documentation Index**: `libs/common/src/cache/README.md`
-
----
-
-## ✅ When Stuck
-
-1. **"What's the structure of this key?"** → Read CACHE_SEMANTICS.md
-2. **"How do I generate a key?"** → Use `tsCacheKeys` with IDE autocomplete
-3. **"What Redis command should I use?"** → Check CACHE_KEYS_QUICK_REF.md table
-4. **"Is this a common mistake?"** → Check CACHE_KEYS_QUICK_REF.md → "Common Mistakes"
-5. **"How do I paginate?"** → Copy code snippet from CACHE_KEYS_QUICK_REF.md
-
----
-
-**Last Updated**: December 6, 2025  
-**Version**: 1.0 (Documentation Phase)

+ 0 - 215
libs/common/src/cache/README.md

@@ -1,215 +0,0 @@
-# Redis Cache Documentation Index
-
-This directory contains comprehensive documentation for Redis cache keys and semantics in the Box monorepo. Use these files to understand how cache keys are structured, typed, and used.
-
-## Documentation Files
-
-### 1. **CACHE_SEMANTICS.md** (START HERE for new developers)
-
-- **Purpose**: Complete specification of Redis key semantics
-- **Audience**: All developers, especially those modifying cache code
-- **Contents**:
-  - Core principles (type safety, atomicity, versioning)
-  - Detailed breakdown of each key pattern
-  - Complete reference table of all keys
-  - Key naming conventions
-  - Developer checklist
-
-**When to read**: Understanding how a specific key works, planning new cache features
-
-### 2. **CACHE_KEYS_QUICK_REF.md** (QUICK LOOKUP)
-
-- **Purpose**: Quick reference tables and common patterns
-- **Audience**: Developers writing or debugging cache code
-- **Contents**:
-  - Quick lookup tables for all key patterns
-  - Common mistakes to avoid
-  - TypeScript usage examples
-  - Builder information
-  - Copy-paste code snippets
-
-**When to read**: Need to quickly remember a key pattern or find example code
-
-### 3. **cache-keys.ts** (SOURCE OF TRUTH)
-
-- **Purpose**: Type definitions and JSDoc comments for all cache keys
-- **Audience**: Developers using the CacheKeys object
-- **Contents**:
-  - TypeScript function definitions
-  - Detailed JSDoc comments per key
-  - Links to documentation files
-  - Cross-references to builders and readers
-
-**When to read**: IDE hover tooltips, understanding key function signatures
-
-### 4. **ts-cache-key.provider.ts** (CONVENIENT API)
-
-- **Purpose**: Structured, typed interface for cache key generation
-- **Audience**: Developers writing service code
-- **Contents**:
-  - TsCacheKeyBuilder interface with extensive docs
-  - Organized by entity (video, tag, channel, etc.)
-  - Usage examples in JSDoc
-  - Semantic distinctions (list vs. pool vs. detail)
-
-**When to read**: Writing code that needs cache keys, IDE autocomplete reference
-
----
-
-## Quick Start for Developers
-
-### I need to use a cache key
-
-1. Check **CACHE_KEYS_QUICK_REF.md** for quick lookup
-2. Use `tsCacheKeys` from `ts-cache-key.provider.ts` for type safety
-3. Read the JSDoc tooltip in your IDE (from `cache-keys.ts`)
-
-### I need to understand key semantics
-
-1. Start with **CACHE_SEMANTICS.md** → "Core Principles"
-2. Find your key in the "Complete Key Reference Table"
-3. Read the detailed section for that key pattern
-
-### I'm building a new cache feature
-
-1. Read **CACHE_SEMANTICS.md** → "Developer Checklist"
-2. Use the **CACHE_KEYS_QUICK_REF.md** to understand existing patterns
-3. Define your new key following the conventions
-4. Update all four documentation files consistently
-
-### I found a bug in cache code
-
-1. Check **CACHE_KEYS_QUICK_REF.md** → "Common Mistakes" section
-2. Verify Redis type and operations in **CACHE_SEMANTICS.md**
-3. Check the builder file from the "Builder Information" table
-
----
-
-## Critical Concepts (tl;dr)
-
-### Redis Types Used
-
-- **LIST**: Ordered collection (video IDs or JSON objects)
-- **ZSET**: Scored/sorted collection (for pagination)
-- **STRING**: Single JSON value
-- **SET**: Unordered collection (not used for videos)
-
-### Key Pattern Rules
-
-| Suffix                 | Redis Type | Contents          | Example                                         |
-| ---------------------- | ---------- | ----------------- | ----------------------------------------------- |
-| `:list:<id>`           | LIST       | Video IDs only    | `box:app:video:category:list:cat-1`             |
-| `:tag:list:<id>:<id2>` | LIST       | Video IDs only    | `box:app:video:tag:list:cat-1:tag-1`            |
-| `:pool:<id>:<sort>`    | ZSET       | Video IDs + score | `box:app:video:list:category:ch-1:cat-1:latest` |
-| `:detail:<id>`         | STRING     | JSON object       | `box:app:video:detail:vid-1`                    |
-| `:tag:all`             | STRING     | JSON array        | `box:app:tag:all`                               |
-
-### Common Operations
-
-```typescript
-import { tsCacheKeys } from '@box/common/cache/ts-cache-key.provider';
-
-// READ video IDs for a category (LIST)
-const key = tsCacheKeys.video.categoryList('channel-1');
-const videoIds = await redis.lrange(key, 0, -1); // Returns string[]
-
-// READ paginated videos (ZSET)
-const poolKey = tsCacheKeys.video.categoryPool('ch-1', 'cat-1', 'latest');
-const page1 = await redis.zrevrange(poolKey, 0, 19);
-
-// READ all tags (STRING → parse JSON)
-const tagKey = tsCacheKeys.tag.all();
-const tagsJson = await redis.get(tagKey);
-const tags = JSON.parse(tagsJson);
-
-// WRITE category videos (LIST)
-await redis.rpushList(key, ['video-1', 'video-2', 'video-3']);
-
-// WRITE video pool (ZSET)
-await redis.zadd(poolKey, [
-  { member: 'video-1', score: 1700000000 },
-  { member: 'video-2', score: 1700000001 },
-]);
-```
-
----
-
-## Where These Keys Are Built
-
-| Key Pattern                     | Builder                   | File                                                                 | Method                           |
-| ------------------------------- | ------------------------- | -------------------------------------------------------------------- | -------------------------------- |
-| `box:app:video:category:list:*` | VideoCategoryCacheBuilder | `libs/core/src/cache/video/category/video-category-cache.builder.ts` | `buildCategoryListForChannel()`  |
-| `box:app:video:tag:list:*`      | VideoCategoryCacheBuilder | `libs/core/src/cache/video/category/video-category-cache.builder.ts` | `buildTagListForCategory()`      |
-| `box:app:video:list:category:*` | VideoListCacheBuilder     | `libs/core/src/cache/video/list/video-list-cache.builder.ts`         | `buildCategoryPoolsForChannel()` |
-| `box:app:video:list:tag:*`      | VideoListCacheBuilder     | `libs/core/src/cache/video/list/video-list-cache.builder.ts`         | `buildTagPoolsForChannel()`      |
-| `box:app:video:list:home:*`     | VideoListCacheBuilder     | `libs/core/src/cache/video/list/video-list-cache.builder.ts`         | `buildHomeSectionsForChannel()`  |
-| `box:app:tag:all`               | TagCacheBuilder           | `libs/core/src/cache/tag/tag-cache.builder.ts`                       | `buildAll()`                     |
-
----
-
-## Where These Keys Are Read
-
-| Key Pattern                     | Reader          | File                                                  | Method                            |
-| ------------------------------- | --------------- | ----------------------------------------------------- | --------------------------------- |
-| `box:app:video:category:list:*` | VideoService    | `apps/box-app-api/src/feature/video/video.service.ts` | `getCategoryListForChannel()`     |
-| `box:app:video:tag:list:*`      | VideoService    | `apps/box-app-api/src/feature/video/video.service.ts` | `getTagListForCategory()`         |
-| `box:app:video:list:category:*` | VideoService    | `apps/box-app-api/src/feature/video/video.service.ts` | `getVideosByCategoryWithPaging()` |
-| `box:app:video:list:tag:*`      | VideoService    | `apps/box-app-api/src/feature/video/video.service.ts` | `getVideosByTagWithPaging()`      |
-| `box:app:video:detail:*`        | VideoService    | `apps/box-app-api/src/feature/video/video.service.ts` | `getVideoDetail()`                |
-| `box:app:tag:all`               | TagCacheService | `libs/core/src/cache/tag/tag-cache.service.ts`        | `getAllTags()`                    |
-
----
-
-## Related Code Files
-
-- `libs/common/src/cache/cache-keys.ts` — Key definitions
-- `libs/common/src/cache/ts-cache-key.provider.ts` — Typed interface
-- `libs/db/src/redis/redis.service.ts` — Redis operations (get, set, lrange, zadd, etc.)
-- `libs/core/src/cache/video/category/video-category-cache.builder.ts` — Category/tag list builder
-- `libs/core/src/cache/video/list/video-list-cache.builder.ts` — Pool/section builder
-- `libs/core/src/cache/tag/tag-cache.builder.ts` — Tag builder
-- `apps/box-app-api/src/feature/video/video.service.ts` — Cache reader
-
----
-
-## Naming Convention Reference
-
-When creating new cache keys, follow these patterns:
-
-- **Singular entity + "all"**: `app:category:all`, `app:tag:all`
-- **Single entity + type**: `app:video:detail:<id>`, `app:category:by-id:<id>`
-- **List of entities**: `app:video:category:list:<categoryId>`
-- **Filtered list**: `app:video:tag:list:<categoryId>:<tagId>`
-- **Scored pool**: `app:video:list:category:<channelId>:<categoryId>:<sort>`
-- **Hierarchical**: Parent first, then child, then qualifier
-
----
-
-## Document Maintenance
-
-These files should be updated together when:
-
-1. Adding a new cache key
-2. Changing key pattern structure
-3. Changing Redis type for a key
-4. Adding new builders or readers
-5. Discovering semantic issues
-
-**Update order**:
-
-1. `CACHE_SEMANTICS.md` — Update "Complete Key Reference Table" and add detailed section
-2. `cache-keys.ts` — Add JSDoc with examples and links
-3. `ts-cache-key.provider.ts` — Add to interface with detailed comments
-4. `CACHE_KEYS_QUICK_REF.md` — Update quick tables
-5. This file (if builders/readers changed)
-
----
-
-## Questions? Common Searches
-
-- **What's the difference between LIST and ZSET?** → CACHE_SEMANTICS.md "Complete Key Reference"
-- **How do I read a list of video IDs?** → CACHE_KEYS_QUICK_REF.md "Reading in Code"
-- **Can I store JSON in a video list key?** → CACHE_KEYS_QUICK_REF.md "Common Mistakes"
-- **Where is this key built?** → Check builder table in this file or CACHE_SEMANTICS.md
-- **What operations does this key support?** → CACHE_SEMANTICS.md detailed section
-- **I'm writing new code, what should I do?** → CACHE_SEMANTICS.md "Developer Checklist"

+ 1 - 3
package.json

@@ -28,9 +28,7 @@
     "typecheck": "tsc --noEmit --project tsconfig.base.json",
     "typecheck:watch": "tsc --noEmit --watch --project tsconfig.base.json",
     "lint": "eslint \"{apps,libs}/**/*.ts\" --max-warnings 0",
-    "lint:fix": "eslint \"{apps,libs}/**/*.ts\" --fix",
-    "test": "pnpm typecheck && pnpm lint && pnpm build:mgnt",
-    "test:e2e:mgnt": "dotenv -e .env.mgnt.test -- jest --config apps/box-mgnt-api/test/jest-e2e.json --runInBand --detectOpenHandles"
+    "lint:fix": "eslint \"{apps,libs}/**/*.ts\" --fix"
   },
   "dependencies": {
     "@aws-sdk/client-s3": "3.828.0",