]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/lib/activitypub/process/process-update.ts
73db461c30c7962766d45d16ec79d8c3f87e6f19
[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
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))
111 await Promise.all(tasks)
112
113 const tags = videoObject.tag.map(t => t.name)
114 const tagInstances = await TagModel.findOrCreateTags(tags, t)
115 await videoInstance.$set('Tags', tagInstances, sequelizeOptions)
116 })
117
118 logger.info('Remote video with uuid %s updated', videoObject.uuid)
119 } catch (err) {
120 if (videoInstance !== undefined && videoFieldsSave !== undefined) {
121 resetSequelizeInstance(videoInstance, videoFieldsSave)
122 }
123
124 // This is just a debug because we will retry the insert
125 logger.debug('Cannot update the remote video.', { err })
126 throw err
127 }
128 }
129
130 async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) {
131 const actorAttributesToUpdate = activity.object as ActivityPubActor
132
133 logger.debug('Updating remote account "%s".', actorAttributesToUpdate.uuid)
134 let accountOrChannelInstance: AccountModel | VideoChannelModel
135 let actorFieldsSave: object
136 let accountOrChannelFieldsSave: object
137
138 // Fetch icon?
139 const avatarName = await fetchAvatarIfExists(actorAttributesToUpdate)
140
141 try {
142 await sequelizeTypescript.transaction(async t => {
143 actorFieldsSave = actor.toJSON()
144
145 if (actorAttributesToUpdate.type === 'Group') accountOrChannelInstance = actor.VideoChannel
146 else accountOrChannelInstance = actor.Account
147
148 accountOrChannelFieldsSave = accountOrChannelInstance.toJSON()
149
150 await updateActorInstance(actor, actorAttributesToUpdate)
151
152 if (avatarName !== undefined) {
153 await updateActorAvatarInstance(actor, avatarName, t)
154 }
155
156 await actor.save({ transaction: t })
157
158 accountOrChannelInstance.set('name', actorAttributesToUpdate.name || actorAttributesToUpdate.preferredUsername)
159 accountOrChannelInstance.set('description', actorAttributesToUpdate.summary)
160 accountOrChannelInstance.set('support', actorAttributesToUpdate.support)
161 await accountOrChannelInstance.save({ transaction: t })
162 })
163
164 logger.info('Remote account with uuid %s updated', actorAttributesToUpdate.uuid)
165 } catch (err) {
166 if (actor !== undefined && actorFieldsSave !== undefined) {
167 resetSequelizeInstance(actor, actorFieldsSave)
168 }
169
170 if (accountOrChannelInstance !== undefined && accountOrChannelFieldsSave !== undefined) {
171 resetSequelizeInstance(accountOrChannelInstance, accountOrChannelFieldsSave)
172 }
173
174 // This is just a debug because we will retry the insert
175 logger.debug('Cannot update the remote account.', { err })
176 throw err
177 }
178 }