|
|
@@ -19,6 +19,12 @@ import {
|
|
|
} from './video-media.dto';
|
|
|
import { MEDIA_STORAGE_STRATEGY } from '../../../shared/tokens';
|
|
|
|
|
|
+type MongoAggregateResult = {
|
|
|
+ cursor?: {
|
|
|
+ firstBatch?: any[];
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
@Injectable()
|
|
|
export class VideoMediaService {
|
|
|
constructor(
|
|
|
@@ -35,51 +41,52 @@ export class VideoMediaService {
|
|
|
const skip = (page - 1) * pageSize;
|
|
|
const take = pageSize;
|
|
|
|
|
|
- const where: any = {};
|
|
|
-
|
|
|
- if (query.keyword) {
|
|
|
- where.OR = [
|
|
|
- { title: { contains: query.keyword, mode: 'insensitive' } },
|
|
|
- { filename: { contains: query.keyword, mode: 'insensitive' } },
|
|
|
- ];
|
|
|
- }
|
|
|
-
|
|
|
- if (query.categoryId) {
|
|
|
- where.categoryIds = { has: query.categoryId };
|
|
|
- }
|
|
|
-
|
|
|
- if (query.tagId) {
|
|
|
- where.tagIds = { has: query.tagId };
|
|
|
+ const baseWhere = this.buildVideoListBaseFilter(query);
|
|
|
+ const keyword = query.keyword?.trim();
|
|
|
+
|
|
|
+ let total: number;
|
|
|
+ let rows: any[];
|
|
|
+
|
|
|
+ if (!keyword) {
|
|
|
+ [total, rows] = await Promise.all([
|
|
|
+ this.prisma.videoMedia.count({ where: baseWhere }),
|
|
|
+ this.prisma.videoMedia.findMany({
|
|
|
+ where: baseWhere,
|
|
|
+ skip,
|
|
|
+ take,
|
|
|
+ orderBy: { addedTime: 'desc' },
|
|
|
+ }),
|
|
|
+ ]);
|
|
|
+ } else {
|
|
|
+ const regex = new RegExp(this.escapeRegex(keyword), 'i');
|
|
|
+ const matchFilter = this.buildKeywordMatchFilter(baseWhere, regex);
|
|
|
+
|
|
|
+ // Prisma Mongo cannot express regex searches inside array elements, so we fall back to a raw aggregate that uses sanitizedSecondTags.
|
|
|
+ const countRes = (await this.prisma.$runCommandRaw({
|
|
|
+ aggregate: 'videoMedia',
|
|
|
+ pipeline: [{ $match: matchFilter }, { $count: 'total' }],
|
|
|
+ cursor: {},
|
|
|
+ })) as unknown as MongoAggregateResult;
|
|
|
+
|
|
|
+ total = Number(countRes.cursor.firstBatch?.[0]?.total ?? 0);
|
|
|
+
|
|
|
+ const dataRes = (await this.prisma.$runCommandRaw({
|
|
|
+ aggregate: 'videoMedia',
|
|
|
+ pipeline: [
|
|
|
+ { $match: matchFilter },
|
|
|
+ { $sort: { addedTime: -1 } },
|
|
|
+ { $skip: skip },
|
|
|
+ { $limit: take },
|
|
|
+ ],
|
|
|
+ cursor: {},
|
|
|
+ })) as unknown as MongoAggregateResult;
|
|
|
+
|
|
|
+ rows = (dataRes.cursor.firstBatch ?? []).map((doc: any) => ({
|
|
|
+ ...doc,
|
|
|
+ id: doc.id ?? doc._id,
|
|
|
+ }));
|
|
|
}
|
|
|
|
|
|
- if (typeof query.listStatus === 'number') {
|
|
|
- where.listStatus = query.listStatus;
|
|
|
- }
|
|
|
-
|
|
|
- // filter by editedFrom and editedTo
|
|
|
- if (
|
|
|
- typeof query.editedFrom === 'number' ||
|
|
|
- typeof query.editedTo === 'number'
|
|
|
- ) {
|
|
|
- where.editedAt = {};
|
|
|
- if (typeof query.editedFrom === 'number') {
|
|
|
- where.editedAt.gte = BigInt(query.editedFrom);
|
|
|
- }
|
|
|
- if (typeof query.editedTo === 'number') {
|
|
|
- where.editedAt.lte = BigInt(query.editedTo);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- const [total, rows] = await Promise.all([
|
|
|
- this.prisma.videoMedia.count({ where }),
|
|
|
- this.prisma.videoMedia.findMany({
|
|
|
- where,
|
|
|
- skip,
|
|
|
- take,
|
|
|
- orderBy: { addedTime: 'desc' },
|
|
|
- }),
|
|
|
- ]);
|
|
|
-
|
|
|
return {
|
|
|
total,
|
|
|
page,
|
|
|
@@ -105,6 +112,58 @@ export class VideoMediaService {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+ private buildVideoListBaseFilter(
|
|
|
+ query: VideoMediaListQueryDto,
|
|
|
+ ): Record<string, any> {
|
|
|
+ const where: Record<string, any> = {};
|
|
|
+
|
|
|
+ if (typeof query.listStatus === 'number') {
|
|
|
+ where.listStatus = query.listStatus;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (
|
|
|
+ typeof query.editedFrom === 'number' ||
|
|
|
+ typeof query.editedTo === 'number'
|
|
|
+ ) {
|
|
|
+ const editedAt: Record<string, bigint> = {};
|
|
|
+ if (typeof query.editedFrom === 'number') {
|
|
|
+ editedAt.gte = BigInt(query.editedFrom);
|
|
|
+ }
|
|
|
+ if (typeof query.editedTo === 'number') {
|
|
|
+ editedAt.lte = BigInt(query.editedTo);
|
|
|
+ }
|
|
|
+ where.editedAt = editedAt;
|
|
|
+ }
|
|
|
+
|
|
|
+ return where;
|
|
|
+ }
|
|
|
+
|
|
|
+ private buildKeywordMatchFilter(
|
|
|
+ baseFilter: Record<string, any>,
|
|
|
+ regex: RegExp,
|
|
|
+ ): Record<string, any> {
|
|
|
+ const matchFilter = { ...baseFilter };
|
|
|
+ if (Array.isArray(matchFilter.$and)) {
|
|
|
+ matchFilter.$and = [...matchFilter.$and];
|
|
|
+ }
|
|
|
+
|
|
|
+ const keywordClause = {
|
|
|
+ $or: [
|
|
|
+ { title: { $regex: regex } },
|
|
|
+ { sanitizedSecondTags: { $elemMatch: { $regex: regex } } },
|
|
|
+ ],
|
|
|
+ };
|
|
|
+
|
|
|
+ matchFilter.$and = matchFilter.$and ?? [];
|
|
|
+ matchFilter.$and.push(keywordClause);
|
|
|
+
|
|
|
+ return matchFilter;
|
|
|
+ }
|
|
|
+
|
|
|
+ private escapeRegex(input: string): string {
|
|
|
+ return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
+ }
|
|
|
+
|
|
|
async findOne(id: string): Promise<any> {
|
|
|
const video = await this.prisma.videoMedia.findUnique({
|
|
|
where: { id },
|