]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/models/video/video-playlist.ts
Search typeahead initial design
[github/Chocobozzz/PeerTube.git] / server / models / video / video-playlist.ts
index 3e436acfc23a1bee1007a14d44ee3030557ec782..4ca17ebec0126f49ace0f97be84c932f5f2e19ea 100644 (file)
@@ -33,7 +33,7 @@ import {
   WEBSERVER
 } from '../../initializers/constants'
 import { VideoPlaylist } from '../../../shared/models/videos/playlist/video-playlist.model'
-import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account'
+import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions } from '../account/account'
 import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel'
 import { join } from 'path'
 import { VideoPlaylistElementModel } from './video-playlist-element'
@@ -42,7 +42,17 @@ import { activityPubCollectionPagination } from '../../helpers/activitypub'
 import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model'
 import { ThumbnailModel } from './thumbnail'
 import { ActivityIconObject } from '../../../shared/models/activitypub/objects'
-import { fn, literal, Op, Transaction } from 'sequelize'
+import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize'
+import * as Bluebird from 'bluebird'
+import {
+  MVideoPlaylistAccountThumbnail,
+  MVideoPlaylistAP,
+  MVideoPlaylistFormattable,
+  MVideoPlaylistFull,
+  MVideoPlaylistFullSummary,
+  MVideoPlaylistIdWithElements
+} from '../../typings/models/video/video-playlist'
+import { MThumbnail } from '../../typings/models/video/thumbnail'
 
 enum ScopeNames {
   AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST',
@@ -58,86 +68,87 @@ type AvailableForListOptions = {
   type?: VideoPlaylistType
   accountId?: number
   videoChannelId?: number
-  privateAndUnlisted?: boolean
+  listMyPlaylists?: boolean
+  search?: string
 }
 
-@Scopes({
-  [ ScopeNames.WITH_THUMBNAIL ]: {
+@Scopes(() => ({
+  [ScopeNames.WITH_THUMBNAIL]: {
     include: [
       {
-        model: () => ThumbnailModel,
+        model: ThumbnailModel,
         required: false
       }
     ]
   },
-  [ ScopeNames.WITH_VIDEOS_LENGTH ]: {
+  [ScopeNames.WITH_VIDEOS_LENGTH]: {
     attributes: {
       include: [
-        [
-          fn('COUNT', 'toto'),
-          'coucou'
-        ],
         [
           literal('(SELECT COUNT("id") FROM "videoPlaylistElement" WHERE "videoPlaylistId" = "VideoPlaylistModel"."id")'),
           'videosLength'
         ]
       ]
     }
-  },
-  [ ScopeNames.WITH_ACCOUNT ]: {
+  } as FindOptions,
+  [ScopeNames.WITH_ACCOUNT]: {
     include: [
       {
-        model: () => AccountModel,
+        model: AccountModel,
         required: true
       }
     ]
   },
-  [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY ]: {
+  [ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY]: {
     include: [
       {
-        model: () => AccountModel.scope(AccountScopeNames.SUMMARY),
+        model: AccountModel.scope(AccountScopeNames.SUMMARY),
         required: true
       },
       {
-        model: () => VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY),
+        model: VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY),
         required: false
       }
     ]
   },
-  [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL ]: {
+  [ScopeNames.WITH_ACCOUNT_AND_CHANNEL]: {
     include: [
       {
-        model: () => AccountModel,
+        model: AccountModel,
         required: true
       },
       {
-        model: () => VideoChannelModel,
+        model: VideoChannelModel,
         required: false
       }
     ]
   },
-  [ ScopeNames.AVAILABLE_FOR_LIST ]: (options: AvailableForListOptions) => {
-    // Only list local playlists OR playlists that are on an instance followed by actorId
-    const inQueryInstanceFollow = buildServerIdsFollowedBy(options.followerActorId)
-    const actorWhere = {
-      [ Op.or ]: [
-        {
-          serverId: null
-        },
-        {
-          serverId: {
-            [ Op.in ]: literal(inQueryInstanceFollow)
-          }
-        }
-      ]
-    }
+  [ScopeNames.AVAILABLE_FOR_LIST]: (options: AvailableForListOptions) => {
 
-    const whereAnd: any[] = []
+    let whereActor: WhereOptions = {}
 
-    if (options.privateAndUnlisted !== true) {
+    const whereAnd: WhereOptions[] = []
+
+    if (options.listMyPlaylists !== true) {
       whereAnd.push({
         privacy: VideoPlaylistPrivacy.PUBLIC
       })
+
+      // Only list local playlists OR playlists that are on an instance followed by actorId
+      const inQueryInstanceFollow = buildServerIdsFollowedBy(options.followerActorId)
+
+      whereActor = {
+        [Op.or]: [
+          {
+            serverId: null
+          },
+          {
+            serverId: {
+              [Op.in]: literal(inQueryInstanceFollow)
+            }
+          }
+        ]
+      }
     }
 
     if (options.accountId) {
@@ -158,12 +169,20 @@ type AvailableForListOptions = {
       })
     }
 
+    if (options.search) {
+      whereAnd.push({
+        name: {
+          [Op.iLike]: '%' + options.search + '%'
+        }
+      })
+    }
+
     const where = {
       [Op.and]: whereAnd
     }
 
     const accountScope = {
-      method: [ AccountScopeNames.SUMMARY, actorWhere ]
+      method: [ AccountScopeNames.SUMMARY, { whereActor } as SummaryOptions ]
     }
 
     return {
@@ -178,9 +197,9 @@ type AvailableForListOptions = {
           required: false
         }
       ]
-    }
+    } as FindOptions
   }
-})
+}))
 
 @Table({
   tableName: 'videoPlaylist',
@@ -280,13 +299,14 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
 
   static listForApi (options: {
     followerActorId: number
-    start: number,
-    count: number,
-    sort: string,
-    type?: VideoPlaylistType,
-    accountId?: number,
-    videoChannelId?: number,
-    privateAndUnlisted?: boolean
+    start: number
+    count: number
+    sort: string
+    type?: VideoPlaylistType
+    accountId?: number
+    videoChannelId?: number
+    listMyPlaylists?: boolean
+    search?: string
   }) {
     const query = {
       offset: options.start,
@@ -294,7 +314,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
       order: getSort(options.sort)
     }
 
-    const scopes = [
+    const scopes: (string | ScopeOptions)[] = [
       {
         method: [
           ScopeNames.AVAILABLE_FOR_LIST,
@@ -303,10 +323,11 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
             followerActorId: options.followerActorId,
             accountId: options.accountId,
             videoChannelId: options.videoChannelId,
-            privateAndUnlisted: options.privateAndUnlisted
+            listMyPlaylists: options.listMyPlaylists,
+            search: options.search
           } as AvailableForListOptions
         ]
-      } as any, // FIXME: typings
+      },
       ScopeNames.WITH_VIDEOS_LENGTH,
       ScopeNames.WITH_THUMBNAIL
     ]
@@ -336,7 +357,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
                              })
   }
 
-  static listPlaylistIdsOf (accountId: number, videoIds: number[]) {
+  static listPlaylistIdsOf (accountId: number, videoIds: number[]): Bluebird<MVideoPlaylistIdWithElements[]> {
     const query = {
       attributes: [ 'id' ],
       where: {
@@ -344,11 +365,11 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
       },
       include: [
         {
-          attributes: [ 'videoId', 'startTimestamp', 'stopTimestamp' ],
+          attributes: [ 'id', 'videoId', 'startTimestamp', 'stopTimestamp' ],
           model: VideoPlaylistElementModel.unscoped(),
           where: {
             videoId: {
-              [Op.any]: videoIds
+              [Op.in]: videoIds
             }
           },
           required: true
@@ -372,7 +393,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
       .then(e => !!e)
   }
 
-  static loadWithAccountAndChannelSummary (id: number | string, transaction: Transaction) {
+  static loadWithAccountAndChannelSummary (id: number | string, transaction: Transaction): Bluebird<MVideoPlaylistFullSummary> {
     const where = buildWhereIdOrUUID(id)
 
     const query = {
@@ -385,7 +406,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
       .findOne(query)
   }
 
-  static loadWithAccountAndChannel (id: number | string, transaction: Transaction) {
+  static loadWithAccountAndChannel (id: number | string, transaction: Transaction): Bluebird<MVideoPlaylistFull> {
     const where = buildWhereIdOrUUID(id)
 
     const query = {
@@ -398,7 +419,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
       .findOne(query)
   }
 
-  static loadByUrlAndPopulateAccount (url: string) {
+  static loadByUrlAndPopulateAccount (url: string): Bluebird<MVideoPlaylistAccountThumbnail> {
     const query = {
       where: {
         url
@@ -427,18 +448,20 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
     return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query)
   }
 
-  setThumbnail (thumbnail: ThumbnailModel) {
-    this.Thumbnail = thumbnail
-  }
+  async setAndSaveThumbnail (thumbnail: MThumbnail, t: Transaction) {
+    thumbnail.videoPlaylistId = this.id
 
-  getThumbnail () {
-    return this.Thumbnail
+    this.Thumbnail = await thumbnail.save({ transaction: t })
   }
 
   hasThumbnail () {
     return !!this.Thumbnail
   }
 
+  hasGeneratedThumbnail () {
+    return this.hasThumbnail() && this.Thumbnail.automaticallyGenerated === true
+  }
+
   generateThumbnailName () {
     const extension = '.jpg'
 
@@ -448,13 +471,13 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
   getThumbnailUrl () {
     if (!this.hasThumbnail()) return null
 
-    return WEBSERVER.URL + STATIC_PATHS.THUMBNAILS + this.getThumbnail().filename
+    return WEBSERVER.URL + STATIC_PATHS.THUMBNAILS + this.Thumbnail.filename
   }
 
   getThumbnailStaticPath () {
     if (!this.hasThumbnail()) return null
 
-    return join(STATIC_PATHS.THUMBNAILS, this.getThumbnail().filename)
+    return join(STATIC_PATHS.THUMBNAILS, this.Thumbnail.filename)
   }
 
   setAsRefreshed () {
@@ -473,7 +496,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
     return isOutdated(this, ACTIVITY_PUB.VIDEO_PLAYLIST_REFRESH_INTERVAL)
   }
 
-  toFormattedJSON (): VideoPlaylist {
+  toFormattedJSON (this: MVideoPlaylistFormattable): VideoPlaylist {
     return {
       id: this.id,
       uuid: this.uuid,
@@ -503,7 +526,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
     }
   }
 
-  toActivityPubObject (page: number, t: Transaction): Promise<PlaylistObject> {
+  toActivityPubObject (this: MVideoPlaylistAP, page: number, t: Transaction): Promise<PlaylistObject> {
     const handler = (start: number, count: number) => {
       return VideoPlaylistElementModel.listUrlsOfForAP(this.id, start, count, t)
     }