|
|
@@ -3,6 +3,7 @@ import {
|
|
|
NotFoundException,
|
|
|
BadRequestException,
|
|
|
Inject,
|
|
|
+ Logger,
|
|
|
} from '@nestjs/common';
|
|
|
import type { MultipartFile } from '@fastify/multipart';
|
|
|
import { MongoPrismaService } from '@box/db/prisma/mongo-prisma.service';
|
|
|
@@ -27,6 +28,9 @@ type MongoAggregateResult = {
|
|
|
|
|
|
@Injectable()
|
|
|
export class VideoMediaService {
|
|
|
+ private readonly logger = new Logger(VideoMediaService.name);
|
|
|
+ private isBackfillingVid = false;
|
|
|
+
|
|
|
constructor(
|
|
|
private readonly prisma: MongoPrismaService,
|
|
|
private readonly cacheSyncService: CacheSyncService,
|
|
|
@@ -35,7 +39,89 @@ export class VideoMediaService {
|
|
|
private readonly mediaStorageStrategy: StorageStrategy,
|
|
|
) {}
|
|
|
|
|
|
+ // helper to generate next vid
|
|
|
+ private async generateNextVid(): Promise<number> {
|
|
|
+ const last = await this.prisma.videoMedia.findFirst({
|
|
|
+ where: { vid: { isSet: true } },
|
|
|
+ orderBy: { vid: 'desc' },
|
|
|
+ select: { vid: true },
|
|
|
+ });
|
|
|
+
|
|
|
+ return (last?.vid ?? 0) + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ // backfill vid for videoMedia documents without one
|
|
|
+ private async backfillVids(): Promise<void> {
|
|
|
+ if (this.isBackfillingVid) {
|
|
|
+ this.logger.warn('backfillVids is already running, skipping.');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.isBackfillingVid = true;
|
|
|
+ this.logger.log('Starting backfill of vid...');
|
|
|
+ try {
|
|
|
+ const videosWithoutVid = await this.prisma.videoMedia.findMany({
|
|
|
+ where: {
|
|
|
+ OR: [{ vid: { isSet: false } }, { vid: null }],
|
|
|
+ },
|
|
|
+ orderBy: { createdAt: 'asc' },
|
|
|
+ select: { id: true },
|
|
|
+ });
|
|
|
+
|
|
|
+ if (videosWithoutVid.length === 0) {
|
|
|
+ this.logger.log('No videos need backfilling vid.');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.logger.log(
|
|
|
+ `Found ${videosWithoutVid.length} videos without vid. Starting backfill...`,
|
|
|
+ );
|
|
|
+
|
|
|
+ let nextVid = await this.generateNextVid();
|
|
|
+
|
|
|
+ for (const video of videosWithoutVid) {
|
|
|
+ let assigned = false;
|
|
|
+
|
|
|
+ while (!assigned) {
|
|
|
+ try {
|
|
|
+ await this.prisma.videoMedia.update({
|
|
|
+ where: { id: video.id },
|
|
|
+ data: { vid: nextVid },
|
|
|
+ });
|
|
|
+
|
|
|
+ this.logger.log(
|
|
|
+ `Backfilled vid ${nextVid} for videoMedia id ${video.id}`,
|
|
|
+ );
|
|
|
+
|
|
|
+ nextVid += 1;
|
|
|
+ assigned = true;
|
|
|
+ } catch (e: any) {
|
|
|
+ // Unique constraint violation → retry with a fresh number
|
|
|
+ if (e?.code === 'P2002') {
|
|
|
+ this.logger.warn(`Duplicate vid ${nextVid}, retrying...`);
|
|
|
+ nextVid = await this.generateNextVid();
|
|
|
+ } else {
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.logger.log(
|
|
|
+ `Backfilled ${videosWithoutVid.length} vids successfully.`,
|
|
|
+ );
|
|
|
+ } finally {
|
|
|
+ this.isBackfillingVid = false;
|
|
|
+ this.logger.log('Finished backfillVids process.');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
async findAll(query: VideoMediaListQueryDto): Promise<any> {
|
|
|
+ // ensure vids are backfilled
|
|
|
+ await this.backfillVids().catch((err) =>
|
|
|
+ this.logger.error('Backfill vid failed', err?.stack ?? String(err)),
|
|
|
+ );
|
|
|
+
|
|
|
const page = query.page ?? 1;
|
|
|
const pageSize = query.size ?? 20;
|
|
|
const skip = (page - 1) * pageSize;
|
|
|
@@ -93,6 +179,7 @@ export class VideoMediaService {
|
|
|
pageSize,
|
|
|
items: rows.map((row) => ({
|
|
|
id: row.id,
|
|
|
+ vid: row.vid ?? null,
|
|
|
title: row.title,
|
|
|
filename: row.filename,
|
|
|
videoTime: row.videoTime,
|