]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/lib/activitypub/process/process-create.ts
Better admin tables
[github/Chocobozzz/PeerTube.git] / server / lib / activitypub / process / process-create.ts
CommitLineData
50d6de9c
C
1import * as Bluebird from 'bluebird'
2import { ActivityCreate, VideoTorrentObject } from '../../../../shared'
3fd3ab2d 3import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects'
50d6de9c 4import { VideoRateType } from '../../../../shared/models/videos'
54141398 5import { logger, retryTransactionWrapper } from '../../../helpers'
3fd3ab2d 6import { sequelizeTypescript } from '../../../initializers'
3fd3ab2d 7import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
50d6de9c
C
8import { ActorModel } from '../../../models/activitypub/actor'
9import { TagModel } from '../../../models/video/tag'
3fd3ab2d
C
10import { VideoModel } from '../../../models/video/video'
11import { VideoAbuseModel } from '../../../models/video/video-abuse'
50d6de9c
C
12import { VideoFileModel } from '../../../models/video/video-file'
13import { getOrCreateActorAndServerAndModel } from '../actor'
63c93323 14import { forwardActivity } from '../send/misc'
50d6de9c
C
15import { generateThumbnailFromUrl } from '../videos'
16import { addVideoShares, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
e4f97bab 17
0d0e8dd0 18async function processCreateActivity (activity: ActivityCreate) {
e4f97bab
C
19 const activityObject = activity.object
20 const activityType = activityObject.type
50d6de9c 21 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
e4f97bab 22
40ff5707 23 if (activityType === 'View') {
50d6de9c 24 return processCreateView(actor, activity)
0032ebe9 25 } else if (activityType === 'Dislike') {
50d6de9c
C
26 return processCreateDislike(actor, activity)
27 } else if (activityType === 'Video') {
28 return processCreateVideo(actor, activity)
8e13fa7d 29 } else if (activityType === 'Flag') {
50d6de9c 30 return processCreateVideoAbuse(actor, activityObject as VideoAbuseObject)
e4f97bab
C
31 }
32
33 logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
0d0e8dd0 34 return Promise.resolve(undefined)
e4f97bab
C
35}
36
37// ---------------------------------------------------------------------------
38
39export {
40 processCreateActivity
41}
42
43// ---------------------------------------------------------------------------
44
50d6de9c
C
45async function processCreateVideo (
46 actor: ActorModel,
47 activity: ActivityCreate
48) {
49 const videoToCreateData = activity.object as VideoTorrentObject
50
51 const channel = videoToCreateData.attributedTo.find(a => a.type === 'Group')
52 if (!channel) throw new Error('Cannot find associated video channel to video ' + videoToCreateData.url)
53
54 const channelActor = await getOrCreateActorAndServerAndModel(channel.id)
55
56 const options = {
57 arguments: [ actor, activity, videoToCreateData, channelActor ],
58 errorMessage: 'Cannot insert the remote video with many retries.'
59 }
60
61 const video = await retryTransactionWrapper(createRemoteVideo, options)
62
63 // Process outside the transaction because we could fetch remote data
64 if (videoToCreateData.likes && Array.isArray(videoToCreateData.likes.orderedItems)) {
65 await createRates(videoToCreateData.likes.orderedItems, video, 'like')
66 }
67
68 if (videoToCreateData.dislikes && Array.isArray(videoToCreateData.dislikes.orderedItems)) {
69 await createRates(videoToCreateData.dislikes.orderedItems, video, 'dislike')
70 }
71
72 if (videoToCreateData.shares && Array.isArray(videoToCreateData.shares.orderedItems)) {
73 await addVideoShares(video, videoToCreateData.shares.orderedItems)
74 }
75
76 return video
77}
78
79function createRemoteVideo (
80 account: ActorModel,
81 activity: ActivityCreate,
82 videoToCreateData: VideoTorrentObject,
83 channelActor: ActorModel
84) {
85 logger.debug('Adding remote video %s.', videoToCreateData.id)
86
87 return sequelizeTypescript.transaction(async t => {
88 const sequelizeOptions = {
89 transaction: t
90 }
91 const videoFromDatabase = await VideoModel.loadByUUIDOrURL(videoToCreateData.uuid, videoToCreateData.id, t)
92 if (videoFromDatabase) return videoFromDatabase
93
94 const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoToCreateData, activity.to, activity.cc)
95 const video = VideoModel.build(videoData)
96
97 // Don't block on request
98 generateThumbnailFromUrl(video, videoToCreateData.icon)
99 .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoToCreateData.id, err))
100
101 const videoCreated = await video.save(sequelizeOptions)
102
103 const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoToCreateData)
104 if (videoFileAttributes.length === 0) {
105 throw new Error('Cannot find valid files for video %s ' + videoToCreateData.url)
106 }
107
108 const tasks: Bluebird<any>[] = videoFileAttributes.map(f => VideoFileModel.create(f, { transaction: t }))
109 await Promise.all(tasks)
110
111 const tags = videoToCreateData.tag.map(t => t.name)
112 const tagInstances = await TagModel.findOrCreateTags(tags, t)
113 await videoCreated.$set('Tags', tagInstances, sequelizeOptions)
114
115 logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid)
116
117 return videoCreated
118 })
119}
120
121async function createRates (actorUrls: string[], video: VideoModel, rate: VideoRateType) {
122 let rateCounts = 0
123 const tasks: Bluebird<any>[] = []
124
125 for (const actorUrl of actorUrls) {
126 const actor = await getOrCreateActorAndServerAndModel(actorUrl)
127 const p = AccountVideoRateModel
128 .create({
129 videoId: video.id,
130 accountId: actor.Account.id,
131 type: rate
132 })
133 .then(() => rateCounts += 1)
134
135 tasks.push(p)
136 }
137
138 await Promise.all(tasks)
139
140 logger.info('Adding %d %s to video %s.', rateCounts, rate, video.uuid)
141
142 // This is "likes" and "dislikes"
143 await video.increment(rate + 's', { by: rateCounts })
144
145 return
146}
147
148async function processCreateDislike (byActor: ActorModel, activity: ActivityCreate) {
0032ebe9 149 const options = {
50d6de9c 150 arguments: [ byActor, activity ],
0032ebe9
C
151 errorMessage: 'Cannot dislike the video with many retries.'
152 }
153
154 return retryTransactionWrapper(createVideoDislike, options)
155}
156
50d6de9c 157function createVideoDislike (byActor: ActorModel, activity: ActivityCreate) {
63c93323 158 const dislike = activity.object as DislikeObject
50d6de9c
C
159 const byAccount = byActor.Account
160
161 if (!byAccount) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)
0032ebe9 162
3fd3ab2d
C
163 return sequelizeTypescript.transaction(async t => {
164 const video = await VideoModel.loadByUrlAndPopulateAccount(dislike.object, t)
0032ebe9
C
165 if (!video) throw new Error('Unknown video ' + dislike.object)
166
167 const rate = {
168 type: 'dislike' as 'dislike',
169 videoId: video.id,
170 accountId: byAccount.id
171 }
3fd3ab2d 172 const [ , created ] = await AccountVideoRateModel.findOrCreate({
0032ebe9 173 where: rate,
63c93323
C
174 defaults: rate,
175 transaction: t
0032ebe9 176 })
f00984c0 177 if (created === true) await video.increment('dislikes', { transaction: t })
0032ebe9 178
63c93323
C
179 if (video.isOwned() && created === true) {
180 // Don't resend the activity to the sender
50d6de9c 181 const exceptions = [ byActor ]
63c93323
C
182 await forwardActivity(activity, t, exceptions)
183 }
0032ebe9
C
184 })
185}
186
50d6de9c 187async function processCreateView (byAccount: ActorModel, activity: ActivityCreate) {
63c93323
C
188 const view = activity.object as ViewObject
189
3fd3ab2d 190 const video = await VideoModel.loadByUrlAndPopulateAccount(view.object)
40ff5707
C
191
192 if (!video) throw new Error('Unknown video ' + view.object)
193
50d6de9c 194 const account = await ActorModel.loadByUrl(view.actor)
40ff5707
C
195 if (!account) throw new Error('Unknown account ' + view.actor)
196
197 await video.increment('views')
198
63c93323
C
199 if (video.isOwned()) {
200 // Don't resend the activity to the sender
201 const exceptions = [ byAccount ]
202 await forwardActivity(activity, undefined, exceptions)
203 }
40ff5707
C
204}
205
50d6de9c 206function processCreateVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) {
8e13fa7d 207 const options = {
50d6de9c 208 arguments: [ actor, videoAbuseToCreateData ],
8e13fa7d
C
209 errorMessage: 'Cannot insert the remote video abuse with many retries.'
210 }
211
212 return retryTransactionWrapper(addRemoteVideoAbuse, options)
213}
214
50d6de9c 215function addRemoteVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) {
8e13fa7d
C
216 logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object)
217
50d6de9c
C
218 const account = actor.Account
219 if (!account) throw new Error('Cannot create dislike with the non account actor ' + actor.url)
220
3fd3ab2d
C
221 return sequelizeTypescript.transaction(async t => {
222 const video = await VideoModel.loadByUrlAndPopulateAccount(videoAbuseToCreateData.object, t)
8e13fa7d
C
223 if (!video) {
224 logger.warn('Unknown video %s for remote video abuse.', videoAbuseToCreateData.object)
79d5caf9 225 return undefined
8e13fa7d
C
226 }
227
228 const videoAbuseData = {
229 reporterAccountId: account.id,
230 reason: videoAbuseToCreateData.content,
231 videoId: video.id
232 }
233
3fd3ab2d 234 await VideoAbuseModel.create(videoAbuseData)
8e13fa7d
C
235
236 logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object)
237 })
238}