瀏覽代碼

feat(provider-video-sync): add history endpoint and DTOs; remove sync-videomedia module

Dave 3 月之前
父節點
當前提交
f0daea510c

+ 0 - 2
apps/box-mgnt-api/src/mgnt-backend/feature/feature.module.ts

@@ -4,7 +4,6 @@ import { Module } from '@nestjs/common';
 import { S3Module } from './s3/s3.module';
 import { SystemParamsModule } from './system-params/system-params.module';
 import { MgntHttpServiceModule } from './mgnt-http-service/mgnt-http-service.module';
-import { SyncVideomediaModule } from './sync-videomedia/sync-videomedia.module';
 import { ProviderVideoSyncModule } from './provider-video-sync/provider-video-sync.module';
 import { AdsModule } from './ads/ads.module';
 import { CategoryModule } from './category/category.module';
@@ -23,7 +22,6 @@ import { HealthModule } from './health/health.module';
     ChannelModule,
     TagModule,
     MgntHttpServiceModule,
-    SyncVideomediaModule,
     ProviderVideoSyncModule,
     VideoMediaModule,
     HealthModule,

+ 19 - 71
apps/box-mgnt-api/src/mgnt-backend/feature/provider-video-sync/provider-video-sync.controller.ts

@@ -8,81 +8,14 @@ import {
   ApiTags,
 } from '@nestjs/swagger';
 import {
-  IsBoolean,
-  IsInt,
-  IsObject,
-  IsOptional,
-  IsString,
-  Max,
-  Min,
-  ValidateNested,
-} from 'class-validator';
-import { Type } from 'class-transformer';
-import {
   ProviderVideoSyncOptions,
   ProviderVideoSyncResult,
   ProviderVideoSyncService,
 } from './provider-video-sync.service';
-
-class ProviderVideoSyncParamDto {
-  @IsOptional()
-  @IsString()
-  status?: string;
-
-  /**
-   * ISO string; provider filters "updated after"
-   * Example: "2025-12-18T21:19:09.227Z"
-   */
-  @IsOptional()
-  @IsString()
-  updatedAt?: string;
-
-  /**
-   * Allow extra provider param keys without strict validation.
-   * If you want to lock this down later, replace with explicit fields.
-   */
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  [k: string]: any;
-}
-
-export class ProviderVideoSyncRunDto {
-  @IsOptional()
-  @IsString()
-  providerCode?: string;
-
-  @IsOptional()
-  @IsBoolean()
-  fullSync?: boolean;
-
-  @IsOptional()
-  @IsBoolean()
-  resetState?: boolean;
-
-  /**
-   * Optional override. In normal usage:
-   * - fullSync: resumes from SyncState cursor
-   * - incremental: pageNum is forced to 1
-   */
-  @IsOptional()
-  @IsInt()
-  @Min(1)
-  pageNum?: number;
-
-  /**
-   * Default is handled by service; capped at 500 by service.
-   */
-  @IsOptional()
-  @IsInt()
-  @Min(1)
-  @Max(500)
-  pageSize?: number;
-
-  @IsOptional()
-  @IsObject()
-  @ValidateNested()
-  @Type(() => ProviderVideoSyncParamDto)
-  param?: ProviderVideoSyncParamDto;
-}
+import {
+  ProviderVideoSyncHistoryResponseDto,
+  ProviderVideoSyncRunDto,
+} from './provider-video-sync.dto';
 
 @ApiTags('Provider Video Sync')
 @Controller('provider-video-sync')
@@ -191,6 +124,21 @@ export class ProviderVideoSyncController {
 
     return this.service.syncFromProvider(options);
   }
+
+  @Get('history')
+  @Post('history')
+  @ApiOperation({
+    summary: 'Get the latest provider video sync cursor info',
+  })
+  @ApiResponse({
+    status: 200,
+    description: 'Latest syncState row',
+    type: ProviderVideoSyncHistoryResponseDto,
+  })
+  async history(): Promise<ProviderVideoSyncHistoryResponseDto> {
+    const history = await this.service.getHistory();
+    return { data: history };
+  }
 }
 
 function parsePageSize(value?: string): number | undefined {

+ 125 - 0
apps/box-mgnt-api/src/mgnt-backend/feature/provider-video-sync/provider-video-sync.dto.ts

@@ -0,0 +1,125 @@
+import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
+import {
+  IsBoolean,
+  IsInt,
+  IsObject,
+  IsOptional,
+  IsString,
+  Max,
+  Min,
+  ValidateNested,
+} from 'class-validator';
+import { Type } from 'class-transformer';
+
+export class ProviderVideoSyncHistoryItemDto {
+  @ApiProperty({ type: String, example: 'Provider Video Sync' })
+  title!: string;
+
+  @ApiPropertyOptional({
+    type: Number,
+    description: 'Epoch seconds for last run',
+  })
+  runAt?: number;
+
+  @ApiPropertyOptional({
+    type: Number,
+    description: 'Epoch seconds for full sync completion',
+  })
+  completedAt?: number;
+
+  @ApiPropertyOptional({
+    type: Number,
+    description: 'Known processed count (not stored yet)',
+  })
+  processedCount: null = null;
+
+  @ApiPropertyOptional({
+    type: String,
+    description: 'Checkpoint cursor value stored in referId',
+  })
+  updatedAtCursor?: string;
+
+  @ApiPropertyOptional({
+    type: Number,
+    description: 'syncState.updatedAt (epoch seconds)',
+  })
+  updatedAt?: number;
+}
+
+export class ProviderVideoSyncHistoryDataDto {
+  @ApiProperty({
+    type: [ProviderVideoSyncHistoryItemDto],
+    description: 'Cursor records for provider sync',
+  })
+  list!: ProviderVideoSyncHistoryItemDto[];
+
+  @ApiProperty({ type: Number, description: 'Total rows available' })
+  total!: number;
+}
+
+export class ProviderVideoSyncHistoryResponseDto {
+  @ApiProperty({
+    type: ProviderVideoSyncHistoryDataDto,
+  })
+  data!: ProviderVideoSyncHistoryDataDto;
+}
+
+class ProviderVideoSyncParamDto {
+  @IsOptional()
+  @IsString()
+  status?: string;
+
+  /**
+   * ISO string; provider filters "updated after"
+   * Example: "2025-12-18T21:19:09.227Z"
+   */
+  @IsOptional()
+  @IsString()
+  updatedAt?: string;
+
+  /**
+   * Allow extra provider param keys without strict validation.
+   * If you want to lock this down later, replace with explicit fields.
+   */
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  [k: string]: any;
+}
+
+export class ProviderVideoSyncRunDto {
+  @IsOptional()
+  @IsString()
+  providerCode?: string;
+
+  @IsOptional()
+  @IsBoolean()
+  fullSync?: boolean;
+
+  @IsOptional()
+  @IsBoolean()
+  resetState?: boolean;
+
+  /**
+   * Optional override. In normal usage:
+   * - fullSync: resumes from SyncState cursor
+   * - incremental: pageNum is forced to 1
+   */
+  @IsOptional()
+  @IsInt()
+  @Min(1)
+  pageNum?: number;
+
+  /**
+   * Default is handled by service; capped at 500 by service.
+   */
+  @IsOptional()
+  @IsInt()
+  @Min(1)
+  @Max(500)
+  pageSize?: number;
+
+  @IsOptional()
+  @IsObject()
+  @ValidateNested()
+  @Type(() => ProviderVideoSyncParamDto)
+  param?: ProviderVideoSyncParamDto;
+}

+ 57 - 0
apps/box-mgnt-api/src/mgnt-backend/feature/provider-video-sync/provider-video-sync.service.ts

@@ -907,6 +907,63 @@ export class ProviderVideoSyncService {
     });
   }
 
+  async getHistory(): Promise<{
+    list: Array<{
+      title: string;
+      runAt?: number;
+      completedAt?: number;
+      processedCount: null;
+      updatedAtCursor?: string;
+      updatedAt?: number;
+    }>;
+    total: number;
+  }> {
+    const nowSec = Math.floor(Date.now() / 1000);
+    const state = await this.mongo.syncState.upsert({
+      where: { entity: EntityType.VIDEO },
+      update: {
+        updatedAt: nowSec,
+      },
+      create: {
+        entity: EntityType.VIDEO,
+        referId: null,
+        lastRunAt: null,
+        lastFullSyncAt: null,
+        createdAt: nowSec,
+        updatedAt: nowSec,
+      },
+    });
+
+    const cursor = this.safeParseCursor(state.referId);
+    const runAt =
+      state.lastRunAt instanceof Date
+        ? Math.floor(state.lastRunAt.getTime() / 1000)
+        : undefined;
+    const completedAt =
+      state.lastFullSyncAt instanceof Date
+        ? Math.floor(state.lastFullSyncAt.getTime() / 1000)
+        : undefined;
+
+    const hasRunData =
+      runAt !== undefined ||
+      completedAt !== undefined ||
+      Boolean(cursor?.updatedAtCursor);
+    if (!hasRunData) {
+      return { list: [], total: 0 };
+    }
+
+    const row = {
+      title: 'Provider Video Sync',
+      runAt,
+      completedAt,
+      processedCount: null as null,
+      updatedAtCursor: cursor?.updatedAtCursor ?? undefined,
+      updatedAt: state.updatedAt ?? undefined,
+    };
+
+    return { list: [row], total: 1 };
+  }
+
   private safeParseCursor(
     raw: string | null | undefined,
   ): Partial<SyncCursor> | null {

+ 0 - 111
apps/box-mgnt-api/src/mgnt-backend/feature/sync-videomedia/sync-videomedia.controller.ts

@@ -1,111 +0,0 @@
-// sync-videomedia.controller.ts
-import {
-  BadRequestException,
-  Controller,
-  Logger,
-  Post,
-  Req,
-} from '@nestjs/common';
-import {
-  ApiTags,
-  ApiOperation,
-  ApiConsumes,
-  ApiBody,
-  ApiResponse,
-} from '@nestjs/swagger';
-import type { MultipartFile } from '@fastify/multipart';
-import { SyncVideomediaService } from './sync-videomedia.service';
-
-@ApiTags('Sync Video Media')
-@Controller('sync-videomedia')
-export class SyncVideomediaController {
-  private readonly logger = new Logger(SyncVideomediaController.name);
-
-  constructor(private readonly syncVideomediaService: SyncVideomediaService) {}
-
-  @Post('upload-json')
-  @ApiOperation({
-    summary: 'Upload and sync video media data from JSON file',
-    description:
-      'Uploads a JSON file containing video media records and syncs them to the database. Supports batch import with upsert logic.',
-  })
-  @ApiConsumes('multipart/form-data')
-  @ApiBody({
-    schema: {
-      type: 'object',
-      properties: {
-        file: {
-          type: 'string',
-          format: 'binary',
-          description: 'JSON file containing video media data',
-        },
-      },
-    },
-  })
-  @ApiResponse({
-    status: 200,
-    description: 'Video media data synced successfully',
-    schema: {
-      type: 'object',
-      properties: {
-        message: {
-          type: 'string',
-          example: 'Video media data synced successfully.',
-        },
-      },
-    },
-  })
-  @ApiResponse({
-    status: 400,
-    description: 'Bad request - invalid file format or JSON structure',
-  })
-  async uploadVideoData(@Req() req: any) {
-    // this.logger.debug('[uploadVideoData] content-type:', req.headers['content-type']);
-    // this.logger.debug('[uploadVideoData] isMultipart:', typeof req.isMultipart);
-    // this.logger.debug('[uploadVideoData] raw:', req.raw);
-
-    if (typeof req.isMultipart !== 'function') {
-      this.logger.debug('[uploadVideoData] isMultipart is not a function');
-      throw new BadRequestException('Multipart not available on request');
-    }
-
-    const isMultipartResult = req.isMultipart();
-    this.logger.debug('[uploadVideoData] isMultipart():', isMultipartResult);
-
-    if (!isMultipartResult) {
-      throw new BadRequestException('Request must be multipart/form-data');
-    }
-
-    const parts = req.parts();
-
-    for await (const part of parts) {
-      // this.logger.debug(
-      //   '[uploadVideoData] part:',
-      //   part.type,
-      //   part.fieldname,
-      //   (part as MultipartFile).filename,
-      // );
-
-      if (part.type === 'file' && part.fieldname === 'file') {
-        if (!(part as MultipartFile).filename?.endsWith('.json')) {
-          throw new BadRequestException('File must be .json');
-        }
-
-        const buffer = await part.toBuffer();
-
-        let json: unknown;
-        try {
-          json = JSON.parse(buffer.toString('utf8'));
-        } catch {
-          throw new BadRequestException('Invalid JSON file');
-        }
-
-        await this.syncVideomediaService.syncFromJson(json);
-
-        return { message: 'Video media data synced successfully.' };
-      }
-    }
-
-    throw new BadRequestException('File not found in upload');
-  }
-}

+ 0 - 12
apps/box-mgnt-api/src/mgnt-backend/feature/sync-videomedia/sync-videomedia.module.ts

@@ -1,12 +0,0 @@
-import { Module } from '@nestjs/common';
-import { SyncVideomediaService } from './sync-videomedia.service';
-import { SyncVideomediaController } from './sync-videomedia.controller';
-import { PrismaModule } from '@box/db/prisma/prisma.module';
-
-@Module({
-  imports: [PrismaModule],
-  providers: [SyncVideomediaService],
-  controllers: [SyncVideomediaController],
-  exports: [SyncVideomediaService],
-})
-export class SyncVideomediaModule {}

+ 0 - 399
apps/box-mgnt-api/src/mgnt-backend/feature/sync-videomedia/sync-videomedia.service.ts

@@ -1,399 +0,0 @@
-// sync-videomedia.service.ts
-import { Injectable, BadRequestException, Logger } from '@nestjs/common';
-import { MongoPrismaService } from '@box/db/prisma/mongo-prisma.service';
-
-interface RawVideoMedia {
-  id: string;
-  srcId?: number;
-  title?: string;
-  checkSum?: string;
-  type?: string;
-  formatType?: number;
-  contentType?: number;
-  coverType?: number;
-  coverImg?: string;
-  coverImgNew?: string;
-  videoTime?: number;
-  publish?: string;
-  country?: string;
-  firstTag?: string;
-  secondTags?: string[] | null;
-  mediaSet?: string | null;
-  preFileName?: string;
-  status?: string;
-  desc?: string;
-  size?: number;
-  bango?: string;
-  actors?: string[] | null;
-  studio?: string;
-  addedTime: string;
-  appids?: number[] | null;
-  japanNames?: string[] | null;
-  filename?: string;
-  fieldNameFs?: string;
-  ext?: string;
-  taskId?: string;
-  width?: number;
-  height?: number;
-  ratio?: number;
-  frameRate?: string;
-  syBitRate?: string;
-  vidBitRate?: string;
-  createdAt: string;
-  updatedAt: string;
-  proxyUpload?: number | null;
-  isAdd?: boolean;
-  retry?: number;
-  notifySignal?: boolean;
-  mergeRetry?: number;
-  compressRetry?: number;
-  segmentRetry?: number;
-  linodeRetry?: number;
-  failReason?: string;
-  deleteDisk?: boolean;
-  infoTsName?: string;
-}
-
-@Injectable()
-export class SyncVideomediaService {
-  private readonly logger = new Logger(SyncVideomediaService.name);
-
-  constructor(private readonly mongo: MongoPrismaService) {}
-
-  async list() {
-    return this.mongo.videoMedia.findMany();
-  }
-
-  /**
-   * Sync video media from uploaded JSON.
-   * Supports:
-   * - { code, data: { list: [...] } }
-   * - { list: [...] }
-   * - [ ... ]
-   */
-  async syncFromJson(jsonData: unknown) {
-    this.logger.log('[syncFromJson] syncFromJson called');
-    const list = this.extractList(jsonData);
-
-    this.logger.log(`[syncFromJson] Extracted ${list.length} items from JSON`);
-
-    if (!list.length) {
-      // No data to import; treat as client error for now
-      throw new BadRequestException('JSON contains no video media records');
-    }
-
-    const normalized = list.map((item) => this.normalizeItem(item));
-
-    this.logger.log(
-      `[syncFromJson] Ready to import ${normalized.length} records`,
-    );
-    this.logger.debug('[syncFromJson] First record sample:', normalized[0]);
-
-    const hasSecondTags = normalized.some(
-      (v) => Array.isArray(v.secondTags) && v.secondTags.length > 0,
-    );
-
-    if (hasSecondTags) {
-      // this.logger.log(
-      //   `[syncFromJson] Extracted secondTags from ${normalized.length} records`,
-      // );
-      await this.upsertSecondTagsFromVideos_NoUniqueName(normalized);
-    }
-
-    // Batch processing - try to create each record individually and catch duplicate errors
-    const BATCH_SIZE = 100;
-    let created = 0;
-    let updated = 0;
-    let skipped = 0;
-    const errors: any[] = [];
-
-    for (let i = 0; i < normalized.length; i += BATCH_SIZE) {
-      const batch = normalized.slice(i, i + BATCH_SIZE);
-      this.logger.debug(
-        `[syncFromJson] Processing batch ${i / BATCH_SIZE + 1}, size: ${batch.length}`,
-      );
-
-      await Promise.all(
-        batch.map(async (record) => {
-          try {
-            const exists = await this.mongo.videoMedia.findUnique({
-              where: { id: record.id },
-              select: { id: true },
-            });
-
-            if (exists?.id) {
-              const { id, ...updateData } = record;
-              await this.mongo.videoMedia.update({
-                where: { id },
-                data: updateData,
-              });
-              updated++;
-              this.logger.debug(`[syncFromJson] Updated record: ${id}`);
-              return;
-            }
-
-            await this.mongo.videoMedia.create({ data: record });
-            created++;
-            this.logger.debug(`[syncFromJson] Created record: ${record.id}`);
-          } catch (error: any) {
-            this.logger.error(
-              `[syncFromJson] Failed for ${record.id}: ${error.message}`,
-            );
-            skipped++;
-            errors.push({ id: record.id, error: error.message });
-          }
-        }),
-      );
-    }
-
-    this.logger.log(
-      `[syncFromJson] Import complete: ${created} created, ${updated} updated, ${skipped} skipped`,
-    );
-    if (errors.length > 0) {
-      this.logger.log(`[syncFromJson] Errors:`, errors.slice(0, 5));
-    }
-
-    return {
-      imported: normalized.length,
-      created,
-      updated,
-      skipped,
-      errors: errors.length > 0 ? errors.slice(0, 10) : undefined,
-    };
-  }
-
-  private async upsertSecondTagsFromVideos_NoUniqueName(
-    normalizedVideos: Array<{ secondTags?: string[] }>,
-  ): Promise<any> {
-    try {
-      const set = new Set<string>();
-
-      for (const v of normalizedVideos) {
-        const tags = v.secondTags ?? [];
-        for (const t of tags) {
-          if (typeof t !== 'string') continue;
-          const name = t.trim();
-          if (!name) continue;
-          set.add(name);
-        }
-      }
-
-      // this.logger.log(
-      //   `[upsertSecondTagsFromVideos] secondTags found in: ${normalizedVideos}`,
-      // );
-
-      const names = Array.from(set);
-      if (!names.length)
-        return { unique: 0, upserted: 0, skipped: 0, errors: [] };
-
-      // Concurrency limit to reduce race collisions and DB pressure
-      const CONCURRENCY = 20;
-      let idx = 0;
-
-      let upserted = 0;
-      let skipped = 0;
-      const errors: Array<{ name: string; error: string }> = [];
-
-      const worker = async () => {
-        while (true) {
-          const current = idx;
-          idx += 1;
-          if (current >= names.length) return;
-
-          const name = names[current];
-
-          try {
-            // 1) check existence by name (NOT unique)
-            const exists = await this.mongo.tag.findFirst({
-              where: { name },
-              select: { id: true },
-            });
-
-            if (exists?.id) {
-              // already exists
-              continue;
-            }
-
-            // 2) create if not exists
-            await this.mongo.tag.create({
-              data: {
-                name,
-                // If your Tag schema requires seconds fields:
-                // createdAt: Math.floor(Date.now() / 1000),
-                // updatedAt: Math.floor(Date.now() / 1000),
-              },
-            });
-
-            upserted += 1;
-          } catch (e: any) {
-            // If another worker created it after our check, create may fail (duplicate on some index)
-            // We treat that as skipped (safe).
-            const msg = e?.message ?? 'Tag create failed';
-            skipped += 1;
-            errors.push({ name, error: msg });
-          }
-        }
-      };
-
-      await Promise.all(Array.from({ length: CONCURRENCY }, () => worker()));
-
-      if (errors.length) {
-        this.logger.warn(
-          `[upsertSecondTagsFromVideos] errors=${errors.length}, sample=${JSON.stringify(
-            errors.slice(0, 3),
-          )}`,
-        );
-      } else {
-        this.logger.log(
-          `[upsertSecondTagsFromVideos] unique=${names.length} created=${upserted}`,
-        );
-      }
-
-      return { unique: names.length, upserted, skipped, errors };
-    } catch (error: any) {
-      const message = error?.message ?? 'Unhandled tag upsert error';
-      const trace = error?.stack ?? undefined;
-      this.logger.error(
-        `[upsertSecondTagsFromVideos_NoUniqueName] ${message}`,
-        trace,
-      );
-      return {
-        unique: 0,
-        upserted: 0,
-        skipped: 0,
-        errors: [{ name: 'global', error: message }],
-      };
-    }
-  }
-
-  /**
-   * Extracts the list of items from different possible JSON shapes.
-   */
-  private extractList(jsonData: unknown): RawVideoMedia[] {
-    const data = jsonData as any;
-
-    if (Array.isArray(data)) {
-      return data as RawVideoMedia[];
-    }
-
-    if (data?.data?.list && Array.isArray(data.data.list)) {
-      return data.data.list as RawVideoMedia[];
-    }
-
-    if (data?.list && Array.isArray(data.list)) {
-      return data.list as RawVideoMedia[];
-    }
-
-    throw new BadRequestException(
-      'Invalid JSON structure: expected array, data.list, or list',
-    );
-  }
-
-  /**
-   * Maps RawVideoMedia to Prisma videoMedia create/update input.
-   * Applies defaults and type conversions to match the Prisma model.
-   */
-  private normalizeItem(item: RawVideoMedia) {
-    // Basic validation
-    if (!item.id) {
-      throw new BadRequestException('Each item must have an id');
-    }
-    if (!item.addedTime || !item.createdAt || !item.updatedAt) {
-      throw new BadRequestException(
-        `Item ${item.id} is missing required datetime fields`,
-      );
-    }
-
-    const addedTime = new Date(item.addedTime);
-    const createdAt = new Date(item.createdAt);
-    const updatedAt = new Date(item.updatedAt);
-
-    if (
-      isNaN(addedTime.getTime()) ||
-      isNaN(createdAt.getTime()) ||
-      isNaN(updatedAt.getTime())
-    ) {
-      throw new BadRequestException(
-        `Item ${item.id} has invalid datetime format`,
-      );
-    }
-
-    return {
-      id: item.id, // String mapped to Mongo ObjectId via @db.ObjectId
-
-      srcId: item.srcId ?? 0,
-      title: item.title ?? '',
-      checkSum: item.checkSum ?? '',
-      type: item.type ?? '',
-
-      formatType: item.formatType ?? 0,
-      contentType: item.contentType ?? 0,
-
-      coverType: item.coverType ?? 0,
-      coverImg: item.coverImg ?? '',
-      coverImgNew: item.coverImgNew ?? '',
-
-      videoTime: item.videoTime ?? 0,
-
-      publish: item.publish ?? '',
-      country: item.country ?? '',
-      firstTag: item.firstTag ?? '',
-
-      // null → []
-      secondTags: item.secondTags ?? [],
-
-      // null → ""
-      mediaSet: item.mediaSet ?? '',
-
-      preFileName: item.preFileName ?? '',
-
-      status: item.status ?? '',
-      desc: item.desc ?? '',
-
-      // number → BigInt
-      size: BigInt(item.size ?? 0),
-
-      bango: item.bango ?? '',
-
-      // null → []
-      actors: item.actors ?? [],
-
-      studio: item.studio ?? '',
-
-      addedTime,
-      appids: item.appids ?? [],
-
-      japanNames: item.japanNames ?? [],
-
-      filename: item.filename ?? '',
-      fieldNameFs: item.fieldNameFs ?? '',
-      ext: item.ext ?? '',
-      taskId: item.taskId ?? '',
-
-      width: item.width ?? 0,
-      height: item.height ?? 0,
-      ratio: item.ratio ?? 0,
-
-      frameRate: item.frameRate ?? '',
-      syBitRate: item.syBitRate ?? '',
-      vidBitRate: item.vidBitRate ?? '',
-
-      proxyUpload: item.proxyUpload ?? 0,
-      isAdd: item.isAdd ?? false,
-      retry: item.retry ?? 0,
-      notifySignal: item.notifySignal ?? false,
-
-      mergeRetry: item.mergeRetry ?? 0,
-      compressRetry: item.compressRetry ?? 0,
-      segmentRetry: item.segmentRetry ?? 0,
-      linodeRetry: item.linodeRetry ?? 0,
-
-      failReason: item.failReason ?? '',
-      deleteDisk: item.deleteDisk ?? false,
-      infoTsName: item.infoTsName ?? '',
-
-      createdAt,
-      updatedAt,
-    };
-  }
-}

+ 0 - 2
apps/box-mgnt-api/src/mgnt-backend/mgnt-backend.module.ts

@@ -12,7 +12,6 @@ import { OperationLogModule } from './core/logging/operation-log/operation-log.m
 import { QuotaLogModule } from './core/logging/quota-log/quota-log.module';
 import { S3Module } from './feature/s3/s3.module';
 import { SystemParamsModule } from './feature/system-params/system-params.module';
-import { SyncVideomediaModule } from './feature/sync-videomedia/sync-videomedia.module';
 import { AdsModule } from './feature/ads/ads.module';
 import { CategoryModule } from './feature/category/category.module';
 import { ChannelModule } from './feature/channel/channel.module';
@@ -42,7 +41,6 @@ import { CacheSyncModule } from '../cache-sync/cache-sync.module';
           CategoryModule,
           ChannelModule,
           TagModule,
-          SyncVideomediaModule,
           VideoMediaModule,
           HealthModule,
           CacheSyncModule,