| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- // box-nestjs-monorepo/apps/box-mgnt-api/src/dev/services/video-cache-coverage.service.ts
- import { Injectable, Logger } from '@nestjs/common';
- import { RedisService } from '@box/db/redis/redis.service';
- import { MongoPrismaService } from '@box/db/prisma/mongo-prisma.service';
- import { tsCacheKeys } from '@box/common/cache/ts-cache-key.provider';
- /**
- * Response structure for video cache coverage check
- */
- export interface VideoCacheCoverageResponse {
- totalChannels: number;
- totalCategories: number;
- totalTags: number;
- missingCategoryVideoLists: string[];
- missingTagVideoLists: string[];
- missingTagMetadataLists: string[];
- }
- /**
- * Service for checking video cache coverage.
- * Dev-only: verifies that all expected cache keys exist in Redis.
- *
- * Compares database structure (channels, categories, tags) with actual Redis keys
- * to identify missing cache entries.
- *
- * Smart detection:
- * - Only reports missing category video list if videos exist and Redis key is missing
- * - Only reports missing tag video list if videos exist and Redis key is missing
- * - Tag metadata lists always checked (expected if category has tags)
- */
- @Injectable()
- export class VideoCacheCoverageService {
- private readonly logger = new Logger(VideoCacheCoverageService.name);
- constructor(
- private readonly redis: RedisService,
- private readonly mongoPrisma: MongoPrismaService,
- ) {}
- /**
- * Check video cache coverage across all channels, categories, and tags.
- * Identifies missing category lists, tag lists, and tag metadata lists.
- *
- * SMART MISSING-KEY DETECTION:
- * - Only reports category video list as missing if: videoCount > 0 AND Redis key missing
- * - Only reports tag video list as missing if: videoCount > 0 AND Redis key missing
- * - Tag metadata lists are checked regardless (always expected if tags exist)
- *
- * This avoids false positives for empty categories/tags with no videos.
- */
- async checkCoverage(): Promise<VideoCacheCoverageResponse> {
- this.logger.log('[Coverage] Starting video cache coverage check');
- const missingCategoryVideoLists: string[] = [];
- const missingTagVideoLists: string[] = [];
- const missingTagMetadataLists: string[] = [];
- let totalChannels = 0;
- let totalCategories = 0;
- let totalTags = 0;
- // Get all channels
- const channels = await this.mongoPrisma.channel.findMany();
- totalChannels = channels.length;
- this.logger.debug(`[Coverage] Found ${totalChannels} channels`);
- for (const channel of channels) {
- // Get all enabled categories (no longer tied to channel)
- const categories = await this.mongoPrisma.category.findMany({
- where: { status: 1 },
- });
- for (const category of categories) {
- totalCategories++;
- // Count videos in this category (using same filter as builder)
- const categoryVideoCount = await this.mongoPrisma.videoMedia.count({
- where: {
- categoryIds: { has: category.id },
- status: 'Completed',
- // listStatus: 1, // Only "on shelf" videos
- },
- });
- // Check category video list cache key ONLY if there are videos
- const categoryListKey = tsCacheKeys.video.categoryList(category.id);
- const categoryListExists = await this.redis.exists(categoryListKey);
- if (categoryVideoCount > 0 && !categoryListExists) {
- missingCategoryVideoLists.push(categoryListKey);
- this.logger.warn(
- `[Coverage] Missing category list: ${categoryListKey} (videos: ${categoryVideoCount})`,
- );
- } else if (categoryVideoCount === 0) {
- this.logger.debug(
- `[Coverage] Empty category OK (no videos, no cache required): ${categoryListKey}`,
- );
- }
- // Check tag metadata list cache key for this category
- const tagMetadataKey = tsCacheKeys.tag.metadataByCategory(category.id);
- const tagMetadataExists = await this.redis.exists(tagMetadataKey);
- if (!tagMetadataExists) {
- missingTagMetadataLists.push(tagMetadataKey);
- this.logger.warn(
- `[Coverage] Missing tag metadata list: ${tagMetadataKey}`,
- );
- }
- // Get all enabled tags for this category (no longer filtered by channel)
- const tags = await this.mongoPrisma.tag.findMany({
- where: {
- status: 1,
- categoryId: category.id,
- },
- });
- for (const tag of tags) {
- totalTags++;
- // Count videos with this tag in this category (same filter as builder)
- const tagVideoCount = await this.mongoPrisma.videoMedia.count({
- where: {
- categoryIds: { has: category.id },
- status: 'Completed',
- // listStatus: 1, // Only "on shelf" videos
- // tagIds: { has: tag.id }, // Has this specific tag
- },
- });
- // Check tag-filtered video list cache key ONLY if there are videos
- const tagListKey = tsCacheKeys.video.tagList(category.id, tag.id);
- const tagListExists = await this.redis.exists(tagListKey);
- if (tagVideoCount > 0 && !tagListExists) {
- missingTagVideoLists.push(tagListKey);
- this.logger.warn(
- `[Coverage] Missing tag video list: ${tagListKey} (videos: ${tagVideoCount})`,
- );
- } else if (tagVideoCount === 0) {
- this.logger.debug(
- `[Coverage] Empty tag OK (no videos, no cache required): ${tagListKey}`,
- );
- }
- }
- }
- }
- const totalMissing =
- missingCategoryVideoLists.length +
- missingTagVideoLists.length +
- missingTagMetadataLists.length;
- this.logger.log(
- `[Coverage] Complete: channels=${totalChannels} categories=${totalCategories} tags=${totalTags} missing=${totalMissing}`,
- );
- if (totalMissing > 0) {
- this.logger.warn(
- `[Coverage] Found ${totalMissing} missing cache keys (categoryLists: ${missingCategoryVideoLists.length}, tagLists: ${missingTagVideoLists.length}, tagMetadata: ${missingTagMetadataLists.length})`,
- );
- } else {
- this.logger.log('[Coverage] All expected cache keys exist');
- }
- return {
- totalChannels,
- totalCategories,
- totalTags,
- missingCategoryVideoLists,
- missingTagVideoLists,
- missingTagMetadataLists,
- };
- }
- }
|