|
|
@@ -14,61 +14,8 @@ import {
|
|
|
type TagMetadata,
|
|
|
} from '@box/common/cache/video-cache.helper';
|
|
|
|
|
|
-/**
|
|
|
- * Tag metadata payload for Redis cache (used in box:app:tag:list:{categoryId}).
|
|
|
- * ⚠️ IMPORTANT: This is for Tag metadata only. Video IDs are stored separately.
|
|
|
- */
|
|
|
export interface TagMetadataPayload extends TagMetadata {}
|
|
|
|
|
|
-/**
|
|
|
- * Cache builder for video category/tag lists following new semantics.
|
|
|
- *
|
|
|
- * ═══════════════════════════════════════════════════════════════════════════
|
|
|
- * NEW SEMANTICS EXPLANATION
|
|
|
- * ═══════════════════════════════════════════════════════════════════════════
|
|
|
- *
|
|
|
- * This builder maintains THREE separate cache structures:
|
|
|
- *
|
|
|
- * 1. CATEGORY VIDEO LISTS
|
|
|
- * Key: box:app:video:category:list:{categoryId}
|
|
|
- * Type: LIST
|
|
|
- * Contents: Video IDs ONLY (strings like "64a2b3c4d5e6f7...")
|
|
|
- * ✅ CORRECT: Store only video IDs
|
|
|
- * ❌ WRONG: Never store Tag JSON, Category JSON, or any metadata objects
|
|
|
- * WHY: Video IDs are the minimal data needed to:
|
|
|
- * - Return to client (they fetch full details separately)
|
|
|
- * - Paginate in client (they use ZSET pools for ordering)
|
|
|
- * - Filter videos by category quickly
|
|
|
- *
|
|
|
- * 2. TAG-FILTERED VIDEO LISTS
|
|
|
- * Key: box:app:video:tag:list:{categoryId}:{tagId}
|
|
|
- * Type: LIST
|
|
|
- * Contents: Video IDs ONLY (subset of category videos with this tag)
|
|
|
- * ✅ CORRECT: Store only video IDs (filtered by tag membership)
|
|
|
- * ❌ WRONG: Never store Tag JSON or Video metadata
|
|
|
- * WHY: Same as above - minimal data for tag-based filtering
|
|
|
- *
|
|
|
- * 3. TAG METADATA LISTS (THE ONLY PLACE FOR TAG JSON)
|
|
|
- * Key: box:app:tag:list:{categoryId}
|
|
|
- * Type: LIST
|
|
|
- * Contents: Tag JSON objects (stringified)
|
|
|
- * ✅ CORRECT: This is the ONLY key where Tag JSON should be stored
|
|
|
- * ❌ WRONG: Never store Tag JSON in video list keys (items 1 & 2 above)
|
|
|
- * WHY: Tags are used for filter UI (dropdown, checkboxes)
|
|
|
- * - Client needs id, name, seq for display
|
|
|
- * - Stored separately from videos to avoid duplication
|
|
|
- * - Each element is parsed as JSON, not stored as raw strings
|
|
|
- *
|
|
|
- * ═══════════════════════════════════════════════════════════════════════════
|
|
|
- * KEY INVARIANT: NEVER store Tag/Category JSON in video list keys!
|
|
|
- * ═══════════════════════════════════════════════════════════════════════════
|
|
|
- *
|
|
|
- * WRITER CONTRACT:
|
|
|
- * ────────────────
|
|
|
- * - buildCategoryVideoListForCategory() → uses saveVideoIdList() → writes to box:app:video:category:list:*
|
|
|
- * - buildTagFilteredVideoListForTag() → uses saveVideoIdList() → writes to box:app:video:tag:list:*
|
|
|
- * - buildTagMetadataListForCategory() → uses saveTagList() → writes to box:app:tag:list:*
|
|
|
- */
|
|
|
@Injectable()
|
|
|
export class VideoCategoryCacheBuilder extends BaseCacheBuilder {
|
|
|
private readonly cacheHelper: VideoCacheHelper;
|
|
|
@@ -84,22 +31,6 @@ export class VideoCategoryCacheBuilder extends BaseCacheBuilder {
|
|
|
* 1. Category video lists (video IDs only, using saveVideoIdList)
|
|
|
* 2. Tag-filtered video lists (video IDs only, using saveVideoIdList)
|
|
|
* 3. Tag metadata lists (Tag JSON objects, using saveTagList)
|
|
|
- *
|
|
|
- * ⚠️ IMPORTANT IMPLEMENTATION DETAILS:
|
|
|
- * ───────────────────────────────────
|
|
|
- * - All writes go through VideoCacheHelper (never direct RedisService calls)
|
|
|
- * - saveVideoIdList() is used for both category and tag-filtered video lists
|
|
|
- * - saveTagList() is ONLY used for tag metadata (box:app:tag:list:{categoryId})
|
|
|
- * - This ensures atomicity (DEL + RPUSH + EXPIRE) for all operations
|
|
|
- * - Tag JSON is NEVER written to video list keys
|
|
|
- *
|
|
|
- * LOGGING:
|
|
|
- * ────────
|
|
|
- * Provides detailed statistics about cache rebuild:
|
|
|
- * - Number of channels processed
|
|
|
- * - Number of categories rebuilt (category video lists)
|
|
|
- * - Number of tag-filtered video lists rebuilt
|
|
|
- * - Number of tag metadata lists rebuilt
|
|
|
*/
|
|
|
async buildAll(): Promise<void> {
|
|
|
const channels = await this.mongoPrisma.channel.findMany();
|
|
|
@@ -178,32 +109,6 @@ export class VideoCategoryCacheBuilder extends BaseCacheBuilder {
|
|
|
|
|
|
/**
|
|
|
* Build category video list (LIST of video IDs only).
|
|
|
- *
|
|
|
- * QUERY STRATEGY:
|
|
|
- * ──────────────
|
|
|
- * - Fetch all videos with listStatus === 1 (on shelf)
|
|
|
- * - Order by addedTime DESC (newest/most recently added first)
|
|
|
- * - Fallback to createdAt DESC if addedTime is not set
|
|
|
- * - Extract ONLY video IDs (no metadata)
|
|
|
- *
|
|
|
- * WRITE STRATEGY:
|
|
|
- * ───────────────
|
|
|
- * - Key: box:app:video:category:list:{categoryId}
|
|
|
- * - Type: Redis LIST (ordered set of strings)
|
|
|
- * - Helper: VideoCacheHelper.saveVideoIdList() for atomic DEL + RPUSH + EXPIRE
|
|
|
- * - If no videos found: clear the key (DEL) and skip RPUSH
|
|
|
- *
|
|
|
- * ⚠️ CRITICAL CONTRACT:
|
|
|
- * ────────────────────
|
|
|
- * - ✅ ONLY store Video IDs (strings)
|
|
|
- * - ❌ NEVER store Tag JSON, Category JSON, or any metadata objects
|
|
|
- * - ❌ NEVER store Tag JSON in this key (use box:app:tag:list:{categoryId} instead)
|
|
|
- *
|
|
|
- * ERROR HANDLING:
|
|
|
- * ───────────────
|
|
|
- * - If no videos found: log debug message and clear the key
|
|
|
- * - If Redis operation fails: log error with stack trace and re-throw
|
|
|
- * - All operations are atomic (DEL + RPUSH + EXPIRE)
|
|
|
*/
|
|
|
async buildCategoryVideoListForCategory(categoryId: string): Promise<number> {
|
|
|
try {
|
|
|
@@ -237,33 +142,6 @@ export class VideoCategoryCacheBuilder extends BaseCacheBuilder {
|
|
|
|
|
|
/**
|
|
|
* Build tag-filtered video list (LIST of video IDs only).
|
|
|
- *
|
|
|
- * QUERY STRATEGY:
|
|
|
- * ──────────────
|
|
|
- * - Fetch all videos with listStatus === 1 (on shelf) AND tagIds contains this tagId
|
|
|
- * - Order by addedTime DESC (newest/most recently added first)
|
|
|
- * - Fallback to createdAt DESC if addedTime is not set
|
|
|
- * - Extract ONLY video IDs (no metadata)
|
|
|
- *
|
|
|
- * WRITE STRATEGY:
|
|
|
- * ───────────────
|
|
|
- * - Key: box:app:video:tag:list:{categoryId}:{tagId}
|
|
|
- * - Type: Redis LIST (ordered set of strings)
|
|
|
- * - Helper: VideoCacheHelper.saveVideoIdList() for atomic DEL + RPUSH + EXPIRE
|
|
|
- * - If no videos found: clear the key (DEL) and skip RPUSH
|
|
|
- *
|
|
|
- * ⚠️ CRITICAL CONTRACT:
|
|
|
- * ────────────────────
|
|
|
- * - ✅ ONLY store Video IDs (strings) that have this tag
|
|
|
- * - ❌ NEVER store Tag JSON or metadata objects in this key
|
|
|
- * - ❌ NEVER store Tag JSON (use box:app:tag:list:{categoryId} instead)
|
|
|
- * - This key is DISTINCT from box:app:tag:list:{categoryId} which stores Tag JSON
|
|
|
- *
|
|
|
- * ERROR HANDLING:
|
|
|
- * ───────────────
|
|
|
- * - If no videos found with this tag: log debug message and clear the key
|
|
|
- * - If Redis operation fails: log error with stack trace and re-throw
|
|
|
- * - All operations are atomic (DEL + RPUSH + EXPIRE)
|
|
|
*/
|
|
|
async buildTagFilteredVideoListForTag(
|
|
|
categoryId: string,
|
|
|
@@ -304,52 +182,6 @@ export class VideoCategoryCacheBuilder extends BaseCacheBuilder {
|
|
|
|
|
|
/**
|
|
|
* Build tag metadata list (LIST of Tag JSON objects).
|
|
|
- *
|
|
|
- * QUERY STRATEGY:
|
|
|
- * ──────────────
|
|
|
- * - Fetch all tags with status === 1 (enabled) in this category
|
|
|
- * - Order by seq ASC (business order for dropdown display)
|
|
|
- * - Fallback to createAt ASC for consistent ordering
|
|
|
- * - Transform to TagMetadataPayload (convert timestamps to strings)
|
|
|
- *
|
|
|
- * WRITE STRATEGY:
|
|
|
- * ───────────────
|
|
|
- * - Key: box:app:tag:list:{categoryId}
|
|
|
- * - Type: Redis LIST (each element is stringified Tag JSON)
|
|
|
- * - Helper: VideoCacheHelper.saveTagList() for atomic DEL + RPUSH + EXPIRE
|
|
|
- * - If no tags found: clear the key (DEL) and skip RPUSH
|
|
|
- *
|
|
|
- * ⚠️ CRITICAL CONTRACT: THIS IS THE ONLY PLACE WHERE TAG JSON SHOULD BE STORED
|
|
|
- * ──────────────────────────────────────────────────────────────────────────
|
|
|
- * - ✅ ONLY store Tag JSON objects in this key
|
|
|
- * - ✅ Each element is a stringified Tag object with {id, name, seq, status, ...}
|
|
|
- * - ❌ NEVER store Tag JSON in "box:app:video:category:list:*" keys
|
|
|
- * - ❌ NEVER store Tag JSON in "box:app:video:tag:list:*" keys
|
|
|
- * - Video list keys MUST contain ONLY video IDs, never Tag metadata
|
|
|
- *
|
|
|
- * PURPOSE:
|
|
|
- * ────────
|
|
|
- * Used by frontend for tag filter UI (dropdowns, checkboxes)
|
|
|
- * Each tag is a separate list element and parsed as JSON when read
|
|
|
- *
|
|
|
- * ERROR HANDLING:
|
|
|
- * ───────────────
|
|
|
- * - If no tags found: log debug message and clear the key
|
|
|
- * - If Redis operation fails: log error with stack trace and re-throw
|
|
|
- * - All operations are atomic (DEL + RPUSH + EXPIRE)
|
|
|
- *
|
|
|
- * FORMAT PER ELEMENT IN LIST:
|
|
|
- * ──────────────────────────
|
|
|
- * {
|
|
|
- * id: string (tag ID),
|
|
|
- * name: string (display name),
|
|
|
- * seq: number (sort order),
|
|
|
- * status: number (0=disabled, 1=enabled),
|
|
|
- * createAt: string (ISO timestamp),
|
|
|
- * updateAt: string (ISO timestamp),
|
|
|
- * channelId: string,
|
|
|
- * categoryId: string
|
|
|
- * }
|
|
|
*/
|
|
|
async buildTagMetadataListForCategory(categoryId: string): Promise<number> {
|
|
|
try {
|