Fix lint
[oweals/peertube.git] / server / lib / activitypub / process / process-update.ts
1 import * as Bluebird from 'bluebird'
2 import { ActivityUpdate } from '../../../../shared/models/activitypub'
3 import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor'
4 import { VideoTorrentObject } from '../../../../shared/models/activitypub/objects'
5 import { retryTransactionWrapper } from '../../../helpers/database-utils'
6 import { logger } from '../../../helpers/logger'
7 import { resetSequelizeInstance } from '../../../helpers/utils'
8 import { sequelizeTypescript } from '../../../initializers'
9 import { AccountModel } from '../../../models/account/account'
10 import { ActorModel } from '../../../models/activitypub/actor'
11 import { TagModel } from '../../../models/video/tag'
12 import { VideoFileModel } from '../../../models/video/video-file'
13 import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor'
14 import { getOrCreateAccountAndVideoAndChannel, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from '../videos'
15
16 async function processUpdateActivity (activity: ActivityUpdate) {
17   const actor = await getOrCreateActorAndServerAndModel(activity.actor)
18
19   if (activity.object.type === 'Video') {
20     return processUpdateVideo(actor, activity)
21   } else if (activity.object.type === 'Person') {
22     return processUpdateAccount(actor, activity)
23   }
24
25   return
26 }
27
28 // ---------------------------------------------------------------------------
29
30 export {
31   processUpdateActivity
32 }
33
34 // ---------------------------------------------------------------------------
35
36 function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate) {
37   const options = {
38     arguments: [ actor, activity ],
39     errorMessage: 'Cannot update the remote video with many retries'
40   }
41
42   return retryTransactionWrapper(updateRemoteVideo, options)
43 }
44
45 async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
46   const videoAttributesToUpdate = activity.object as VideoTorrentObject
47
48   const res = await getOrCreateAccountAndVideoAndChannel(videoAttributesToUpdate.id)
49
50   logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
51   let videoInstance = res.video
52   let videoFieldsSave: any
53
54   try {
55     await sequelizeTypescript.transaction(async t => {
56       const sequelizeOptions = {
57         transaction: t
58       }
59
60       videoFieldsSave = videoInstance.toJSON()
61
62       const videoChannel = videoInstance.VideoChannel
63       if (videoChannel.Account.Actor.id !== actor.id) {
64         throw new Error('Account ' + actor.url + ' does not own video channel ' + videoChannel.Actor.url)
65       }
66
67       const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoAttributesToUpdate, activity.to, activity.cc)
68       videoInstance.set('name', videoData.name)
69       videoInstance.set('uuid', videoData.uuid)
70       videoInstance.set('url', videoData.url)
71       videoInstance.set('category', videoData.category)
72       videoInstance.set('licence', videoData.licence)
73       videoInstance.set('language', videoData.language)
74       videoInstance.set('description', videoData.description)
75       videoInstance.set('nsfw', videoData.nsfw)
76       videoInstance.set('commentsEnabled', videoData.commentsEnabled)
77       videoInstance.set('duration', videoData.duration)
78       videoInstance.set('createdAt', videoData.createdAt)
79       videoInstance.set('updatedAt', videoData.updatedAt)
80       videoInstance.set('views', videoData.views)
81       videoInstance.set('privacy', videoData.privacy)
82
83       await videoInstance.save(sequelizeOptions)
84
85       // Remove old video files
86       const videoFileDestroyTasks: Bluebird<void>[] = []
87       for (const videoFile of videoInstance.VideoFiles) {
88         videoFileDestroyTasks.push(videoFile.destroy(sequelizeOptions))
89       }
90       await Promise.all(videoFileDestroyTasks)
91
92       const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoInstance, videoAttributesToUpdate)
93       const tasks = videoFileAttributes.map(f => VideoFileModel.create(f))
94       await Promise.all(tasks)
95
96       const tags = videoAttributesToUpdate.tag.map(t => t.name)
97       const tagInstances = await TagModel.findOrCreateTags(tags, t)
98       await videoInstance.$set('Tags', tagInstances, sequelizeOptions)
99     })
100
101     logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid)
102   } catch (err) {
103     if (videoInstance !== undefined && videoFieldsSave !== undefined) {
104       resetSequelizeInstance(videoInstance, videoFieldsSave)
105     }
106
107     // This is just a debug because we will retry the insert
108     logger.debug('Cannot update the remote video.', err)
109     throw err
110   }
111 }
112
113 function processUpdateAccount (actor: ActorModel, activity: ActivityUpdate) {
114   const options = {
115     arguments: [ actor, activity ],
116     errorMessage: 'Cannot update the remote account with many retries'
117   }
118
119   return retryTransactionWrapper(updateRemoteAccount, options)
120 }
121
122 async function updateRemoteAccount (actor: ActorModel, activity: ActivityUpdate) {
123   const accountAttributesToUpdate = activity.object as ActivityPubActor
124
125   logger.debug('Updating remote account "%s".', accountAttributesToUpdate.uuid)
126   let accountInstance: AccountModel
127   let actorFieldsSave: object
128   let accountFieldsSave: object
129
130   // Fetch icon?
131   const avatarName = await fetchAvatarIfExists(accountAttributesToUpdate)
132
133   try {
134     await sequelizeTypescript.transaction(async t => {
135       actorFieldsSave = actor.toJSON()
136       accountInstance = actor.Account
137       accountFieldsSave = actor.Account.toJSON()
138
139       await updateActorInstance(actor, accountAttributesToUpdate)
140
141       if (avatarName !== undefined) {
142         await updateActorAvatarInstance(actor, avatarName, t)
143       }
144
145       await actor.save({ transaction: t })
146
147       actor.Account.set('name', accountAttributesToUpdate.name || accountAttributesToUpdate.preferredUsername)
148       await actor.Account.save({ transaction: t })
149     })
150
151     logger.info('Remote account with uuid %s updated', accountAttributesToUpdate.uuid)
152   } catch (err) {
153     if (actor !== undefined && actorFieldsSave !== undefined) {
154       resetSequelizeInstance(actor, actorFieldsSave)
155     }
156
157     if (accountInstance !== undefined && accountFieldsSave !== undefined) {
158       resetSequelizeInstance(accountInstance, accountFieldsSave)
159     }
160
161     // This is just a debug because we will retry the insert
162     logger.debug('Cannot update the remote account.', err)
163     throw err
164   }
165 }