aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers')
-rw-r--r--server/controllers/activitypub/inbox.ts30
-rw-r--r--server/controllers/activitypub/pods.ts138
-rw-r--r--server/controllers/activitypub/videos.ts928
-rw-r--r--server/controllers/api/videos/index.ts30
4 files changed, 435 insertions, 691 deletions
diff --git a/server/controllers/activitypub/inbox.ts b/server/controllers/activitypub/inbox.ts
index 79d989c2c..eee217650 100644
--- a/server/controllers/activitypub/inbox.ts
+++ b/server/controllers/activitypub/inbox.ts
@@ -1,26 +1,15 @@
1import * as express from 'express' 1import * as express from 'express'
2 2import { Activity, ActivityPubCollection, ActivityPubOrderedCollection, ActivityType, RootActivity } from '../../../shared'
3import {
4 processCreateActivity,
5 processUpdateActivity,
6 processFlagActivity
7} from '../../lib'
8import {
9 Activity,
10 ActivityType,
11 RootActivity,
12 ActivityPubCollection,
13 ActivityPubOrderedCollection
14} from '../../../shared'
15import {
16 signatureValidator,
17 checkSignature,
18 asyncMiddleware
19} from '../../middlewares'
20import { logger } from '../../helpers' 3import { logger } from '../../helpers'
4import { isActivityValid } from '../../helpers/custom-validators/activitypub/activity'
5import { processCreateActivity, processFlagActivity, processUpdateActivity } from '../../lib'
6import { processAddActivity } from '../../lib/activitypub/process-add'
7import { asyncMiddleware, checkSignature, signatureValidator } from '../../middlewares'
8import { activityPubValidator } from '../../middlewares/validators/activitypub/activity'
21 9
22const processActivity: { [ P in ActivityType ]: (activity: Activity) => Promise<any> } = { 10const processActivity: { [ P in ActivityType ]: (activity: Activity) => Promise<any> } = {
23 Create: processCreateActivity, 11 Create: processCreateActivity,
12 Add: processAddActivity,
24 Update: processUpdateActivity, 13 Update: processUpdateActivity,
25 Flag: processFlagActivity 14 Flag: processFlagActivity
26} 15}
@@ -30,7 +19,7 @@ const inboxRouter = express.Router()
30inboxRouter.post('/', 19inboxRouter.post('/',
31 signatureValidator, 20 signatureValidator,
32 asyncMiddleware(checkSignature), 21 asyncMiddleware(checkSignature),
33 // inboxValidator, 22 activityPubValidator,
34 asyncMiddleware(inboxController) 23 asyncMiddleware(inboxController)
35) 24)
36 25
@@ -54,6 +43,9 @@ async function inboxController (req: express.Request, res: express.Response, nex
54 activities = [ rootActivity as Activity ] 43 activities = [ rootActivity as Activity ]
55 } 44 }
56 45
46 // Only keep activities we are able to process
47 activities = activities.filter(a => isActivityValid(a))
48
57 await processActivities(activities) 49 await processActivities(activities)
58 50
59 res.status(204).end() 51 res.status(204).end()
diff --git a/server/controllers/activitypub/pods.ts b/server/controllers/activitypub/pods.ts
index 326eb61ac..6cce57c1c 100644
--- a/server/controllers/activitypub/pods.ts
+++ b/server/controllers/activitypub/pods.ts
@@ -1,69 +1,69 @@
1import * as express from 'express' 1// import * as express from 'express'
2 2//
3import { database as db } from '../../../initializers/database' 3// import { database as db } from '../../../initializers/database'
4import { 4// import {
5 checkSignature, 5// checkSignature,
6 signatureValidator, 6// signatureValidator,
7 setBodyHostPort, 7// setBodyHostPort,
8 remotePodsAddValidator, 8// remotePodsAddValidator,
9 asyncMiddleware 9// asyncMiddleware
10} from '../../../middlewares' 10// } from '../../../middlewares'
11import { sendOwnedDataToPod } from '../../../lib' 11// import { sendOwnedDataToPod } from '../../../lib'
12import { getMyPublicCert, getFormattedObjects } from '../../../helpers' 12// import { getMyPublicCert, getFormattedObjects } from '../../../helpers'
13import { CONFIG } from '../../../initializers' 13// import { CONFIG } from '../../../initializers'
14import { PodInstance } from '../../../models' 14// import { PodInstance } from '../../../models'
15import { PodSignature, Pod as FormattedPod } from '../../../../shared' 15// import { PodSignature, Pod as FormattedPod } from '../../../../shared'
16 16//
17const remotePodsRouter = express.Router() 17// const remotePodsRouter = express.Router()
18 18//
19remotePodsRouter.post('/remove', 19// remotePodsRouter.post('/remove',
20 signatureValidator, 20// signatureValidator,
21 checkSignature, 21// checkSignature,
22 asyncMiddleware(removePods) 22// asyncMiddleware(removePods)
23) 23// )
24 24//
25remotePodsRouter.post('/list', 25// remotePodsRouter.post('/list',
26 asyncMiddleware(remotePodsList) 26// asyncMiddleware(remotePodsList)
27) 27// )
28 28//
29remotePodsRouter.post('/add', 29// remotePodsRouter.post('/add',
30 setBodyHostPort, // We need to modify the host before running the validator! 30// setBodyHostPort, // We need to modify the host before running the validator!
31 remotePodsAddValidator, 31// remotePodsAddValidator,
32 asyncMiddleware(addPods) 32// asyncMiddleware(addPods)
33) 33// )
34 34//
35// --------------------------------------------------------------------------- 35// // ---------------------------------------------------------------------------
36 36//
37export { 37// export {
38 remotePodsRouter 38// remotePodsRouter
39} 39// }
40 40//
41// --------------------------------------------------------------------------- 41// // ---------------------------------------------------------------------------
42 42//
43async function addPods (req: express.Request, res: express.Response, next: express.NextFunction) { 43// async function addPods (req: express.Request, res: express.Response, next: express.NextFunction) {
44 const information = req.body 44// const information = req.body
45 45//
46 const pod = db.Pod.build(information) 46// const pod = db.Pod.build(information)
47 const podCreated = await pod.save() 47// const podCreated = await pod.save()
48 48//
49 await sendOwnedDataToPod(podCreated.id) 49// await sendOwnedDataToPod(podCreated.id)
50 50//
51 const cert = await getMyPublicCert() 51// const cert = await getMyPublicCert()
52 return res.json({ cert, email: CONFIG.ADMIN.EMAIL }) 52// return res.json({ cert, email: CONFIG.ADMIN.EMAIL })
53} 53// }
54 54//
55async function remotePodsList (req: express.Request, res: express.Response, next: express.NextFunction) { 55// async function remotePodsList (req: express.Request, res: express.Response, next: express.NextFunction) {
56 const pods = await db.Pod.list() 56// const pods = await db.Pod.list()
57 57//
58 return res.json(getFormattedObjects<FormattedPod, PodInstance>(pods, pods.length)) 58// return res.json(getFormattedObjects<FormattedPod, PodInstance>(pods, pods.length))
59} 59// }
60 60//
61async function removePods (req: express.Request, res: express.Response, next: express.NextFunction) { 61// async function removePods (req: express.Request, res: express.Response, next: express.NextFunction) {
62 const signature: PodSignature = req.body.signature 62// const signature: PodSignature = req.body.signature
63 const host = signature.host 63// const host = signature.host
64 64//
65 const pod = await db.Pod.loadByHost(host) 65// const pod = await db.Pod.loadByHost(host)
66 await pod.destroy() 66// await pod.destroy()
67 67//
68 return res.type('json').status(204).end() 68// return res.type('json').status(204).end()
69} 69// }
diff --git a/server/controllers/activitypub/videos.ts b/server/controllers/activitypub/videos.ts
index cba47f0a1..9a1868ff7 100644
--- a/server/controllers/activitypub/videos.ts
+++ b/server/controllers/activitypub/videos.ts
@@ -1,589 +1,339 @@
1import * as express from 'express' 1// import * as express from 'express'
2import * as Bluebird from 'bluebird' 2// import * as Bluebird from 'bluebird'
3import * as Sequelize from 'sequelize' 3// import * as Sequelize from 'sequelize'
4 4//
5import { database as db } from '../../../initializers/database' 5// import { database as db } from '../../../initializers/database'
6import { 6// import {
7 REQUEST_ENDPOINT_ACTIONS, 7// REQUEST_ENDPOINT_ACTIONS,
8 REQUEST_ENDPOINTS, 8// REQUEST_ENDPOINTS,
9 REQUEST_VIDEO_EVENT_TYPES, 9// REQUEST_VIDEO_EVENT_TYPES,
10 REQUEST_VIDEO_QADU_TYPES 10// REQUEST_VIDEO_QADU_TYPES
11} from '../../../initializers' 11// } from '../../../initializers'
12import { 12// import {
13 checkSignature, 13// checkSignature,
14 signatureValidator, 14// signatureValidator,
15 remoteVideosValidator, 15// remoteVideosValidator,
16 remoteQaduVideosValidator, 16// remoteQaduVideosValidator,
17 remoteEventsVideosValidator 17// remoteEventsVideosValidator
18} from '../../../middlewares' 18// } from '../../../middlewares'
19import { logger, retryTransactionWrapper, resetSequelizeInstance } from '../../../helpers' 19// import { logger, retryTransactionWrapper, resetSequelizeInstance } from '../../../helpers'
20import { quickAndDirtyUpdatesVideoToFriends, fetchVideoChannelByHostAndUUID } from '../../../lib' 20// import { quickAndDirtyUpdatesVideoToFriends, fetchVideoChannelByHostAndUUID } from '../../../lib'
21import { PodInstance, VideoFileInstance } from '../../../models' 21// import { PodInstance, VideoFileInstance } from '../../../models'
22import { 22// import {
23 RemoteVideoRequest, 23// RemoteVideoRequest,
24 RemoteVideoCreateData, 24// RemoteVideoCreateData,
25 RemoteVideoUpdateData, 25// RemoteVideoUpdateData,
26 RemoteVideoRemoveData, 26// RemoteVideoRemoveData,
27 RemoteVideoReportAbuseData, 27// RemoteVideoReportAbuseData,
28 RemoteQaduVideoRequest, 28// RemoteQaduVideoRequest,
29 RemoteQaduVideoData, 29// RemoteQaduVideoData,
30 RemoteVideoEventRequest, 30// RemoteVideoEventRequest,
31 RemoteVideoEventData, 31// RemoteVideoEventData,
32 RemoteVideoChannelCreateData, 32// RemoteVideoChannelCreateData,
33 RemoteVideoChannelUpdateData, 33// RemoteVideoChannelUpdateData,
34 RemoteVideoChannelRemoveData, 34// RemoteVideoChannelRemoveData,
35 RemoteVideoAuthorRemoveData, 35// RemoteVideoAuthorRemoveData,
36 RemoteVideoAuthorCreateData 36// RemoteVideoAuthorCreateData
37} from '../../../../shared' 37// } from '../../../../shared'
38import { VideoInstance } from '../../../models/video/video-interface' 38// import { VideoInstance } from '../../../models/video/video-interface'
39 39//
40const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS] 40// const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS]
41 41//
42// Functions to call when processing a remote request 42// // Functions to call when processing a remote request
43// FIXME: use RemoteVideoRequestType as id type 43// // FIXME: use RemoteVideoRequestType as id type
44const functionsHash: { [ id: string ]: (...args) => Promise<any> } = {} 44// const functionsHash: { [ id: string ]: (...args) => Promise<any> } = {}
45functionsHash[ENDPOINT_ACTIONS.ADD_VIDEO] = addRemoteVideoRetryWrapper 45// functionsHash[ENDPOINT_ACTIONS.ADD_VIDEO] = addRemoteVideoRetryWrapper
46functionsHash[ENDPOINT_ACTIONS.UPDATE_VIDEO] = updateRemoteVideoRetryWrapper 46// functionsHash[ENDPOINT_ACTIONS.UPDATE_VIDEO] = updateRemoteVideoRetryWrapper
47functionsHash[ENDPOINT_ACTIONS.REMOVE_VIDEO] = removeRemoteVideoRetryWrapper 47// functionsHash[ENDPOINT_ACTIONS.REMOVE_VIDEO] = removeRemoteVideoRetryWrapper
48functionsHash[ENDPOINT_ACTIONS.ADD_CHANNEL] = addRemoteVideoChannelRetryWrapper 48// functionsHash[ENDPOINT_ACTIONS.ADD_CHANNEL] = addRemoteVideoChannelRetryWrapper
49functionsHash[ENDPOINT_ACTIONS.UPDATE_CHANNEL] = updateRemoteVideoChannelRetryWrapper 49// functionsHash[ENDPOINT_ACTIONS.UPDATE_CHANNEL] = updateRemoteVideoChannelRetryWrapper
50functionsHash[ENDPOINT_ACTIONS.REMOVE_CHANNEL] = removeRemoteVideoChannelRetryWrapper 50// functionsHash[ENDPOINT_ACTIONS.REMOVE_CHANNEL] = removeRemoteVideoChannelRetryWrapper
51functionsHash[ENDPOINT_ACTIONS.REPORT_ABUSE] = reportAbuseRemoteVideoRetryWrapper 51// functionsHash[ENDPOINT_ACTIONS.REPORT_ABUSE] = reportAbuseRemoteVideoRetryWrapper
52functionsHash[ENDPOINT_ACTIONS.ADD_AUTHOR] = addRemoteVideoAuthorRetryWrapper 52// functionsHash[ENDPOINT_ACTIONS.ADD_AUTHOR] = addRemoteVideoAuthorRetryWrapper
53functionsHash[ENDPOINT_ACTIONS.REMOVE_AUTHOR] = removeRemoteVideoAuthorRetryWrapper 53// functionsHash[ENDPOINT_ACTIONS.REMOVE_AUTHOR] = removeRemoteVideoAuthorRetryWrapper
54 54//
55const remoteVideosRouter = express.Router() 55// const remoteVideosRouter = express.Router()
56 56//
57remoteVideosRouter.post('/', 57// remoteVideosRouter.post('/',
58 signatureValidator, 58// signatureValidator,
59 checkSignature, 59// checkSignature,
60 remoteVideosValidator, 60// remoteVideosValidator,
61 remoteVideos 61// remoteVideos
62) 62// )
63 63//
64remoteVideosRouter.post('/qadu', 64// remoteVideosRouter.post('/qadu',
65 signatureValidator, 65// signatureValidator,
66 checkSignature, 66// checkSignature,
67 remoteQaduVideosValidator, 67// remoteQaduVideosValidator,
68 remoteVideosQadu 68// remoteVideosQadu
69) 69// )
70 70//
71remoteVideosRouter.post('/events', 71// remoteVideosRouter.post('/events',
72 signatureValidator, 72// signatureValidator,
73 checkSignature, 73// checkSignature,
74 remoteEventsVideosValidator, 74// remoteEventsVideosValidator,
75 remoteVideosEvents 75// remoteVideosEvents
76) 76// )
77 77//
78// --------------------------------------------------------------------------- 78// // ---------------------------------------------------------------------------
79 79//
80export { 80// export {
81 remoteVideosRouter 81// remoteVideosRouter
82} 82// }
83 83//
84// --------------------------------------------------------------------------- 84// // ---------------------------------------------------------------------------
85 85//
86function remoteVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 86// function remoteVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
87 const requests: RemoteVideoRequest[] = req.body.data 87// const requests: RemoteVideoRequest[] = req.body.data
88 const fromPod = res.locals.secure.pod 88// const fromPod = res.locals.secure.pod
89 89//
90 // We need to process in the same order to keep consistency 90// // We need to process in the same order to keep consistency
91 Bluebird.each(requests, request => { 91// Bluebird.each(requests, request => {
92 const data = request.data 92// const data = request.data
93 93//
94 // Get the function we need to call in order to process the request 94// // Get the function we need to call in order to process the request
95 const fun = functionsHash[request.type] 95// const fun = functionsHash[request.type]
96 if (fun === undefined) { 96// if (fun === undefined) {
97 logger.error('Unknown remote request type %s.', request.type) 97// logger.error('Unknown remote request type %s.', request.type)
98 return 98// return
99 } 99// }
100 100//
101 return fun.call(this, data, fromPod) 101// return fun.call(this, data, fromPod)
102 }) 102// })
103 .catch(err => logger.error('Error managing remote videos.', err)) 103// .catch(err => logger.error('Error managing remote videos.', err))
104 104//
105 // Don't block the other pod 105// // Don't block the other pod
106 return res.type('json').status(204).end() 106// return res.type('json').status(204).end()
107} 107// }
108 108//
109function remoteVideosQadu (req: express.Request, res: express.Response, next: express.NextFunction) { 109// function remoteVideosQadu (req: express.Request, res: express.Response, next: express.NextFunction) {
110 const requests: RemoteQaduVideoRequest[] = req.body.data 110// const requests: RemoteQaduVideoRequest[] = req.body.data
111 const fromPod = res.locals.secure.pod 111// const fromPod = res.locals.secure.pod
112 112//
113 Bluebird.each(requests, request => { 113// Bluebird.each(requests, request => {
114 const videoData = request.data 114// const videoData = request.data
115 115//
116 return quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod) 116// return quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod)
117 }) 117// })
118 .catch(err => logger.error('Error managing remote videos.', err)) 118// .catch(err => logger.error('Error managing remote videos.', err))
119 119//
120 return res.type('json').status(204).end() 120// return res.type('json').status(204).end()
121} 121// }
122 122//
123function remoteVideosEvents (req: express.Request, res: express.Response, next: express.NextFunction) { 123// function remoteVideosEvents (req: express.Request, res: express.Response, next: express.NextFunction) {
124 const requests: RemoteVideoEventRequest[] = req.body.data 124// const requests: RemoteVideoEventRequest[] = req.body.data
125 const fromPod = res.locals.secure.pod 125// const fromPod = res.locals.secure.pod
126 126//
127 Bluebird.each(requests, request => { 127// Bluebird.each(requests, request => {
128 const eventData = request.data 128// const eventData = request.data
129 129//
130 return processVideosEventsRetryWrapper(eventData, fromPod) 130// return processVideosEventsRetryWrapper(eventData, fromPod)
131 }) 131// })
132 .catch(err => logger.error('Error managing remote videos.', err)) 132// .catch(err => logger.error('Error managing remote videos.', err))
133 133//
134 return res.type('json').status(204).end() 134// return res.type('json').status(204).end()
135} 135// }
136 136//
137async function processVideosEventsRetryWrapper (eventData: RemoteVideoEventData, fromPod: PodInstance) { 137// async function processVideosEventsRetryWrapper (eventData: RemoteVideoEventData, fromPod: PodInstance) {
138 const options = { 138// const options = {
139 arguments: [ eventData, fromPod ], 139// arguments: [ eventData, fromPod ],
140 errorMessage: 'Cannot process videos events with many retries.' 140// errorMessage: 'Cannot process videos events with many retries.'
141 } 141// }
142 142//
143 await retryTransactionWrapper(processVideosEvents, options) 143// await retryTransactionWrapper(processVideosEvents, options)
144} 144// }
145 145//
146async function processVideosEvents (eventData: RemoteVideoEventData, fromPod: PodInstance) { 146// async function processVideosEvents (eventData: RemoteVideoEventData, fromPod: PodInstance) {
147 await db.sequelize.transaction(async t => { 147// await db.sequelize.transaction(async t => {
148 const sequelizeOptions = { transaction: t } 148// const sequelizeOptions = { transaction: t }
149 const videoInstance = await fetchLocalVideoByUUID(eventData.uuid, t) 149// const videoInstance = await fetchLocalVideoByUUID(eventData.uuid, t)
150 150//
151 let columnToUpdate 151// let columnToUpdate
152 let qaduType 152// let qaduType
153 153//
154 switch (eventData.eventType) { 154// switch (eventData.eventType) {
155 case REQUEST_VIDEO_EVENT_TYPES.VIEWS: 155// case REQUEST_VIDEO_EVENT_TYPES.VIEWS:
156 columnToUpdate = 'views' 156// columnToUpdate = 'views'
157 qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS 157// qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS
158 break 158// break
159 159//
160 case REQUEST_VIDEO_EVENT_TYPES.LIKES: 160// case REQUEST_VIDEO_EVENT_TYPES.LIKES:
161 columnToUpdate = 'likes' 161// columnToUpdate = 'likes'
162 qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES 162// qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES
163 break 163// break
164 164//
165 case REQUEST_VIDEO_EVENT_TYPES.DISLIKES: 165// case REQUEST_VIDEO_EVENT_TYPES.DISLIKES:
166 columnToUpdate = 'dislikes' 166// columnToUpdate = 'dislikes'
167 qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES 167// qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES
168 break 168// break
169 169//
170 default: 170// default:
171 throw new Error('Unknown video event type.') 171// throw new Error('Unknown video event type.')
172 } 172// }
173 173//
174 const query = {} 174// const query = {}
175 query[columnToUpdate] = eventData.count 175// query[columnToUpdate] = eventData.count
176 176//
177 await videoInstance.increment(query, sequelizeOptions) 177// await videoInstance.increment(query, sequelizeOptions)
178 178//
179 const qadusParams = [ 179// const qadusParams = [
180 { 180// {
181 videoId: videoInstance.id, 181// videoId: videoInstance.id,
182 type: qaduType 182// type: qaduType
183 } 183// }
184 ] 184// ]
185 await quickAndDirtyUpdatesVideoToFriends(qadusParams, t) 185// await quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
186 }) 186// })
187 187//
188 logger.info('Remote video event processed for video with uuid %s.', eventData.uuid) 188// logger.info('Remote video event processed for video with uuid %s.', eventData.uuid)
189} 189// }
190 190//
191async function quickAndDirtyUpdateVideoRetryWrapper (videoData: RemoteQaduVideoData, fromPod: PodInstance) { 191// async function quickAndDirtyUpdateVideoRetryWrapper (videoData: RemoteQaduVideoData, fromPod: PodInstance) {
192 const options = { 192// const options = {
193 arguments: [ videoData, fromPod ], 193// arguments: [ videoData, fromPod ],
194 errorMessage: 'Cannot update quick and dirty the remote video with many retries.' 194// errorMessage: 'Cannot update quick and dirty the remote video with many retries.'
195 } 195// }
196 196//
197 await retryTransactionWrapper(quickAndDirtyUpdateVideo, options) 197// await retryTransactionWrapper(quickAndDirtyUpdateVideo, options)
198} 198// }
199 199//
200async function quickAndDirtyUpdateVideo (videoData: RemoteQaduVideoData, fromPod: PodInstance) { 200// async function quickAndDirtyUpdateVideo (videoData: RemoteQaduVideoData, fromPod: PodInstance) {
201 let videoUUID = '' 201// let videoUUID = ''
202 202//
203 await db.sequelize.transaction(async t => { 203// await db.sequelize.transaction(async t => {
204 const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoData.uuid, t) 204// const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoData.uuid, t)
205 const sequelizeOptions = { transaction: t } 205// const sequelizeOptions = { transaction: t }
206 206//
207 videoUUID = videoInstance.uuid 207// videoUUID = videoInstance.uuid
208 208//
209 if (videoData.views) { 209// if (videoData.views) {
210 videoInstance.set('views', videoData.views) 210// videoInstance.set('views', videoData.views)
211 } 211// }
212 212//
213 if (videoData.likes) { 213// if (videoData.likes) {
214 videoInstance.set('likes', videoData.likes) 214// videoInstance.set('likes', videoData.likes)
215 } 215// }
216 216//
217 if (videoData.dislikes) { 217// if (videoData.dislikes) {
218 videoInstance.set('dislikes', videoData.dislikes) 218// videoInstance.set('dislikes', videoData.dislikes)
219 } 219// }
220 220//
221 await videoInstance.save(sequelizeOptions) 221// await videoInstance.save(sequelizeOptions)
222 }) 222// })
223 223//
224 logger.info('Remote video with uuid %s quick and dirty updated', videoUUID) 224// logger.info('Remote video with uuid %s quick and dirty updated', videoUUID)
225} 225// }
226 226//
227// Handle retries on fail 227// async function removeRemoteVideoRetryWrapper (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
228async function addRemoteVideoRetryWrapper (videoToCreateData: RemoteVideoCreateData, fromPod: PodInstance) { 228// const options = {
229 const options = { 229// arguments: [ videoToRemoveData, fromPod ],
230 arguments: [ videoToCreateData, fromPod ], 230// errorMessage: 'Cannot remove the remote video channel with many retries.'
231 errorMessage: 'Cannot insert the remote video with many retries.' 231// }
232 } 232//
233 233// await retryTransactionWrapper(removeRemoteVideo, options)
234 await retryTransactionWrapper(addRemoteVideo, options) 234// }
235} 235//
236 236// async function removeRemoteVideo (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
237async function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod: PodInstance) { 237// logger.debug('Removing remote video "%s".', videoToRemoveData.uuid)
238 logger.debug('Adding remote video "%s".', videoToCreateData.uuid) 238//
239 239// await db.sequelize.transaction(async t => {
240 await db.sequelize.transaction(async t => { 240// // We need the instance because we have to remove some other stuffs (thumbnail etc)
241 const sequelizeOptions = { 241// const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid, t)
242 transaction: t 242// await videoInstance.destroy({ transaction: t })
243 } 243// })
244 244//
245 const videoFromDatabase = await db.Video.loadByUUID(videoToCreateData.uuid) 245// logger.info('Remote video with uuid %s removed.', videoToRemoveData.uuid)
246 if (videoFromDatabase) throw new Error('UUID already exists.') 246// }
247 247//
248 const videoChannel = await db.VideoChannel.loadByHostAndUUID(fromPod.host, videoToCreateData.channelUUID, t) 248// async function removeRemoteVideoAuthorRetryWrapper (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
249 if (!videoChannel) throw new Error('Video channel ' + videoToCreateData.channelUUID + ' not found.') 249// const options = {
250 250// arguments: [ authorAttributesToRemove, fromPod ],
251 const tags = videoToCreateData.tags 251// errorMessage: 'Cannot remove the remote video author with many retries.'
252 const tagInstances = await db.Tag.findOrCreateTags(tags, t) 252// }
253 253//
254 const videoData = { 254// await retryTransactionWrapper(removeRemoteVideoAuthor, options)
255 name: videoToCreateData.name, 255// }
256 uuid: videoToCreateData.uuid, 256//
257 category: videoToCreateData.category, 257// async function removeRemoteVideoAuthor (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
258 licence: videoToCreateData.licence, 258// logger.debug('Removing remote video author "%s".', authorAttributesToRemove.uuid)
259 language: videoToCreateData.language, 259//
260 nsfw: videoToCreateData.nsfw, 260// await db.sequelize.transaction(async t => {
261 description: videoToCreateData.truncatedDescription, 261// const videoAuthor = await db.Author.loadAuthorByPodAndUUID(authorAttributesToRemove.uuid, fromPod.id, t)
262 channelId: videoChannel.id, 262// await videoAuthor.destroy({ transaction: t })
263 duration: videoToCreateData.duration, 263// })
264 createdAt: videoToCreateData.createdAt, 264//
265 // FIXME: updatedAt does not seems to be considered by Sequelize 265// logger.info('Remote video author with uuid %s removed.', authorAttributesToRemove.uuid)
266 updatedAt: videoToCreateData.updatedAt, 266// }
267 views: videoToCreateData.views, 267//
268 likes: videoToCreateData.likes, 268// async function removeRemoteVideoChannelRetryWrapper (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
269 dislikes: videoToCreateData.dislikes, 269// const options = {
270 remote: true, 270// arguments: [ videoChannelAttributesToRemove, fromPod ],
271 privacy: videoToCreateData.privacy 271// errorMessage: 'Cannot remove the remote video channel with many retries.'
272 } 272// }
273 273//
274 const video = db.Video.build(videoData) 274// await retryTransactionWrapper(removeRemoteVideoChannel, options)
275 await db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData) 275// }
276 const videoCreated = await video.save(sequelizeOptions) 276//
277 277// async function removeRemoteVideoChannel (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
278 const tasks = [] 278// logger.debug('Removing remote video channel "%s".', videoChannelAttributesToRemove.uuid)
279 for (const fileData of videoToCreateData.files) { 279//
280 const videoFileInstance = db.VideoFile.build({ 280// await db.sequelize.transaction(async t => {
281 extname: fileData.extname, 281// const videoChannel = await fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToRemove.uuid, t)
282 infoHash: fileData.infoHash, 282// await videoChannel.destroy({ transaction: t })
283 resolution: fileData.resolution, 283// })
284 size: fileData.size, 284//
285 videoId: videoCreated.id 285// logger.info('Remote video channel with uuid %s removed.', videoChannelAttributesToRemove.uuid)
286 }) 286// }
287 287//
288 tasks.push(videoFileInstance.save(sequelizeOptions)) 288// async function reportAbuseRemoteVideoRetryWrapper (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
289 } 289// const options = {
290 290// arguments: [ reportData, fromPod ],
291 await Promise.all(tasks) 291// errorMessage: 'Cannot create remote abuse video with many retries.'
292 292// }
293 await videoCreated.setTags(tagInstances, sequelizeOptions) 293//
294 }) 294// await retryTransactionWrapper(reportAbuseRemoteVideo, options)
295 295// }
296 logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid) 296//
297} 297// async function reportAbuseRemoteVideo (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
298 298// logger.debug('Reporting remote abuse for video %s.', reportData.videoUUID)
299// Handle retries on fail 299//
300async function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: RemoteVideoUpdateData, fromPod: PodInstance) { 300// await db.sequelize.transaction(async t => {
301 const options = { 301// const videoInstance = await fetchLocalVideoByUUID(reportData.videoUUID, t)
302 arguments: [ videoAttributesToUpdate, fromPod ], 302// const videoAbuseData = {
303 errorMessage: 'Cannot update the remote video with many retries' 303// reporterUsername: reportData.reporterUsername,
304 } 304// reason: reportData.reportReason,
305 305// reporterPodId: fromPod.id,
306 await retryTransactionWrapper(updateRemoteVideo, options) 306// videoId: videoInstance.id
307} 307// }
308 308//
309async function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData, fromPod: PodInstance) { 309// await db.VideoAbuse.create(videoAbuseData)
310 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid) 310//
311 let videoInstance: VideoInstance 311// })
312 let videoFieldsSave: object 312//
313 313// logger.info('Remote abuse for video uuid %s created', reportData.videoUUID)
314 try { 314// }
315 await db.sequelize.transaction(async t => { 315//
316 const sequelizeOptions = { 316// async function fetchLocalVideoByUUID (id: string, t: Sequelize.Transaction) {
317 transaction: t 317// try {
318 } 318// const video = await db.Video.loadLocalVideoByUUID(id, t)
319 319//
320 const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoAttributesToUpdate.uuid, t) 320// if (!video) throw new Error('Video ' + id + ' not found')
321 videoFieldsSave = videoInstance.toJSON() 321//
322 const tags = videoAttributesToUpdate.tags 322// return video
323 323// } catch (err) {
324 const tagInstances = await db.Tag.findOrCreateTags(tags, t) 324// logger.error('Cannot load owned video from id.', { error: err.stack, id })
325 325// throw err
326 videoInstance.set('name', videoAttributesToUpdate.name) 326// }
327 videoInstance.set('category', videoAttributesToUpdate.category) 327// }
328 videoInstance.set('licence', videoAttributesToUpdate.licence) 328//
329 videoInstance.set('language', videoAttributesToUpdate.language) 329// async function fetchVideoByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) {
330 videoInstance.set('nsfw', videoAttributesToUpdate.nsfw) 330// try {
331 videoInstance.set('description', videoAttributesToUpdate.truncatedDescription) 331// const video = await db.Video.loadByHostAndUUID(podHost, uuid, t)
332 videoInstance.set('duration', videoAttributesToUpdate.duration) 332// if (!video) throw new Error('Video not found')
333 videoInstance.set('createdAt', videoAttributesToUpdate.createdAt) 333//
334 videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt) 334// return video
335 videoInstance.set('views', videoAttributesToUpdate.views) 335// } catch (err) {
336 videoInstance.set('likes', videoAttributesToUpdate.likes) 336// logger.error('Cannot load video from host and uuid.', { error: err.stack, podHost, uuid })
337 videoInstance.set('dislikes', videoAttributesToUpdate.dislikes) 337// throw err
338 videoInstance.set('privacy', videoAttributesToUpdate.privacy) 338// }
339 339// }
340 await videoInstance.save(sequelizeOptions)
341
342 // Remove old video files
343 const videoFileDestroyTasks: Bluebird<void>[] = []
344 for (const videoFile of videoInstance.VideoFiles) {
345 videoFileDestroyTasks.push(videoFile.destroy(sequelizeOptions))
346 }
347 await Promise.all(videoFileDestroyTasks)
348
349 const videoFileCreateTasks: Bluebird<VideoFileInstance>[] = []
350 for (const fileData of videoAttributesToUpdate.files) {
351 const videoFileInstance = db.VideoFile.build({
352 extname: fileData.extname,
353 infoHash: fileData.infoHash,
354 resolution: fileData.resolution,
355 size: fileData.size,
356 videoId: videoInstance.id
357 })
358
359 videoFileCreateTasks.push(videoFileInstance.save(sequelizeOptions))
360 }
361
362 await Promise.all(videoFileCreateTasks)
363
364 await videoInstance.setTags(tagInstances, sequelizeOptions)
365 })
366
367 logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid)
368 } catch (err) {
369 if (videoInstance !== undefined && videoFieldsSave !== undefined) {
370 resetSequelizeInstance(videoInstance, videoFieldsSave)
371 }
372
373 // This is just a debug because we will retry the insert
374 logger.debug('Cannot update the remote video.', err)
375 throw err
376 }
377}
378
379async function removeRemoteVideoRetryWrapper (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
380 const options = {
381 arguments: [ videoToRemoveData, fromPod ],
382 errorMessage: 'Cannot remove the remote video channel with many retries.'
383 }
384
385 await retryTransactionWrapper(removeRemoteVideo, options)
386}
387
388async function removeRemoteVideo (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
389 logger.debug('Removing remote video "%s".', videoToRemoveData.uuid)
390
391 await db.sequelize.transaction(async t => {
392 // We need the instance because we have to remove some other stuffs (thumbnail etc)
393 const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid, t)
394 await videoInstance.destroy({ transaction: t })
395 })
396
397 logger.info('Remote video with uuid %s removed.', videoToRemoveData.uuid)
398}
399
400async function addRemoteVideoAuthorRetryWrapper (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) {
401 const options = {
402 arguments: [ authorToCreateData, fromPod ],
403 errorMessage: 'Cannot insert the remote video author with many retries.'
404 }
405
406 await retryTransactionWrapper(addRemoteVideoAuthor, options)
407}
408
409async function addRemoteVideoAuthor (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) {
410 logger.debug('Adding remote video author "%s".', authorToCreateData.uuid)
411
412 await db.sequelize.transaction(async t => {
413 const authorInDatabase = await db.Author.loadAuthorByPodAndUUID(authorToCreateData.uuid, fromPod.id, t)
414 if (authorInDatabase) throw new Error('Author with UUID ' + authorToCreateData.uuid + ' already exists.')
415
416 const videoAuthorData = {
417 name: authorToCreateData.name,
418 uuid: authorToCreateData.uuid,
419 userId: null, // Not on our pod
420 podId: fromPod.id
421 }
422
423 const author = db.Author.build(videoAuthorData)
424 await author.save({ transaction: t })
425 })
426
427 logger.info('Remote video author with uuid %s inserted.', authorToCreateData.uuid)
428}
429
430async function removeRemoteVideoAuthorRetryWrapper (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
431 const options = {
432 arguments: [ authorAttributesToRemove, fromPod ],
433 errorMessage: 'Cannot remove the remote video author with many retries.'
434 }
435
436 await retryTransactionWrapper(removeRemoteVideoAuthor, options)
437}
438
439async function removeRemoteVideoAuthor (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
440 logger.debug('Removing remote video author "%s".', authorAttributesToRemove.uuid)
441
442 await db.sequelize.transaction(async t => {
443 const videoAuthor = await db.Author.loadAuthorByPodAndUUID(authorAttributesToRemove.uuid, fromPod.id, t)
444 await videoAuthor.destroy({ transaction: t })
445 })
446
447 logger.info('Remote video author with uuid %s removed.', authorAttributesToRemove.uuid)
448}
449
450async function addRemoteVideoChannelRetryWrapper (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) {
451 const options = {
452 arguments: [ videoChannelToCreateData, fromPod ],
453 errorMessage: 'Cannot insert the remote video channel with many retries.'
454 }
455
456 await retryTransactionWrapper(addRemoteVideoChannel, options)
457}
458
459async function addRemoteVideoChannel (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) {
460 logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid)
461
462 await db.sequelize.transaction(async t => {
463 const videoChannelInDatabase = await db.VideoChannel.loadByUUID(videoChannelToCreateData.uuid)
464 if (videoChannelInDatabase) {
465 throw new Error('Video channel with UUID ' + videoChannelToCreateData.uuid + ' already exists.')
466 }
467
468 const authorUUID = videoChannelToCreateData.ownerUUID
469 const podId = fromPod.id
470
471 const author = await db.Author.loadAuthorByPodAndUUID(authorUUID, podId, t)
472 if (!author) throw new Error('Unknown author UUID' + authorUUID + '.')
473
474 const videoChannelData = {
475 name: videoChannelToCreateData.name,
476 description: videoChannelToCreateData.description,
477 uuid: videoChannelToCreateData.uuid,
478 createdAt: videoChannelToCreateData.createdAt,
479 updatedAt: videoChannelToCreateData.updatedAt,
480 remote: true,
481 authorId: author.id
482 }
483
484 const videoChannel = db.VideoChannel.build(videoChannelData)
485 await videoChannel.save({ transaction: t })
486 })
487
488 logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid)
489}
490
491async function updateRemoteVideoChannelRetryWrapper (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) {
492 const options = {
493 arguments: [ videoChannelAttributesToUpdate, fromPod ],
494 errorMessage: 'Cannot update the remote video channel with many retries.'
495 }
496
497 await retryTransactionWrapper(updateRemoteVideoChannel, options)
498}
499
500async function updateRemoteVideoChannel (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) {
501 logger.debug('Updating remote video channel "%s".', videoChannelAttributesToUpdate.uuid)
502
503 await db.sequelize.transaction(async t => {
504 const sequelizeOptions = { transaction: t }
505
506 const videoChannelInstance = await fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToUpdate.uuid, t)
507 videoChannelInstance.set('name', videoChannelAttributesToUpdate.name)
508 videoChannelInstance.set('description', videoChannelAttributesToUpdate.description)
509 videoChannelInstance.set('createdAt', videoChannelAttributesToUpdate.createdAt)
510 videoChannelInstance.set('updatedAt', videoChannelAttributesToUpdate.updatedAt)
511
512 await videoChannelInstance.save(sequelizeOptions)
513 })
514
515 logger.info('Remote video channel with uuid %s updated', videoChannelAttributesToUpdate.uuid)
516}
517
518async function removeRemoteVideoChannelRetryWrapper (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
519 const options = {
520 arguments: [ videoChannelAttributesToRemove, fromPod ],
521 errorMessage: 'Cannot remove the remote video channel with many retries.'
522 }
523
524 await retryTransactionWrapper(removeRemoteVideoChannel, options)
525}
526
527async function removeRemoteVideoChannel (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
528 logger.debug('Removing remote video channel "%s".', videoChannelAttributesToRemove.uuid)
529
530 await db.sequelize.transaction(async t => {
531 const videoChannel = await fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToRemove.uuid, t)
532 await videoChannel.destroy({ transaction: t })
533 })
534
535 logger.info('Remote video channel with uuid %s removed.', videoChannelAttributesToRemove.uuid)
536}
537
538async function reportAbuseRemoteVideoRetryWrapper (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
539 const options = {
540 arguments: [ reportData, fromPod ],
541 errorMessage: 'Cannot create remote abuse video with many retries.'
542 }
543
544 await retryTransactionWrapper(reportAbuseRemoteVideo, options)
545}
546
547async function reportAbuseRemoteVideo (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
548 logger.debug('Reporting remote abuse for video %s.', reportData.videoUUID)
549
550 await db.sequelize.transaction(async t => {
551 const videoInstance = await fetchLocalVideoByUUID(reportData.videoUUID, t)
552 const videoAbuseData = {
553 reporterUsername: reportData.reporterUsername,
554 reason: reportData.reportReason,
555 reporterPodId: fromPod.id,
556 videoId: videoInstance.id
557 }
558
559 await db.VideoAbuse.create(videoAbuseData)
560
561 })
562
563 logger.info('Remote abuse for video uuid %s created', reportData.videoUUID)
564}
565
566async function fetchLocalVideoByUUID (id: string, t: Sequelize.Transaction) {
567 try {
568 const video = await db.Video.loadLocalVideoByUUID(id, t)
569
570 if (!video) throw new Error('Video ' + id + ' not found')
571
572 return video
573 } catch (err) {
574 logger.error('Cannot load owned video from id.', { error: err.stack, id })
575 throw err
576 }
577}
578
579async function fetchVideoByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) {
580 try {
581 const video = await db.Video.loadByHostAndUUID(podHost, uuid, t)
582 if (!video) throw new Error('Video not found')
583
584 return video
585 } catch (err) {
586 logger.error('Cannot load video from host and uuid.', { error: err.stack, podHost, uuid })
587 throw err
588 }
589}
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 4dd09917b..964db151d 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -10,7 +10,8 @@ import {
10 VIDEO_CATEGORIES, 10 VIDEO_CATEGORIES,
11 VIDEO_LICENCES, 11 VIDEO_LICENCES,
12 VIDEO_LANGUAGES, 12 VIDEO_LANGUAGES,
13 VIDEO_PRIVACIES 13 VIDEO_PRIVACIES,
14 VIDEO_MIMETYPE_EXT
14} from '../../../initializers' 15} from '../../../initializers'
15import { 16import {
16 addEventToRemoteVideo, 17 addEventToRemoteVideo,
@@ -50,6 +51,7 @@ import { abuseVideoRouter } from './abuse'
50import { blacklistRouter } from './blacklist' 51import { blacklistRouter } from './blacklist'
51import { rateVideoRouter } from './rate' 52import { rateVideoRouter } from './rate'
52import { videoChannelRouter } from './channel' 53import { videoChannelRouter } from './channel'
54import { getActivityPubUrl } from '../../../helpers/activitypub'
53 55
54const videosRouter = express.Router() 56const videosRouter = express.Router()
55 57
@@ -59,19 +61,18 @@ const storage = multer.diskStorage({
59 cb(null, CONFIG.STORAGE.VIDEOS_DIR) 61 cb(null, CONFIG.STORAGE.VIDEOS_DIR)
60 }, 62 },
61 63
62 filename: (req, file, cb) => { 64 filename: async (req, file, cb) => {
63 let extension = '' 65 const extension = VIDEO_MIMETYPE_EXT[file.mimetype]
64 if (file.mimetype === 'video/webm') extension = 'webm' 66 let randomString = ''
65 else if (file.mimetype === 'video/mp4') extension = 'mp4' 67
66 else if (file.mimetype === 'video/ogg') extension = 'ogv' 68 try {
67 generateRandomString(16) 69 randomString = await generateRandomString(16)
68 .then(randomString => { 70 } catch (err) {
69 cb(null, randomString + '.' + extension) 71 logger.error('Cannot generate random string for file name.', err)
70 }) 72 randomString = 'fake-random-string'
71 .catch(err => { 73 }
72 logger.error('Cannot generate random string for file name.', err) 74
73 throw err 75 cb(null, randomString + '.' + extension)
74 })
75 } 76 }
76}) 77})
77 78
@@ -190,6 +191,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
190 channelId: res.locals.videoChannel.id 191 channelId: res.locals.videoChannel.id
191 } 192 }
192 const video = db.Video.build(videoData) 193 const video = db.Video.build(videoData)
194 video.url = getActivityPubUrl('video', video.uuid)
193 195
194 const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename) 196 const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
195 const videoFileHeight = await getVideoFileHeight(videoFilePath) 197 const videoFileHeight = await getVideoFileHeight(videoFilePath)