]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/models/video/video.ts
Refractor activity pub lib/helpers
[github/Chocobozzz/PeerTube.git] / server / models / video / video.ts
index 94af1ece5e2ab0ab6c6950abd3e997b9e954c282..e2069eb0c6d8110280433e4ba978616f627649a2 100644 (file)
@@ -1,57 +1,53 @@
-import * as safeBuffer from 'safe-buffer'
-const Buffer = safeBuffer.Buffer
-import * as magnetUtil from 'magnet-uri'
 import { map, maxBy, truncate } from 'lodash'
+import * as magnetUtil from 'magnet-uri'
 import * as parseTorrent from 'parse-torrent'
 import { join } from 'path'
+import * as safeBuffer from 'safe-buffer'
 import * as Sequelize from 'sequelize'
-
-import { TagInstance } from './tag-interface'
+import { VideoPrivacy, VideoResolution } from '../../../shared'
+import { VideoTorrentObject } from '../../../shared/models/activitypub/objects/video-torrent-object'
 import {
-  logger,
-  isVideoNameValid,
+  createTorrentPromise,
+  generateImageFromVideoFile,
+  getVideoFileHeight,
   isVideoCategoryValid,
-  isVideoLicenceValid,
-  isVideoLanguageValid,
-  isVideoNSFWValid,
   isVideoDescriptionValid,
   isVideoDurationValid,
+  isVideoLanguageValid,
+  isVideoLicenceValid,
+  isVideoNameValid,
+  isVideoNSFWValid,
   isVideoPrivacyValid,
-  readFileBufferPromise,
-  unlinkPromise,
+  logger,
   renamePromise,
-  writeFilePromise,
-  createTorrentPromise,
   statPromise,
-  generateImageFromVideoFile,
   transcode,
-  getVideoFileHeight
+  unlinkPromise,
+  writeFilePromise
 } from '../../helpers'
+import { isVideoUrlValid } from '../../helpers/custom-validators/videos'
 import {
+  API_VERSION,
   CONFIG,
+  CONSTRAINTS_FIELDS,
+  PREVIEWS_SIZE,
   REMOTE_SCHEME,
   STATIC_PATHS,
+  THUMBNAILS_SIZE,
   VIDEO_CATEGORIES,
-  VIDEO_LICENCES,
   VIDEO_LANGUAGES,
-  THUMBNAILS_SIZE,
-  PREVIEWS_SIZE,
-  CONSTRAINTS_FIELDS,
-  API_VERSION,
+  VIDEO_LICENCES,
   VIDEO_PRIVACIES
 } from '../../initializers'
-import { removeVideoToFriends } from '../../lib'
-import { VideoResolution, VideoPrivacy } from '../../../shared'
-import { VideoFileInstance, VideoFileModel } from './video-file-interface'
 
 import { addMethodsToModel, getSort } from '../utils'
-import {
-  VideoInstance,
-  VideoAttributes,
 
-  VideoMethods
-} from './video-interface'
-import { VideoTorrentObject } from '../../../shared/models/activitypub/objects/video-torrent-object'
+import { TagInstance } from './tag-interface'
+import { VideoFileInstance, VideoFileModel } from './video-file-interface'
+import { VideoAttributes, VideoInstance, VideoMethods } from './video-interface'
+import { sendDeleteVideo } from '../../lib/index'
+
+const Buffer = safeBuffer.Buffer
 
 let Video: Sequelize.Model<VideoInstance, VideoAttributes>
 let getOriginalFile: VideoMethods.GetOriginalFile
@@ -87,13 +83,14 @@ let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID
 let listOwnedAndPopulateAccountAndTags: VideoMethods.ListOwnedAndPopulateAccountAndTags
 let listOwnedByAccount: VideoMethods.ListOwnedByAccount
 let load: VideoMethods.Load
+let loadByUrlAndPopulateAccount: VideoMethods.LoadByUrlAndPopulateAccount
 let loadByUUID: VideoMethods.LoadByUUID
-let loadByUrl: VideoMethods.LoadByUrl
+let loadByUUIDOrURL: VideoMethods.LoadByUUIDOrURL
 let loadLocalVideoByUUID: VideoMethods.LoadLocalVideoByUUID
 let loadAndPopulateAccount: VideoMethods.LoadAndPopulateAccount
-let loadAndPopulateAccountAndPodAndTags: VideoMethods.LoadAndPopulateAccountAndPodAndTags
-let loadByUUIDAndPopulateAccountAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAccountAndPodAndTags
-let searchAndPopulateAccountAndPodAndTags: VideoMethods.SearchAndPopulateAccountAndPodAndTags
+let loadAndPopulateAccountAndServerAndTags: VideoMethods.LoadAndPopulateAccountAndServerAndTags
+let loadByUUIDAndPopulateAccountAndServerAndTags: VideoMethods.LoadByUUIDAndPopulateAccountAndServerAndTags
+let searchAndPopulateAccountAndServerAndTags: VideoMethods.SearchAndPopulateAccountAndServerAndTags
 let removeThumbnail: VideoMethods.RemoveThumbnail
 let removePreview: VideoMethods.RemovePreview
 let removeFile: VideoMethods.RemoveFile
@@ -224,10 +221,13 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
         defaultValue: false
       },
       url: {
-        type: DataTypes.STRING,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max),
         allowNull: false,
         validate: {
-          isUrl: true
+          urlValid: value => {
+            const res = isVideoUrlValid(value)
+            if (res === false) throw new Error('Video URL is not valid.')
+          }
         }
       }
     },
@@ -253,9 +253,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
         },
         {
           fields: [ 'channelId' ]
-        },
-        {
-          fields: [ 'parentId' ]
         }
       ],
       hooks: {
@@ -274,13 +271,15 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
     listOwnedAndPopulateAccountAndTags,
     listOwnedByAccount,
     load,
+    loadByUrlAndPopulateAccount,
     loadAndPopulateAccount,
-    loadAndPopulateAccountAndPodAndTags,
+    loadAndPopulateAccountAndServerAndTags,
     loadByHostAndUUID,
+    loadByUUIDOrURL,
     loadByUUID,
     loadLocalVideoByUUID,
-    loadByUUIDAndPopulateAccountAndPodAndTags,
-    searchAndPopulateAccountAndPodAndTags
+    loadByUUIDAndPopulateAccountAndServerAndTags,
+    searchAndPopulateAccountAndServerAndTags
   ]
   const instanceMethods = [
     createPreview,
@@ -328,14 +327,6 @@ function associate (models) {
     onDelete: 'cascade'
   })
 
-  Video.belongsTo(models.VideoChannel, {
-    foreignKey: {
-      name: 'parentId',
-      allowNull: true
-    },
-    onDelete: 'cascade'
-  })
-
   Video.belongsToMany(models.Tag, {
     foreignKey: 'videoId',
     through: models.VideoTag,
@@ -367,13 +358,9 @@ function afterDestroy (video: VideoInstance) {
   )
 
   if (video.isOwned()) {
-    const removeVideoToFriendsParams = {
-      uuid: video.uuid
-    }
-
     tasks.push(
       video.removePreview(),
-      removeVideoToFriends(removeVideoToFriendsParams)
+      sendDeleteVideo(video, undefined)
     )
 
     // Remove physical files and torrents
@@ -480,13 +467,13 @@ getPreviewPath = function (this: VideoInstance) {
 }
 
 toFormattedJSON = function (this: VideoInstance) {
-  let podHost
+  let serverHost
 
-  if (this.VideoChannel.Account.Pod) {
-    podHost = this.VideoChannel.Account.Pod.host
+  if (this.VideoChannel.Account.Server) {
+    serverHost = this.VideoChannel.Account.Server.host
   } else {
     // It means it's our video
-    podHost = CONFIG.WEBSERVER.HOST
+    serverHost = CONFIG.WEBSERVER.HOST
   }
 
   const json = {
@@ -501,7 +488,7 @@ toFormattedJSON = function (this: VideoInstance) {
     languageLabel: this.getLanguageLabel(),
     nsfw: this.nsfw,
     description: this.getTruncatedDescription(),
-    podHost,
+    serverHost,
     isLocal: this.isOwned(),
     account: this.VideoChannel.Account.name,
     duration: this.duration,
@@ -522,7 +509,7 @@ toFormattedJSON = function (this: VideoInstance) {
 toFormattedDetailsJSON = function (this: VideoInstance) {
   const formattedJson = this.toFormattedJSON()
 
-  // Maybe our pod is not up to date and there are new privacy settings since our version
+  // Maybe our server is not up to date and there are new privacy settings since our version
   let privacyLabel = VIDEO_PRIVACIES[this.privacy]
   if (!privacyLabel) privacyLabel = 'Unknown'
 
@@ -562,9 +549,10 @@ toFormattedDetailsJSON = function (this: VideoInstance) {
 
 toActivityPubObject = function (this: VideoInstance) {
   const { baseUrlHttp, baseUrlWs } = getBaseUrls(this)
+  if (!this.Tags) this.Tags = []
 
   const tag = this.Tags.map(t => ({
-    type: 'Hashtag',
+    type: 'Hashtag' as 'Hashtag',
     name: t.name
   }))
 
@@ -572,7 +560,7 @@ toActivityPubObject = function (this: VideoInstance) {
   for (const file of this.VideoFiles) {
     url.push({
       type: 'Link',
-      mimeType: 'video/' + file.extname,
+      mimeType: 'video/' + file.extname.replace('.', ''),
       url: getVideoFileUrl(this, file, baseUrlHttp),
       width: file.resolution,
       size: file.size
@@ -594,28 +582,29 @@ toActivityPubObject = function (this: VideoInstance) {
   }
 
   const videoObject: VideoTorrentObject = {
-    type: 'Video',
+    type: 'Video' as 'Video',
+    id: this.url,
     name: this.name,
     // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration
     duration: 'PT' + this.duration + 'S',
     uuid: this.uuid,
     tag,
     category: {
-      id: this.category,
-      label: this.getCategoryLabel()
+      identifier: this.category + '',
+      name: this.getCategoryLabel()
     },
     licence: {
-      id: this.licence,
+      identifier: this.licence + '',
       name: this.getLicenceLabel()
     },
     language: {
-      id: this.language,
+      identifier: this.language + '',
       name: this.getLanguageLabel()
     },
     views: this.views,
     nsfw: this.nsfw,
-    published: this.createdAt,
-    updated: this.updatedAt,
+    published: this.createdAt.toISOString(),
+    updated: this.updatedAt.toISOString(),
     mediaType: 'text/markdown',
     content: this.getTruncatedDescription(),
     icon: {
@@ -625,7 +614,7 @@ toActivityPubObject = function (this: VideoInstance) {
       width: THUMBNAILS_SIZE.width,
       height: THUMBNAILS_SIZE.height
     },
-    url
+    url // FIXME: needed?
   }
 
   return videoObject
@@ -723,7 +712,7 @@ getDescriptionPath = function (this: VideoInstance) {
 getCategoryLabel = function (this: VideoInstance) {
   let categoryLabel = VIDEO_CATEGORIES[this.category]
 
-  // Maybe our pod is not up to date and there are new categories since our version
+  // Maybe our server is not up to date and there are new categories since our version
   if (!categoryLabel) categoryLabel = 'Misc'
 
   return categoryLabel
@@ -731,7 +720,8 @@ getCategoryLabel = function (this: VideoInstance) {
 
 getLicenceLabel = function (this: VideoInstance) {
   let licenceLabel = VIDEO_LICENCES[this.licence]
-  // Maybe our pod is not up to date and there are new licences since our version
+
+  // Maybe our server is not up to date and there are new licences since our version
   if (!licenceLabel) licenceLabel = 'Unknown'
 
   return licenceLabel
@@ -826,12 +816,14 @@ listForApi = function (start: number, count: number, sort: string) {
     include: [
       {
         model: Video['sequelize'].models.VideoChannel,
+        required: true,
         include: [
           {
             model: Video['sequelize'].models.Account,
+            required: true,
             include: [
               {
-                model: Video['sequelize'].models.Pod,
+                model: Video['sequelize'].models.Server,
                 required: false
               }
             ]
@@ -867,7 +859,7 @@ loadByHostAndUUID = function (fromHost: string, uuid: string, t?: Sequelize.Tran
             model: Video['sequelize'].models.Account,
             include: [
               {
-                model: Video['sequelize'].models.Pod,
+                model: Video['sequelize'].models.Server,
                 required: true,
                 where: {
                   host: fromHost
@@ -946,6 +938,41 @@ loadByUUID = function (uuid: string, t?: Sequelize.Transaction) {
   return Video.findOne(query)
 }
 
+loadByUrlAndPopulateAccount = function (url: string, t?: Sequelize.Transaction) {
+  const query: Sequelize.FindOptions<VideoAttributes> = {
+    where: {
+      url
+    },
+    include: [
+      Video['sequelize'].models.VideoFile,
+      {
+        model: Video['sequelize'].models.VideoChannel,
+        include: [ Video['sequelize'].models.Account ]
+      }
+    ]
+  }
+
+  if (t !== undefined) query.transaction = t
+
+  return Video.findOne(query)
+}
+
+loadByUUIDOrURL = function (uuid: string, url: string, t?: Sequelize.Transaction) {
+  const query: Sequelize.FindOptions<VideoAttributes> = {
+    where: {
+      [Sequelize.Op.or]: [
+        { uuid },
+        { url }
+      ]
+    },
+    include: [ Video['sequelize'].models.VideoFile ]
+  }
+
+  if (t !== undefined) query.transaction = t
+
+  return Video.findOne(query)
+}
+
 loadLocalVideoByUUID = function (uuid: string, t?: Sequelize.Transaction) {
   const query: Sequelize.FindOptions<VideoAttributes> = {
     where: {
@@ -974,7 +1001,7 @@ loadAndPopulateAccount = function (id: number) {
   return Video.findById(id, options)
 }
 
-loadAndPopulateAccountAndPodAndTags = function (id: number) {
+loadAndPopulateAccountAndServerAndTags = function (id: number) {
   const options = {
     include: [
       {
@@ -982,7 +1009,7 @@ loadAndPopulateAccountAndPodAndTags = function (id: number) {
         include: [
           {
             model: Video['sequelize'].models.Account,
-            include: [ { model: Video['sequelize'].models.Pod, required: false } ]
+            include: [ { model: Video['sequelize'].models.Server, required: false } ]
           }
         ]
       },
@@ -994,7 +1021,7 @@ loadAndPopulateAccountAndPodAndTags = function (id: number) {
   return Video.findById(id, options)
 }
 
-loadByUUIDAndPopulateAccountAndPodAndTags = function (uuid: string) {
+loadByUUIDAndPopulateAccountAndServerAndTags = function (uuid: string) {
   const options = {
     where: {
       uuid
@@ -1005,7 +1032,7 @@ loadByUUIDAndPopulateAccountAndPodAndTags = function (uuid: string) {
         include: [
           {
             model: Video['sequelize'].models.Account,
-            include: [ { model: Video['sequelize'].models.Pod, required: false } ]
+            include: [ { model: Video['sequelize'].models.Server, required: false } ]
           }
         ]
       },
@@ -1017,15 +1044,15 @@ loadByUUIDAndPopulateAccountAndPodAndTags = function (uuid: string) {
   return Video.findOne(options)
 }
 
-searchAndPopulateAccountAndPodAndTags = function (value: string, field: string, start: number, count: number, sort: string) {
-  const podInclude: Sequelize.IncludeOptions = {
-    model: Video['sequelize'].models.Pod,
+searchAndPopulateAccountAndServerAndTags = function (value: string, field: string, start: number, count: number, sort: string) {
+  const serverInclude: Sequelize.IncludeOptions = {
+    model: Video['sequelize'].models.Server,
     required: false
   }
 
   const accountInclude: Sequelize.IncludeOptions = {
     model: Video['sequelize'].models.Account,
-    include: [ podInclude ]
+    include: [ serverInclude ]
   }
 
   const videoChannelInclude: Sequelize.IncludeOptions = {
@@ -1056,13 +1083,13 @@ searchAndPopulateAccountAndPodAndTags = function (value: string, field: string,
        )`
     )
   } else if (field === 'host') {
-    // FIXME: Include our pod? (not stored in the database)
-    podInclude.where = {
+    // FIXME: Include our server? (not stored in the database)
+    serverInclude.where = {
       host: {
         [Sequelize.Op.iLike]: '%' + value + '%'
       }
     }
-    podInclude.required = true
+    serverInclude.required = true
   } else if (field === 'account') {
     accountInclude.where = {
       name: {
@@ -1108,8 +1135,8 @@ function getBaseUrls (video: VideoInstance) {
     baseUrlHttp = CONFIG.WEBSERVER.URL
     baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
   } else {
-    baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + video.VideoChannel.Account.Pod.host
-    baseUrlWs = REMOTE_SCHEME.WS + '://' + video.VideoChannel.Account.Pod.host
+    baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + video.VideoChannel.Account.Server.host
+    baseUrlWs = REMOTE_SCHEME.WS + '://' + video.VideoChannel.Account.Server.host
   }
 
   return { baseUrlHttp, baseUrlWs }