aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/video/video.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/models/video/video.ts')
-rw-r--r--server/models/video/video.ts140
1 files changed, 113 insertions, 27 deletions
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 15b4dda5b..68116e309 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -93,6 +93,7 @@ import { VideoShareModel } from './video-share'
93import { VideoTagModel } from './video-tag' 93import { VideoTagModel } from './video-tag'
94import { ScheduleVideoUpdateModel } from './schedule-video-update' 94import { ScheduleVideoUpdateModel } from './schedule-video-update'
95import { VideoCaptionModel } from './video-caption' 95import { VideoCaptionModel } from './video-caption'
96import { VideosSearchQuery } from '../../../shared/models/search'
96 97
97// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation 98// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
98const indexes: Sequelize.DefineIndexesOptions[] = [ 99const indexes: Sequelize.DefineIndexesOptions[] = [
@@ -133,16 +134,22 @@ export enum ScopeNames {
133 WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE' 134 WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE'
134} 135}
135 136
137type AvailableForListOptions = {
138 actorId: number,
139 filter?: VideoFilter,
140 categoryOneOf?: number[],
141 nsfw?: boolean,
142 licenceOneOf?: number[],
143 languageOneOf?: string[],
144 tagsOneOf?: string[],
145 tagsAllOf?: string[],
146 withFiles?: boolean,
147 accountId?: number,
148 videoChannelId?: number
149}
150
136@Scopes({ 151@Scopes({
137 [ScopeNames.AVAILABLE_FOR_LIST]: (options: { 152 [ScopeNames.AVAILABLE_FOR_LIST]: (options: AvailableForListOptions) => {
138 actorId: number,
139 hideNSFW: boolean,
140 filter?: VideoFilter,
141 category?: number,
142 withFiles?: boolean,
143 accountId?: number,
144 videoChannelId?: number
145 }) => {
146 const accountInclude = { 153 const accountInclude = {
147 attributes: [ 'id', 'name' ], 154 attributes: [ 'id', 'name' ],
148 model: AccountModel.unscoped(), 155 model: AccountModel.unscoped(),
@@ -243,13 +250,55 @@ export enum ScopeNames {
243 }) 250 })
244 } 251 }
245 252
246 // Hide nsfw videos? 253 // FIXME: issues with sequelize count when making a join on n:m relation, so we just make a IN()
247 if (options.hideNSFW === true) { 254 if (options.tagsAllOf || options.tagsOneOf) {
248 query.where['nsfw'] = false 255 const createTagsIn = (tags: string[]) => {
256 return tags.map(t => VideoModel.sequelize.escape(t))
257 .join(', ')
258 }
259
260 if (options.tagsOneOf) {
261 query.where['id'][Sequelize.Op.in] = Sequelize.literal(
262 '(' +
263 'SELECT "videoId" FROM "videoTag" ' +
264 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
265 'WHERE "tag"."name" IN (' + createTagsIn(options.tagsOneOf) + ')' +
266 ')'
267 )
268 }
269
270 if (options.tagsAllOf) {
271 query.where['id'][Sequelize.Op.in] = Sequelize.literal(
272 '(' +
273 'SELECT "videoId" FROM "videoTag" ' +
274 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
275 'WHERE "tag"."name" IN (' + createTagsIn(options.tagsAllOf) + ')' +
276 'GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + options.tagsAllOf.length +
277 ')'
278 )
279 }
280 }
281
282 if (options.nsfw === true || options.nsfw === false) {
283 query.where['nsfw'] = options.nsfw
284 }
285
286 if (options.categoryOneOf) {
287 query.where['category'] = {
288 [Sequelize.Op.or]: options.categoryOneOf
289 }
290 }
291
292 if (options.licenceOneOf) {
293 query.where['licence'] = {
294 [Sequelize.Op.or]: options.licenceOneOf
295 }
249 } 296 }
250 297
251 if (options.category) { 298 if (options.languageOneOf) {
252 query.where['category'] = options.category 299 query.where['language'] = {
300 [Sequelize.Op.or]: options.languageOneOf
301 }
253 } 302 }
254 303
255 if (options.accountId) { 304 if (options.accountId) {
@@ -756,9 +805,13 @@ export class VideoModel extends Model<VideoModel> {
756 start: number, 805 start: number,
757 count: number, 806 count: number,
758 sort: string, 807 sort: string,
759 hideNSFW: boolean, 808 nsfw: boolean,
760 withFiles: boolean, 809 withFiles: boolean,
761 category?: number, 810 categoryOneOf?: number[],
811 licenceOneOf?: number[],
812 languageOneOf?: string[],
813 tagsOneOf?: string[],
814 tagsAllOf?: string[],
762 filter?: VideoFilter, 815 filter?: VideoFilter,
763 accountId?: number, 816 accountId?: number,
764 videoChannelId?: number 817 videoChannelId?: number
@@ -774,13 +827,17 @@ export class VideoModel extends Model<VideoModel> {
774 method: [ 827 method: [
775 ScopeNames.AVAILABLE_FOR_LIST, { 828 ScopeNames.AVAILABLE_FOR_LIST, {
776 actorId: serverActor.id, 829 actorId: serverActor.id,
777 hideNSFW: options.hideNSFW, 830 nsfw: options.nsfw,
778 category: options.category, 831 categoryOneOf: options.categoryOneOf,
832 licenceOneOf: options.licenceOneOf,
833 languageOneOf: options.languageOneOf,
834 tagsOneOf: options.tagsOneOf,
835 tagsAllOf: options.tagsAllOf,
779 filter: options.filter, 836 filter: options.filter,
780 withFiles: options.withFiles, 837 withFiles: options.withFiles,
781 accountId: options.accountId, 838 accountId: options.accountId,
782 videoChannelId: options.videoChannelId 839 videoChannelId: options.videoChannelId
783 } 840 } as AvailableForListOptions
784 ] 841 ]
785 } 842 }
786 843
@@ -794,15 +851,39 @@ export class VideoModel extends Model<VideoModel> {
794 }) 851 })
795 } 852 }
796 853
797 static async searchAndPopulateAccountAndServer (value: string, start: number, count: number, sort: string, hideNSFW: boolean) { 854 static async searchAndPopulateAccountAndServer (options: VideosSearchQuery) {
855 const whereAnd = [ ]
856
857 if (options.startDate || options.endDate) {
858 const publishedAtRange = { }
859
860 if (options.startDate) publishedAtRange[Sequelize.Op.gte] = options.startDate
861 if (options.endDate) publishedAtRange[Sequelize.Op.lte] = options.endDate
862
863 whereAnd.push({ publishedAt: publishedAtRange })
864 }
865
866 if (options.durationMin || options.durationMax) {
867 const durationRange = { }
868
869 if (options.durationMin) durationRange[Sequelize.Op.gte] = options.durationMin
870 if (options.durationMax) durationRange[Sequelize.Op.lte] = options.durationMax
871
872 whereAnd.push({ duration: durationRange })
873 }
874
875 whereAnd.push(createSearchTrigramQuery('VideoModel.name', options.search))
876
798 const query: IFindOptions<VideoModel> = { 877 const query: IFindOptions<VideoModel> = {
799 attributes: { 878 attributes: {
800 include: [ createSimilarityAttribute('VideoModel.name', value) ] 879 include: [ createSimilarityAttribute('VideoModel.name', options.search) ]
801 }, 880 },
802 offset: start, 881 offset: options.start,
803 limit: count, 882 limit: options.count,
804 order: getSort(sort), 883 order: getSort(options.sort),
805 where: createSearchTrigramQuery('VideoModel.name', value) 884 where: {
885 [ Sequelize.Op.and ]: whereAnd
886 }
806 } 887 }
807 888
808 const serverActor = await getServerActor() 889 const serverActor = await getServerActor()
@@ -810,8 +891,13 @@ export class VideoModel extends Model<VideoModel> {
810 method: [ 891 method: [
811 ScopeNames.AVAILABLE_FOR_LIST, { 892 ScopeNames.AVAILABLE_FOR_LIST, {
812 actorId: serverActor.id, 893 actorId: serverActor.id,
813 hideNSFW 894 nsfw: options.nsfw,
814 } 895 categoryOneOf: options.categoryOneOf,
896 licenceOneOf: options.licenceOneOf,
897 languageOneOf: options.languageOneOf,
898 tagsOneOf: options.tagsOneOf,
899 tagsAllOf: options.tagsAllOf
900 } as AvailableForListOptions
815 ] 901 ]
816 } 902 }
817 903