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