Handle announces in inbox
authorChocobozzz <florian.bigard@gmail.com>
Wed, 15 Nov 2017 16:56:21 +0000 (17:56 +0100)
committerChocobozzz <florian.bigard@gmail.com>
Mon, 27 Nov 2017 18:40:52 +0000 (19:40 +0100)
17 files changed:
server/controllers/activitypub/inbox.ts
server/helpers/custom-validators/activitypub/activity.ts
server/helpers/custom-validators/activitypub/videos.ts
server/initializers/database.ts
server/lib/activitypub/index.ts
server/lib/activitypub/process-add.ts
server/lib/activitypub/process-announce.ts [new file with mode: 0644]
server/lib/activitypub/process-create.ts
server/models/video/index.ts
server/models/video/video-channel-share-interface.ts [new file with mode: 0644]
server/models/video/video-channel-share.ts [new file with mode: 0644]
server/models/video/video-share-interface.ts [new file with mode: 0644]
server/models/video/video-share.ts [new file with mode: 0644]
server/models/video/video.ts
shared/models/activitypub/activity.ts
shared/models/activitypub/objects/video-channel-object.ts
shared/models/activitypub/objects/video-torrent-object.ts

index e62125d8503d46002bdbc4890ab294b81c861435..5f47648df064c41bcd92bd40604f63313b9e506c 100644 (file)
@@ -5,6 +5,7 @@ import { isActivityValid } from '../../helpers/custom-validators/activitypub/act
 import { processCreateActivity, processFlagActivity, processUpdateActivity } from '../../lib'
 import { processAcceptActivity } from '../../lib/activitypub/process-accept'
 import { processAddActivity } from '../../lib/activitypub/process-add'
+import { processAnnounceActivity } from '../../lib/activitypub/process-announce'
 import { processDeleteActivity } from '../../lib/activitypub/process-delete'
 import { processFollowActivity } from '../../lib/activitypub/process-follow'
 import { asyncMiddleware, checkSignature, localAccountValidator, signatureValidator } from '../../middlewares'
@@ -18,7 +19,8 @@ const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccoun
   Flag: processFlagActivity,
   Delete: processDeleteActivity,
   Follow: processFollowActivity,
-  Accept: processAcceptActivity
+  Accept: processAcceptActivity,
+  Announce: processAnnounceActivity
 }
 
 const inboxRouter = express.Router()
index b5ba0f7af75f03d233c9465d7b5fd2a71e7e1305..08e5ae0aad6b8e0052187aeb4fabe25f95be3433 100644 (file)
@@ -2,9 +2,12 @@ import * as validator from 'validator'
 import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './account'
 import { isActivityPubUrlValid } from './misc'
 import {
+  isVideoAnnounceValid,
+  isVideoChannelAnnounceValid,
   isVideoChannelCreateActivityValid,
   isVideoChannelDeleteActivityValid,
   isVideoChannelUpdateActivityValid,
+  isVideoFlagValid,
   isVideoTorrentAddActivityValid,
   isVideoTorrentDeleteActivityValid,
   isVideoTorrentUpdateActivityValid
@@ -32,7 +35,10 @@ function isActivityValid (activity: any) {
     isVideoChannelDeleteActivityValid(activity) ||
     isAccountDeleteActivityValid(activity) ||
     isAccountFollowActivityValid(activity) ||
-    isAccountAcceptActivityValid(activity)
+    isAccountAcceptActivityValid(activity) ||
+    isVideoFlagValid(activity) ||
+    isVideoAnnounceValid(activity) ||
+    isVideoChannelAnnounceValid(activity)
 }
 
 // ---------------------------------------------------------------------------
index a46757397e74e7bf80c6bab35a43a2c883b85386..9ddacd6010ab25628902e8a6a47ba9851b5ccc9d 100644 (file)
@@ -3,6 +3,7 @@ import { ACTIVITY_PUB } from '../../../initializers'
 import { exists, isDateValid, isUUIDValid } from '../misc'
 import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels'
 import {
+  isVideoAbuseReasonValid,
   isVideoDurationValid,
   isVideoNameValid,
   isVideoNSFWValid,
@@ -11,7 +12,7 @@ import {
   isVideoUrlValid,
   isVideoViewsValid
 } from '../videos'
-import { isBaseActivityValid } from './misc'
+import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
 
 function isVideoTorrentAddActivityValid (activity: any) {
   return isBaseActivityValid(activity, 'Add') &&
@@ -54,6 +55,22 @@ function isVideoTorrentObjectValid (video: any) {
     setValidRemoteVideoUrls(video.url)
 }
 
+function isVideoFlagValid (activity: any) {
+  return isBaseActivityValid(activity, 'Flag') &&
+    isVideoAbuseReasonValid(activity.content) &&
+    isActivityPubUrlValid(activity.object)
+}
+
+function isVideoAnnounceValid (activity: any) {
+  return isBaseActivityValid(activity, 'Announce') &&
+    isVideoTorrentObjectValid(activity.object)
+}
+
+function isVideoChannelAnnounceValid (activity: any) {
+  return isBaseActivityValid(activity, 'Announce') &&
+    isVideoChannelObjectValid(activity.object)
+}
+
 function isVideoChannelCreateActivityValid (activity: any) {
   return isBaseActivityValid(activity, 'Create') &&
     isVideoChannelObjectValid(activity.object)
@@ -83,7 +100,10 @@ export {
   isVideoTorrentUpdateActivityValid,
   isVideoChannelUpdateActivityValid,
   isVideoChannelDeleteActivityValid,
-  isVideoTorrentDeleteActivityValid
+  isVideoTorrentDeleteActivityValid,
+  isVideoFlagValid,
+  isVideoAnnounceValid,
+  isVideoChannelAnnounceValid
 }
 
 // ---------------------------------------------------------------------------
index 0a716e4fb0fc34e95f84fa58bb9fc00ab86694b6..5c757694e8ff606787d88b7eb4f1a66abcde6135 100644 (file)
@@ -24,6 +24,8 @@ import { OAuthClientModel } from './../models/oauth/oauth-client-interface'
 import { JobModel } from './../models/job/job-interface'
 import { AccountModel } from './../models/account/account-interface'
 import { ApplicationModel } from './../models/application/application-interface'
+import { VideoChannelShareModel } from '../models/video/video-channel-share-interface'
+import { VideoShareModel } from '../models/video/video-share-interface'
 
 const dbname = CONFIG.DATABASE.DBNAME
 const username = CONFIG.DATABASE.USERNAME
@@ -45,6 +47,8 @@ const database: {
   User?: UserModel,
   VideoAbuse?: VideoAbuseModel,
   VideoChannel?: VideoChannelModel,
+  VideoChannelShare?: VideoChannelShareModel,
+  VideoShare?: VideoShareModel,
   VideoFile?: VideoFileModel,
   BlacklistedVideo?: BlacklistedVideoModel,
   VideoTag?: VideoTagModel,
index f8d56528a2f07f4ef3810b5671d3edfe3904ec75..dca446fd400ef7584cfb619c115704adaf666032 100644 (file)
@@ -1,4 +1,9 @@
+export * from './process-accept'
+export * from './process-add'
+export * from './process-announce'
 export * from './process-create'
+export * from './process-delete'
 export * from './process-flag'
+export * from './process-follow'
 export * from './process-update'
 export * from './send-request'
index 06d23a2ea4e5436a5c73d8f864fe8988945d7224..98e414dbb9372bfa0710900c1c8a3d1a7160c933 100644 (file)
@@ -39,7 +39,7 @@ function processAddVideo (account: AccountInstance, videoChannelUrl: string, vid
 async function addRemoteVideo (account: AccountInstance, videoChannelUrl: string, videoToCreateData: VideoTorrentObject) {
   logger.debug('Adding remote video %s.', videoToCreateData.url)
 
-  await db.sequelize.transaction(async t => {
+  return db.sequelize.transaction(async t => {
     const sequelizeOptions = {
       transaction: t
     }
@@ -66,7 +66,10 @@ async function addRemoteVideo (account: AccountInstance, videoChannelUrl: string
     const tags = videoToCreateData.tag.map(t => t.name)
     const tagInstances = await db.Tag.findOrCreateTags(tags, t)
     await videoCreated.setTags(tagInstances, sequelizeOptions)
+
+    logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid)
+
+    return videoCreated
   })
 
-  logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid)
 }
diff --git a/server/lib/activitypub/process-announce.ts b/server/lib/activitypub/process-announce.ts
new file mode 100644 (file)
index 0000000..d67958a
--- /dev/null
@@ -0,0 +1,52 @@
+import { ActivityAnnounce } from '../../../shared/models/activitypub/activity'
+import { VideoChannelObject } from '../../../shared/models/activitypub/objects/video-channel-object'
+import { VideoTorrentObject } from '../../../shared/models/activitypub/objects/video-torrent-object'
+import { logger } from '../../helpers/logger'
+import { processAddActivity } from './process-add'
+import { processCreateActivity } from './process-create'
+import { database as db } from '../../initializers/index'
+import { getOrCreateAccount } from '../../helpers/activitypub'
+import { VideoChannelInstance } from '../../models/video/video-channel-interface'
+import { VideoInstance } from '../../models/index'
+
+async function processAnnounceActivity (activity: ActivityAnnounce) {
+  const activityType = activity.object.type
+  const accountAnnouncer = await getOrCreateAccount(activity.actor)
+
+  if (activityType === 'VideoChannel') {
+    const activityCreate = Object.assign(activity, {
+      type: 'Create' as 'Create',
+      actor: activity.object.actor,
+      object: activity.object as VideoChannelObject
+    })
+
+    // Add share entry
+    const videoChannel: VideoChannelInstance = await processCreateActivity(activityCreate)
+    await db.VideoChannelShare.create({
+      accountId: accountAnnouncer.id,
+      videoChannelId: videoChannel.id
+    })
+  } else if (activityType === 'Video') {
+    const activityAdd = Object.assign(activity, {
+      type: 'Add' as 'Add',
+      actor: activity.object.actor,
+      object: activity.object as VideoTorrentObject
+    })
+
+    // Add share entry
+    const video: VideoInstance = await processAddActivity(activityAdd)
+    await db.VideoShare.create({
+      accountId: accountAnnouncer.id,
+      videoId: video.id
+    })
+  }
+
+  logger.warn('Unknown activity object type %s when announcing activity.', activityType, { activity: activity.id })
+  return Promise.resolve(undefined)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  processAnnounceActivity
+}
index 8d842b8221589f5d791c00931191036ab1a684b4..1b825ebbcab24dcee3abb23b330910b07327c8de 100644 (file)
@@ -40,7 +40,7 @@ function processCreateVideoChannel (account: AccountInstance, videoChannelToCrea
 async function addRemoteVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) {
   logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid)
 
-  await db.sequelize.transaction(async t => {
+  return db.sequelize.transaction(async t => {
     let videoChannel = await db.VideoChannel.loadByUUIDOrUrl(videoChannelToCreateData.uuid, videoChannelToCreateData.id, t)
     if (videoChannel) throw new Error('Video channel with this URL/UUID already exists.')
 
@@ -57,10 +57,11 @@ async function addRemoteVideoChannel (account: AccountInstance, videoChannelToCr
     videoChannel = db.VideoChannel.build(videoChannelData)
     videoChannel.url = getActivityPubUrl('videoChannel', videoChannel.uuid)
 
-    await videoChannel.save({ transaction: t })
-  })
+    videoChannel = await videoChannel.save({ transaction: t })
+    logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid)
 
-  logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid)
+    return videoChannel
+  })
 }
 
 function processCreateVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) {
index 20d97931fe368c712f89354be87ace68c9291463..e17bbfab4b3d31a633093191f9e9fd1b51747bc9 100644 (file)
@@ -5,3 +5,5 @@ export * from './video-channel-interface'
 export * from './video-tag-interface'
 export * from './video-file-interface'
 export * from './video-interface'
+export * from './video-share-interface'
+export * from './video-channel-share-interface'
diff --git a/server/models/video/video-channel-share-interface.ts b/server/models/video/video-channel-share-interface.ts
new file mode 100644 (file)
index 0000000..9ac6d7b
--- /dev/null
@@ -0,0 +1,27 @@
+import * as Sequelize from 'sequelize'
+import { AccountInstance } from '../account/account-interface'
+import { VideoChannelInstance } from './video-channel-interface'
+
+export namespace VideoChannelShareMethods {
+}
+
+export interface VideoChannelShareClass {
+}
+
+export interface VideoChannelShareAttributes {
+  accountId: number
+  videoChannelId: number
+}
+
+export interface VideoChannelShareInstance
+  extends VideoChannelShareClass, VideoChannelShareAttributes, Sequelize.Instance<VideoChannelShareAttributes> {
+  id: number
+  createdAt: Date
+  updatedAt: Date
+
+  Account?: AccountInstance
+  VideoChannel?: VideoChannelInstance
+}
+
+export interface VideoChannelShareModel
+  extends VideoChannelShareClass, Sequelize.Model<VideoChannelShareInstance, VideoChannelShareAttributes> {}
diff --git a/server/models/video/video-channel-share.ts b/server/models/video/video-channel-share.ts
new file mode 100644 (file)
index 0000000..b619927
--- /dev/null
@@ -0,0 +1,49 @@
+import * as Sequelize from 'sequelize'
+
+import { addMethodsToModel } from '../utils'
+import { VideoChannelShareAttributes, VideoChannelShareInstance } from './video-channel-share-interface'
+
+let VideoChannelShare: Sequelize.Model<VideoChannelShareInstance, VideoChannelShareAttributes>
+
+export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
+  VideoChannelShare = sequelize.define<VideoChannelShareInstance, VideoChannelShareAttributes>('VideoChannelShare',
+    { },
+    {
+      indexes: [
+        {
+          fields: [ 'accountId' ]
+        },
+        {
+          fields: [ 'videoChannelId' ]
+        }
+      ]
+    }
+  )
+
+  const classMethods = [
+    associate
+  ]
+  addMethodsToModel(VideoChannelShare, classMethods)
+
+  return VideoChannelShare
+}
+
+// ------------------------------ METHODS ------------------------------
+
+function associate (models) {
+  VideoChannelShare.belongsTo(models.Account, {
+    foreignKey: {
+      name: 'accountId',
+      allowNull: false
+    },
+    onDelete: 'cascade'
+  })
+
+  VideoChannelShare.belongsTo(models.VideoChannel, {
+    foreignKey: {
+      name: 'videoChannelId',
+      allowNull: true
+    },
+    onDelete: 'cascade'
+  })
+}
diff --git a/server/models/video/video-share-interface.ts b/server/models/video/video-share-interface.ts
new file mode 100644 (file)
index 0000000..7928b9a
--- /dev/null
@@ -0,0 +1,25 @@
+import * as Sequelize from 'sequelize'
+import { AccountInstance } from '../account/account-interface'
+import { VideoInstance } from './video-interface'
+
+export namespace VideoShareMethods {
+}
+
+export interface VideoShareClass {
+}
+
+export interface VideoShareAttributes {
+  accountId: number
+  videoId: number
+}
+
+export interface VideoShareInstance extends VideoShareClass, VideoShareAttributes, Sequelize.Instance<VideoShareAttributes> {
+  id: number
+  createdAt: Date
+  updatedAt: Date
+
+  Account?: AccountInstance
+  Video?: VideoInstance
+}
+
+export interface VideoShareModel extends VideoShareClass, Sequelize.Model<VideoShareInstance, VideoShareAttributes> {}
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts
new file mode 100644 (file)
index 0000000..358491f
--- /dev/null
@@ -0,0 +1,49 @@
+import * as Sequelize from 'sequelize'
+
+import { addMethodsToModel } from '../utils'
+import { VideoShareAttributes, VideoShareInstance } from './video-share-interface'
+
+let VideoShare: Sequelize.Model<VideoShareInstance, VideoShareAttributes>
+
+export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
+  VideoShare = sequelize.define<VideoShareInstance, VideoShareAttributes>('VideoShare',
+    { },
+    {
+      indexes: [
+        {
+          fields: [ 'accountId' ]
+        },
+        {
+          fields: [ 'videoId' ]
+        }
+      ]
+    }
+  )
+
+  const classMethods = [
+    associate
+  ]
+  addMethodsToModel(VideoShare, classMethods)
+
+  return VideoShare
+}
+
+// ------------------------------ METHODS ------------------------------
+
+function associate (models) {
+  VideoShare.belongsTo(models.Account, {
+    foreignKey: {
+      name: 'accountId',
+      allowNull: false
+    },
+    onDelete: 'cascade'
+  })
+
+  VideoShare.belongsTo(models.Video, {
+    foreignKey: {
+      name: 'videoId',
+      allowNull: true
+    },
+    onDelete: 'cascade'
+  })
+}
index b00081f25ef54966f49a588be353de849d24aafe..480e54276dcc85a5850df47d0e26d44000eaf340 100644 (file)
@@ -253,9 +253,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
         },
         {
           fields: [ 'channelId' ]
-        },
-        {
-          fields: [ 'parentId' ]
         }
       ],
       hooks: {
@@ -329,14 +326,6 @@ function associate (models) {
     onDelete: 'cascade'
   })
 
-  Video.belongsTo(models.Video, {
-    foreignKey: {
-      name: 'parentId',
-      allowNull: true
-    },
-    onDelete: 'cascade'
-  })
-
   Video.belongsToMany(models.Tag, {
     foreignKey: 'videoId',
     through: models.VideoTag,
index 506e64effb46068b8abb68a23a8884f6f7e652d4..b858bf7594071d5503c822427baff741a7ec4c20 100644 (file)
@@ -3,10 +3,10 @@ import { ActivityPubSignature } from './activitypub-signature'
 import { VideoAbuseObject } from './objects/video-abuse-object'
 
 export type Activity = ActivityCreate | ActivityAdd | ActivityUpdate | ActivityFlag |
-  ActivityDelete | ActivityFollow | ActivityAccept
+  ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce
 
 // Flag -> report abuse
-export type ActivityType = 'Create' | 'Add' | 'Update' | 'Flag' | 'Delete' | 'Follow' | 'Accept'
+export type ActivityType = 'Create' | 'Add' | 'Update' | 'Flag' | 'Delete' | 'Follow' | 'Accept' | 'Announce'
 
 export interface BaseActivity {
   '@context'?: any[]
@@ -49,3 +49,8 @@ export interface ActivityFollow extends BaseActivity {
 export interface ActivityAccept extends BaseActivity {
   type: 'Accept'
 }
+
+export interface ActivityAnnounce extends BaseActivity {
+  type: 'Announce'
+  object: VideoChannelObject | VideoTorrentObject
+}
index de504d84c05eab8b9fe4f2e78db3326f1877495b..468e1535ea04e7c7118156ffc45c31ca31d773f0 100644 (file)
@@ -6,4 +6,5 @@ export interface VideoChannelObject {
   uuid: string
   published: Date
   updated: Date
+  actor?: string
 }
index 5685a43e0a367d4a301e68d24ca730a057e44494..99e7157b8c1cd3324c9007975e0d038a8cb32186 100644 (file)
@@ -23,4 +23,5 @@ export interface VideoTorrentObject {
   content: string
   icon: ActivityIconObject
   url: ActivityUrlObject[]
+  actor?: string
 }