aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers/api
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers/api')
-rw-r--r--server/controllers/api/accounts.ts97
-rw-r--r--server/controllers/api/config.ts44
-rw-r--r--server/controllers/api/index.ts4
-rw-r--r--server/controllers/api/oauth-clients.ts2
-rw-r--r--server/controllers/api/overviews.ts4
-rw-r--r--server/controllers/api/server/debug.ts25
-rw-r--r--server/controllers/api/server/follows.ts71
-rw-r--r--server/controllers/api/server/index.ts4
-rw-r--r--server/controllers/api/server/logs.ts95
-rw-r--r--server/controllers/api/server/redundancy.ts5
-rw-r--r--server/controllers/api/server/server-blocklist.ts10
-rw-r--r--server/controllers/api/server/stats.ts3
-rw-r--r--server/controllers/api/users/index.ts78
-rw-r--r--server/controllers/api/users/me.ts38
-rw-r--r--server/controllers/api/users/my-blocklist.ts19
-rw-r--r--server/controllers/api/users/my-history.ts6
-rw-r--r--server/controllers/api/users/my-notifications.ts15
-rw-r--r--server/controllers/api/users/my-subscriptions.ts20
-rw-r--r--server/controllers/api/users/my-video-playlists.ts46
-rw-r--r--server/controllers/api/video-channel.ts55
-rw-r--r--server/controllers/api/video-playlist.ts445
-rw-r--r--server/controllers/api/videos/abuse.ts12
-rw-r--r--server/controllers/api/videos/blacklist.ts34
-rw-r--r--server/controllers/api/videos/captions.ts11
-rw-r--r--server/controllers/api/videos/comment.ts23
-rw-r--r--server/controllers/api/videos/import.ts71
-rw-r--r--server/controllers/api/videos/index.ts122
-rw-r--r--server/controllers/api/videos/ownership.ts29
-rw-r--r--server/controllers/api/videos/rate.ts8
-rw-r--r--server/controllers/api/videos/watching.ts3
30 files changed, 1127 insertions, 272 deletions
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts
index 8c0237203..8d4db1e75 100644
--- a/server/controllers/api/accounts.ts
+++ b/server/controllers/api/accounts.ts
@@ -1,21 +1,32 @@
1import * as express from 'express' 1import * as express from 'express'
2import { getFormattedObjects } from '../../helpers/utils' 2import { getFormattedObjects, getServerActor } from '../../helpers/utils'
3import { 3import {
4 asyncMiddleware, 4 asyncMiddleware,
5 authenticate,
5 commonVideosFiltersValidator, 6 commonVideosFiltersValidator,
6 listVideoAccountChannelsValidator,
7 optionalAuthenticate, 7 optionalAuthenticate,
8 paginationValidator, 8 paginationValidator,
9 setDefaultPagination, 9 setDefaultPagination,
10 setDefaultSort 10 setDefaultSort,
11 videoPlaylistsSortValidator,
12 videoRatesSortValidator,
13 videoRatingValidator
11} from '../../middlewares' 14} from '../../middlewares'
12import { accountsNameWithHostGetValidator, accountsSortValidator, videosSortValidator } from '../../middlewares/validators' 15import {
16 accountNameWithHostGetValidator,
17 accountsSortValidator,
18 ensureAuthUserOwnsAccountValidator,
19 videosSortValidator
20} from '../../middlewares/validators'
13import { AccountModel } from '../../models/account/account' 21import { AccountModel } from '../../models/account/account'
22import { AccountVideoRateModel } from '../../models/account/account-video-rate'
14import { VideoModel } from '../../models/video/video' 23import { VideoModel } from '../../models/video/video'
15import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' 24import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
16import { VideoChannelModel } from '../../models/video/video-channel' 25import { VideoChannelModel } from '../../models/video/video-channel'
17import { JobQueue } from '../../lib/job-queue' 26import { JobQueue } from '../../lib/job-queue'
18import { logger } from '../../helpers/logger' 27import { logger } from '../../helpers/logger'
28import { VideoPlaylistModel } from '../../models/video/video-playlist'
29import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists'
19 30
20const accountsRouter = express.Router() 31const accountsRouter = express.Router()
21 32
@@ -28,12 +39,12 @@ accountsRouter.get('/',
28) 39)
29 40
30accountsRouter.get('/:accountName', 41accountsRouter.get('/:accountName',
31 asyncMiddleware(accountsNameWithHostGetValidator), 42 asyncMiddleware(accountNameWithHostGetValidator),
32 getAccount 43 getAccount
33) 44)
34 45
35accountsRouter.get('/:accountName/videos', 46accountsRouter.get('/:accountName/videos',
36 asyncMiddleware(accountsNameWithHostGetValidator), 47 asyncMiddleware(accountNameWithHostGetValidator),
37 paginationValidator, 48 paginationValidator,
38 videosSortValidator, 49 videosSortValidator,
39 setDefaultSort, 50 setDefaultSort,
@@ -44,8 +55,31 @@ accountsRouter.get('/:accountName/videos',
44) 55)
45 56
46accountsRouter.get('/:accountName/video-channels', 57accountsRouter.get('/:accountName/video-channels',
47 asyncMiddleware(listVideoAccountChannelsValidator), 58 asyncMiddleware(accountNameWithHostGetValidator),
48 asyncMiddleware(listVideoAccountChannels) 59 asyncMiddleware(listAccountChannels)
60)
61
62accountsRouter.get('/:accountName/video-playlists',
63 optionalAuthenticate,
64 asyncMiddleware(accountNameWithHostGetValidator),
65 paginationValidator,
66 videoPlaylistsSortValidator,
67 setDefaultSort,
68 setDefaultPagination,
69 commonVideoPlaylistFiltersValidator,
70 asyncMiddleware(listAccountPlaylists)
71)
72
73accountsRouter.get('/:accountName/ratings',
74 authenticate,
75 asyncMiddleware(accountNameWithHostGetValidator),
76 ensureAuthUserOwnsAccountValidator,
77 paginationValidator,
78 videoRatesSortValidator,
79 setDefaultSort,
80 setDefaultPagination,
81 videoRatingValidator,
82 asyncMiddleware(listAccountRatings)
49) 83)
50 84
51// --------------------------------------------------------------------------- 85// ---------------------------------------------------------------------------
@@ -56,8 +90,8 @@ export {
56 90
57// --------------------------------------------------------------------------- 91// ---------------------------------------------------------------------------
58 92
59function getAccount (req: express.Request, res: express.Response, next: express.NextFunction) { 93function getAccount (req: express.Request, res: express.Response) {
60 const account: AccountModel = res.locals.account 94 const account = res.locals.account
61 95
62 if (account.isOutdated()) { 96 if (account.isOutdated()) {
63 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: account.Actor.url } }) 97 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: account.Actor.url } })
@@ -67,20 +101,42 @@ function getAccount (req: express.Request, res: express.Response, next: express.
67 return res.json(account.toFormattedJSON()) 101 return res.json(account.toFormattedJSON())
68} 102}
69 103
70async function listAccounts (req: express.Request, res: express.Response, next: express.NextFunction) { 104async function listAccounts (req: express.Request, res: express.Response) {
71 const resultList = await AccountModel.listForApi(req.query.start, req.query.count, req.query.sort) 105 const resultList = await AccountModel.listForApi(req.query.start, req.query.count, req.query.sort)
72 106
73 return res.json(getFormattedObjects(resultList.data, resultList.total)) 107 return res.json(getFormattedObjects(resultList.data, resultList.total))
74} 108}
75 109
76async function listVideoAccountChannels (req: express.Request, res: express.Response, next: express.NextFunction) { 110async function listAccountChannels (req: express.Request, res: express.Response) {
77 const resultList = await VideoChannelModel.listByAccount(res.locals.account.id) 111 const resultList = await VideoChannelModel.listByAccount(res.locals.account.id)
78 112
79 return res.json(getFormattedObjects(resultList.data, resultList.total)) 113 return res.json(getFormattedObjects(resultList.data, resultList.total))
80} 114}
81 115
82async function listAccountVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 116async function listAccountPlaylists (req: express.Request, res: express.Response) {
83 const account: AccountModel = res.locals.account 117 const serverActor = await getServerActor()
118
119 // Allow users to see their private/unlisted video playlists
120 let privateAndUnlisted = false
121 if (res.locals.oauth && res.locals.oauth.token.User.Account.id === res.locals.account.id) {
122 privateAndUnlisted = true
123 }
124
125 const resultList = await VideoPlaylistModel.listForApi({
126 followerActorId: serverActor.id,
127 start: req.query.start,
128 count: req.query.count,
129 sort: req.query.sort,
130 accountId: res.locals.account.id,
131 privateAndUnlisted,
132 type: req.query.playlistType
133 })
134
135 return res.json(getFormattedObjects(resultList.data, resultList.total))
136}
137
138async function listAccountVideos (req: express.Request, res: express.Response) {
139 const account = res.locals.account
84 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined 140 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
85 141
86 const resultList = await VideoModel.listForApi({ 142 const resultList = await VideoModel.listForApi({
@@ -103,3 +159,16 @@ async function listAccountVideos (req: express.Request, res: express.Response, n
103 159
104 return res.json(getFormattedObjects(resultList.data, resultList.total)) 160 return res.json(getFormattedObjects(resultList.data, resultList.total))
105} 161}
162
163async function listAccountRatings (req: express.Request, res: express.Response) {
164 const account = res.locals.account
165
166 const resultList = await AccountVideoRateModel.listByAccountForApi({
167 accountId: account.id,
168 start: req.query.start,
169 count: req.query.count,
170 sort: req.query.sort,
171 type: req.query.rating
172 })
173 return res.json(getFormattedObjects(resultList.rows, resultList.count))
174}
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index 255026f46..40012c03b 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -1,10 +1,10 @@
1import * as express from 'express' 1import * as express from 'express'
2import { omit, snakeCase } from 'lodash' 2import { snakeCase } from 'lodash'
3import { ServerConfig, UserRight } from '../../../shared' 3import { ServerConfig, UserRight } from '../../../shared'
4import { About } from '../../../shared/models/server/about.model' 4import { About } from '../../../shared/models/server/about.model'
5import { CustomConfig } from '../../../shared/models/server/custom-config.model' 5import { CustomConfig } from '../../../shared/models/server/custom-config.model'
6import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' 6import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
7import { CONFIG, CONSTRAINTS_FIELDS, reloadConfig } from '../../initializers' 7import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
8import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares' 8import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
9import { customConfigUpdateValidator } from '../../middlewares/validators/config' 9import { customConfigUpdateValidator } from '../../middlewares/validators/config'
10import { ClientHtml } from '../../lib/client-html' 10import { ClientHtml } from '../../lib/client-html'
@@ -14,6 +14,7 @@ import { getServerCommit } from '../../helpers/utils'
14import { Emailer } from '../../lib/emailer' 14import { Emailer } from '../../lib/emailer'
15import { isNumeric } from 'validator' 15import { isNumeric } from 'validator'
16import { objectConverter } from '../../helpers/core-utils' 16import { objectConverter } from '../../helpers/core-utils'
17import { CONFIG, reloadConfig } from '../../initializers/config'
17 18
18const packageJSON = require('../../../../package.json') 19const packageJSON = require('../../../../package.json')
19const configRouter = express.Router() 20const configRouter = express.Router()
@@ -58,6 +59,7 @@ async function getConfig (req: express.Request, res: express.Response) {
58 name: CONFIG.INSTANCE.NAME, 59 name: CONFIG.INSTANCE.NAME,
59 shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, 60 shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
60 defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE, 61 defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
62 isNSFW: CONFIG.INSTANCE.IS_NSFW,
61 defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, 63 defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
62 customizations: { 64 customizations: {
63 javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT, 65 javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT,
@@ -78,6 +80,9 @@ async function getConfig (req: express.Request, res: express.Response) {
78 requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION 80 requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
79 }, 81 },
80 transcoding: { 82 transcoding: {
83 hls: {
84 enabled: CONFIG.TRANSCODING.HLS.ENABLED
85 },
81 enabledResolutions 86 enabledResolutions
82 }, 87 },
83 import: { 88 import: {
@@ -90,6 +95,13 @@ async function getConfig (req: express.Request, res: express.Response) {
90 } 95 }
91 } 96 }
92 }, 97 },
98 autoBlacklist: {
99 videos: {
100 ofUsers: {
101 enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
102 }
103 }
104 },
93 avatar: { 105 avatar: {
94 file: { 106 file: {
95 size: { 107 size: {
@@ -125,13 +137,16 @@ async function getConfig (req: express.Request, res: express.Response) {
125 videos: { 137 videos: {
126 intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS 138 intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
127 } 139 }
140 },
141 tracker: {
142 enabled: CONFIG.TRACKER.ENABLED
128 } 143 }
129 } 144 }
130 145
131 return res.json(json) 146 return res.json(json)
132} 147}
133 148
134function getAbout (req: express.Request, res: express.Response, next: express.NextFunction) { 149function getAbout (req: express.Request, res: express.Response) {
135 const about: About = { 150 const about: About = {
136 instance: { 151 instance: {
137 name: CONFIG.INSTANCE.NAME, 152 name: CONFIG.INSTANCE.NAME,
@@ -144,13 +159,13 @@ function getAbout (req: express.Request, res: express.Response, next: express.Ne
144 return res.json(about).end() 159 return res.json(about).end()
145} 160}
146 161
147async function getCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { 162async function getCustomConfig (req: express.Request, res: express.Response) {
148 const data = customConfig() 163 const data = customConfig()
149 164
150 return res.json(data).end() 165 return res.json(data).end()
151} 166}
152 167
153async function deleteCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { 168async function deleteCustomConfig (req: express.Request, res: express.Response) {
154 await remove(CONFIG.CUSTOM_FILE) 169 await remove(CONFIG.CUSTOM_FILE)
155 170
156 auditLogger.delete(getAuditIdFromRes(res), new CustomConfigAuditView(customConfig())) 171 auditLogger.delete(getAuditIdFromRes(res), new CustomConfigAuditView(customConfig()))
@@ -163,7 +178,7 @@ async function deleteCustomConfig (req: express.Request, res: express.Response,
163 return res.json(data).end() 178 return res.json(data).end()
164} 179}
165 180
166async function updateCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { 181async function updateCustomConfig (req: express.Request, res: express.Response) {
167 const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig()) 182 const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig())
168 183
169 // camelCase to snake_case key + Force number conversion 184 // camelCase to snake_case key + Force number conversion
@@ -200,6 +215,7 @@ function customConfig (): CustomConfig {
200 shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, 215 shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
201 description: CONFIG.INSTANCE.DESCRIPTION, 216 description: CONFIG.INSTANCE.DESCRIPTION,
202 terms: CONFIG.INSTANCE.TERMS, 217 terms: CONFIG.INSTANCE.TERMS,
218 isNSFW: CONFIG.INSTANCE.IS_NSFW,
203 defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE, 219 defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
204 defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, 220 defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
205 customizations: { 221 customizations: {
@@ -246,6 +262,9 @@ function customConfig (): CustomConfig {
246 '480p': CONFIG.TRANSCODING.RESOLUTIONS[ '480p' ], 262 '480p': CONFIG.TRANSCODING.RESOLUTIONS[ '480p' ],
247 '720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ], 263 '720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ],
248 '1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ] 264 '1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ]
265 },
266 hls: {
267 enabled: CONFIG.TRANSCODING.HLS.ENABLED
249 } 268 }
250 }, 269 },
251 import: { 270 import: {
@@ -257,6 +276,19 @@ function customConfig (): CustomConfig {
257 enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED 276 enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
258 } 277 }
259 } 278 }
279 },
280 autoBlacklist: {
281 videos: {
282 ofUsers: {
283 enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
284 }
285 }
286 },
287 followers: {
288 instance: {
289 enabled: CONFIG.FOLLOWERS.INSTANCE.ENABLED,
290 manualApproval: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL
291 }
260 } 292 }
261 } 293 }
262} 294}
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts
index 8a58b5466..60a84036e 100644
--- a/server/controllers/api/index.ts
+++ b/server/controllers/api/index.ts
@@ -11,6 +11,7 @@ import { videoChannelRouter } from './video-channel'
11import * as cors from 'cors' 11import * as cors from 'cors'
12import { searchRouter } from './search' 12import { searchRouter } from './search'
13import { overviewsRouter } from './overviews' 13import { overviewsRouter } from './overviews'
14import { videoPlaylistRouter } from './video-playlist'
14 15
15const apiRouter = express.Router() 16const apiRouter = express.Router()
16 17
@@ -26,6 +27,7 @@ apiRouter.use('/config', configRouter)
26apiRouter.use('/users', usersRouter) 27apiRouter.use('/users', usersRouter)
27apiRouter.use('/accounts', accountsRouter) 28apiRouter.use('/accounts', accountsRouter)
28apiRouter.use('/video-channels', videoChannelRouter) 29apiRouter.use('/video-channels', videoChannelRouter)
30apiRouter.use('/video-playlists', videoPlaylistRouter)
29apiRouter.use('/videos', videosRouter) 31apiRouter.use('/videos', videosRouter)
30apiRouter.use('/jobs', jobsRouter) 32apiRouter.use('/jobs', jobsRouter)
31apiRouter.use('/search', searchRouter) 33apiRouter.use('/search', searchRouter)
@@ -39,6 +41,6 @@ export { apiRouter }
39 41
40// --------------------------------------------------------------------------- 42// ---------------------------------------------------------------------------
41 43
42function pong (req: express.Request, res: express.Response, next: express.NextFunction) { 44function pong (req: express.Request, res: express.Response) {
43 return res.send('pong').status(200).end() 45 return res.send('pong').status(200).end()
44} 46}
diff --git a/server/controllers/api/oauth-clients.ts b/server/controllers/api/oauth-clients.ts
index 3dcc023e6..b2de8bcf5 100644
--- a/server/controllers/api/oauth-clients.ts
+++ b/server/controllers/api/oauth-clients.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { OAuthClientLocal } from '../../../shared' 2import { OAuthClientLocal } from '../../../shared'
3import { logger } from '../../helpers/logger' 3import { logger } from '../../helpers/logger'
4import { CONFIG } from '../../initializers' 4import { CONFIG } from '../../initializers/config'
5import { asyncMiddleware } from '../../middlewares' 5import { asyncMiddleware } from '../../middlewares'
6import { OAuthClientModel } from '../../models/oauth/oauth-client' 6import { OAuthClientModel } from '../../models/oauth/oauth-client'
7 7
diff --git a/server/controllers/api/overviews.ts b/server/controllers/api/overviews.ts
index 8b6773056..37ac152db 100644
--- a/server/controllers/api/overviews.ts
+++ b/server/controllers/api/overviews.ts
@@ -4,7 +4,7 @@ import { VideoModel } from '../../models/video/video'
4import { asyncMiddleware } from '../../middlewares' 4import { asyncMiddleware } from '../../middlewares'
5import { TagModel } from '../../models/video/tag' 5import { TagModel } from '../../models/video/tag'
6import { VideosOverview } from '../../../shared/models/overviews' 6import { VideosOverview } from '../../../shared/models/overviews'
7import { MEMOIZE_TTL, OVERVIEWS, ROUTE_CACHE_LIFETIME } from '../../initializers' 7import { MEMOIZE_TTL, OVERVIEWS, ROUTE_CACHE_LIFETIME } from '../../initializers/constants'
8import { cacheRoute } from '../../middlewares/cache' 8import { cacheRoute } from '../../middlewares/cache'
9import * as memoizee from 'memoizee' 9import * as memoizee from 'memoizee'
10 10
@@ -94,7 +94,7 @@ async function getVideos (
94) { 94) {
95 const query = Object.assign({ 95 const query = Object.assign({
96 start: 0, 96 start: 0,
97 count: 10, 97 count: 12,
98 sort: '-createdAt', 98 sort: '-createdAt',
99 includeLocalVideos: true, 99 includeLocalVideos: true,
100 nsfw: buildNSFWFilter(res), 100 nsfw: buildNSFWFilter(res),
diff --git a/server/controllers/api/server/debug.ts b/server/controllers/api/server/debug.ts
new file mode 100644
index 000000000..4450038f6
--- /dev/null
+++ b/server/controllers/api/server/debug.ts
@@ -0,0 +1,25 @@
1import * as express from 'express'
2import { UserRight } from '../../../../shared/models/users'
3import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares'
4
5const debugRouter = express.Router()
6
7debugRouter.get('/debug',
8 authenticate,
9 ensureUserHasRight(UserRight.MANAGE_DEBUG),
10 asyncMiddleware(getDebug)
11)
12
13// ---------------------------------------------------------------------------
14
15export {
16 debugRouter
17}
18
19// ---------------------------------------------------------------------------
20
21async function getDebug (req: express.Request, res: express.Response) {
22 return res.json({
23 ip: req.ip
24 }).end()
25}
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts
index 9fa6c34ba..d38ce91de 100644
--- a/server/controllers/api/server/follows.ts
+++ b/server/controllers/api/server/follows.ts
@@ -2,22 +2,29 @@ import * as express from 'express'
2import { UserRight } from '../../../../shared/models/users' 2import { UserRight } from '../../../../shared/models/users'
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { getFormattedObjects, getServerActor } from '../../../helpers/utils' 4import { getFormattedObjects, getServerActor } from '../../../helpers/utils'
5import { sequelizeTypescript, SERVER_ACTOR_NAME } from '../../../initializers' 5import { SERVER_ACTOR_NAME } from '../../../initializers/constants'
6import { sendUndoFollow } from '../../../lib/activitypub/send' 6import { sendAccept, sendReject, sendUndoFollow } from '../../../lib/activitypub/send'
7import { 7import {
8 asyncMiddleware, 8 asyncMiddleware,
9 authenticate, 9 authenticate,
10 ensureUserHasRight, 10 ensureUserHasRight,
11 paginationValidator, 11 paginationValidator,
12 removeFollowingValidator,
13 setBodyHostsPort, 12 setBodyHostsPort,
14 setDefaultPagination, 13 setDefaultPagination,
15 setDefaultSort 14 setDefaultSort
16} from '../../../middlewares' 15} from '../../../middlewares'
17import { followersSortValidator, followingSortValidator, followValidator } from '../../../middlewares/validators' 16import {
17 acceptOrRejectFollowerValidator,
18 followersSortValidator,
19 followingSortValidator,
20 followValidator,
21 getFollowerValidator,
22 removeFollowingValidator
23} from '../../../middlewares/validators'
18import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 24import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
19import { JobQueue } from '../../../lib/job-queue' 25import { JobQueue } from '../../../lib/job-queue'
20import { removeRedundancyOf } from '../../../lib/redundancy' 26import { removeRedundancyOf } from '../../../lib/redundancy'
27import { sequelizeTypescript } from '../../../initializers/database'
21 28
22const serverFollowsRouter = express.Router() 29const serverFollowsRouter = express.Router()
23serverFollowsRouter.get('/following', 30serverFollowsRouter.get('/following',
@@ -40,7 +47,7 @@ serverFollowsRouter.delete('/following/:host',
40 authenticate, 47 authenticate,
41 ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW), 48 ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
42 asyncMiddleware(removeFollowingValidator), 49 asyncMiddleware(removeFollowingValidator),
43 asyncMiddleware(removeFollow) 50 asyncMiddleware(removeFollowing)
44) 51)
45 52
46serverFollowsRouter.get('/followers', 53serverFollowsRouter.get('/followers',
@@ -51,6 +58,29 @@ serverFollowsRouter.get('/followers',
51 asyncMiddleware(listFollowers) 58 asyncMiddleware(listFollowers)
52) 59)
53 60
61serverFollowsRouter.delete('/followers/:nameWithHost',
62 authenticate,
63 ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
64 asyncMiddleware(getFollowerValidator),
65 asyncMiddleware(removeOrRejectFollower)
66)
67
68serverFollowsRouter.post('/followers/:nameWithHost/reject',
69 authenticate,
70 ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
71 asyncMiddleware(getFollowerValidator),
72 acceptOrRejectFollowerValidator,
73 asyncMiddleware(removeOrRejectFollower)
74)
75
76serverFollowsRouter.post('/followers/:nameWithHost/accept',
77 authenticate,
78 ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
79 asyncMiddleware(getFollowerValidator),
80 acceptOrRejectFollowerValidator,
81 asyncMiddleware(acceptFollower)
82)
83
54// --------------------------------------------------------------------------- 84// ---------------------------------------------------------------------------
55 85
56export { 86export {
@@ -59,7 +89,7 @@ export {
59 89
60// --------------------------------------------------------------------------- 90// ---------------------------------------------------------------------------
61 91
62async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) { 92async function listFollowing (req: express.Request, res: express.Response) {
63 const serverActor = await getServerActor() 93 const serverActor = await getServerActor()
64 const resultList = await ActorFollowModel.listFollowingForApi( 94 const resultList = await ActorFollowModel.listFollowingForApi(
65 serverActor.id, 95 serverActor.id,
@@ -72,7 +102,7 @@ async function listFollowing (req: express.Request, res: express.Response, next:
72 return res.json(getFormattedObjects(resultList.data, resultList.total)) 102 return res.json(getFormattedObjects(resultList.data, resultList.total))
73} 103}
74 104
75async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) { 105async function listFollowers (req: express.Request, res: express.Response) {
76 const serverActor = await getServerActor() 106 const serverActor = await getServerActor()
77 const resultList = await ActorFollowModel.listFollowersForApi( 107 const resultList = await ActorFollowModel.listFollowersForApi(
78 serverActor.id, 108 serverActor.id,
@@ -85,7 +115,7 @@ async function listFollowers (req: express.Request, res: express.Response, next:
85 return res.json(getFormattedObjects(resultList.data, resultList.total)) 115 return res.json(getFormattedObjects(resultList.data, resultList.total))
86} 116}
87 117
88async function followInstance (req: express.Request, res: express.Response, next: express.NextFunction) { 118async function followInstance (req: express.Request, res: express.Response) {
89 const hosts = req.body.hosts as string[] 119 const hosts = req.body.hosts as string[]
90 const follower = await getServerActor() 120 const follower = await getServerActor()
91 121
@@ -103,8 +133,8 @@ async function followInstance (req: express.Request, res: express.Response, next
103 return res.status(204).end() 133 return res.status(204).end()
104} 134}
105 135
106async function removeFollow (req: express.Request, res: express.Response, next: express.NextFunction) { 136async function removeFollowing (req: express.Request, res: express.Response) {
107 const follow: ActorFollowModel = res.locals.follow 137 const follow = res.locals.follow
108 138
109 await sequelizeTypescript.transaction(async t => { 139 await sequelizeTypescript.transaction(async t => {
110 if (follow.state === 'accepted') await sendUndoFollow(follow, t) 140 if (follow.state === 'accepted') await sendUndoFollow(follow, t)
@@ -123,3 +153,24 @@ async function removeFollow (req: express.Request, res: express.Response, next:
123 153
124 return res.status(204).end() 154 return res.status(204).end()
125} 155}
156
157async function removeOrRejectFollower (req: express.Request, res: express.Response) {
158 const follow = res.locals.follow
159
160 await sendReject(follow.ActorFollower, follow.ActorFollowing)
161
162 await follow.destroy()
163
164 return res.status(204).end()
165}
166
167async function acceptFollower (req: express.Request, res: express.Response) {
168 const follow = res.locals.follow
169
170 await sendAccept(follow)
171
172 follow.state = 'accepted'
173 await follow.save()
174
175 return res.status(204).end()
176}
diff --git a/server/controllers/api/server/index.ts b/server/controllers/api/server/index.ts
index 814248e5f..6b8793a19 100644
--- a/server/controllers/api/server/index.ts
+++ b/server/controllers/api/server/index.ts
@@ -4,6 +4,8 @@ import { statsRouter } from './stats'
4import { serverRedundancyRouter } from './redundancy' 4import { serverRedundancyRouter } from './redundancy'
5import { serverBlocklistRouter } from './server-blocklist' 5import { serverBlocklistRouter } from './server-blocklist'
6import { contactRouter } from './contact' 6import { contactRouter } from './contact'
7import { logsRouter } from './logs'
8import { debugRouter } from './debug'
7 9
8const serverRouter = express.Router() 10const serverRouter = express.Router()
9 11
@@ -12,6 +14,8 @@ serverRouter.use('/', serverRedundancyRouter)
12serverRouter.use('/', statsRouter) 14serverRouter.use('/', statsRouter)
13serverRouter.use('/', serverBlocklistRouter) 15serverRouter.use('/', serverBlocklistRouter)
14serverRouter.use('/', contactRouter) 16serverRouter.use('/', contactRouter)
17serverRouter.use('/', logsRouter)
18serverRouter.use('/', debugRouter)
15 19
16// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
17 21
diff --git a/server/controllers/api/server/logs.ts b/server/controllers/api/server/logs.ts
new file mode 100644
index 000000000..e9d1f2efd
--- /dev/null
+++ b/server/controllers/api/server/logs.ts
@@ -0,0 +1,95 @@
1import * as express from 'express'
2import { UserRight } from '../../../../shared/models/users'
3import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares'
4import { mtimeSortFilesDesc } from '../../../../shared/core-utils/logs/logs'
5import { readdir, readFile } from 'fs-extra'
6import { MAX_LOGS_OUTPUT_CHARACTERS } from '../../../initializers/constants'
7import { join } from 'path'
8import { getLogsValidator } from '../../../middlewares/validators/logs'
9import { LogLevel } from '../../../../shared/models/server/log-level.type'
10import { CONFIG } from '../../../initializers/config'
11
12const logsRouter = express.Router()
13
14logsRouter.get('/logs',
15 authenticate,
16 ensureUserHasRight(UserRight.MANAGE_LOGS),
17 getLogsValidator,
18 asyncMiddleware(getLogs)
19)
20
21// ---------------------------------------------------------------------------
22
23export {
24 logsRouter
25}
26
27// ---------------------------------------------------------------------------
28
29async function getLogs (req: express.Request, res: express.Response) {
30 const logFiles = await readdir(CONFIG.STORAGE.LOG_DIR)
31 const sortedLogFiles = await mtimeSortFilesDesc(logFiles, CONFIG.STORAGE.LOG_DIR)
32 let currentSize = 0
33
34 const startDate = new Date(req.query.startDate)
35 const endDate = req.query.endDate ? new Date(req.query.endDate) : new Date()
36 const level: LogLevel = req.query.level || 'info'
37
38 let output: string[] = []
39
40 for (const meta of sortedLogFiles) {
41 const path = join(CONFIG.STORAGE.LOG_DIR, meta.file)
42
43 const result = await getOutputFromFile(path, startDate, endDate, level, currentSize)
44 if (!result.output) break
45
46 output = result.output.concat(output)
47 currentSize = result.currentSize
48
49 if (currentSize > MAX_LOGS_OUTPUT_CHARACTERS || (result.logTime && result.logTime < startDate.getTime())) break
50 }
51
52 return res.json(output).end()
53}
54
55async function getOutputFromFile (path: string, startDate: Date, endDate: Date, level: LogLevel, currentSize: number) {
56 const startTime = startDate.getTime()
57 const endTime = endDate.getTime()
58 let logTime: number
59
60 const logsLevel: { [ id in LogLevel ]: number } = {
61 debug: 0,
62 info: 1,
63 warn: 2,
64 error: 3
65 }
66
67 const content = await readFile(path)
68 const lines = content.toString().split('\n')
69 const output: any[] = []
70
71 for (let i = lines.length - 1; i >= 0; i--) {
72 const line = lines[ i ]
73 let log: any
74
75 try {
76 log = JSON.parse(line)
77 } catch {
78 // Maybe there a multiple \n at the end of the file
79 continue
80 }
81
82 logTime = new Date(log.timestamp).getTime()
83 if (logTime >= startTime && logTime <= endTime && logsLevel[ log.level ] >= logsLevel[ level ]) {
84 output.push(log)
85
86 currentSize += line.length
87
88 if (currentSize > MAX_LOGS_OUTPUT_CHARACTERS) break
89 } else if (logTime < startTime) {
90 break
91 }
92 }
93
94 return { currentSize, output: output.reverse(), logTime }
95}
diff --git a/server/controllers/api/server/redundancy.ts b/server/controllers/api/server/redundancy.ts
index 4140c4991..f8109070d 100644
--- a/server/controllers/api/server/redundancy.ts
+++ b/server/controllers/api/server/redundancy.ts
@@ -2,7 +2,6 @@ import * as express from 'express'
2import { UserRight } from '../../../../shared/models/users' 2import { UserRight } from '../../../../shared/models/users'
3import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares' 3import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares'
4import { updateServerRedundancyValidator } from '../../../middlewares/validators/redundancy' 4import { updateServerRedundancyValidator } from '../../../middlewares/validators/redundancy'
5import { ServerModel } from '../../../models/server/server'
6import { removeRedundancyOf } from '../../../lib/redundancy' 5import { removeRedundancyOf } from '../../../lib/redundancy'
7import { logger } from '../../../helpers/logger' 6import { logger } from '../../../helpers/logger'
8 7
@@ -23,8 +22,8 @@ export {
23 22
24// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
25 24
26async function updateRedundancy (req: express.Request, res: express.Response, next: express.NextFunction) { 25async function updateRedundancy (req: express.Request, res: express.Response) {
27 const server = res.locals.server as ServerModel 26 const server = res.locals.server
28 27
29 server.redundancyAllowed = req.body.redundancyAllowed 28 server.redundancyAllowed = req.body.redundancyAllowed
30 29
diff --git a/server/controllers/api/server/server-blocklist.ts b/server/controllers/api/server/server-blocklist.ts
index 3cb3a96e2..d165db191 100644
--- a/server/controllers/api/server/server-blocklist.ts
+++ b/server/controllers/api/server/server-blocklist.ts
@@ -18,11 +18,9 @@ import {
18 unblockAccountByServerValidator, 18 unblockAccountByServerValidator,
19 unblockServerByServerValidator 19 unblockServerByServerValidator
20} from '../../../middlewares/validators' 20} from '../../../middlewares/validators'
21import { AccountModel } from '../../../models/account/account'
22import { AccountBlocklistModel } from '../../../models/account/account-blocklist' 21import { AccountBlocklistModel } from '../../../models/account/account-blocklist'
23import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist' 22import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist'
24import { ServerBlocklistModel } from '../../../models/server/server-blocklist' 23import { ServerBlocklistModel } from '../../../models/server/server-blocklist'
25import { ServerModel } from '../../../models/server/server'
26import { UserRight } from '../../../../shared/models/users' 24import { UserRight } from '../../../../shared/models/users'
27 25
28const serverBlocklistRouter = express.Router() 26const serverBlocklistRouter = express.Router()
@@ -91,7 +89,7 @@ async function listBlockedAccounts (req: express.Request, res: express.Response)
91 89
92async function blockAccount (req: express.Request, res: express.Response) { 90async function blockAccount (req: express.Request, res: express.Response) {
93 const serverActor = await getServerActor() 91 const serverActor = await getServerActor()
94 const accountToBlock: AccountModel = res.locals.account 92 const accountToBlock = res.locals.account
95 93
96 await addAccountInBlocklist(serverActor.Account.id, accountToBlock.id) 94 await addAccountInBlocklist(serverActor.Account.id, accountToBlock.id)
97 95
@@ -99,7 +97,7 @@ async function blockAccount (req: express.Request, res: express.Response) {
99} 97}
100 98
101async function unblockAccount (req: express.Request, res: express.Response) { 99async function unblockAccount (req: express.Request, res: express.Response) {
102 const accountBlock: AccountBlocklistModel = res.locals.accountBlock 100 const accountBlock = res.locals.accountBlock
103 101
104 await removeAccountFromBlocklist(accountBlock) 102 await removeAccountFromBlocklist(accountBlock)
105 103
@@ -116,7 +114,7 @@ async function listBlockedServers (req: express.Request, res: express.Response)
116 114
117async function blockServer (req: express.Request, res: express.Response) { 115async function blockServer (req: express.Request, res: express.Response) {
118 const serverActor = await getServerActor() 116 const serverActor = await getServerActor()
119 const serverToBlock: ServerModel = res.locals.server 117 const serverToBlock = res.locals.server
120 118
121 await addServerInBlocklist(serverActor.Account.id, serverToBlock.id) 119 await addServerInBlocklist(serverActor.Account.id, serverToBlock.id)
122 120
@@ -124,7 +122,7 @@ async function blockServer (req: express.Request, res: express.Response) {
124} 122}
125 123
126async function unblockServer (req: express.Request, res: express.Response) { 124async function unblockServer (req: express.Request, res: express.Response) {
127 const serverBlock: ServerBlocklistModel = res.locals.serverBlock 125 const serverBlock = res.locals.serverBlock
128 126
129 await removeServerFromBlocklist(serverBlock) 127 await removeServerFromBlocklist(serverBlock)
130 128
diff --git a/server/controllers/api/server/stats.ts b/server/controllers/api/server/stats.ts
index 89ffd1717..951b98209 100644
--- a/server/controllers/api/server/stats.ts
+++ b/server/controllers/api/server/stats.ts
@@ -6,9 +6,10 @@ import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
6import { VideoModel } from '../../../models/video/video' 6import { VideoModel } from '../../../models/video/video'
7import { VideoCommentModel } from '../../../models/video/video-comment' 7import { VideoCommentModel } from '../../../models/video/video-comment'
8import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' 8import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
9import { CONFIG, ROUTE_CACHE_LIFETIME } from '../../../initializers/constants' 9import { ROUTE_CACHE_LIFETIME } from '../../../initializers/constants'
10import { cacheRoute } from '../../../middlewares/cache' 10import { cacheRoute } from '../../../middlewares/cache'
11import { VideoFileModel } from '../../../models/video/video-file' 11import { VideoFileModel } from '../../../models/video/video-file'
12import { CONFIG } from '../../../initializers/config'
12 13
13const statsRouter = express.Router() 14const statsRouter = express.Router()
14 15
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index dbe0718d4..0aafba66e 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -3,10 +3,10 @@ import * as RateLimit from 'express-rate-limit'
3import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared' 3import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared'
4import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
5import { getFormattedObjects } from '../../../helpers/utils' 5import { getFormattedObjects } from '../../../helpers/utils'
6import { CONFIG, RATES_LIMIT, sequelizeTypescript } from '../../../initializers' 6import { RATES_LIMIT, WEBSERVER } from '../../../initializers/constants'
7import { Emailer } from '../../../lib/emailer' 7import { Emailer } from '../../../lib/emailer'
8import { Redis } from '../../../lib/redis' 8import { Redis } from '../../../lib/redis'
9import { createUserAccountAndChannel } from '../../../lib/user' 9import { createUserAccountAndChannelAndPlaylist } from '../../../lib/user'
10import { 10import {
11 asyncMiddleware, 11 asyncMiddleware,
12 asyncRetryTransactionMiddleware, 12 asyncRetryTransactionMiddleware,
@@ -38,23 +38,25 @@ import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../h
38import { meRouter } from './me' 38import { meRouter } from './me'
39import { deleteUserToken } from '../../../lib/oauth-model' 39import { deleteUserToken } from '../../../lib/oauth-model'
40import { myBlocklistRouter } from './my-blocklist' 40import { myBlocklistRouter } from './my-blocklist'
41import { myVideoPlaylistsRouter } from './my-video-playlists'
41import { myVideosHistoryRouter } from './my-history' 42import { myVideosHistoryRouter } from './my-history'
42import { myNotificationsRouter } from './my-notifications' 43import { myNotificationsRouter } from './my-notifications'
43import { Notifier } from '../../../lib/notifier' 44import { Notifier } from '../../../lib/notifier'
44import { mySubscriptionsRouter } from './my-subscriptions' 45import { mySubscriptionsRouter } from './my-subscriptions'
46import { CONFIG } from '../../../initializers/config'
47import { sequelizeTypescript } from '../../../initializers/database'
48import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
45 49
46const auditLogger = auditLoggerFactory('users') 50const auditLogger = auditLoggerFactory('users')
47 51
48const loginRateLimiter = new RateLimit({ 52const loginRateLimiter = new RateLimit({
49 windowMs: RATES_LIMIT.LOGIN.WINDOW_MS, 53 windowMs: RATES_LIMIT.LOGIN.WINDOW_MS,
50 max: RATES_LIMIT.LOGIN.MAX, 54 max: RATES_LIMIT.LOGIN.MAX
51 delayMs: 0
52}) 55})
53 56
54const askSendEmailLimiter = new RateLimit({ 57const askSendEmailLimiter = new RateLimit({
55 windowMs: RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS, 58 windowMs: RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS,
56 max: RATES_LIMIT.ASK_SEND_EMAIL.MAX, 59 max: RATES_LIMIT.ASK_SEND_EMAIL.MAX
57 delayMs: 0
58}) 60})
59 61
60const usersRouter = express.Router() 62const usersRouter = express.Router()
@@ -62,6 +64,7 @@ usersRouter.use('/', myNotificationsRouter)
62usersRouter.use('/', mySubscriptionsRouter) 64usersRouter.use('/', mySubscriptionsRouter)
63usersRouter.use('/', myBlocklistRouter) 65usersRouter.use('/', myBlocklistRouter)
64usersRouter.use('/', myVideosHistoryRouter) 66usersRouter.use('/', myVideosHistoryRouter)
67usersRouter.use('/', myVideoPlaylistsRouter)
65usersRouter.use('/', meRouter) 68usersRouter.use('/', meRouter)
66 69
67usersRouter.get('/autocomplete', 70usersRouter.get('/autocomplete',
@@ -173,10 +176,11 @@ async function createUser (req: express.Request, res: express.Response) {
173 autoPlayVideo: true, 176 autoPlayVideo: true,
174 role: body.role, 177 role: body.role,
175 videoQuota: body.videoQuota, 178 videoQuota: body.videoQuota,
176 videoQuotaDaily: body.videoQuotaDaily 179 videoQuotaDaily: body.videoQuotaDaily,
180 adminFlags: body.adminFlags || UserAdminFlag.NONE
177 }) 181 })
178 182
179 const { user, account } = await createUserAccountAndChannel(userToCreate) 183 const { user, account } = await createUserAccountAndChannelAndPlaylist(userToCreate)
180 184
181 auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON())) 185 auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
182 logger.info('User %s with its channel and account created.', body.username) 186 logger.info('User %s with its channel and account created.', body.username)
@@ -207,7 +211,7 @@ async function registerUser (req: express.Request, res: express.Response) {
207 emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null 211 emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
208 }) 212 })
209 213
210 const { user } = await createUserAccountAndChannel(userToCreate) 214 const { user } = await createUserAccountAndChannelAndPlaylist(userToCreate)
211 215
212 auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON())) 216 auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON()))
213 logger.info('User %s with its channel and account registered.', body.username) 217 logger.info('User %s with its channel and account registered.', body.username)
@@ -221,16 +225,16 @@ async function registerUser (req: express.Request, res: express.Response) {
221 return res.type('json').status(204).end() 225 return res.type('json').status(204).end()
222} 226}
223 227
224async function unblockUser (req: express.Request, res: express.Response, next: express.NextFunction) { 228async function unblockUser (req: express.Request, res: express.Response) {
225 const user: UserModel = res.locals.user 229 const user = res.locals.user
226 230
227 await changeUserBlock(res, user, false) 231 await changeUserBlock(res, user, false)
228 232
229 return res.status(204).end() 233 return res.status(204).end()
230} 234}
231 235
232async function blockUser (req: express.Request, res: express.Response, next: express.NextFunction) { 236async function blockUser (req: express.Request, res: express.Response) {
233 const user: UserModel = res.locals.user 237 const user = res.locals.user
234 const reason = req.body.reason 238 const reason = req.body.reason
235 239
236 await changeUserBlock(res, user, true, reason) 240 await changeUserBlock(res, user, true, reason)
@@ -238,24 +242,24 @@ async function blockUser (req: express.Request, res: express.Response, next: exp
238 return res.status(204).end() 242 return res.status(204).end()
239} 243}
240 244
241function getUser (req: express.Request, res: express.Response, next: express.NextFunction) { 245function getUser (req: express.Request, res: express.Response) {
242 return res.json((res.locals.user as UserModel).toFormattedJSON()) 246 return res.json(res.locals.user.toFormattedJSON({ withAdminFlags: true }))
243} 247}
244 248
245async function autocompleteUsers (req: express.Request, res: express.Response, next: express.NextFunction) { 249async function autocompleteUsers (req: express.Request, res: express.Response) {
246 const resultList = await UserModel.autoComplete(req.query.search as string) 250 const resultList = await UserModel.autoComplete(req.query.search as string)
247 251
248 return res.json(resultList) 252 return res.json(resultList)
249} 253}
250 254
251async function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) { 255async function listUsers (req: express.Request, res: express.Response) {
252 const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.search) 256 const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.search)
253 257
254 return res.json(getFormattedObjects(resultList.data, resultList.total)) 258 return res.json(getFormattedObjects(resultList.data, resultList.total, { withAdminFlags: true }))
255} 259}
256 260
257async function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) { 261async function removeUser (req: express.Request, res: express.Response) {
258 const user: UserModel = res.locals.user 262 const user = res.locals.user
259 263
260 await user.destroy() 264 await user.destroy()
261 265
@@ -264,42 +268,44 @@ async function removeUser (req: express.Request, res: express.Response, next: ex
264 return res.sendStatus(204) 268 return res.sendStatus(204)
265} 269}
266 270
267async function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) { 271async function updateUser (req: express.Request, res: express.Response) {
268 const body: UserUpdate = req.body 272 const body: UserUpdate = req.body
269 const userToUpdate = res.locals.user as UserModel 273 const userToUpdate = res.locals.user
270 const oldUserAuditView = new UserAuditView(userToUpdate.toFormattedJSON()) 274 const oldUserAuditView = new UserAuditView(userToUpdate.toFormattedJSON())
271 const roleChanged = body.role !== undefined && body.role !== userToUpdate.role 275 const roleChanged = body.role !== undefined && body.role !== userToUpdate.role
272 276
277 if (body.password !== undefined) userToUpdate.password = body.password
273 if (body.email !== undefined) userToUpdate.email = body.email 278 if (body.email !== undefined) userToUpdate.email = body.email
274 if (body.emailVerified !== undefined) userToUpdate.emailVerified = body.emailVerified 279 if (body.emailVerified !== undefined) userToUpdate.emailVerified = body.emailVerified
275 if (body.videoQuota !== undefined) userToUpdate.videoQuota = body.videoQuota 280 if (body.videoQuota !== undefined) userToUpdate.videoQuota = body.videoQuota
276 if (body.videoQuotaDaily !== undefined) userToUpdate.videoQuotaDaily = body.videoQuotaDaily 281 if (body.videoQuotaDaily !== undefined) userToUpdate.videoQuotaDaily = body.videoQuotaDaily
277 if (body.role !== undefined) userToUpdate.role = body.role 282 if (body.role !== undefined) userToUpdate.role = body.role
283 if (body.adminFlags !== undefined) userToUpdate.adminFlags = body.adminFlags
278 284
279 const user = await userToUpdate.save() 285 const user = await userToUpdate.save()
280 286
281 // Destroy user token to refresh rights 287 // Destroy user token to refresh rights
282 if (roleChanged) await deleteUserToken(userToUpdate.id) 288 if (roleChanged || body.password !== undefined) await deleteUserToken(userToUpdate.id)
283 289
284 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) 290 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)
285 291
286 // Don't need to send this update to followers, these attributes are not propagated 292 // Don't need to send this update to followers, these attributes are not federated
287 293
288 return res.sendStatus(204) 294 return res.sendStatus(204)
289} 295}
290 296
291async function askResetUserPassword (req: express.Request, res: express.Response, next: express.NextFunction) { 297async function askResetUserPassword (req: express.Request, res: express.Response) {
292 const user = res.locals.user as UserModel 298 const user = res.locals.user
293 299
294 const verificationString = await Redis.Instance.setResetPasswordVerificationString(user.id) 300 const verificationString = await Redis.Instance.setResetPasswordVerificationString(user.id)
295 const url = CONFIG.WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString 301 const url = WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString
296 await Emailer.Instance.addForgetPasswordEmailJob(user.email, url) 302 await Emailer.Instance.addPasswordResetEmailJob(user.email, url)
297 303
298 return res.status(204).end() 304 return res.status(204).end()
299} 305}
300 306
301async function resetUserPassword (req: express.Request, res: express.Response, next: express.NextFunction) { 307async function resetUserPassword (req: express.Request, res: express.Response) {
302 const user = res.locals.user as UserModel 308 const user = res.locals.user
303 user.password = req.body.password 309 user.password = req.body.password
304 310
305 await user.save() 311 await user.save()
@@ -309,21 +315,21 @@ async function resetUserPassword (req: express.Request, res: express.Response, n
309 315
310async function sendVerifyUserEmail (user: UserModel) { 316async function sendVerifyUserEmail (user: UserModel) {
311 const verificationString = await Redis.Instance.setVerifyEmailVerificationString(user.id) 317 const verificationString = await Redis.Instance.setVerifyEmailVerificationString(user.id)
312 const url = CONFIG.WEBSERVER.URL + '/verify-account/email?userId=' + user.id + '&verificationString=' + verificationString 318 const url = WEBSERVER.URL + '/verify-account/email?userId=' + user.id + '&verificationString=' + verificationString
313 await Emailer.Instance.addVerifyEmailJob(user.email, url) 319 await Emailer.Instance.addVerifyEmailJob(user.email, url)
314 return 320 return
315} 321}
316 322
317async function askSendVerifyUserEmail (req: express.Request, res: express.Response, next: express.NextFunction) { 323async function askSendVerifyUserEmail (req: express.Request, res: express.Response) {
318 const user = res.locals.user as UserModel 324 const user = res.locals.user
319 325
320 await sendVerifyUserEmail(user) 326 await sendVerifyUserEmail(user)
321 327
322 return res.status(204).end() 328 return res.status(204).end()
323} 329}
324 330
325async function verifyUserEmail (req: express.Request, res: express.Response, next: express.NextFunction) { 331async function verifyUserEmail (req: express.Request, res: express.Response) {
326 const user = res.locals.user as UserModel 332 const user = res.locals.user
327 user.emailVerified = true 333 user.emailVerified = true
328 334
329 await user.save() 335 await user.save()
@@ -331,7 +337,7 @@ async function verifyUserEmail (req: express.Request, res: express.Response, nex
331 return res.status(204).end() 337 return res.status(204).end()
332} 338}
333 339
334function success (req: express.Request, res: express.Response, next: express.NextFunction) { 340function success (req: express.Request, res: express.Response) {
335 res.end() 341 res.end()
336} 342}
337 343
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index 94a2b8732..ddb239e7b 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -2,7 +2,7 @@ import * as express from 'express'
2import 'multer' 2import 'multer'
3import { UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../../shared' 3import { UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../../shared'
4import { getFormattedObjects } from '../../../helpers/utils' 4import { getFormattedObjects } from '../../../helpers/utils'
5import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../../initializers' 5import { MIMETYPES } from '../../../initializers/constants'
6import { sendUpdateActor } from '../../../lib/activitypub/send' 6import { sendUpdateActor } from '../../../lib/activitypub/send'
7import { 7import {
8 asyncMiddleware, 8 asyncMiddleware,
@@ -26,6 +26,8 @@ import { updateActorAvatarFile } from '../../../lib/avatar'
26import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' 26import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
27import { VideoImportModel } from '../../../models/video/video-import' 27import { VideoImportModel } from '../../../models/video/video-import'
28import { AccountModel } from '../../../models/account/account' 28import { AccountModel } from '../../../models/account/account'
29import { CONFIG } from '../../../initializers/config'
30import { sequelizeTypescript } from '../../../initializers/database'
29 31
30const auditLogger = auditLoggerFactory('users-me') 32const auditLogger = auditLoggerFactory('users-me')
31 33
@@ -93,8 +95,8 @@ export {
93 95
94// --------------------------------------------------------------------------- 96// ---------------------------------------------------------------------------
95 97
96async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 98async function getUserVideos (req: express.Request, res: express.Response) {
97 const user = res.locals.oauth.token.User as UserModel 99 const user = res.locals.oauth.token.User
98 const resultList = await VideoModel.listUserVideosForApi( 100 const resultList = await VideoModel.listUserVideosForApi(
99 user.Account.id, 101 user.Account.id,
100 req.query.start as number, 102 req.query.start as number,
@@ -111,8 +113,8 @@ async function getUserVideos (req: express.Request, res: express.Response, next:
111 return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes })) 113 return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes }))
112} 114}
113 115
114async function getUserVideoImports (req: express.Request, res: express.Response, next: express.NextFunction) { 116async function getUserVideoImports (req: express.Request, res: express.Response) {
115 const user = res.locals.oauth.token.User as UserModel 117 const user = res.locals.oauth.token.User
116 const resultList = await VideoImportModel.listUserVideoImportsForApi( 118 const resultList = await VideoImportModel.listUserVideoImportsForApi(
117 user.id, 119 user.id,
118 req.query.start as number, 120 req.query.start as number,
@@ -123,14 +125,14 @@ async function getUserVideoImports (req: express.Request, res: express.Response,
123 return res.json(getFormattedObjects(resultList.data, resultList.total)) 125 return res.json(getFormattedObjects(resultList.data, resultList.total))
124} 126}
125 127
126async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { 128async function getUserInformation (req: express.Request, res: express.Response) {
127 // We did not load channels in res.locals.user 129 // We did not load channels in res.locals.user
128 const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) 130 const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
129 131
130 return res.json(user.toFormattedJSON()) 132 return res.json(user.toFormattedJSON({}))
131} 133}
132 134
133async function getUserVideoQuotaUsed (req: express.Request, res: express.Response, next: express.NextFunction) { 135async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) {
134 // We did not load channels in res.locals.user 136 // We did not load channels in res.locals.user
135 const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) 137 const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
136 const videoQuotaUsed = await UserModel.getOriginalVideoFileTotalFromUser(user) 138 const videoQuotaUsed = await UserModel.getOriginalVideoFileTotalFromUser(user)
@@ -143,7 +145,7 @@ async function getUserVideoQuotaUsed (req: express.Request, res: express.Respons
143 return res.json(data) 145 return res.json(data)
144} 146}
145 147
146async function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) { 148async function getUserVideoRating (req: express.Request, res: express.Response) {
147 const videoId = res.locals.video.id 149 const videoId = res.locals.video.id
148 const accountId = +res.locals.oauth.token.User.Account.id 150 const accountId = +res.locals.oauth.token.User.Account.id
149 151
@@ -158,20 +160,20 @@ async function getUserVideoRating (req: express.Request, res: express.Response,
158} 160}
159 161
160async function deleteMe (req: express.Request, res: express.Response) { 162async function deleteMe (req: express.Request, res: express.Response) {
161 const user: UserModel = res.locals.oauth.token.User 163 const user = res.locals.oauth.token.User
162 164
163 await user.destroy() 165 await user.destroy()
164 166
165 auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON())) 167 auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})))
166 168
167 return res.sendStatus(204) 169 return res.sendStatus(204)
168} 170}
169 171
170async function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) { 172async function updateMe (req: express.Request, res: express.Response) {
171 const body: UserUpdateMe = req.body 173 const body: UserUpdateMe = req.body
172 174
173 const user: UserModel = res.locals.oauth.token.user 175 const user = res.locals.oauth.token.user
174 const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) 176 const oldUserAuditView = new UserAuditView(user.toFormattedJSON({}))
175 177
176 if (body.password !== undefined) user.password = body.password 178 if (body.password !== undefined) user.password = body.password
177 if (body.email !== undefined) user.email = body.email 179 if (body.email !== undefined) user.email = body.email
@@ -191,7 +193,7 @@ async function updateMe (req: express.Request, res: express.Response, next: expr
191 193
192 await sendUpdateActor(userAccount, t) 194 await sendUpdateActor(userAccount, t)
193 195
194 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) 196 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})), oldUserAuditView)
195 }) 197 })
196 198
197 return res.sendStatus(204) 199 return res.sendStatus(204)
@@ -199,14 +201,14 @@ async function updateMe (req: express.Request, res: express.Response, next: expr
199 201
200async function updateMyAvatar (req: express.Request, res: express.Response) { 202async function updateMyAvatar (req: express.Request, res: express.Response) {
201 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] 203 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ]
202 const user: UserModel = res.locals.oauth.token.user 204 const user = res.locals.oauth.token.user
203 const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) 205 const oldUserAuditView = new UserAuditView(user.toFormattedJSON({}))
204 206
205 const userAccount = await AccountModel.load(user.Account.id) 207 const userAccount = await AccountModel.load(user.Account.id)
206 208
207 const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount) 209 const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount)
208 210
209 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) 211 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})), oldUserAuditView)
210 212
211 return res.json({ avatar: avatar.toFormattedJSON() }) 213 return res.json({ avatar: avatar.toFormattedJSON() })
212} 214}
diff --git a/server/controllers/api/users/my-blocklist.ts b/server/controllers/api/users/my-blocklist.ts
index 9575eab46..713c16022 100644
--- a/server/controllers/api/users/my-blocklist.ts
+++ b/server/controllers/api/users/my-blocklist.ts
@@ -17,12 +17,9 @@ import {
17 serversBlocklistSortValidator, 17 serversBlocklistSortValidator,
18 unblockServerByAccountValidator 18 unblockServerByAccountValidator
19} from '../../../middlewares/validators' 19} from '../../../middlewares/validators'
20import { UserModel } from '../../../models/account/user'
21import { AccountModel } from '../../../models/account/account'
22import { AccountBlocklistModel } from '../../../models/account/account-blocklist' 20import { AccountBlocklistModel } from '../../../models/account/account-blocklist'
23import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist' 21import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist'
24import { ServerBlocklistModel } from '../../../models/server/server-blocklist' 22import { ServerBlocklistModel } from '../../../models/server/server-blocklist'
25import { ServerModel } from '../../../models/server/server'
26 23
27const myBlocklistRouter = express.Router() 24const myBlocklistRouter = express.Router()
28 25
@@ -75,7 +72,7 @@ export {
75// --------------------------------------------------------------------------- 72// ---------------------------------------------------------------------------
76 73
77async function listBlockedAccounts (req: express.Request, res: express.Response) { 74async function listBlockedAccounts (req: express.Request, res: express.Response) {
78 const user: UserModel = res.locals.oauth.token.User 75 const user = res.locals.oauth.token.User
79 76
80 const resultList = await AccountBlocklistModel.listForApi(user.Account.id, req.query.start, req.query.count, req.query.sort) 77 const resultList = await AccountBlocklistModel.listForApi(user.Account.id, req.query.start, req.query.count, req.query.sort)
81 78
@@ -83,8 +80,8 @@ async function listBlockedAccounts (req: express.Request, res: express.Response)
83} 80}
84 81
85async function blockAccount (req: express.Request, res: express.Response) { 82async function blockAccount (req: express.Request, res: express.Response) {
86 const user: UserModel = res.locals.oauth.token.User 83 const user = res.locals.oauth.token.User
87 const accountToBlock: AccountModel = res.locals.account 84 const accountToBlock = res.locals.account
88 85
89 await addAccountInBlocklist(user.Account.id, accountToBlock.id) 86 await addAccountInBlocklist(user.Account.id, accountToBlock.id)
90 87
@@ -92,7 +89,7 @@ async function blockAccount (req: express.Request, res: express.Response) {
92} 89}
93 90
94async function unblockAccount (req: express.Request, res: express.Response) { 91async function unblockAccount (req: express.Request, res: express.Response) {
95 const accountBlock: AccountBlocklistModel = res.locals.accountBlock 92 const accountBlock = res.locals.accountBlock
96 93
97 await removeAccountFromBlocklist(accountBlock) 94 await removeAccountFromBlocklist(accountBlock)
98 95
@@ -100,7 +97,7 @@ async function unblockAccount (req: express.Request, res: express.Response) {
100} 97}
101 98
102async function listBlockedServers (req: express.Request, res: express.Response) { 99async function listBlockedServers (req: express.Request, res: express.Response) {
103 const user: UserModel = res.locals.oauth.token.User 100 const user = res.locals.oauth.token.User
104 101
105 const resultList = await ServerBlocklistModel.listForApi(user.Account.id, req.query.start, req.query.count, req.query.sort) 102 const resultList = await ServerBlocklistModel.listForApi(user.Account.id, req.query.start, req.query.count, req.query.sort)
106 103
@@ -108,8 +105,8 @@ async function listBlockedServers (req: express.Request, res: express.Response)
108} 105}
109 106
110async function blockServer (req: express.Request, res: express.Response) { 107async function blockServer (req: express.Request, res: express.Response) {
111 const user: UserModel = res.locals.oauth.token.User 108 const user = res.locals.oauth.token.User
112 const serverToBlock: ServerModel = res.locals.server 109 const serverToBlock = res.locals.server
113 110
114 await addServerInBlocklist(user.Account.id, serverToBlock.id) 111 await addServerInBlocklist(user.Account.id, serverToBlock.id)
115 112
@@ -117,7 +114,7 @@ async function blockServer (req: express.Request, res: express.Response) {
117} 114}
118 115
119async function unblockServer (req: express.Request, res: express.Response) { 116async function unblockServer (req: express.Request, res: express.Response) {
120 const serverBlock: ServerBlocklistModel = res.locals.serverBlock 117 const serverBlock = res.locals.serverBlock
121 118
122 await removeServerFromBlocklist(serverBlock) 119 await removeServerFromBlocklist(serverBlock)
123 120
diff --git a/server/controllers/api/users/my-history.ts b/server/controllers/api/users/my-history.ts
index 6cd782c47..7025c0ff1 100644
--- a/server/controllers/api/users/my-history.ts
+++ b/server/controllers/api/users/my-history.ts
@@ -36,7 +36,7 @@ export {
36// --------------------------------------------------------------------------- 36// ---------------------------------------------------------------------------
37 37
38async function listMyVideosHistory (req: express.Request, res: express.Response) { 38async function listMyVideosHistory (req: express.Request, res: express.Response) {
39 const user: UserModel = res.locals.oauth.token.User 39 const user = res.locals.oauth.token.User
40 40
41 const resultList = await UserVideoHistoryModel.listForApi(user, req.query.start, req.query.count) 41 const resultList = await UserVideoHistoryModel.listForApi(user, req.query.start, req.query.count)
42 42
@@ -44,11 +44,11 @@ async function listMyVideosHistory (req: express.Request, res: express.Response)
44} 44}
45 45
46async function removeUserHistory (req: express.Request, res: express.Response) { 46async function removeUserHistory (req: express.Request, res: express.Response) {
47 const user: UserModel = res.locals.oauth.token.User 47 const user = res.locals.oauth.token.User
48 const beforeDate = req.body.beforeDate || null 48 const beforeDate = req.body.beforeDate || null
49 49
50 await sequelizeTypescript.transaction(t => { 50 await sequelizeTypescript.transaction(t => {
51 return UserVideoHistoryModel.removeHistoryBefore(user, beforeDate, t) 51 return UserVideoHistoryModel.removeUserHistoryBefore(user, beforeDate, t)
52 }) 52 })
53 53
54 // Do not send the delete to other instances, we delete OUR copy of this video abuse 54 // Do not send the delete to other instances, we delete OUR copy of this video abuse
diff --git a/server/controllers/api/users/my-notifications.ts b/server/controllers/api/users/my-notifications.ts
index 76cf97587..f146284e4 100644
--- a/server/controllers/api/users/my-notifications.ts
+++ b/server/controllers/api/users/my-notifications.ts
@@ -9,7 +9,6 @@ import {
9 setDefaultSort, 9 setDefaultSort,
10 userNotificationsSortValidator 10 userNotificationsSortValidator
11} from '../../../middlewares' 11} from '../../../middlewares'
12import { UserModel } from '../../../models/account/user'
13import { getFormattedObjects } from '../../../helpers/utils' 12import { getFormattedObjects } from '../../../helpers/utils'
14import { UserNotificationModel } from '../../../models/account/user-notification' 13import { UserNotificationModel } from '../../../models/account/user-notification'
15import { meRouter } from './me' 14import { meRouter } from './me'
@@ -57,8 +56,8 @@ export {
57// --------------------------------------------------------------------------- 56// ---------------------------------------------------------------------------
58 57
59async function updateNotificationSettings (req: express.Request, res: express.Response) { 58async function updateNotificationSettings (req: express.Request, res: express.Response) {
60 const user: UserModel = res.locals.oauth.token.User 59 const user = res.locals.oauth.token.User
61 const body = req.body 60 const body = req.body as UserNotificationSetting
62 61
63 const query = { 62 const query = {
64 where: { 63 where: {
@@ -70,12 +69,14 @@ async function updateNotificationSettings (req: express.Request, res: express.Re
70 newVideoFromSubscription: body.newVideoFromSubscription, 69 newVideoFromSubscription: body.newVideoFromSubscription,
71 newCommentOnMyVideo: body.newCommentOnMyVideo, 70 newCommentOnMyVideo: body.newCommentOnMyVideo,
72 videoAbuseAsModerator: body.videoAbuseAsModerator, 71 videoAbuseAsModerator: body.videoAbuseAsModerator,
72 videoAutoBlacklistAsModerator: body.videoAutoBlacklistAsModerator,
73 blacklistOnMyVideo: body.blacklistOnMyVideo, 73 blacklistOnMyVideo: body.blacklistOnMyVideo,
74 myVideoPublished: body.myVideoPublished, 74 myVideoPublished: body.myVideoPublished,
75 myVideoImportFinished: body.myVideoImportFinished, 75 myVideoImportFinished: body.myVideoImportFinished,
76 newFollow: body.newFollow, 76 newFollow: body.newFollow,
77 newUserRegistration: body.newUserRegistration, 77 newUserRegistration: body.newUserRegistration,
78 commentMention: body.commentMention 78 commentMention: body.commentMention,
79 newInstanceFollower: body.newInstanceFollower
79 } 80 }
80 81
81 await UserNotificationSettingModel.update(values, query) 82 await UserNotificationSettingModel.update(values, query)
@@ -84,7 +85,7 @@ async function updateNotificationSettings (req: express.Request, res: express.Re
84} 85}
85 86
86async function listUserNotifications (req: express.Request, res: express.Response) { 87async function listUserNotifications (req: express.Request, res: express.Response) {
87 const user: UserModel = res.locals.oauth.token.User 88 const user = res.locals.oauth.token.User
88 89
89 const resultList = await UserNotificationModel.listForApi(user.id, req.query.start, req.query.count, req.query.sort, req.query.unread) 90 const resultList = await UserNotificationModel.listForApi(user.id, req.query.start, req.query.count, req.query.sort, req.query.unread)
90 91
@@ -92,7 +93,7 @@ async function listUserNotifications (req: express.Request, res: express.Respons
92} 93}
93 94
94async function markAsReadUserNotifications (req: express.Request, res: express.Response) { 95async function markAsReadUserNotifications (req: express.Request, res: express.Response) {
95 const user: UserModel = res.locals.oauth.token.User 96 const user = res.locals.oauth.token.User
96 97
97 await UserNotificationModel.markAsRead(user.id, req.body.ids) 98 await UserNotificationModel.markAsRead(user.id, req.body.ids)
98 99
@@ -100,7 +101,7 @@ async function markAsReadUserNotifications (req: express.Request, res: express.R
100} 101}
101 102
102async function markAsReadAllUserNotifications (req: express.Request, res: express.Response) { 103async function markAsReadAllUserNotifications (req: express.Request, res: express.Response) {
103 const user: UserModel = res.locals.oauth.token.User 104 const user = res.locals.oauth.token.User
104 105
105 await UserNotificationModel.markAllAsRead(user.id) 106 await UserNotificationModel.markAllAsRead(user.id)
106 107
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts
index accca6d52..c52df3154 100644
--- a/server/controllers/api/users/my-subscriptions.ts
+++ b/server/controllers/api/users/my-subscriptions.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import 'multer' 2import 'multer'
3import { getFormattedObjects } from '../../../helpers/utils' 3import { getFormattedObjects } from '../../../helpers/utils'
4import { CONFIG, sequelizeTypescript } from '../../../initializers' 4import { WEBSERVER } from '../../../initializers/constants'
5import { 5import {
6 asyncMiddleware, 6 asyncMiddleware,
7 asyncRetryTransactionMiddleware, 7 asyncRetryTransactionMiddleware,
@@ -14,13 +14,13 @@ import {
14 userSubscriptionGetValidator 14 userSubscriptionGetValidator
15} from '../../../middlewares' 15} from '../../../middlewares'
16import { areSubscriptionsExistValidator, userSubscriptionsSortValidator, videosSortValidator } from '../../../middlewares/validators' 16import { areSubscriptionsExistValidator, userSubscriptionsSortValidator, videosSortValidator } from '../../../middlewares/validators'
17import { UserModel } from '../../../models/account/user'
18import { VideoModel } from '../../../models/video/video' 17import { VideoModel } from '../../../models/video/video'
19import { buildNSFWFilter } from '../../../helpers/express-utils' 18import { buildNSFWFilter } from '../../../helpers/express-utils'
20import { VideoFilter } from '../../../../shared/models/videos/video-query.type' 19import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
21import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 20import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
22import { JobQueue } from '../../../lib/job-queue' 21import { JobQueue } from '../../../lib/job-queue'
23import { logger } from '../../../helpers/logger' 22import { logger } from '../../../helpers/logger'
23import { sequelizeTypescript } from '../../../initializers/database'
24 24
25const mySubscriptionsRouter = express.Router() 25const mySubscriptionsRouter = express.Router()
26 26
@@ -77,11 +77,11 @@ export {
77 77
78async function areSubscriptionsExist (req: express.Request, res: express.Response) { 78async function areSubscriptionsExist (req: express.Request, res: express.Response) {
79 const uris = req.query.uris as string[] 79 const uris = req.query.uris as string[]
80 const user = res.locals.oauth.token.User as UserModel 80 const user = res.locals.oauth.token.User
81 81
82 const handles = uris.map(u => { 82 const handles = uris.map(u => {
83 let [ name, host ] = u.split('@') 83 let [ name, host ] = u.split('@')
84 if (host === CONFIG.WEBSERVER.HOST) host = null 84 if (host === WEBSERVER.HOST) host = null
85 85
86 return { name, host, uri: u } 86 return { name, host, uri: u }
87 }) 87 })
@@ -107,7 +107,7 @@ async function areSubscriptionsExist (req: express.Request, res: express.Respons
107} 107}
108 108
109async function addUserSubscription (req: express.Request, res: express.Response) { 109async function addUserSubscription (req: express.Request, res: express.Response) {
110 const user = res.locals.oauth.token.User as UserModel 110 const user = res.locals.oauth.token.User
111 const [ name, host ] = req.body.uri.split('@') 111 const [ name, host ] = req.body.uri.split('@')
112 112
113 const payload = { 113 const payload = {
@@ -123,13 +123,13 @@ async function addUserSubscription (req: express.Request, res: express.Response)
123} 123}
124 124
125function getUserSubscription (req: express.Request, res: express.Response) { 125function getUserSubscription (req: express.Request, res: express.Response) {
126 const subscription: ActorFollowModel = res.locals.subscription 126 const subscription = res.locals.subscription
127 127
128 return res.json(subscription.ActorFollowing.VideoChannel.toFormattedJSON()) 128 return res.json(subscription.ActorFollowing.VideoChannel.toFormattedJSON())
129} 129}
130 130
131async function deleteUserSubscription (req: express.Request, res: express.Response) { 131async function deleteUserSubscription (req: express.Request, res: express.Response) {
132 const subscription: ActorFollowModel = res.locals.subscription 132 const subscription = res.locals.subscription
133 133
134 await sequelizeTypescript.transaction(async t => { 134 await sequelizeTypescript.transaction(async t => {
135 return subscription.destroy({ transaction: t }) 135 return subscription.destroy({ transaction: t })
@@ -139,7 +139,7 @@ async function deleteUserSubscription (req: express.Request, res: express.Respon
139} 139}
140 140
141async function getUserSubscriptions (req: express.Request, res: express.Response) { 141async function getUserSubscriptions (req: express.Request, res: express.Response) {
142 const user = res.locals.oauth.token.User as UserModel 142 const user = res.locals.oauth.token.User
143 const actorId = user.Account.Actor.id 143 const actorId = user.Account.Actor.id
144 144
145 const resultList = await ActorFollowModel.listSubscriptionsForApi(actorId, req.query.start, req.query.count, req.query.sort) 145 const resultList = await ActorFollowModel.listSubscriptionsForApi(actorId, req.query.start, req.query.count, req.query.sort)
@@ -147,8 +147,8 @@ async function getUserSubscriptions (req: express.Request, res: express.Response
147 return res.json(getFormattedObjects(resultList.data, resultList.total)) 147 return res.json(getFormattedObjects(resultList.data, resultList.total))
148} 148}
149 149
150async function getUserSubscriptionVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 150async function getUserSubscriptionVideos (req: express.Request, res: express.Response) {
151 const user = res.locals.oauth.token.User as UserModel 151 const user = res.locals.oauth.token.User
152 const resultList = await VideoModel.listForApi({ 152 const resultList = await VideoModel.listForApi({
153 start: req.query.start, 153 start: req.query.start,
154 count: req.query.count, 154 count: req.query.count,
diff --git a/server/controllers/api/users/my-video-playlists.ts b/server/controllers/api/users/my-video-playlists.ts
new file mode 100644
index 000000000..15e92f4f3
--- /dev/null
+++ b/server/controllers/api/users/my-video-playlists.ts
@@ -0,0 +1,46 @@
1import * as express from 'express'
2import { asyncMiddleware, authenticate } from '../../../middlewares'
3import { doVideosInPlaylistExistValidator } from '../../../middlewares/validators/videos/video-playlists'
4import { VideoPlaylistModel } from '../../../models/video/video-playlist'
5import { VideoExistInPlaylist } from '../../../../shared/models/videos/playlist/video-exist-in-playlist.model'
6
7const myVideoPlaylistsRouter = express.Router()
8
9myVideoPlaylistsRouter.get('/me/video-playlists/videos-exist',
10 authenticate,
11 doVideosInPlaylistExistValidator,
12 asyncMiddleware(doVideosInPlaylistExist)
13)
14
15// ---------------------------------------------------------------------------
16
17export {
18 myVideoPlaylistsRouter
19}
20
21// ---------------------------------------------------------------------------
22
23async function doVideosInPlaylistExist (req: express.Request, res: express.Response) {
24 const videoIds = req.query.videoIds.map(i => parseInt(i + '', 10))
25 const user = res.locals.oauth.token.User
26
27 const results = await VideoPlaylistModel.listPlaylistIdsOf(user.Account.id, videoIds)
28
29 const existObject: VideoExistInPlaylist = {}
30
31 for (const videoId of videoIds) {
32 existObject[videoId] = []
33 }
34
35 for (const result of results) {
36 for (const element of result.VideoPlaylistElements) {
37 existObject[element.videoId].push({
38 playlistId: result.id,
39 startTimestamp: element.startTimestamp,
40 stopTimestamp: element.stopTimestamp
41 })
42 }
43 }
44
45 return res.json(existObject)
46}
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index db7602139..3d6dbfe70 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -12,7 +12,8 @@ import {
12 videoChannelsAddValidator, 12 videoChannelsAddValidator,
13 videoChannelsRemoveValidator, 13 videoChannelsRemoveValidator,
14 videoChannelsSortValidator, 14 videoChannelsSortValidator,
15 videoChannelsUpdateValidator 15 videoChannelsUpdateValidator,
16 videoPlaylistsSortValidator
16} from '../../middlewares' 17} from '../../middlewares'
17import { VideoChannelModel } from '../../models/video/video-channel' 18import { VideoChannelModel } from '../../models/video/video-channel'
18import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators' 19import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators'
@@ -22,15 +23,18 @@ import { createVideoChannel } from '../../lib/video-channel'
22import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' 23import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
23import { setAsyncActorKeys } from '../../lib/activitypub' 24import { setAsyncActorKeys } from '../../lib/activitypub'
24import { AccountModel } from '../../models/account/account' 25import { AccountModel } from '../../models/account/account'
25import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../initializers' 26import { MIMETYPES } from '../../initializers/constants'
26import { logger } from '../../helpers/logger' 27import { logger } from '../../helpers/logger'
27import { VideoModel } from '../../models/video/video' 28import { VideoModel } from '../../models/video/video'
28import { updateAvatarValidator } from '../../middlewares/validators/avatar' 29import { updateAvatarValidator } from '../../middlewares/validators/avatar'
29import { updateActorAvatarFile } from '../../lib/avatar' 30import { updateActorAvatarFile } from '../../lib/avatar'
30import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' 31import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
31import { resetSequelizeInstance } from '../../helpers/database-utils' 32import { resetSequelizeInstance } from '../../helpers/database-utils'
32import { UserModel } from '../../models/account/user'
33import { JobQueue } from '../../lib/job-queue' 33import { JobQueue } from '../../lib/job-queue'
34import { VideoPlaylistModel } from '../../models/video/video-playlist'
35import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists'
36import { CONFIG } from '../../initializers/config'
37import { sequelizeTypescript } from '../../initializers/database'
34 38
35const auditLogger = auditLoggerFactory('channels') 39const auditLogger = auditLoggerFactory('channels')
36const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) 40const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
@@ -77,6 +81,16 @@ videoChannelRouter.get('/:nameWithHost',
77 asyncMiddleware(getVideoChannel) 81 asyncMiddleware(getVideoChannel)
78) 82)
79 83
84videoChannelRouter.get('/:nameWithHost/video-playlists',
85 asyncMiddleware(videoChannelsNameWithHostValidator),
86 paginationValidator,
87 videoPlaylistsSortValidator,
88 setDefaultSort,
89 setDefaultPagination,
90 commonVideoPlaylistFiltersValidator,
91 asyncMiddleware(listVideoChannelPlaylists)
92)
93
80videoChannelRouter.get('/:nameWithHost/videos', 94videoChannelRouter.get('/:nameWithHost/videos',
81 asyncMiddleware(videoChannelsNameWithHostValidator), 95 asyncMiddleware(videoChannelsNameWithHostValidator),
82 paginationValidator, 96 paginationValidator,
@@ -96,16 +110,16 @@ export {
96 110
97// --------------------------------------------------------------------------- 111// ---------------------------------------------------------------------------
98 112
99async function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) { 113async function listVideoChannels (req: express.Request, res: express.Response) {
100 const serverActor = await getServerActor() 114 const serverActor = await getServerActor()
101 const resultList = await VideoChannelModel.listForApi(serverActor.id, req.query.start, req.query.count, req.query.sort) 115 const resultList = await VideoChannelModel.listForApi(serverActor.id, req.query.start, req.query.count, req.query.sort)
102 116
103 return res.json(getFormattedObjects(resultList.data, resultList.total)) 117 return res.json(getFormattedObjects(resultList.data, resultList.total))
104} 118}
105 119
106async function updateVideoChannelAvatar (req: express.Request, res: express.Response, next: express.NextFunction) { 120async function updateVideoChannelAvatar (req: express.Request, res: express.Response) {
107 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] 121 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ]
108 const videoChannel = res.locals.videoChannel as VideoChannelModel 122 const videoChannel = res.locals.videoChannel
109 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON()) 123 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON())
110 124
111 const avatar = await updateActorAvatarFile(avatarPhysicalFile, videoChannel) 125 const avatar = await updateActorAvatarFile(avatarPhysicalFile, videoChannel)
@@ -123,7 +137,7 @@ async function addVideoChannel (req: express.Request, res: express.Response) {
123 const videoChannelInfo: VideoChannelCreate = req.body 137 const videoChannelInfo: VideoChannelCreate = req.body
124 138
125 const videoChannelCreated: VideoChannelModel = await sequelizeTypescript.transaction(async t => { 139 const videoChannelCreated: VideoChannelModel = await sequelizeTypescript.transaction(async t => {
126 const account = await AccountModel.load((res.locals.oauth.token.User as UserModel).Account.id, t) 140 const account = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
127 141
128 return createVideoChannel(videoChannelInfo, account, t) 142 return createVideoChannel(videoChannelInfo, account, t)
129 }) 143 })
@@ -143,7 +157,7 @@ async function addVideoChannel (req: express.Request, res: express.Response) {
143} 157}
144 158
145async function updateVideoChannel (req: express.Request, res: express.Response) { 159async function updateVideoChannel (req: express.Request, res: express.Response) {
146 const videoChannelInstance = res.locals.videoChannel as VideoChannelModel 160 const videoChannelInstance = res.locals.videoChannel
147 const videoChannelFieldsSave = videoChannelInstance.toJSON() 161 const videoChannelFieldsSave = videoChannelInstance.toJSON()
148 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannelInstance.toFormattedJSON()) 162 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannelInstance.toFormattedJSON())
149 const videoChannelInfoToUpdate = req.body as VideoChannelUpdate 163 const videoChannelInfoToUpdate = req.body as VideoChannelUpdate
@@ -183,9 +197,11 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
183} 197}
184 198
185async function removeVideoChannel (req: express.Request, res: express.Response) { 199async function removeVideoChannel (req: express.Request, res: express.Response) {
186 const videoChannelInstance: VideoChannelModel = res.locals.videoChannel 200 const videoChannelInstance = res.locals.videoChannel
187 201
188 await sequelizeTypescript.transaction(async t => { 202 await sequelizeTypescript.transaction(async t => {
203 await VideoPlaylistModel.resetPlaylistsOfChannel(videoChannelInstance.id, t)
204
189 await videoChannelInstance.destroy({ transaction: t }) 205 await videoChannelInstance.destroy({ transaction: t })
190 206
191 auditLogger.delete(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelInstance.toFormattedJSON())) 207 auditLogger.delete(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelInstance.toFormattedJSON()))
@@ -195,7 +211,7 @@ async function removeVideoChannel (req: express.Request, res: express.Response)
195 return res.type('json').status(204).end() 211 return res.type('json').status(204).end()
196} 212}
197 213
198async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) { 214async function getVideoChannel (req: express.Request, res: express.Response) {
199 const videoChannelWithVideos = await VideoChannelModel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id) 215 const videoChannelWithVideos = await VideoChannelModel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id)
200 216
201 if (videoChannelWithVideos.isOutdated()) { 217 if (videoChannelWithVideos.isOutdated()) {
@@ -206,8 +222,23 @@ async function getVideoChannel (req: express.Request, res: express.Response, nex
206 return res.json(videoChannelWithVideos.toFormattedJSON()) 222 return res.json(videoChannelWithVideos.toFormattedJSON())
207} 223}
208 224
209async function listVideoChannelVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 225async function listVideoChannelPlaylists (req: express.Request, res: express.Response) {
210 const videoChannelInstance: VideoChannelModel = res.locals.videoChannel 226 const serverActor = await getServerActor()
227
228 const resultList = await VideoPlaylistModel.listForApi({
229 followerActorId: serverActor.id,
230 start: req.query.start,
231 count: req.query.count,
232 sort: req.query.sort,
233 videoChannelId: res.locals.videoChannel.id,
234 type: req.query.playlistType
235 })
236
237 return res.json(getFormattedObjects(resultList.data, resultList.total))
238}
239
240async function listVideoChannelVideos (req: express.Request, res: express.Response) {
241 const videoChannelInstance = res.locals.videoChannel
211 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined 242 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
212 243
213 const resultList = await VideoModel.listForApi({ 244 const resultList = await VideoModel.listForApi({
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts
new file mode 100644
index 000000000..a17136401
--- /dev/null
+++ b/server/controllers/api/video-playlist.ts
@@ -0,0 +1,445 @@
1import * as express from 'express'
2import { getFormattedObjects, getServerActor } from '../../helpers/utils'
3import {
4 asyncMiddleware,
5 asyncRetryTransactionMiddleware,
6 authenticate,
7 commonVideosFiltersValidator,
8 optionalAuthenticate,
9 paginationValidator,
10 setDefaultPagination,
11 setDefaultSort
12} from '../../middlewares'
13import { videoPlaylistsSortValidator } from '../../middlewares/validators'
14import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
15import { MIMETYPES, VIDEO_PLAYLIST_PRIVACIES } from '../../initializers/constants'
16import { logger } from '../../helpers/logger'
17import { resetSequelizeInstance } from '../../helpers/database-utils'
18import { VideoPlaylistModel } from '../../models/video/video-playlist'
19import {
20 commonVideoPlaylistFiltersValidator,
21 videoPlaylistsAddValidator,
22 videoPlaylistsAddVideoValidator,
23 videoPlaylistsDeleteValidator,
24 videoPlaylistsGetValidator,
25 videoPlaylistsReorderVideosValidator,
26 videoPlaylistsUpdateOrRemoveVideoValidator,
27 videoPlaylistsUpdateValidator
28} from '../../middlewares/validators/videos/video-playlists'
29import { VideoPlaylistCreate } from '../../../shared/models/videos/playlist/video-playlist-create.model'
30import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
31import { join } from 'path'
32import { sendCreateVideoPlaylist, sendDeleteVideoPlaylist, sendUpdateVideoPlaylist } from '../../lib/activitypub/send'
33import { getVideoPlaylistActivityPubUrl, getVideoPlaylistElementActivityPubUrl } from '../../lib/activitypub/url'
34import { VideoPlaylistUpdate } from '../../../shared/models/videos/playlist/video-playlist-update.model'
35import { VideoModel } from '../../models/video/video'
36import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element'
37import { VideoPlaylistElementCreate } from '../../../shared/models/videos/playlist/video-playlist-element-create.model'
38import { VideoPlaylistElementUpdate } from '../../../shared/models/videos/playlist/video-playlist-element-update.model'
39import { AccountModel } from '../../models/account/account'
40import { VideoPlaylistReorder } from '../../../shared/models/videos/playlist/video-playlist-reorder.model'
41import { JobQueue } from '../../lib/job-queue'
42import { CONFIG } from '../../initializers/config'
43import { sequelizeTypescript } from '../../initializers/database'
44import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail'
45
46const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR })
47
48const videoPlaylistRouter = express.Router()
49
50videoPlaylistRouter.get('/privacies', listVideoPlaylistPrivacies)
51
52videoPlaylistRouter.get('/',
53 paginationValidator,
54 videoPlaylistsSortValidator,
55 setDefaultSort,
56 setDefaultPagination,
57 commonVideoPlaylistFiltersValidator,
58 asyncMiddleware(listVideoPlaylists)
59)
60
61videoPlaylistRouter.get('/:playlistId',
62 asyncMiddleware(videoPlaylistsGetValidator),
63 getVideoPlaylist
64)
65
66videoPlaylistRouter.post('/',
67 authenticate,
68 reqThumbnailFile,
69 asyncMiddleware(videoPlaylistsAddValidator),
70 asyncRetryTransactionMiddleware(addVideoPlaylist)
71)
72
73videoPlaylistRouter.put('/:playlistId',
74 authenticate,
75 reqThumbnailFile,
76 asyncMiddleware(videoPlaylistsUpdateValidator),
77 asyncRetryTransactionMiddleware(updateVideoPlaylist)
78)
79
80videoPlaylistRouter.delete('/:playlistId',
81 authenticate,
82 asyncMiddleware(videoPlaylistsDeleteValidator),
83 asyncRetryTransactionMiddleware(removeVideoPlaylist)
84)
85
86videoPlaylistRouter.get('/:playlistId/videos',
87 asyncMiddleware(videoPlaylistsGetValidator),
88 paginationValidator,
89 setDefaultPagination,
90 optionalAuthenticate,
91 commonVideosFiltersValidator,
92 asyncMiddleware(getVideoPlaylistVideos)
93)
94
95videoPlaylistRouter.post('/:playlistId/videos',
96 authenticate,
97 asyncMiddleware(videoPlaylistsAddVideoValidator),
98 asyncRetryTransactionMiddleware(addVideoInPlaylist)
99)
100
101videoPlaylistRouter.post('/:playlistId/videos/reorder',
102 authenticate,
103 asyncMiddleware(videoPlaylistsReorderVideosValidator),
104 asyncRetryTransactionMiddleware(reorderVideosPlaylist)
105)
106
107videoPlaylistRouter.put('/:playlistId/videos/:videoId',
108 authenticate,
109 asyncMiddleware(videoPlaylistsUpdateOrRemoveVideoValidator),
110 asyncRetryTransactionMiddleware(updateVideoPlaylistElement)
111)
112
113videoPlaylistRouter.delete('/:playlistId/videos/:videoId',
114 authenticate,
115 asyncMiddleware(videoPlaylistsUpdateOrRemoveVideoValidator),
116 asyncRetryTransactionMiddleware(removeVideoFromPlaylist)
117)
118
119// ---------------------------------------------------------------------------
120
121export {
122 videoPlaylistRouter
123}
124
125// ---------------------------------------------------------------------------
126
127function listVideoPlaylistPrivacies (req: express.Request, res: express.Response) {
128 res.json(VIDEO_PLAYLIST_PRIVACIES)
129}
130
131async function listVideoPlaylists (req: express.Request, res: express.Response) {
132 const serverActor = await getServerActor()
133 const resultList = await VideoPlaylistModel.listForApi({
134 followerActorId: serverActor.id,
135 start: req.query.start,
136 count: req.query.count,
137 sort: req.query.sort,
138 type: req.query.type
139 })
140
141 return res.json(getFormattedObjects(resultList.data, resultList.total))
142}
143
144function getVideoPlaylist (req: express.Request, res: express.Response) {
145 const videoPlaylist = res.locals.videoPlaylist
146
147 if (videoPlaylist.isOutdated()) {
148 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video-playlist', url: videoPlaylist.url } })
149 .catch(err => logger.error('Cannot create AP refresher job for playlist %s.', videoPlaylist.url, { err }))
150 }
151
152 return res.json(videoPlaylist.toFormattedJSON())
153}
154
155async function addVideoPlaylist (req: express.Request, res: express.Response) {
156 const videoPlaylistInfo: VideoPlaylistCreate = req.body
157 const user = res.locals.oauth.token.User
158
159 const videoPlaylist = new VideoPlaylistModel({
160 name: videoPlaylistInfo.displayName,
161 description: videoPlaylistInfo.description,
162 privacy: videoPlaylistInfo.privacy || VideoPlaylistPrivacy.PRIVATE,
163 ownerAccountId: user.Account.id
164 })
165
166 videoPlaylist.url = getVideoPlaylistActivityPubUrl(videoPlaylist) // We use the UUID, so set the URL after building the object
167
168 if (videoPlaylistInfo.videoChannelId) {
169 const videoChannel = res.locals.videoChannel
170
171 videoPlaylist.videoChannelId = videoChannel.id
172 videoPlaylist.VideoChannel = videoChannel
173 }
174
175 const thumbnailField = req.files['thumbnailfile']
176 const thumbnailModel = thumbnailField
177 ? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist)
178 : undefined
179
180 const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => {
181 const videoPlaylistCreated = await videoPlaylist.save({ transaction: t })
182
183 if (thumbnailModel) await videoPlaylistCreated.setAndSaveThumbnail(thumbnailModel, t)
184
185 // We need more attributes for the federation
186 videoPlaylistCreated.OwnerAccount = await AccountModel.load(user.Account.id, t)
187 await sendCreateVideoPlaylist(videoPlaylistCreated, t)
188
189 return videoPlaylistCreated
190 })
191
192 logger.info('Video playlist with uuid %s created.', videoPlaylist.uuid)
193
194 return res.json({
195 videoPlaylist: {
196 id: videoPlaylistCreated.id,
197 uuid: videoPlaylistCreated.uuid
198 }
199 }).end()
200}
201
202async function updateVideoPlaylist (req: express.Request, res: express.Response) {
203 const videoPlaylistInstance = res.locals.videoPlaylist
204 const videoPlaylistFieldsSave = videoPlaylistInstance.toJSON()
205 const videoPlaylistInfoToUpdate = req.body as VideoPlaylistUpdate
206 const wasPrivatePlaylist = videoPlaylistInstance.privacy === VideoPlaylistPrivacy.PRIVATE
207
208 const thumbnailField = req.files['thumbnailfile']
209 const thumbnailModel = thumbnailField
210 ? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylistInstance)
211 : undefined
212
213 try {
214 await sequelizeTypescript.transaction(async t => {
215 const sequelizeOptions = {
216 transaction: t
217 }
218
219 if (videoPlaylistInfoToUpdate.videoChannelId !== undefined) {
220 if (videoPlaylistInfoToUpdate.videoChannelId === null) {
221 videoPlaylistInstance.videoChannelId = null
222 } else {
223 const videoChannel = res.locals.videoChannel
224
225 videoPlaylistInstance.videoChannelId = videoChannel.id
226 videoPlaylistInstance.VideoChannel = videoChannel
227 }
228 }
229
230 if (videoPlaylistInfoToUpdate.displayName !== undefined) videoPlaylistInstance.name = videoPlaylistInfoToUpdate.displayName
231 if (videoPlaylistInfoToUpdate.description !== undefined) videoPlaylistInstance.description = videoPlaylistInfoToUpdate.description
232
233 if (videoPlaylistInfoToUpdate.privacy !== undefined) {
234 videoPlaylistInstance.privacy = parseInt(videoPlaylistInfoToUpdate.privacy.toString(), 10)
235 }
236
237 const playlistUpdated = await videoPlaylistInstance.save(sequelizeOptions)
238
239 if (thumbnailModel) await playlistUpdated.setAndSaveThumbnail(thumbnailModel, t)
240
241 const isNewPlaylist = wasPrivatePlaylist && playlistUpdated.privacy !== VideoPlaylistPrivacy.PRIVATE
242
243 if (isNewPlaylist) {
244 await sendCreateVideoPlaylist(playlistUpdated, t)
245 } else {
246 await sendUpdateVideoPlaylist(playlistUpdated, t)
247 }
248
249 logger.info('Video playlist %s updated.', videoPlaylistInstance.uuid)
250
251 return playlistUpdated
252 })
253 } catch (err) {
254 logger.debug('Cannot update the video playlist.', { err })
255
256 // Force fields we want to update
257 // If the transaction is retried, sequelize will think the object has not changed
258 // So it will skip the SQL request, even if the last one was ROLLBACKed!
259 resetSequelizeInstance(videoPlaylistInstance, videoPlaylistFieldsSave)
260
261 throw err
262 }
263
264 return res.type('json').status(204).end()
265}
266
267async function removeVideoPlaylist (req: express.Request, res: express.Response) {
268 const videoPlaylistInstance = res.locals.videoPlaylist
269
270 await sequelizeTypescript.transaction(async t => {
271 await videoPlaylistInstance.destroy({ transaction: t })
272
273 await sendDeleteVideoPlaylist(videoPlaylistInstance, t)
274
275 logger.info('Video playlist %s deleted.', videoPlaylistInstance.uuid)
276 })
277
278 return res.type('json').status(204).end()
279}
280
281async function addVideoInPlaylist (req: express.Request, res: express.Response) {
282 const body: VideoPlaylistElementCreate = req.body
283 const videoPlaylist = res.locals.videoPlaylist
284 const video = res.locals.video
285
286 const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => {
287 const position = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id, t)
288
289 const playlistElement = await VideoPlaylistElementModel.create({
290 url: getVideoPlaylistElementActivityPubUrl(videoPlaylist, video),
291 position,
292 startTimestamp: body.startTimestamp || null,
293 stopTimestamp: body.stopTimestamp || null,
294 videoPlaylistId: videoPlaylist.id,
295 videoId: video.id
296 }, { transaction: t })
297
298 videoPlaylist.changed('updatedAt', true)
299 await videoPlaylist.save({ transaction: t })
300
301 await sendUpdateVideoPlaylist(videoPlaylist, t)
302
303 return playlistElement
304 })
305
306 // If the user did not set a thumbnail, automatically take the video thumbnail
307 if (videoPlaylist.hasThumbnail() === false) {
308 logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url)
309
310 const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getMiniature().filename)
311 const thumbnailModel = await createPlaylistMiniatureFromExisting(inputPath, videoPlaylist, true)
312
313 thumbnailModel.videoPlaylistId = videoPlaylist.id
314
315 await thumbnailModel.save()
316 }
317
318 logger.info('Video added in playlist %s at position %d.', videoPlaylist.uuid, playlistElement.position)
319
320 return res.json({
321 videoPlaylistElement: {
322 id: playlistElement.id
323 }
324 }).end()
325}
326
327async function updateVideoPlaylistElement (req: express.Request, res: express.Response) {
328 const body: VideoPlaylistElementUpdate = req.body
329 const videoPlaylist = res.locals.videoPlaylist
330 const videoPlaylistElement = res.locals.videoPlaylistElement
331
332 const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => {
333 if (body.startTimestamp !== undefined) videoPlaylistElement.startTimestamp = body.startTimestamp
334 if (body.stopTimestamp !== undefined) videoPlaylistElement.stopTimestamp = body.stopTimestamp
335
336 const element = await videoPlaylistElement.save({ transaction: t })
337
338 videoPlaylist.changed('updatedAt', true)
339 await videoPlaylist.save({ transaction: t })
340
341 await sendUpdateVideoPlaylist(videoPlaylist, t)
342
343 return element
344 })
345
346 logger.info('Element of position %d of playlist %s updated.', playlistElement.position, videoPlaylist.uuid)
347
348 return res.type('json').status(204).end()
349}
350
351async function removeVideoFromPlaylist (req: express.Request, res: express.Response) {
352 const videoPlaylistElement = res.locals.videoPlaylistElement
353 const videoPlaylist = res.locals.videoPlaylist
354 const positionToDelete = videoPlaylistElement.position
355
356 await sequelizeTypescript.transaction(async t => {
357 await videoPlaylistElement.destroy({ transaction: t })
358
359 // Decrease position of the next elements
360 await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, positionToDelete, null, -1, t)
361
362 videoPlaylist.changed('updatedAt', true)
363 await videoPlaylist.save({ transaction: t })
364
365 await sendUpdateVideoPlaylist(videoPlaylist, t)
366
367 logger.info('Video playlist element %d of playlist %s deleted.', videoPlaylistElement.position, videoPlaylist.uuid)
368 })
369
370 return res.type('json').status(204).end()
371}
372
373async function reorderVideosPlaylist (req: express.Request, res: express.Response) {
374 const videoPlaylist = res.locals.videoPlaylist
375 const body: VideoPlaylistReorder = req.body
376
377 const start: number = body.startPosition
378 const insertAfter: number = body.insertAfterPosition
379 const reorderLength: number = body.reorderLength || 1
380
381 if (start === insertAfter) {
382 return res.status(204).end()
383 }
384
385 // Example: if we reorder position 2 and insert after position 5 (so at position 6): # 1 2 3 4 5 6 7 8 9
386 // * increase position when position > 5 # 1 2 3 4 5 7 8 9 10
387 // * update position 2 -> position 6 # 1 3 4 5 6 7 8 9 10
388 // * decrease position when position position > 2 # 1 2 3 4 5 6 7 8 9
389 await sequelizeTypescript.transaction(async t => {
390 const newPosition = insertAfter + 1
391
392 // Add space after the position when we want to insert our reordered elements (increase)
393 await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, newPosition, null, reorderLength, t)
394
395 let oldPosition = start
396
397 // We incremented the position of the elements we want to reorder
398 if (start >= newPosition) oldPosition += reorderLength
399
400 const endOldPosition = oldPosition + reorderLength - 1
401 // Insert our reordered elements in their place (update)
402 await VideoPlaylistElementModel.reassignPositionOf(videoPlaylist.id, oldPosition, endOldPosition, newPosition, t)
403
404 // Decrease positions of elements after the old position of our ordered elements (decrease)
405 await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, oldPosition, null, -reorderLength, t)
406
407 videoPlaylist.changed('updatedAt', true)
408 await videoPlaylist.save({ transaction: t })
409
410 await sendUpdateVideoPlaylist(videoPlaylist, t)
411 })
412
413 logger.info(
414 'Reordered playlist %s (inserted after %d elements %d - %d).',
415 videoPlaylist.uuid, insertAfter, start, start + reorderLength - 1
416 )
417
418 return res.type('json').status(204).end()
419}
420
421async function getVideoPlaylistVideos (req: express.Request, res: express.Response) {
422 const videoPlaylistInstance = res.locals.videoPlaylist
423 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
424
425 const resultList = await VideoModel.listForApi({
426 followerActorId,
427 start: req.query.start,
428 count: req.query.count,
429 sort: 'VideoPlaylistElements.position',
430 includeLocalVideos: true,
431 categoryOneOf: req.query.categoryOneOf,
432 licenceOneOf: req.query.licenceOneOf,
433 languageOneOf: req.query.languageOneOf,
434 tagsOneOf: req.query.tagsOneOf,
435 tagsAllOf: req.query.tagsAllOf,
436 filter: req.query.filter,
437 nsfw: buildNSFWFilter(res, req.query.nsfw),
438 withFiles: false,
439 videoPlaylistId: videoPlaylistInstance.id,
440 user: res.locals.oauth ? res.locals.oauth.token.User : undefined
441 })
442
443 const additionalAttributes = { playlistInfo: true }
444 return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes }))
445}
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts
index fe0a95cd5..ca70230a2 100644
--- a/server/controllers/api/videos/abuse.ts
+++ b/server/controllers/api/videos/abuse.ts
@@ -3,7 +3,6 @@ import { UserRight, VideoAbuseCreate, VideoAbuseState } from '../../../../shared
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { getFormattedObjects } from '../../../helpers/utils' 4import { getFormattedObjects } from '../../../helpers/utils'
5import { sequelizeTypescript } from '../../../initializers' 5import { sequelizeTypescript } from '../../../initializers'
6import { sendVideoAbuse } from '../../../lib/activitypub/send'
7import { 6import {
8 asyncMiddleware, 7 asyncMiddleware,
9 asyncRetryTransactionMiddleware, 8 asyncRetryTransactionMiddleware,
@@ -18,11 +17,10 @@ import {
18 videoAbuseUpdateValidator 17 videoAbuseUpdateValidator
19} from '../../../middlewares' 18} from '../../../middlewares'
20import { AccountModel } from '../../../models/account/account' 19import { AccountModel } from '../../../models/account/account'
21import { VideoModel } from '../../../models/video/video'
22import { VideoAbuseModel } from '../../../models/video/video-abuse' 20import { VideoAbuseModel } from '../../../models/video/video-abuse'
23import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger' 21import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger'
24import { UserModel } from '../../../models/account/user'
25import { Notifier } from '../../../lib/notifier' 22import { Notifier } from '../../../lib/notifier'
23import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag'
26 24
27const auditLogger = auditLoggerFactory('abuse') 25const auditLogger = auditLoggerFactory('abuse')
28const abuseVideoRouter = express.Router() 26const abuseVideoRouter = express.Router()
@@ -69,7 +67,7 @@ async function listVideoAbuses (req: express.Request, res: express.Response) {
69} 67}
70 68
71async function updateVideoAbuse (req: express.Request, res: express.Response) { 69async function updateVideoAbuse (req: express.Request, res: express.Response) {
72 const videoAbuse: VideoAbuseModel = res.locals.videoAbuse 70 const videoAbuse = res.locals.videoAbuse
73 71
74 if (req.body.moderationComment !== undefined) videoAbuse.moderationComment = req.body.moderationComment 72 if (req.body.moderationComment !== undefined) videoAbuse.moderationComment = req.body.moderationComment
75 if (req.body.state !== undefined) videoAbuse.state = req.body.state 73 if (req.body.state !== undefined) videoAbuse.state = req.body.state
@@ -84,7 +82,7 @@ async function updateVideoAbuse (req: express.Request, res: express.Response) {
84} 82}
85 83
86async function deleteVideoAbuse (req: express.Request, res: express.Response) { 84async function deleteVideoAbuse (req: express.Request, res: express.Response) {
87 const videoAbuse: VideoAbuseModel = res.locals.videoAbuse 85 const videoAbuse = res.locals.videoAbuse
88 86
89 await sequelizeTypescript.transaction(t => { 87 await sequelizeTypescript.transaction(t => {
90 return videoAbuse.destroy({ transaction: t }) 88 return videoAbuse.destroy({ transaction: t })
@@ -96,11 +94,11 @@ async function deleteVideoAbuse (req: express.Request, res: express.Response) {
96} 94}
97 95
98async function reportVideoAbuse (req: express.Request, res: express.Response) { 96async function reportVideoAbuse (req: express.Request, res: express.Response) {
99 const videoInstance = res.locals.video as VideoModel 97 const videoInstance = res.locals.video
100 const body: VideoAbuseCreate = req.body 98 const body: VideoAbuseCreate = req.body
101 99
102 const videoAbuse: VideoAbuseModel = await sequelizeTypescript.transaction(async t => { 100 const videoAbuse: VideoAbuseModel = await sequelizeTypescript.transaction(async t => {
103 const reporterAccount = await AccountModel.load((res.locals.oauth.token.User as UserModel).Account.id, t) 101 const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
104 102
105 const abuseToCreate = { 103 const abuseToCreate = {
106 reporterAccountId: reporterAccount.id, 104 reporterAccountId: reporterAccount.id,
diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts
index 43b0516e7..27dcfb761 100644
--- a/server/controllers/api/videos/blacklist.ts
+++ b/server/controllers/api/videos/blacklist.ts
@@ -1,5 +1,5 @@
1import * as express from 'express' 1import * as express from 'express'
2import { VideoBlacklist, UserRight, VideoBlacklistCreate } from '../../../../shared' 2import { VideoBlacklist, UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared'
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { getFormattedObjects } from '../../../helpers/utils' 4import { getFormattedObjects } from '../../../helpers/utils'
5import { 5import {
@@ -12,13 +12,13 @@ import {
12 setDefaultPagination, 12 setDefaultPagination,
13 videosBlacklistAddValidator, 13 videosBlacklistAddValidator,
14 videosBlacklistRemoveValidator, 14 videosBlacklistRemoveValidator,
15 videosBlacklistUpdateValidator 15 videosBlacklistUpdateValidator,
16 videosBlacklistFiltersValidator
16} from '../../../middlewares' 17} from '../../../middlewares'
17import { VideoBlacklistModel } from '../../../models/video/video-blacklist' 18import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
18import { sequelizeTypescript } from '../../../initializers' 19import { sequelizeTypescript } from '../../../initializers'
19import { Notifier } from '../../../lib/notifier' 20import { Notifier } from '../../../lib/notifier'
20import { VideoModel } from '../../../models/video/video' 21import { sendDeleteVideo } from '../../../lib/activitypub/send'
21import { sendCreateVideo, sendDeleteVideo, sendUpdateVideo } from '../../../lib/activitypub/send'
22import { federateVideoIfNeeded } from '../../../lib/activitypub' 22import { federateVideoIfNeeded } from '../../../lib/activitypub'
23 23
24const blacklistRouter = express.Router() 24const blacklistRouter = express.Router()
@@ -37,6 +37,7 @@ blacklistRouter.get('/blacklist',
37 blacklistSortValidator, 37 blacklistSortValidator,
38 setBlacklistSort, 38 setBlacklistSort,
39 setDefaultPagination, 39 setDefaultPagination,
40 videosBlacklistFiltersValidator,
40 asyncMiddleware(listBlacklist) 41 asyncMiddleware(listBlacklist)
41) 42)
42 43
@@ -69,7 +70,8 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response)
69 const toCreate = { 70 const toCreate = {
70 videoId: videoInstance.id, 71 videoId: videoInstance.id,
71 unfederated: body.unfederate === true, 72 unfederated: body.unfederate === true,
72 reason: body.reason 73 reason: body.reason,
74 type: VideoBlacklistType.MANUAL
73 } 75 }
74 76
75 const blacklist = await VideoBlacklistModel.create(toCreate) 77 const blacklist = await VideoBlacklistModel.create(toCreate)
@@ -87,7 +89,7 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response)
87} 89}
88 90
89async function updateVideoBlacklistController (req: express.Request, res: express.Response) { 91async function updateVideoBlacklistController (req: express.Request, res: express.Response) {
90 const videoBlacklist = res.locals.videoBlacklist as VideoBlacklistModel 92 const videoBlacklist = res.locals.videoBlacklist
91 93
92 if (req.body.reason !== undefined) videoBlacklist.reason = req.body.reason 94 if (req.body.reason !== undefined) videoBlacklist.reason = req.body.reason
93 95
@@ -99,27 +101,39 @@ async function updateVideoBlacklistController (req: express.Request, res: expres
99} 101}
100 102
101async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) { 103async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
102 const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort) 104 const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.type)
103 105
104 return res.json(getFormattedObjects<VideoBlacklist, VideoBlacklistModel>(resultList.data, resultList.total)) 106 return res.json(getFormattedObjects<VideoBlacklist, VideoBlacklistModel>(resultList.data, resultList.total))
105} 107}
106 108
107async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) { 109async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) {
108 const videoBlacklist = res.locals.videoBlacklist as VideoBlacklistModel 110 const videoBlacklist = res.locals.videoBlacklist
109 const video: VideoModel = res.locals.video 111 const video = res.locals.video
110 112
111 await sequelizeTypescript.transaction(async t => { 113 const videoBlacklistType = await sequelizeTypescript.transaction(async t => {
112 const unfederated = videoBlacklist.unfederated 114 const unfederated = videoBlacklist.unfederated
115 const videoBlacklistType = videoBlacklist.type
116
113 await videoBlacklist.destroy({ transaction: t }) 117 await videoBlacklist.destroy({ transaction: t })
114 118
115 // Re federate the video 119 // Re federate the video
116 if (unfederated === true) { 120 if (unfederated === true) {
117 await federateVideoIfNeeded(video, true, t) 121 await federateVideoIfNeeded(video, true, t)
118 } 122 }
123
124 return videoBlacklistType
119 }) 125 })
120 126
121 Notifier.Instance.notifyOnVideoUnblacklist(video) 127 Notifier.Instance.notifyOnVideoUnblacklist(video)
122 128
129 if (videoBlacklistType === VideoBlacklistType.AUTO_BEFORE_PUBLISHED) {
130 Notifier.Instance.notifyOnVideoPublishedAfterRemovedFromAutoBlacklist(video)
131
132 // Delete on object so new video notifications will send
133 delete video.VideoBlacklist
134 Notifier.Instance.notifyOnNewVideo(video)
135 }
136
123 logger.info('Video %s removed from blacklist.', res.locals.video.uuid) 137 logger.info('Video %s removed from blacklist.', res.locals.video.uuid)
124 138
125 return res.type('json').status(204).end() 139 return res.type('json').status(204).end()
diff --git a/server/controllers/api/videos/captions.ts b/server/controllers/api/videos/captions.ts
index 9b3661368..44c255232 100644
--- a/server/controllers/api/videos/captions.ts
+++ b/server/controllers/api/videos/captions.ts
@@ -2,13 +2,14 @@ import * as express from 'express'
2import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' 2import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares'
3import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators' 3import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators'
4import { createReqFiles } from '../../../helpers/express-utils' 4import { createReqFiles } from '../../../helpers/express-utils'
5import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../../initializers' 5import { MIMETYPES } from '../../../initializers/constants'
6import { getFormattedObjects } from '../../../helpers/utils' 6import { getFormattedObjects } from '../../../helpers/utils'
7import { VideoCaptionModel } from '../../../models/video/video-caption' 7import { VideoCaptionModel } from '../../../models/video/video-caption'
8import { VideoModel } from '../../../models/video/video'
9import { logger } from '../../../helpers/logger' 8import { logger } from '../../../helpers/logger'
10import { federateVideoIfNeeded } from '../../../lib/activitypub' 9import { federateVideoIfNeeded } from '../../../lib/activitypub'
11import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' 10import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
11import { CONFIG } from '../../../initializers/config'
12import { sequelizeTypescript } from '../../../initializers/database'
12 13
13const reqVideoCaptionAdd = createReqFiles( 14const reqVideoCaptionAdd = createReqFiles(
14 [ 'captionfile' ], 15 [ 'captionfile' ],
@@ -52,7 +53,7 @@ async function listVideoCaptions (req: express.Request, res: express.Response) {
52 53
53async function addVideoCaption (req: express.Request, res: express.Response) { 54async function addVideoCaption (req: express.Request, res: express.Response) {
54 const videoCaptionPhysicalFile = req.files['captionfile'][0] 55 const videoCaptionPhysicalFile = req.files['captionfile'][0]
55 const video = res.locals.video as VideoModel 56 const video = res.locals.video
56 57
57 const videoCaption = new VideoCaptionModel({ 58 const videoCaption = new VideoCaptionModel({
58 videoId: video.id, 59 videoId: video.id,
@@ -74,8 +75,8 @@ async function addVideoCaption (req: express.Request, res: express.Response) {
74} 75}
75 76
76async function deleteVideoCaption (req: express.Request, res: express.Response) { 77async function deleteVideoCaption (req: express.Request, res: express.Response) {
77 const video = res.locals.video as VideoModel 78 const video = res.locals.video
78 const videoCaption = res.locals.videoCaption as VideoCaptionModel 79 const videoCaption = res.locals.videoCaption
79 80
80 await sequelizeTypescript.transaction(async t => { 81 await sequelizeTypescript.transaction(async t => {
81 await videoCaption.destroy({ transaction: t }) 82 await videoCaption.destroy({ transaction: t })
diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts
index 70c1148ba..176ee8bd4 100644
--- a/server/controllers/api/videos/comment.ts
+++ b/server/controllers/api/videos/comment.ts
@@ -8,7 +8,8 @@ import { buildFormattedCommentTree, createVideoComment } from '../../../lib/vide
8import { 8import {
9 asyncMiddleware, 9 asyncMiddleware,
10 asyncRetryTransactionMiddleware, 10 asyncRetryTransactionMiddleware,
11 authenticate, optionalAuthenticate, 11 authenticate,
12 optionalAuthenticate,
12 paginationValidator, 13 paginationValidator,
13 setDefaultPagination, 14 setDefaultPagination,
14 setDefaultSort 15 setDefaultSort
@@ -21,11 +22,9 @@ import {
21 removeVideoCommentValidator, 22 removeVideoCommentValidator,
22 videoCommentThreadsSortValidator 23 videoCommentThreadsSortValidator
23} from '../../../middlewares/validators' 24} from '../../../middlewares/validators'
24import { VideoModel } from '../../../models/video/video'
25import { VideoCommentModel } from '../../../models/video/video-comment' 25import { VideoCommentModel } from '../../../models/video/video-comment'
26import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../../helpers/audit-logger' 26import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../../helpers/audit-logger'
27import { AccountModel } from '../../../models/account/account' 27import { AccountModel } from '../../../models/account/account'
28import { UserModel } from '../../../models/account/user'
29import { Notifier } from '../../../lib/notifier' 28import { Notifier } from '../../../lib/notifier'
30 29
31const auditLogger = auditLoggerFactory('comments') 30const auditLogger = auditLoggerFactory('comments')
@@ -70,9 +69,9 @@ export {
70 69
71// --------------------------------------------------------------------------- 70// ---------------------------------------------------------------------------
72 71
73async function listVideoThreads (req: express.Request, res: express.Response, next: express.NextFunction) { 72async function listVideoThreads (req: express.Request, res: express.Response) {
74 const video = res.locals.video as VideoModel 73 const video = res.locals.video
75 const user: UserModel = res.locals.oauth ? res.locals.oauth.token.User : undefined 74 const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
76 75
77 let resultList: ResultList<VideoCommentModel> 76 let resultList: ResultList<VideoCommentModel>
78 77
@@ -88,9 +87,9 @@ async function listVideoThreads (req: express.Request, res: express.Response, ne
88 return res.json(getFormattedObjects(resultList.data, resultList.total)) 87 return res.json(getFormattedObjects(resultList.data, resultList.total))
89} 88}
90 89
91async function listVideoThreadComments (req: express.Request, res: express.Response, next: express.NextFunction) { 90async function listVideoThreadComments (req: express.Request, res: express.Response) {
92 const video = res.locals.video as VideoModel 91 const video = res.locals.video
93 const user: UserModel = res.locals.oauth ? res.locals.oauth.token.User : undefined 92 const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
94 93
95 let resultList: ResultList<VideoCommentModel> 94 let resultList: ResultList<VideoCommentModel>
96 95
@@ -110,7 +109,7 @@ async function addVideoCommentThread (req: express.Request, res: express.Respons
110 const videoCommentInfo: VideoCommentCreate = req.body 109 const videoCommentInfo: VideoCommentCreate = req.body
111 110
112 const comment = await sequelizeTypescript.transaction(async t => { 111 const comment = await sequelizeTypescript.transaction(async t => {
113 const account = await AccountModel.load((res.locals.oauth.token.User as UserModel).Account.id, t) 112 const account = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
114 113
115 return createVideoComment({ 114 return createVideoComment({
116 text: videoCommentInfo.text, 115 text: videoCommentInfo.text,
@@ -132,7 +131,7 @@ async function addVideoCommentReply (req: express.Request, res: express.Response
132 const videoCommentInfo: VideoCommentCreate = req.body 131 const videoCommentInfo: VideoCommentCreate = req.body
133 132
134 const comment = await sequelizeTypescript.transaction(async t => { 133 const comment = await sequelizeTypescript.transaction(async t => {
135 const account = await AccountModel.load((res.locals.oauth.token.User as UserModel).Account.id, t) 134 const account = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
136 135
137 return createVideoComment({ 136 return createVideoComment({
138 text: videoCommentInfo.text, 137 text: videoCommentInfo.text,
@@ -149,7 +148,7 @@ async function addVideoCommentReply (req: express.Request, res: express.Response
149} 148}
150 149
151async function removeVideoComment (req: express.Request, res: express.Response) { 150async function removeVideoComment (req: express.Request, res: express.Response) {
152 const videoCommentInstance: VideoCommentModel = res.locals.videoComment 151 const videoCommentInstance = res.locals.videoComment
153 152
154 await sequelizeTypescript.transaction(async t => { 153 await sequelizeTypescript.transaction(async t => {
155 await videoCommentInstance.destroy({ transaction: t }) 154 await videoCommentInstance.destroy({ transaction: t })
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts
index 98366cd82..bfb690906 100644
--- a/server/controllers/api/videos/import.ts
+++ b/server/controllers/api/videos/import.ts
@@ -3,7 +3,7 @@ import * as magnetUtil from 'magnet-uri'
3import 'multer' 3import 'multer'
4import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' 4import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger'
5import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' 5import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares'
6import { CONFIG, MIMETYPES, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE } from '../../../initializers' 6import { MIMETYPES } from '../../../initializers/constants'
7import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl' 7import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl'
8import { createReqFiles } from '../../../helpers/express-utils' 8import { createReqFiles } from '../../../helpers/express-utils'
9import { logger } from '../../../helpers/logger' 9import { logger } from '../../../helpers/logger'
@@ -13,15 +13,19 @@ import { getVideoActivityPubUrl } from '../../../lib/activitypub'
13import { TagModel } from '../../../models/video/tag' 13import { TagModel } from '../../../models/video/tag'
14import { VideoImportModel } from '../../../models/video/video-import' 14import { VideoImportModel } from '../../../models/video/video-import'
15import { JobQueue } from '../../../lib/job-queue/job-queue' 15import { JobQueue } from '../../../lib/job-queue/job-queue'
16import { processImage } from '../../../helpers/image-utils'
17import { join } from 'path' 16import { join } from 'path'
18import { isArray } from '../../../helpers/custom-validators/misc' 17import { isArray } from '../../../helpers/custom-validators/misc'
19import { FilteredModelAttributes } from 'sequelize-typescript/lib/models/Model'
20import { VideoChannelModel } from '../../../models/video/video-channel' 18import { VideoChannelModel } from '../../../models/video/video-channel'
21import * as Bluebird from 'bluebird' 19import * as Bluebird from 'bluebird'
22import * as parseTorrent from 'parse-torrent' 20import * as parseTorrent from 'parse-torrent'
23import { getSecureTorrentName } from '../../../helpers/utils' 21import { getSecureTorrentName } from '../../../helpers/utils'
24import { readFile, move } from 'fs-extra' 22import { move, readFile } from 'fs-extra'
23import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
24import { CONFIG } from '../../../initializers/config'
25import { sequelizeTypescript } from '../../../initializers/database'
26import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail'
27import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
28import { ThumbnailModel } from '../../../models/video/thumbnail'
25 29
26const auditLogger = auditLoggerFactory('video-imports') 30const auditLogger = auditLoggerFactory('video-imports')
27const videoImportsRouter = express.Router() 31const videoImportsRouter = express.Router()
@@ -87,8 +91,8 @@ async function addTorrentImport (req: express.Request, res: express.Response, to
87 91
88 const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName }) 92 const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName })
89 93
90 await processThumbnail(req, video) 94 const thumbnailModel = await processThumbnail(req, video)
91 await processPreview(req, video) 95 const previewModel = await processPreview(req, video)
92 96
93 const tags = body.tags || undefined 97 const tags = body.tags || undefined
94 const videoImportAttributes = { 98 const videoImportAttributes = {
@@ -97,7 +101,14 @@ async function addTorrentImport (req: express.Request, res: express.Response, to
97 state: VideoImportState.PENDING, 101 state: VideoImportState.PENDING,
98 userId: user.id 102 userId: user.id
99 } 103 }
100 const videoImport: VideoImportModel = await insertIntoDB(video, res.locals.videoChannel, tags, videoImportAttributes) 104 const videoImport = await insertIntoDB({
105 video,
106 thumbnailModel,
107 previewModel,
108 videoChannel: res.locals.videoChannel,
109 tags,
110 videoImportAttributes
111 })
101 112
102 // Create job to import the video 113 // Create job to import the video
103 const payload = { 114 const payload = {
@@ -130,8 +141,8 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
130 141
131 const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo) 142 const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo)
132 143
133 const downloadThumbnail = !await processThumbnail(req, video) 144 const thumbnailModel = await processThumbnail(req, video)
134 const downloadPreview = !await processPreview(req, video) 145 const previewModel = await processPreview(req, video)
135 146
136 const tags = body.tags || youtubeDLInfo.tags 147 const tags = body.tags || youtubeDLInfo.tags
137 const videoImportAttributes = { 148 const videoImportAttributes = {
@@ -139,15 +150,22 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
139 state: VideoImportState.PENDING, 150 state: VideoImportState.PENDING,
140 userId: user.id 151 userId: user.id
141 } 152 }
142 const videoImport: VideoImportModel = await insertIntoDB(video, res.locals.videoChannel, tags, videoImportAttributes) 153 const videoImport = await insertIntoDB({
154 video: video,
155 thumbnailModel,
156 previewModel,
157 videoChannel: res.locals.videoChannel,
158 tags,
159 videoImportAttributes
160 })
143 161
144 // Create job to import the video 162 // Create job to import the video
145 const payload = { 163 const payload = {
146 type: 'youtube-dl' as 'youtube-dl', 164 type: 'youtube-dl' as 'youtube-dl',
147 videoImportId: videoImport.id, 165 videoImportId: videoImport.id,
148 thumbnailUrl: youtubeDLInfo.thumbnailUrl, 166 thumbnailUrl: youtubeDLInfo.thumbnailUrl,
149 downloadThumbnail, 167 downloadThumbnail: !thumbnailModel,
150 downloadPreview 168 downloadPreview: !previewModel
151 } 169 }
152 await JobQueue.Instance.createJob({ type: 'video-import', payload }) 170 await JobQueue.Instance.createJob({ type: 'video-import', payload })
153 171
@@ -164,6 +182,7 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You
164 licence: body.licence || importData.licence, 182 licence: body.licence || importData.licence,
165 language: body.language || undefined, 183 language: body.language || undefined,
166 commentsEnabled: body.commentsEnabled || true, 184 commentsEnabled: body.commentsEnabled || true,
185 downloadEnabled: body.downloadEnabled || true,
167 waitTranscoding: body.waitTranscoding || false, 186 waitTranscoding: body.waitTranscoding || false,
168 state: VideoState.TO_IMPORT, 187 state: VideoState.TO_IMPORT,
169 nsfw: body.nsfw || importData.nsfw || false, 188 nsfw: body.nsfw || importData.nsfw || false,
@@ -171,7 +190,8 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You
171 support: body.support || null, 190 support: body.support || null,
172 privacy: body.privacy || VideoPrivacy.PRIVATE, 191 privacy: body.privacy || VideoPrivacy.PRIVATE,
173 duration: 0, // duration will be set by the import job 192 duration: 0, // duration will be set by the import job
174 channelId: channelId 193 channelId: channelId,
194 originallyPublishedAt: importData.originallyPublishedAt
175 } 195 }
176 const video = new VideoModel(videoData) 196 const video = new VideoModel(videoData)
177 video.url = getVideoActivityPubUrl(video) 197 video.url = getVideoActivityPubUrl(video)
@@ -183,32 +203,34 @@ async function processThumbnail (req: express.Request, video: VideoModel) {
183 const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined 203 const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined
184 if (thumbnailField) { 204 if (thumbnailField) {
185 const thumbnailPhysicalFile = thumbnailField[ 0 ] 205 const thumbnailPhysicalFile = thumbnailField[ 0 ]
186 await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()), THUMBNAILS_SIZE)
187 206
188 return true 207 return createVideoMiniatureFromExisting(thumbnailPhysicalFile.path, video, ThumbnailType.MINIATURE)
189 } 208 }
190 209
191 return false 210 return undefined
192} 211}
193 212
194async function processPreview (req: express.Request, video: VideoModel) { 213async function processPreview (req: express.Request, video: VideoModel) {
195 const previewField = req.files ? req.files['previewfile'] : undefined 214 const previewField = req.files ? req.files['previewfile'] : undefined
196 if (previewField) { 215 if (previewField) {
197 const previewPhysicalFile = previewField[0] 216 const previewPhysicalFile = previewField[0]
198 await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()), PREVIEWS_SIZE)
199 217
200 return true 218 return createVideoMiniatureFromExisting(previewPhysicalFile.path, video, ThumbnailType.PREVIEW)
201 } 219 }
202 220
203 return false 221 return undefined
204} 222}
205 223
206function insertIntoDB ( 224function insertIntoDB (parameters: {
207 video: VideoModel, 225 video: VideoModel,
226 thumbnailModel: ThumbnailModel,
227 previewModel: ThumbnailModel,
208 videoChannel: VideoChannelModel, 228 videoChannel: VideoChannelModel,
209 tags: string[], 229 tags: string[],
210 videoImportAttributes: FilteredModelAttributes<VideoImportModel> 230 videoImportAttributes: Partial<VideoImportModel>
211): Bluebird<VideoImportModel> { 231}): Bluebird<VideoImportModel> {
232 let { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes } = parameters
233
212 return sequelizeTypescript.transaction(async t => { 234 return sequelizeTypescript.transaction(async t => {
213 const sequelizeOptions = { transaction: t } 235 const sequelizeOptions = { transaction: t }
214 236
@@ -216,6 +238,11 @@ function insertIntoDB (
216 const videoCreated = await video.save(sequelizeOptions) 238 const videoCreated = await video.save(sequelizeOptions)
217 videoCreated.VideoChannel = videoChannel 239 videoCreated.VideoChannel = videoChannel
218 240
241 if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
242 if (previewModel) await videoCreated.addAndSaveThumbnail(previewModel, t)
243
244 await autoBlacklistVideoIfNeeded(video, videoChannel.Account.User, t)
245
219 // Set tags to the video 246 // Set tags to the video
220 if (tags) { 247 if (tags) {
221 const tagInstances = await TagModel.findOrCreateTags(tags, t) 248 const tagInstances = await TagModel.findOrCreateTags(tags, t)
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 2b2dfa7ca..1a18a8ae8 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -2,28 +2,17 @@ import * as express from 'express'
2import { extname, join } from 'path' 2import { extname, join } from 'path'
3import { VideoCreate, VideoPrivacy, VideoState, VideoUpdate } from '../../../../shared' 3import { VideoCreate, VideoPrivacy, VideoState, VideoUpdate } from '../../../../shared'
4import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' 4import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
5import { processImage } from '../../../helpers/image-utils'
6import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
7import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' 6import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
8import { getFormattedObjects, getServerActor } from '../../../helpers/utils' 7import { getFormattedObjects, getServerActor } from '../../../helpers/utils'
9import { 8import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
10 CONFIG, 9import { MIMETYPES, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers/constants'
11 MIMETYPES,
12 PREVIEWS_SIZE,
13 sequelizeTypescript,
14 THUMBNAILS_SIZE,
15 VIDEO_CATEGORIES,
16 VIDEO_LANGUAGES,
17 VIDEO_LICENCES,
18 VIDEO_PRIVACIES
19} from '../../../initializers'
20import { 10import {
21 changeVideoChannelShare, 11 changeVideoChannelShare,
22 federateVideoIfNeeded, 12 federateVideoIfNeeded,
23 fetchRemoteVideoDescription, 13 fetchRemoteVideoDescription,
24 getVideoActivityPubUrl 14 getVideoActivityPubUrl
25} from '../../../lib/activitypub' 15} from '../../../lib/activitypub'
26import { sendCreateView } from '../../../lib/activitypub/send'
27import { JobQueue } from '../../../lib/job-queue' 16import { JobQueue } from '../../../lib/job-queue'
28import { Redis } from '../../../lib/redis' 17import { Redis } from '../../../lib/redis'
29import { 18import {
@@ -37,6 +26,7 @@ import {
37 setDefaultPagination, 26 setDefaultPagination,
38 setDefaultSort, 27 setDefaultSort,
39 videosAddValidator, 28 videosAddValidator,
29 videosCustomGetValidator,
40 videosGetValidator, 30 videosGetValidator,
41 videosRemoveValidator, 31 videosRemoveValidator,
42 videosSortValidator, 32 videosSortValidator,
@@ -59,6 +49,11 @@ import { resetSequelizeInstance } from '../../../helpers/database-utils'
59import { move } from 'fs-extra' 49import { move } from 'fs-extra'
60import { watchingRouter } from './watching' 50import { watchingRouter } from './watching'
61import { Notifier } from '../../../lib/notifier' 51import { Notifier } from '../../../lib/notifier'
52import { sendView } from '../../../lib/activitypub/send/send-view'
53import { CONFIG } from '../../../initializers/config'
54import { sequelizeTypescript } from '../../../initializers/database'
55import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../../lib/thumbnail'
56import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
62 57
63const auditLogger = auditLoggerFactory('videos') 58const auditLogger = auditLoggerFactory('videos')
64const videosRouter = express.Router() 59const videosRouter = express.Router()
@@ -123,9 +118,9 @@ videosRouter.get('/:id/description',
123) 118)
124videosRouter.get('/:id', 119videosRouter.get('/:id',
125 optionalAuthenticate, 120 optionalAuthenticate,
126 asyncMiddleware(videosGetValidator), 121 asyncMiddleware(videosCustomGetValidator('only-video-with-rights')),
127 asyncMiddleware(checkVideoFollowConstraints), 122 asyncMiddleware(checkVideoFollowConstraints),
128 getVideo 123 asyncMiddleware(getVideo)
129) 124)
130videosRouter.post('/:id/views', 125videosRouter.post('/:id/views',
131 asyncMiddleware(videosGetValidator), 126 asyncMiddleware(videosGetValidator),
@@ -181,15 +176,18 @@ async function addVideo (req: express.Request, res: express.Response) {
181 licence: videoInfo.licence, 176 licence: videoInfo.licence,
182 language: videoInfo.language, 177 language: videoInfo.language,
183 commentsEnabled: videoInfo.commentsEnabled || false, 178 commentsEnabled: videoInfo.commentsEnabled || false,
179 downloadEnabled: videoInfo.downloadEnabled || true,
184 waitTranscoding: videoInfo.waitTranscoding || false, 180 waitTranscoding: videoInfo.waitTranscoding || false,
185 state: CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED, 181 state: CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED,
186 nsfw: videoInfo.nsfw || false, 182 nsfw: videoInfo.nsfw || false,
187 description: videoInfo.description, 183 description: videoInfo.description,
188 support: videoInfo.support, 184 support: videoInfo.support,
189 privacy: videoInfo.privacy, 185 privacy: videoInfo.privacy || VideoPrivacy.PRIVATE,
190 duration: videoPhysicalFile['duration'], // duration was added by a previous middleware 186 duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
191 channelId: res.locals.videoChannel.id 187 channelId: res.locals.videoChannel.id,
188 originallyPublishedAt: videoInfo.originallyPublishedAt
192 } 189 }
190
193 const video = new VideoModel(videoData) 191 const video = new VideoModel(videoData)
194 video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object 192 video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object
195 193
@@ -215,29 +213,27 @@ async function addVideo (req: express.Request, res: express.Response) {
215 213
216 // Process thumbnail or create it from the video 214 // Process thumbnail or create it from the video
217 const thumbnailField = req.files['thumbnailfile'] 215 const thumbnailField = req.files['thumbnailfile']
218 if (thumbnailField) { 216 const thumbnailModel = thumbnailField
219 const thumbnailPhysicalFile = thumbnailField[0] 217 ? await createVideoMiniatureFromExisting(thumbnailField[0].path, video, ThumbnailType.MINIATURE)
220 await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()), THUMBNAILS_SIZE) 218 : await generateVideoMiniature(video, videoFile, ThumbnailType.MINIATURE)
221 } else {
222 await video.createThumbnail(videoFile)
223 }
224 219
225 // Process preview or create it from the video 220 // Process preview or create it from the video
226 const previewField = req.files['previewfile'] 221 const previewField = req.files['previewfile']
227 if (previewField) { 222 const previewModel = previewField
228 const previewPhysicalFile = previewField[0] 223 ? await createVideoMiniatureFromExisting(previewField[0].path, video, ThumbnailType.PREVIEW)
229 await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()), PREVIEWS_SIZE) 224 : await generateVideoMiniature(video, videoFile, ThumbnailType.PREVIEW)
230 } else {
231 await video.createPreview(videoFile)
232 }
233 225
234 // Create the torrent file 226 // Create the torrent file
235 await video.createTorrentAndSetInfoHash(videoFile) 227 await video.createTorrentAndSetInfoHash(videoFile)
236 228
237 const videoCreated = await sequelizeTypescript.transaction(async t => { 229 const { videoCreated, videoWasAutoBlacklisted } = await sequelizeTypescript.transaction(async t => {
238 const sequelizeOptions = { transaction: t } 230 const sequelizeOptions = { transaction: t }
239 231
240 const videoCreated = await video.save(sequelizeOptions) 232 const videoCreated = await video.save(sequelizeOptions)
233
234 await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
235 await videoCreated.addAndSaveThumbnail(previewModel, t)
236
241 // Do not forget to add video channel information to the created video 237 // Do not forget to add video channel information to the created video
242 videoCreated.VideoChannel = res.locals.videoChannel 238 videoCreated.VideoChannel = res.locals.videoChannel
243 239
@@ -263,15 +259,23 @@ async function addVideo (req: express.Request, res: express.Response) {
263 }, { transaction: t }) 259 }, { transaction: t })
264 } 260 }
265 261
266 await federateVideoIfNeeded(video, true, t) 262 const videoWasAutoBlacklisted = await autoBlacklistVideoIfNeeded(video, res.locals.oauth.token.User, t)
263
264 if (!videoWasAutoBlacklisted) {
265 await federateVideoIfNeeded(video, true, t)
266 }
267 267
268 auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON())) 268 auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON()))
269 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid) 269 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid)
270 270
271 return videoCreated 271 return { videoCreated, videoWasAutoBlacklisted }
272 }) 272 })
273 273
274 Notifier.Instance.notifyOnNewVideo(videoCreated) 274 if (videoWasAutoBlacklisted) {
275 Notifier.Instance.notifyOnVideoAutoBlacklist(videoCreated)
276 } else {
277 Notifier.Instance.notifyOnNewVideo(videoCreated)
278 }
275 279
276 if (video.state === VideoState.TO_TRANSCODE) { 280 if (video.state === VideoState.TO_TRANSCODE) {
277 // Put uuid because we don't have id auto incremented for now 281 // Put uuid because we don't have id auto incremented for now
@@ -280,7 +284,7 @@ async function addVideo (req: express.Request, res: express.Response) {
280 isNewVideo: true 284 isNewVideo: true
281 } 285 }
282 286
283 await JobQueue.Instance.createJob({ type: 'video-file', payload: dataInput }) 287 await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput })
284 } 288 }
285 289
286 return res.json({ 290 return res.json({
@@ -292,7 +296,7 @@ async function addVideo (req: express.Request, res: express.Response) {
292} 296}
293 297
294async function updateVideo (req: express.Request, res: express.Response) { 298async function updateVideo (req: express.Request, res: express.Response) {
295 const videoInstance: VideoModel = res.locals.video 299 const videoInstance = res.locals.video
296 const videoFieldsSave = videoInstance.toJSON() 300 const videoFieldsSave = videoInstance.toJSON()
297 const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON()) 301 const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON())
298 const videoInfoToUpdate: VideoUpdate = req.body 302 const videoInfoToUpdate: VideoUpdate = req.body
@@ -300,16 +304,13 @@ async function updateVideo (req: express.Request, res: express.Response) {
300 const wasUnlistedVideo = videoInstance.privacy === VideoPrivacy.UNLISTED 304 const wasUnlistedVideo = videoInstance.privacy === VideoPrivacy.UNLISTED
301 305
302 // Process thumbnail or create it from the video 306 // Process thumbnail or create it from the video
303 if (req.files && req.files['thumbnailfile']) { 307 const thumbnailModel = req.files && req.files['thumbnailfile']
304 const thumbnailPhysicalFile = req.files['thumbnailfile'][0] 308 ? await createVideoMiniatureFromExisting(req.files['thumbnailfile'][0].path, videoInstance, ThumbnailType.MINIATURE)
305 await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, videoInstance.getThumbnailName()), THUMBNAILS_SIZE) 309 : undefined
306 }
307 310
308 // Process preview or create it from the video 311 const previewModel = req.files && req.files['previewfile']
309 if (req.files && req.files['previewfile']) { 312 ? await createVideoMiniatureFromExisting(req.files['previewfile'][0].path, videoInstance, ThumbnailType.PREVIEW)
310 const previewPhysicalFile = req.files['previewfile'][0] 313 : undefined
311 await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, videoInstance.getPreviewName()), PREVIEWS_SIZE)
312 }
313 314
314 try { 315 try {
315 const videoInstanceUpdated = await sequelizeTypescript.transaction(async t => { 316 const videoInstanceUpdated = await sequelizeTypescript.transaction(async t => {
@@ -325,17 +326,26 @@ async function updateVideo (req: express.Request, res: express.Response) {
325 if (videoInfoToUpdate.support !== undefined) videoInstance.set('support', videoInfoToUpdate.support) 326 if (videoInfoToUpdate.support !== undefined) videoInstance.set('support', videoInfoToUpdate.support)
326 if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description) 327 if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description)
327 if (videoInfoToUpdate.commentsEnabled !== undefined) videoInstance.set('commentsEnabled', videoInfoToUpdate.commentsEnabled) 328 if (videoInfoToUpdate.commentsEnabled !== undefined) videoInstance.set('commentsEnabled', videoInfoToUpdate.commentsEnabled)
329 if (videoInfoToUpdate.downloadEnabled !== undefined) videoInstance.set('downloadEnabled', videoInfoToUpdate.downloadEnabled)
330
331 if (videoInfoToUpdate.originallyPublishedAt !== undefined && videoInfoToUpdate.originallyPublishedAt !== null) {
332 videoInstance.originallyPublishedAt = new Date(videoInfoToUpdate.originallyPublishedAt)
333 }
334
328 if (videoInfoToUpdate.privacy !== undefined) { 335 if (videoInfoToUpdate.privacy !== undefined) {
329 const newPrivacy = parseInt(videoInfoToUpdate.privacy.toString(), 10) 336 const newPrivacy = parseInt(videoInfoToUpdate.privacy.toString(), 10)
330 videoInstance.set('privacy', newPrivacy) 337 videoInstance.privacy = newPrivacy
331 338
332 if (wasPrivateVideo === true && newPrivacy !== VideoPrivacy.PRIVATE) { 339 if (wasPrivateVideo === true && newPrivacy !== VideoPrivacy.PRIVATE) {
333 videoInstance.set('publishedAt', new Date()) 340 videoInstance.publishedAt = new Date()
334 } 341 }
335 } 342 }
336 343
337 const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) 344 const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)
338 345
346 if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t)
347 if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t)
348
339 // Video tags update? 349 // Video tags update?
340 if (videoInfoToUpdate.tags !== undefined) { 350 if (videoInfoToUpdate.tags !== undefined) {
341 const tagInstances = await TagModel.findOrCreateTags(videoInfoToUpdate.tags, t) 351 const tagInstances = await TagModel.findOrCreateTags(videoInfoToUpdate.tags, t)
@@ -395,22 +405,24 @@ async function updateVideo (req: express.Request, res: express.Response) {
395 return res.type('json').status(204).end() 405 return res.type('json').status(204).end()
396} 406}
397 407
398function getVideo (req: express.Request, res: express.Response) { 408async function getVideo (req: express.Request, res: express.Response) {
399 const videoInstance = res.locals.video 409 // We need more attributes
410 const userId: number = res.locals.oauth ? res.locals.oauth.token.User.id : null
411 const video = await VideoModel.loadForGetAPI(res.locals.video.id, undefined, userId)
400 412
401 if (videoInstance.isOutdated()) { 413 if (video.isOutdated()) {
402 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: videoInstance.url } }) 414 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: video.url } })
403 .catch(err => logger.error('Cannot create AP refresher job for video %s.', videoInstance.url, { err })) 415 .catch(err => logger.error('Cannot create AP refresher job for video %s.', video.url, { err }))
404 } 416 }
405 417
406 return res.json(videoInstance.toFormattedDetailsJSON()) 418 return res.json(video.toFormattedDetailsJSON())
407} 419}
408 420
409async function viewVideo (req: express.Request, res: express.Response) { 421async function viewVideo (req: express.Request, res: express.Response) {
410 const videoInstance = res.locals.video 422 const videoInstance = res.locals.video
411 423
412 const ip = req.ip 424 const ip = req.ip
413 const exists = await Redis.Instance.isVideoIPViewExists(ip, videoInstance.uuid) 425 const exists = await Redis.Instance.doesVideoIPViewExist(ip, videoInstance.uuid)
414 if (exists) { 426 if (exists) {
415 logger.debug('View for ip %s and video %s already exists.', ip, videoInstance.uuid) 427 logger.debug('View for ip %s and video %s already exists.', ip, videoInstance.uuid)
416 return res.status(204).end() 428 return res.status(204).end()
@@ -422,7 +434,7 @@ async function viewVideo (req: express.Request, res: express.Response) {
422 ]) 434 ])
423 435
424 const serverActor = await getServerActor() 436 const serverActor = await getServerActor()
425 await sendCreateView(serverActor, videoInstance, undefined) 437 await sendView(serverActor, videoInstance, undefined)
426 438
427 return res.status(204).end() 439 return res.status(204).end()
428} 440}
@@ -461,7 +473,7 @@ async function listVideos (req: express.Request, res: express.Response) {
461} 473}
462 474
463async function removeVideo (req: express.Request, res: express.Response) { 475async function removeVideo (req: express.Request, res: express.Response) {
464 const videoInstance: VideoModel = res.locals.video 476 const videoInstance = res.locals.video
465 477
466 await sequelizeTypescript.transaction(async t => { 478 await sequelizeTypescript.transaction(async t => {
467 await videoInstance.destroy({ transaction: t }) 479 await videoInstance.destroy({ transaction: t })
diff --git a/server/controllers/api/videos/ownership.ts b/server/controllers/api/videos/ownership.ts
index 5ea7d7c6a..5272c1385 100644
--- a/server/controllers/api/videos/ownership.ts
+++ b/server/controllers/api/videos/ownership.ts
@@ -11,15 +11,13 @@ import {
11 videosChangeOwnershipValidator, 11 videosChangeOwnershipValidator,
12 videosTerminateChangeOwnershipValidator 12 videosTerminateChangeOwnershipValidator
13} from '../../../middlewares' 13} from '../../../middlewares'
14import { AccountModel } from '../../../models/account/account'
15import { VideoModel } from '../../../models/video/video'
16import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ownership' 14import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ownership'
17import { VideoChangeOwnershipStatus, VideoPrivacy, VideoState } from '../../../../shared/models/videos' 15import { VideoChangeOwnershipStatus, VideoPrivacy, VideoState } from '../../../../shared/models/videos'
18import { VideoChannelModel } from '../../../models/video/video-channel' 16import { VideoChannelModel } from '../../../models/video/video-channel'
19import { getFormattedObjects } from '../../../helpers/utils' 17import { getFormattedObjects } from '../../../helpers/utils'
20import { changeVideoChannelShare } from '../../../lib/activitypub' 18import { changeVideoChannelShare } from '../../../lib/activitypub'
21import { sendUpdateVideo } from '../../../lib/activitypub/send' 19import { sendUpdateVideo } from '../../../lib/activitypub/send'
22import { UserModel } from '../../../models/account/user' 20import { VideoModel } from '../../../models/video/video'
23 21
24const ownershipVideoRouter = express.Router() 22const ownershipVideoRouter = express.Router()
25 23
@@ -58,9 +56,9 @@ export {
58// --------------------------------------------------------------------------- 56// ---------------------------------------------------------------------------
59 57
60async function giveVideoOwnership (req: express.Request, res: express.Response) { 58async function giveVideoOwnership (req: express.Request, res: express.Response) {
61 const videoInstance = res.locals.video as VideoModel 59 const videoInstance = res.locals.video
62 const initiatorAccountId = (res.locals.oauth.token.User as UserModel).Account.id 60 const initiatorAccountId = res.locals.oauth.token.User.Account.id
63 const nextOwner = res.locals.nextOwner as AccountModel 61 const nextOwner = res.locals.nextOwner
64 62
65 await sequelizeTypescript.transaction(t => { 63 await sequelizeTypescript.transaction(t => {
66 return VideoChangeOwnershipModel.findOrCreate({ 64 return VideoChangeOwnershipModel.findOrCreate({
@@ -85,7 +83,7 @@ async function giveVideoOwnership (req: express.Request, res: express.Response)
85} 83}
86 84
87async function listVideoOwnership (req: express.Request, res: express.Response) { 85async function listVideoOwnership (req: express.Request, res: express.Response) {
88 const currentAccountId = (res.locals.oauth.token.User as UserModel).Account.id 86 const currentAccountId = res.locals.oauth.token.User.Account.id
89 87
90 const resultList = await VideoChangeOwnershipModel.listForApi( 88 const resultList = await VideoChangeOwnershipModel.listForApi(
91 currentAccountId, 89 currentAccountId,
@@ -99,13 +97,16 @@ async function listVideoOwnership (req: express.Request, res: express.Response)
99 97
100async function acceptOwnership (req: express.Request, res: express.Response) { 98async function acceptOwnership (req: express.Request, res: express.Response) {
101 return sequelizeTypescript.transaction(async t => { 99 return sequelizeTypescript.transaction(async t => {
102 const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel 100 const videoChangeOwnership = res.locals.videoChangeOwnership
103 const targetVideo = videoChangeOwnership.Video 101 const channel = res.locals.videoChannel
104 const channel = res.locals.videoChannel as VideoChannelModel 102
103 // We need more attributes for federation
104 const targetVideo = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoChangeOwnership.Video.id)
105 105
106 const oldVideoChannel = await VideoChannelModel.loadByIdAndPopulateAccount(targetVideo.channelId) 106 const oldVideoChannel = await VideoChannelModel.loadByIdAndPopulateAccount(targetVideo.channelId)
107 107
108 targetVideo.set('channelId', channel.id) 108 targetVideo.channelId = channel.id
109
109 const targetVideoUpdated = await targetVideo.save({ transaction: t }) 110 const targetVideoUpdated = await targetVideo.save({ transaction: t })
110 targetVideoUpdated.VideoChannel = channel 111 targetVideoUpdated.VideoChannel = channel
111 112
@@ -114,7 +115,7 @@ async function acceptOwnership (req: express.Request, res: express.Response) {
114 await sendUpdateVideo(targetVideoUpdated, t, oldVideoChannel.Account.Actor) 115 await sendUpdateVideo(targetVideoUpdated, t, oldVideoChannel.Account.Actor)
115 } 116 }
116 117
117 videoChangeOwnership.set('status', VideoChangeOwnershipStatus.ACCEPTED) 118 videoChangeOwnership.status = VideoChangeOwnershipStatus.ACCEPTED
118 await videoChangeOwnership.save({ transaction: t }) 119 await videoChangeOwnership.save({ transaction: t })
119 120
120 return res.sendStatus(204) 121 return res.sendStatus(204)
@@ -123,9 +124,9 @@ async function acceptOwnership (req: express.Request, res: express.Response) {
123 124
124async function refuseOwnership (req: express.Request, res: express.Response) { 125async function refuseOwnership (req: express.Request, res: express.Response) {
125 return sequelizeTypescript.transaction(async t => { 126 return sequelizeTypescript.transaction(async t => {
126 const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel 127 const videoChangeOwnership = res.locals.videoChangeOwnership
127 128
128 videoChangeOwnership.set('status', VideoChangeOwnershipStatus.REFUSED) 129 videoChangeOwnership.status = VideoChangeOwnershipStatus.REFUSED
129 await videoChangeOwnership.save({ transaction: t }) 130 await videoChangeOwnership.save({ transaction: t })
130 131
131 return res.sendStatus(204) 132 return res.sendStatus(204)
diff --git a/server/controllers/api/videos/rate.ts b/server/controllers/api/videos/rate.ts
index 53952a0a2..b65babedf 100644
--- a/server/controllers/api/videos/rate.ts
+++ b/server/controllers/api/videos/rate.ts
@@ -1,12 +1,12 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserVideoRateUpdate } from '../../../../shared' 2import { UserVideoRateUpdate } from '../../../../shared'
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { sequelizeTypescript, VIDEO_RATE_TYPES } from '../../../initializers' 4import { VIDEO_RATE_TYPES } from '../../../initializers/constants'
5import { getRateUrl, sendVideoRateChange } from '../../../lib/activitypub' 5import { getRateUrl, sendVideoRateChange } from '../../../lib/activitypub'
6import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoUpdateRateValidator } from '../../../middlewares' 6import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoUpdateRateValidator } from '../../../middlewares'
7import { AccountModel } from '../../../models/account/account' 7import { AccountModel } from '../../../models/account/account'
8import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 8import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
9import { VideoModel } from '../../../models/video/video' 9import { sequelizeTypescript } from '../../../initializers/database'
10 10
11const rateVideoRouter = express.Router() 11const rateVideoRouter = express.Router()
12 12
@@ -27,8 +27,8 @@ export {
27async function rateVideo (req: express.Request, res: express.Response) { 27async function rateVideo (req: express.Request, res: express.Response) {
28 const body: UserVideoRateUpdate = req.body 28 const body: UserVideoRateUpdate = req.body
29 const rateType = body.rating 29 const rateType = body.rating
30 const videoInstance: VideoModel = res.locals.video 30 const videoInstance = res.locals.video
31 const userAccount: AccountModel = res.locals.oauth.token.User.Account 31 const userAccount = res.locals.oauth.token.User.Account
32 32
33 await sequelizeTypescript.transaction(async t => { 33 await sequelizeTypescript.transaction(async t => {
34 const sequelizeOptions = { transaction: t } 34 const sequelizeOptions = { transaction: t }
diff --git a/server/controllers/api/videos/watching.ts b/server/controllers/api/videos/watching.ts
index e8876b47a..dcd1f070d 100644
--- a/server/controllers/api/videos/watching.ts
+++ b/server/controllers/api/videos/watching.ts
@@ -2,7 +2,6 @@ import * as express from 'express'
2import { UserWatchingVideo } from '../../../../shared' 2import { UserWatchingVideo } from '../../../../shared'
3import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoWatchingValidator } from '../../../middlewares' 3import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoWatchingValidator } from '../../../middlewares'
4import { UserVideoHistoryModel } from '../../../models/account/user-video-history' 4import { UserVideoHistoryModel } from '../../../models/account/user-video-history'
5import { UserModel } from '../../../models/account/user'
6 5
7const watchingRouter = express.Router() 6const watchingRouter = express.Router()
8 7
@@ -21,7 +20,7 @@ export {
21// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
22 21
23async function userWatchVideo (req: express.Request, res: express.Response) { 22async function userWatchVideo (req: express.Request, res: express.Response) {
24 const user = res.locals.oauth.token.User as UserModel 23 const user = res.locals.oauth.token.User
25 24
26 const body: UserWatchingVideo = req.body 25 const body: UserWatchingVideo = req.body
27 const { id: videoId } = res.locals.video as { id: number } 26 const { id: videoId } = res.locals.video as { id: number }