Optimize video view AP processing
[oweals/peertube.git] / server / lib / activitypub / process / process-create.ts
1 import { ActivityCreate, CacheFileObject, VideoAbuseState, VideoTorrentObject } from '../../../../shared'
2 import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects'
3 import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object'
4 import { retryTransactionWrapper } from '../../../helpers/database-utils'
5 import { logger } from '../../../helpers/logger'
6 import { sequelizeTypescript } from '../../../initializers'
7 import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
8 import { ActorModel } from '../../../models/activitypub/actor'
9 import { VideoAbuseModel } from '../../../models/video/video-abuse'
10 import { getOrCreateActorAndServerAndModel } from '../actor'
11 import { addVideoComment, resolveThread } from '../video-comments'
12 import { getOrCreateVideoAndAccountAndChannel } from '../videos'
13 import { forwardActivity, forwardVideoRelatedActivity } from '../send/utils'
14 import { Redis } from '../../redis'
15 import { createCacheFile } from '../cache-file'
16
17 async function processCreateActivity (activity: ActivityCreate) {
18   const activityObject = activity.object
19   const activityType = activityObject.type
20   const actor = await getOrCreateActorAndServerAndModel(activity.actor)
21
22   if (activityType === 'View') {
23     return processCreateView(actor, activity)
24   } else if (activityType === 'Dislike') {
25     return retryTransactionWrapper(processCreateDislike, actor, activity)
26   } else if (activityType === 'Video') {
27     return processCreateVideo(activity)
28   } else if (activityType === 'Flag') {
29     return retryTransactionWrapper(processCreateVideoAbuse, actor, activityObject as VideoAbuseObject)
30   } else if (activityType === 'Note') {
31     return retryTransactionWrapper(processCreateVideoComment, actor, activity)
32   } else if (activityType === 'CacheFile') {
33     return retryTransactionWrapper(processCacheFile, actor, activity)
34   }
35
36   logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
37   return Promise.resolve(undefined)
38 }
39
40 // ---------------------------------------------------------------------------
41
42 export {
43   processCreateActivity
44 }
45
46 // ---------------------------------------------------------------------------
47
48 async function processCreateVideo (activity: ActivityCreate) {
49   const videoToCreateData = activity.object as VideoTorrentObject
50
51   const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoToCreateData })
52
53   return video
54 }
55
56 async function processCreateDislike (byActor: ActorModel, activity: ActivityCreate) {
57   const dislike = activity.object as DislikeObject
58   const byAccount = byActor.Account
59
60   if (!byAccount) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)
61
62   const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: dislike.object })
63
64   return sequelizeTypescript.transaction(async t => {
65     const rate = {
66       type: 'dislike' as 'dislike',
67       videoId: video.id,
68       accountId: byAccount.id
69     }
70     const [ , created ] = await AccountVideoRateModel.findOrCreate({
71       where: rate,
72       defaults: rate,
73       transaction: t
74     })
75     if (created === true) await video.increment('dislikes', { transaction: t })
76
77     if (video.isOwned() && created === true) {
78       // Don't resend the activity to the sender
79       const exceptions = [ byActor ]
80
81       await forwardVideoRelatedActivity(activity, t, exceptions, video)
82     }
83   })
84 }
85
86 async function processCreateView (byActor: ActorModel, activity: ActivityCreate) {
87   const view = activity.object as ViewObject
88
89   const options = {
90     videoObject: view.object,
91     fetchType: 'only-video' as 'only-video'
92   }
93   const { video } = await getOrCreateVideoAndAccountAndChannel(options)
94
95   const actorExists = await ActorModel.isActorUrlExist(view.actor)
96   if (actorExists === false) throw new Error('Unknown actor ' + view.actor)
97
98   await Redis.Instance.addVideoView(video.id)
99
100   if (video.isOwned()) {
101     // Don't resend the activity to the sender
102     const exceptions = [ byActor ]
103     await forwardActivity(activity, undefined, exceptions)
104   }
105 }
106
107 async function processCacheFile (byActor: ActorModel, activity: ActivityCreate) {
108   const cacheFile = activity.object as CacheFileObject
109
110   const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object })
111
112   await createCacheFile(cacheFile, video, byActor)
113
114   if (video.isOwned()) {
115     // Don't resend the activity to the sender
116     const exceptions = [ byActor ]
117     await forwardActivity(activity, undefined, exceptions)
118   }
119 }
120
121 async function processCreateVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) {
122   logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object)
123
124   const account = actor.Account
125   if (!account) throw new Error('Cannot create dislike with the non account actor ' + actor.url)
126
127   const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoAbuseToCreateData.object })
128
129   return sequelizeTypescript.transaction(async t => {
130     const videoAbuseData = {
131       reporterAccountId: account.id,
132       reason: videoAbuseToCreateData.content,
133       videoId: video.id,
134       state: VideoAbuseState.PENDING
135     }
136
137     await VideoAbuseModel.create(videoAbuseData, { transaction: t })
138
139     logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object)
140   })
141 }
142
143 async function processCreateVideoComment (byActor: ActorModel, activity: ActivityCreate) {
144   const commentObject = activity.object as VideoCommentObject
145   const byAccount = byActor.Account
146
147   if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url)
148
149   const { video } = await resolveThread(commentObject.inReplyTo)
150
151   const { created } = await addVideoComment(video, commentObject.id)
152
153   if (video.isOwned() && created === true) {
154     // Don't resend the activity to the sender
155     const exceptions = [ byActor ]
156
157     await forwardVideoRelatedActivity(activity, undefined, exceptions, video)
158   }
159 }