]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/lib/activitypub/process/process-update.ts
11226e2753661637ba9031c3fa352e2eb2abaa6d
[github/Chocobozzz/PeerTube.git] / server / lib / activitypub / process / process-update.ts
1 import * as Bluebird from 'bluebird'
2 import { ActivityUpdate, VideoTorrentObject } from '../../../../shared/models/activitypub'
3 import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor'
4 import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils'
5 import { logger } from '../../../helpers/logger'
6 import { sequelizeTypescript } from '../../../initializers'
7 import { AccountModel } from '../../../models/account/account'
8 import { ActorModel } from '../../../models/activitypub/actor'
9 import { TagModel } from '../../../models/video/tag'
10 import { VideoChannelModel } from '../../../models/video/video-channel'
11 import { VideoFileModel } from '../../../models/video/video-file'
12 import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor'
13 import {
14 generateThumbnailFromUrl,
15 getOrCreateAccountAndVideoAndChannel,
16 getOrCreateVideoChannel,
17 videoActivityObjectToDBAttributes,
18 videoFileActivityUrlToDBAttributes
19 } from '../videos'
20 import { sanitizeAndCheckVideoTorrentObject } from '../../../helpers/custom-validators/activitypub/videos'
21 import { VideoCaptionModel } from '../../../models/video/video-caption'
22
23 async function processUpdateActivity (activity: ActivityUpdate) {
24 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
25 const objectType = activity.object.type
26
27 if (objectType === 'Video') {
28 return retryTransactionWrapper(processUpdateVideo, actor, activity)
29 } else if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') {
30 return retryTransactionWrapper(processUpdateActor, actor, activity)
31 }
32
33 return undefined
34 }
35
36 // ---------------------------------------------------------------------------
37
38 export {
39 processUpdateActivity
40 }
41
42 // ---------------------------------------------------------------------------
43
44 async function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate) {
45 const videoObject = activity.object as VideoTorrentObject
46
47 if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) {
48 logger.debug('Video sent by update is not valid.', { videoObject })
49 return undefined
50 }
51
52 const res = await getOrCreateAccountAndVideoAndChannel(videoObject.id)
53
54 // Fetch video channel outside the transaction
55 const newVideoChannelActor = await getOrCreateVideoChannel(videoObject)
56 const newVideoChannel = newVideoChannelActor.VideoChannel
57
58 logger.debug('Updating remote video "%s".', videoObject.uuid)
59 let videoInstance = res.video
60 let videoFieldsSave: any
61
62 try {
63 await sequelizeTypescript.transaction(async t => {
64 const sequelizeOptions = {
65 transaction: t
66 }
67
68 videoFieldsSave = videoInstance.toJSON()
69
70 // Check actor has the right to update the video
71 const videoChannel = videoInstance.VideoChannel
72 if (videoChannel.Account.Actor.id !== actor.id) {
73 throw new Error('Account ' + actor.url + ' does not own video channel ' + videoChannel.Actor.url)
74 }
75
76 const videoData = await videoActivityObjectToDBAttributes(newVideoChannel, videoObject, activity.to)
77 videoInstance.set('name', videoData.name)
78 videoInstance.set('uuid', videoData.uuid)
79 videoInstance.set('url', videoData.url)
80 videoInstance.set('category', videoData.category)
81 videoInstance.set('licence', videoData.licence)
82 videoInstance.set('language', videoData.language)
83 videoInstance.set('description', videoData.description)
84 videoInstance.set('support', videoData.support)
85 videoInstance.set('nsfw', videoData.nsfw)
86 videoInstance.set('commentsEnabled', videoData.commentsEnabled)
87 videoInstance.set('waitTranscoding', videoData.waitTranscoding)
88 videoInstance.set('state', videoData.state)
89 videoInstance.set('duration', videoData.duration)
90 videoInstance.set('createdAt', videoData.createdAt)
91 videoInstance.set('updatedAt', videoData.updatedAt)
92 videoInstance.set('views', videoData.views)
93 videoInstance.set('privacy', videoData.privacy)
94 videoInstance.set('channelId', videoData.channelId)
95
96 await videoInstance.save(sequelizeOptions)
97
98 // Don't block on request
99 generateThumbnailFromUrl(videoInstance, videoObject.icon)
100 .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }))
101
102 // Remove old video files
103 const videoFileDestroyTasks: Bluebird<void>[] = []
104 for (const videoFile of videoInstance.VideoFiles) {
105 videoFileDestroyTasks.push(videoFile.destroy(sequelizeOptions))
106 }
107 await Promise.all(videoFileDestroyTasks)
108
109 const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoInstance, videoObject)
110 const tasks = videoFileAttributes.map(f => VideoFileModel.create(f, sequelizeOptions))
111 await Promise.all(tasks)
112
113 // Update Tags
114 const tags = videoObject.tag.map(tag => tag.name)
115 const tagInstances = await TagModel.findOrCreateTags(tags, t)
116 await videoInstance.$set('Tags', tagInstances, sequelizeOptions)
117
118 // Update captions
119 await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoInstance.id, t)
120
121 const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => {
122 return VideoCaptionModel.insertOrReplaceLanguage(videoInstance.id, c.identifier, t)
123 })
124 await Promise.all(videoCaptionsPromises)
125 })
126
127 logger.info('Remote video with uuid %s updated', videoObject.uuid)
128 } catch (err) {
129 if (videoInstance !== undefined && videoFieldsSave !== undefined) {
130 resetSequelizeInstance(videoInstance, videoFieldsSave)
131 }
132
133 // This is just a debug because we will retry the insert
134 logger.debug('Cannot update the remote video.', { err })
135 throw err
136 }
137 }
138
139 async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) {
140 const actorAttributesToUpdate = activity.object as ActivityPubActor
141
142 logger.debug('Updating remote account "%s".', actorAttributesToUpdate.uuid)
143 let accountOrChannelInstance: AccountModel | VideoChannelModel
144 let actorFieldsSave: object
145 let accountOrChannelFieldsSave: object
146
147 // Fetch icon?
148 const avatarName = await fetchAvatarIfExists(actorAttributesToUpdate)
149
150 try {
151 await sequelizeTypescript.transaction(async t => {
152 actorFieldsSave = actor.toJSON()
153
154 if (actorAttributesToUpdate.type === 'Group') accountOrChannelInstance = actor.VideoChannel
155 else accountOrChannelInstance = actor.Account
156
157 accountOrChannelFieldsSave = accountOrChannelInstance.toJSON()
158
159 await updateActorInstance(actor, actorAttributesToUpdate)
160
161 if (avatarName !== undefined) {
162 await updateActorAvatarInstance(actor, avatarName, t)
163 }
164
165 await actor.save({ transaction: t })
166
167 accountOrChannelInstance.set('name', actorAttributesToUpdate.name || actorAttributesToUpdate.preferredUsername)
168 accountOrChannelInstance.set('description', actorAttributesToUpdate.summary)
169 accountOrChannelInstance.set('support', actorAttributesToUpdate.support)
170 await accountOrChannelInstance.save({ transaction: t })
171 })
172
173 logger.info('Remote account with uuid %s updated', actorAttributesToUpdate.uuid)
174 } catch (err) {
175 if (actor !== undefined && actorFieldsSave !== undefined) {
176 resetSequelizeInstance(actor, actorFieldsSave)
177 }
178
179 if (accountOrChannelInstance !== undefined && accountOrChannelFieldsSave !== undefined) {
180 resetSequelizeInstance(accountOrChannelInstance, accountOrChannelFieldsSave)
181 }
182
183 // This is just a debug because we will retry the insert
184 logger.debug('Cannot update the remote account.', { err })
185 throw err
186 }
187 }