aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers/api
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers/api')
-rw-r--r--server/controllers/api/accounts.ts18
-rw-r--r--server/controllers/api/config.ts77
-rw-r--r--server/controllers/api/search.ts3
-rw-r--r--server/controllers/api/server/contact.ts28
-rw-r--r--server/controllers/api/server/follows.ts16
-rw-r--r--server/controllers/api/server/index.ts4
-rw-r--r--server/controllers/api/server/server-blocklist.ts132
-rw-r--r--server/controllers/api/server/stats.ts7
-rw-r--r--server/controllers/api/users/index.ts33
-rw-r--r--server/controllers/api/users/me.ts165
-rw-r--r--server/controllers/api/users/my-blocklist.ts125
-rw-r--r--server/controllers/api/users/my-history.ts57
-rw-r--r--server/controllers/api/users/my-notifications.ts108
-rw-r--r--server/controllers/api/users/my-subscriptions.ts170
-rw-r--r--server/controllers/api/video-channel.ts18
-rw-r--r--server/controllers/api/videos/abuse.ts5
-rw-r--r--server/controllers/api/videos/blacklist.ts31
-rw-r--r--server/controllers/api/videos/captions.ts4
-rw-r--r--server/controllers/api/videos/comment.ts15
-rw-r--r--server/controllers/api/videos/import.ts21
-rw-r--r--server/controllers/api/videos/index.ts72
-rw-r--r--server/controllers/api/videos/rate.ts17
22 files changed, 859 insertions, 267 deletions
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts
index b7691ccba..8c0237203 100644
--- a/server/controllers/api/accounts.ts
+++ b/server/controllers/api/accounts.ts
@@ -1,7 +1,8 @@
1import * as express from 'express' 1import * as express from 'express'
2import { getFormattedObjects } from '../../helpers/utils' 2import { getFormattedObjects } from '../../helpers/utils'
3import { 3import {
4 asyncMiddleware, commonVideosFiltersValidator, 4 asyncMiddleware,
5 commonVideosFiltersValidator,
5 listVideoAccountChannelsValidator, 6 listVideoAccountChannelsValidator,
6 optionalAuthenticate, 7 optionalAuthenticate,
7 paginationValidator, 8 paginationValidator,
@@ -13,6 +14,8 @@ import { AccountModel } from '../../models/account/account'
13import { VideoModel } from '../../models/video/video' 14import { VideoModel } from '../../models/video/video'
14import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' 15import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
15import { VideoChannelModel } from '../../models/video/video-channel' 16import { VideoChannelModel } from '../../models/video/video-channel'
17import { JobQueue } from '../../lib/job-queue'
18import { logger } from '../../helpers/logger'
16 19
17const accountsRouter = express.Router() 20const accountsRouter = express.Router()
18 21
@@ -56,6 +59,11 @@ export {
56function getAccount (req: express.Request, res: express.Response, next: express.NextFunction) { 59function getAccount (req: express.Request, res: express.Response, next: express.NextFunction) {
57 const account: AccountModel = res.locals.account 60 const account: AccountModel = res.locals.account
58 61
62 if (account.isOutdated()) {
63 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: account.Actor.url } })
64 .catch(err => logger.error('Cannot create AP refresher job for actor %s.', account.Actor.url, { err }))
65 }
66
59 return res.json(account.toFormattedJSON()) 67 return res.json(account.toFormattedJSON())
60} 68}
61 69
@@ -73,10 +81,10 @@ async function listVideoAccountChannels (req: express.Request, res: express.Resp
73 81
74async function listAccountVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 82async function listAccountVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
75 const account: AccountModel = res.locals.account 83 const account: AccountModel = res.locals.account
76 const actorId = isUserAbleToSearchRemoteURI(res) ? null : undefined 84 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
77 85
78 const resultList = await VideoModel.listForApi({ 86 const resultList = await VideoModel.listForApi({
79 actorId, 87 followerActorId,
80 start: req.query.start, 88 start: req.query.start,
81 count: req.query.count, 89 count: req.query.count,
82 sort: req.query.sort, 90 sort: req.query.sort,
@@ -86,9 +94,11 @@ async function listAccountVideos (req: express.Request, res: express.Response, n
86 languageOneOf: req.query.languageOneOf, 94 languageOneOf: req.query.languageOneOf,
87 tagsOneOf: req.query.tagsOneOf, 95 tagsOneOf: req.query.tagsOneOf,
88 tagsAllOf: req.query.tagsAllOf, 96 tagsAllOf: req.query.tagsAllOf,
97 filter: req.query.filter,
89 nsfw: buildNSFWFilter(res, req.query.nsfw), 98 nsfw: buildNSFWFilter(res, req.query.nsfw),
90 withFiles: false, 99 withFiles: false,
91 accountId: account.id 100 accountId: account.id,
101 user: res.locals.oauth ? res.locals.oauth.token.User : undefined
92 }) 102 })
93 103
94 return res.json(getFormattedObjects(resultList.data, resultList.total)) 104 return res.json(getFormattedObjects(resultList.data, resultList.total))
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index 03c1cec7b..1f3341bc0 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -1,5 +1,5 @@
1import * as express from 'express' 1import * as express from 'express'
2import { omit } 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'
@@ -10,7 +10,10 @@ import { customConfigUpdateValidator } from '../../middlewares/validators/config
10import { ClientHtml } from '../../lib/client-html' 10import { ClientHtml } from '../../lib/client-html'
11import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger' 11import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger'
12import { remove, writeJSON } from 'fs-extra' 12import { remove, writeJSON } from 'fs-extra'
13import { getVersion } from '../../helpers/utils' 13import { getServerCommit } from '../../helpers/utils'
14import { Emailer } from '../../lib/emailer'
15import { isNumeric } from 'validator'
16import { objectConverter } from '../../helpers/core-utils'
14 17
15const packageJSON = require('../../../../package.json') 18const packageJSON = require('../../../../package.json')
16const configRouter = express.Router() 19const configRouter = express.Router()
@@ -40,11 +43,11 @@ configRouter.delete('/custom',
40) 43)
41 44
42let serverCommit: string 45let serverCommit: string
43async function getConfig (req: express.Request, res: express.Response, next: express.NextFunction) { 46async function getConfig (req: express.Request, res: express.Response) {
44 const allowed = await isSignupAllowed() 47 const allowed = await isSignupAllowed()
45 const allowedForCurrentIP = isSignupAllowedForCurrentIP(req.ip) 48 const allowedForCurrentIP = isSignupAllowedForCurrentIP(req.ip)
46 serverCommit = (serverCommit) ? serverCommit : await getVersion() 49
47 if (serverCommit === packageJSON.version) serverCommit = '' 50 if (serverCommit === undefined) serverCommit = await getServerCommit()
48 51
49 const enabledResolutions = Object.keys(CONFIG.TRANSCODING.RESOLUTIONS) 52 const enabledResolutions = Object.keys(CONFIG.TRANSCODING.RESOLUTIONS)
50 .filter(key => CONFIG.TRANSCODING.ENABLED === CONFIG.TRANSCODING.RESOLUTIONS[key] === true) 53 .filter(key => CONFIG.TRANSCODING.ENABLED === CONFIG.TRANSCODING.RESOLUTIONS[key] === true)
@@ -61,6 +64,12 @@ async function getConfig (req: express.Request, res: express.Response, next: exp
61 css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS 64 css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS
62 } 65 }
63 }, 66 },
67 email: {
68 enabled: Emailer.isEnabled()
69 },
70 contactForm: {
71 enabled: CONFIG.CONTACT_FORM.ENABLED
72 },
64 serverVersion: packageJSON.version, 73 serverVersion: packageJSON.version,
65 serverCommit, 74 serverCommit,
66 signup: { 75 signup: {
@@ -69,6 +78,9 @@ async function getConfig (req: express.Request, res: express.Response, next: exp
69 requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION 78 requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
70 }, 79 },
71 transcoding: { 80 transcoding: {
81 hls: {
82 enabled: CONFIG.TRANSCODING.HLS.ENABLED
83 },
72 enabledResolutions 84 enabledResolutions
73 }, 85 },
74 import: { 86 import: {
@@ -111,6 +123,11 @@ async function getConfig (req: express.Request, res: express.Response, next: exp
111 user: { 123 user: {
112 videoQuota: CONFIG.USER.VIDEO_QUOTA, 124 videoQuota: CONFIG.USER.VIDEO_QUOTA,
113 videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY 125 videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
126 },
127 trending: {
128 videos: {
129 intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
130 }
114 } 131 }
115 } 132 }
116 133
@@ -150,32 +167,10 @@ async function deleteCustomConfig (req: express.Request, res: express.Response,
150} 167}
151 168
152async function updateCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { 169async function updateCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
153 const toUpdate: CustomConfig = req.body
154 const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig()) 170 const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig())
155 171
156 // Force number conversion 172 // camelCase to snake_case key + Force number conversion
157 toUpdate.cache.previews.size = parseInt('' + toUpdate.cache.previews.size, 10) 173 const toUpdateJSON = convertCustomConfigBody(req.body)
158 toUpdate.cache.captions.size = parseInt('' + toUpdate.cache.captions.size, 10)
159 toUpdate.signup.limit = parseInt('' + toUpdate.signup.limit, 10)
160 toUpdate.user.videoQuota = parseInt('' + toUpdate.user.videoQuota, 10)
161 toUpdate.user.videoQuotaDaily = parseInt('' + toUpdate.user.videoQuotaDaily, 10)
162 toUpdate.transcoding.threads = parseInt('' + toUpdate.transcoding.threads, 10)
163
164 // camelCase to snake_case key
165 const toUpdateJSON = omit(
166 toUpdate,
167 'user.videoQuota',
168 'instance.defaultClientRoute',
169 'instance.shortDescription',
170 'cache.videoCaptions',
171 'signup.requiresEmailVerification'
172 )
173 toUpdateJSON.user['video_quota'] = toUpdate.user.videoQuota
174 toUpdateJSON.user['video_quota_daily'] = toUpdate.user.videoQuotaDaily
175 toUpdateJSON.instance['default_client_route'] = toUpdate.instance.defaultClientRoute
176 toUpdateJSON.instance['short_description'] = toUpdate.instance.shortDescription
177 toUpdateJSON.instance['default_nsfw_policy'] = toUpdate.instance.defaultNSFWPolicy
178 toUpdateJSON.signup['requires_email_verification'] = toUpdate.signup.requiresEmailVerification
179 174
180 await writeJSON(CONFIG.CUSTOM_FILE, toUpdateJSON, { spaces: 2 }) 175 await writeJSON(CONFIG.CUSTOM_FILE, toUpdateJSON, { spaces: 2 })
181 176
@@ -237,12 +232,16 @@ function customConfig (): CustomConfig {
237 admin: { 232 admin: {
238 email: CONFIG.ADMIN.EMAIL 233 email: CONFIG.ADMIN.EMAIL
239 }, 234 },
235 contactForm: {
236 enabled: CONFIG.CONTACT_FORM.ENABLED
237 },
240 user: { 238 user: {
241 videoQuota: CONFIG.USER.VIDEO_QUOTA, 239 videoQuota: CONFIG.USER.VIDEO_QUOTA,
242 videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY 240 videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
243 }, 241 },
244 transcoding: { 242 transcoding: {
245 enabled: CONFIG.TRANSCODING.ENABLED, 243 enabled: CONFIG.TRANSCODING.ENABLED,
244 allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
246 threads: CONFIG.TRANSCODING.THREADS, 245 threads: CONFIG.TRANSCODING.THREADS,
247 resolutions: { 246 resolutions: {
248 '240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ], 247 '240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ],
@@ -250,6 +249,9 @@ function customConfig (): CustomConfig {
250 '480p': CONFIG.TRANSCODING.RESOLUTIONS[ '480p' ], 249 '480p': CONFIG.TRANSCODING.RESOLUTIONS[ '480p' ],
251 '720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ], 250 '720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ],
252 '1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ] 251 '1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ]
252 },
253 hls: {
254 enabled: CONFIG.TRANSCODING.HLS.ENABLED
253 } 255 }
254 }, 256 },
255 import: { 257 import: {
@@ -264,3 +266,20 @@ function customConfig (): CustomConfig {
264 } 266 }
265 } 267 }
266} 268}
269
270function convertCustomConfigBody (body: CustomConfig) {
271 function keyConverter (k: string) {
272 // Transcoding resolutions exception
273 if (/^\d{3,4}p$/.exec(k)) return k
274
275 return snakeCase(k)
276 }
277
278 function valueConverter (v: any) {
279 if (isNumeric(v + '')) return parseInt('' + v, 10)
280
281 return v
282 }
283
284 return objectConverter(body, keyConverter, valueConverter)
285}
diff --git a/server/controllers/api/search.ts b/server/controllers/api/search.ts
index 4be2b5ef7..534305ba6 100644
--- a/server/controllers/api/search.ts
+++ b/server/controllers/api/search.ts
@@ -118,7 +118,8 @@ async function searchVideosDB (query: VideosSearchQuery, res: express.Response)
118 const options = Object.assign(query, { 118 const options = Object.assign(query, {
119 includeLocalVideos: true, 119 includeLocalVideos: true,
120 nsfw: buildNSFWFilter(res, query.nsfw), 120 nsfw: buildNSFWFilter(res, query.nsfw),
121 userId: res.locals.oauth ? res.locals.oauth.token.User.id : undefined 121 filter: query.filter,
122 user: res.locals.oauth ? res.locals.oauth.token.User : undefined
122 }) 123 })
123 const resultList = await VideoModel.searchAndPopulateAccountAndServer(options) 124 const resultList = await VideoModel.searchAndPopulateAccountAndServer(options)
124 125
diff --git a/server/controllers/api/server/contact.ts b/server/controllers/api/server/contact.ts
new file mode 100644
index 000000000..b1144c94e
--- /dev/null
+++ b/server/controllers/api/server/contact.ts
@@ -0,0 +1,28 @@
1import * as express from 'express'
2import { asyncMiddleware, contactAdministratorValidator } from '../../../middlewares'
3import { Redis } from '../../../lib/redis'
4import { Emailer } from '../../../lib/emailer'
5import { ContactForm } from '../../../../shared/models/server'
6
7const contactRouter = express.Router()
8
9contactRouter.post('/contact',
10 asyncMiddleware(contactAdministratorValidator),
11 asyncMiddleware(contactAdministrator)
12)
13
14async function contactAdministrator (req: express.Request, res: express.Response) {
15 const data = req.body as ContactForm
16
17 await Emailer.Instance.addContactFormJob(data.fromEmail, data.fromName, data.body)
18
19 await Redis.Instance.setContactFormIp(req.ip)
20
21 return res.status(204).end()
22}
23
24// ---------------------------------------------------------------------------
25
26export {
27 contactRouter
28}
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts
index d62400e42..9fa6c34ba 100644
--- a/server/controllers/api/server/follows.ts
+++ b/server/controllers/api/server/follows.ts
@@ -61,14 +61,26 @@ export {
61 61
62async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) { 62async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) {
63 const serverActor = await getServerActor() 63 const serverActor = await getServerActor()
64 const resultList = await ActorFollowModel.listFollowingForApi(serverActor.id, req.query.start, req.query.count, req.query.sort) 64 const resultList = await ActorFollowModel.listFollowingForApi(
65 serverActor.id,
66 req.query.start,
67 req.query.count,
68 req.query.sort,
69 req.query.search
70 )
65 71
66 return res.json(getFormattedObjects(resultList.data, resultList.total)) 72 return res.json(getFormattedObjects(resultList.data, resultList.total))
67} 73}
68 74
69async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) { 75async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) {
70 const serverActor = await getServerActor() 76 const serverActor = await getServerActor()
71 const resultList = await ActorFollowModel.listFollowersForApi(serverActor.id, req.query.start, req.query.count, req.query.sort) 77 const resultList = await ActorFollowModel.listFollowersForApi(
78 serverActor.id,
79 req.query.start,
80 req.query.count,
81 req.query.sort,
82 req.query.search
83 )
72 84
73 return res.json(getFormattedObjects(resultList.data, resultList.total)) 85 return res.json(getFormattedObjects(resultList.data, resultList.total))
74} 86}
diff --git a/server/controllers/api/server/index.ts b/server/controllers/api/server/index.ts
index 43bca2c10..814248e5f 100644
--- a/server/controllers/api/server/index.ts
+++ b/server/controllers/api/server/index.ts
@@ -2,12 +2,16 @@ import * as express from 'express'
2import { serverFollowsRouter } from './follows' 2import { serverFollowsRouter } from './follows'
3import { statsRouter } from './stats' 3import { statsRouter } from './stats'
4import { serverRedundancyRouter } from './redundancy' 4import { serverRedundancyRouter } from './redundancy'
5import { serverBlocklistRouter } from './server-blocklist'
6import { contactRouter } from './contact'
5 7
6const serverRouter = express.Router() 8const serverRouter = express.Router()
7 9
8serverRouter.use('/', serverFollowsRouter) 10serverRouter.use('/', serverFollowsRouter)
9serverRouter.use('/', serverRedundancyRouter) 11serverRouter.use('/', serverRedundancyRouter)
10serverRouter.use('/', statsRouter) 12serverRouter.use('/', statsRouter)
13serverRouter.use('/', serverBlocklistRouter)
14serverRouter.use('/', contactRouter)
11 15
12// --------------------------------------------------------------------------- 16// ---------------------------------------------------------------------------
13 17
diff --git a/server/controllers/api/server/server-blocklist.ts b/server/controllers/api/server/server-blocklist.ts
new file mode 100644
index 000000000..3cb3a96e2
--- /dev/null
+++ b/server/controllers/api/server/server-blocklist.ts
@@ -0,0 +1,132 @@
1import * as express from 'express'
2import 'multer'
3import { getFormattedObjects, getServerActor } from '../../../helpers/utils'
4import {
5 asyncMiddleware,
6 asyncRetryTransactionMiddleware,
7 authenticate,
8 ensureUserHasRight,
9 paginationValidator,
10 setDefaultPagination,
11 setDefaultSort
12} from '../../../middlewares'
13import {
14 accountsBlocklistSortValidator,
15 blockAccountValidator,
16 blockServerValidator,
17 serversBlocklistSortValidator,
18 unblockAccountByServerValidator,
19 unblockServerByServerValidator
20} from '../../../middlewares/validators'
21import { AccountModel } from '../../../models/account/account'
22import { AccountBlocklistModel } from '../../../models/account/account-blocklist'
23import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist'
24import { ServerBlocklistModel } from '../../../models/server/server-blocklist'
25import { ServerModel } from '../../../models/server/server'
26import { UserRight } from '../../../../shared/models/users'
27
28const serverBlocklistRouter = express.Router()
29
30serverBlocklistRouter.get('/blocklist/accounts',
31 authenticate,
32 ensureUserHasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST),
33 paginationValidator,
34 accountsBlocklistSortValidator,
35 setDefaultSort,
36 setDefaultPagination,
37 asyncMiddleware(listBlockedAccounts)
38)
39
40serverBlocklistRouter.post('/blocklist/accounts',
41 authenticate,
42 ensureUserHasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST),
43 asyncMiddleware(blockAccountValidator),
44 asyncRetryTransactionMiddleware(blockAccount)
45)
46
47serverBlocklistRouter.delete('/blocklist/accounts/:accountName',
48 authenticate,
49 ensureUserHasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST),
50 asyncMiddleware(unblockAccountByServerValidator),
51 asyncRetryTransactionMiddleware(unblockAccount)
52)
53
54serverBlocklistRouter.get('/blocklist/servers',
55 authenticate,
56 ensureUserHasRight(UserRight.MANAGE_SERVERS_BLOCKLIST),
57 paginationValidator,
58 serversBlocklistSortValidator,
59 setDefaultSort,
60 setDefaultPagination,
61 asyncMiddleware(listBlockedServers)
62)
63
64serverBlocklistRouter.post('/blocklist/servers',
65 authenticate,
66 ensureUserHasRight(UserRight.MANAGE_SERVERS_BLOCKLIST),
67 asyncMiddleware(blockServerValidator),
68 asyncRetryTransactionMiddleware(blockServer)
69)
70
71serverBlocklistRouter.delete('/blocklist/servers/:host',
72 authenticate,
73 ensureUserHasRight(UserRight.MANAGE_SERVERS_BLOCKLIST),
74 asyncMiddleware(unblockServerByServerValidator),
75 asyncRetryTransactionMiddleware(unblockServer)
76)
77
78export {
79 serverBlocklistRouter
80}
81
82// ---------------------------------------------------------------------------
83
84async function listBlockedAccounts (req: express.Request, res: express.Response) {
85 const serverActor = await getServerActor()
86
87 const resultList = await AccountBlocklistModel.listForApi(serverActor.Account.id, req.query.start, req.query.count, req.query.sort)
88
89 return res.json(getFormattedObjects(resultList.data, resultList.total))
90}
91
92async function blockAccount (req: express.Request, res: express.Response) {
93 const serverActor = await getServerActor()
94 const accountToBlock: AccountModel = res.locals.account
95
96 await addAccountInBlocklist(serverActor.Account.id, accountToBlock.id)
97
98 return res.status(204).end()
99}
100
101async function unblockAccount (req: express.Request, res: express.Response) {
102 const accountBlock: AccountBlocklistModel = res.locals.accountBlock
103
104 await removeAccountFromBlocklist(accountBlock)
105
106 return res.status(204).end()
107}
108
109async function listBlockedServers (req: express.Request, res: express.Response) {
110 const serverActor = await getServerActor()
111
112 const resultList = await ServerBlocklistModel.listForApi(serverActor.Account.id, req.query.start, req.query.count, req.query.sort)
113
114 return res.json(getFormattedObjects(resultList.data, resultList.total))
115}
116
117async function blockServer (req: express.Request, res: express.Response) {
118 const serverActor = await getServerActor()
119 const serverToBlock: ServerModel = res.locals.server
120
121 await addServerInBlocklist(serverActor.Account.id, serverToBlock.id)
122
123 return res.status(204).end()
124}
125
126async function unblockServer (req: express.Request, res: express.Response) {
127 const serverBlock: ServerBlocklistModel = res.locals.serverBlock
128
129 await removeServerFromBlocklist(serverBlock)
130
131 return res.status(204).end()
132}
diff --git a/server/controllers/api/server/stats.ts b/server/controllers/api/server/stats.ts
index 85803f69e..89ffd1717 100644
--- a/server/controllers/api/server/stats.ts
+++ b/server/controllers/api/server/stats.ts
@@ -8,6 +8,7 @@ import { 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 { CONFIG, ROUTE_CACHE_LIFETIME } from '../../../initializers/constants'
10import { cacheRoute } from '../../../middlewares/cache' 10import { cacheRoute } from '../../../middlewares/cache'
11import { VideoFileModel } from '../../../models/video/video-file'
11 12
12const statsRouter = express.Router() 13const statsRouter = express.Router()
13 14
@@ -16,11 +17,12 @@ statsRouter.get('/stats',
16 asyncMiddleware(getStats) 17 asyncMiddleware(getStats)
17) 18)
18 19
19async function getStats (req: express.Request, res: express.Response, next: express.NextFunction) { 20async function getStats (req: express.Request, res: express.Response) {
20 const { totalLocalVideos, totalLocalVideoViews, totalVideos } = await VideoModel.getStats() 21 const { totalLocalVideos, totalLocalVideoViews, totalVideos } = await VideoModel.getStats()
21 const { totalLocalVideoComments, totalVideoComments } = await VideoCommentModel.getStats() 22 const { totalLocalVideoComments, totalVideoComments } = await VideoCommentModel.getStats()
22 const { totalUsers } = await UserModel.getStats() 23 const { totalUsers } = await UserModel.getStats()
23 const { totalInstanceFollowers, totalInstanceFollowing } = await ActorFollowModel.getStats() 24 const { totalInstanceFollowers, totalInstanceFollowing } = await ActorFollowModel.getStats()
25 const { totalLocalVideoFilesSize } = await VideoFileModel.getStats()
24 26
25 const videosRedundancyStats = await Promise.all( 27 const videosRedundancyStats = await Promise.all(
26 CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.map(r => { 28 CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.map(r => {
@@ -32,8 +34,9 @@ async function getStats (req: express.Request, res: express.Response, next: expr
32 const data: ServerStats = { 34 const data: ServerStats = {
33 totalLocalVideos, 35 totalLocalVideos,
34 totalLocalVideoViews, 36 totalLocalVideoViews,
35 totalVideos, 37 totalLocalVideoFilesSize,
36 totalLocalVideoComments, 38 totalLocalVideoComments,
39 totalVideos,
37 totalVideoComments, 40 totalVideoComments,
38 totalUsers, 41 totalUsers,
39 totalInstanceFollowers, 42 totalInstanceFollowers,
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index 0b0081520..e3533a7f6 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -37,6 +37,11 @@ import { UserModel } from '../../../models/account/user'
37import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' 37import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
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'
41import { myVideosHistoryRouter } from './my-history'
42import { myNotificationsRouter } from './my-notifications'
43import { Notifier } from '../../../lib/notifier'
44import { mySubscriptionsRouter } from './my-subscriptions'
40 45
41const auditLogger = auditLoggerFactory('users') 46const auditLogger = auditLoggerFactory('users')
42 47
@@ -53,6 +58,10 @@ const askSendEmailLimiter = new RateLimit({
53}) 58})
54 59
55const usersRouter = express.Router() 60const usersRouter = express.Router()
61usersRouter.use('/', myNotificationsRouter)
62usersRouter.use('/', mySubscriptionsRouter)
63usersRouter.use('/', myBlocklistRouter)
64usersRouter.use('/', myVideosHistoryRouter)
56usersRouter.use('/', meRouter) 65usersRouter.use('/', meRouter)
57 66
58usersRouter.get('/autocomplete', 67usersRouter.get('/autocomplete',
@@ -207,6 +216,8 @@ async function registerUser (req: express.Request, res: express.Response) {
207 await sendVerifyUserEmail(user) 216 await sendVerifyUserEmail(user)
208 } 217 }
209 218
219 Notifier.Instance.notifyOnNewUserRegistration(user)
220
210 return res.type('json').status(204).end() 221 return res.type('json').status(204).end()
211} 222}
212 223
@@ -218,7 +229,7 @@ async function unblockUser (req: express.Request, res: express.Response, next: e
218 return res.status(204).end() 229 return res.status(204).end()
219} 230}
220 231
221async function blockUser (req: express.Request, res: express.Response, next: express.NextFunction) { 232async function blockUser (req: express.Request, res: express.Response) {
222 const user: UserModel = res.locals.user 233 const user: UserModel = res.locals.user
223 const reason = req.body.reason 234 const reason = req.body.reason
224 235
@@ -227,23 +238,23 @@ async function blockUser (req: express.Request, res: express.Response, next: exp
227 return res.status(204).end() 238 return res.status(204).end()
228} 239}
229 240
230function getUser (req: express.Request, res: express.Response, next: express.NextFunction) { 241function getUser (req: express.Request, res: express.Response) {
231 return res.json((res.locals.user as UserModel).toFormattedJSON()) 242 return res.json((res.locals.user as UserModel).toFormattedJSON())
232} 243}
233 244
234async function autocompleteUsers (req: express.Request, res: express.Response, next: express.NextFunction) { 245async function autocompleteUsers (req: express.Request, res: express.Response) {
235 const resultList = await UserModel.autoComplete(req.query.search as string) 246 const resultList = await UserModel.autoComplete(req.query.search as string)
236 247
237 return res.json(resultList) 248 return res.json(resultList)
238} 249}
239 250
240async function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) { 251async function listUsers (req: express.Request, res: express.Response) {
241 const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort) 252 const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.search)
242 253
243 return res.json(getFormattedObjects(resultList.data, resultList.total)) 254 return res.json(getFormattedObjects(resultList.data, resultList.total))
244} 255}
245 256
246async function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) { 257async function removeUser (req: express.Request, res: express.Response) {
247 const user: UserModel = res.locals.user 258 const user: UserModel = res.locals.user
248 259
249 await user.destroy() 260 await user.destroy()
@@ -253,13 +264,15 @@ async function removeUser (req: express.Request, res: express.Response, next: ex
253 return res.sendStatus(204) 264 return res.sendStatus(204)
254} 265}
255 266
256async function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) { 267async function updateUser (req: express.Request, res: express.Response) {
257 const body: UserUpdate = req.body 268 const body: UserUpdate = req.body
258 const userToUpdate = res.locals.user as UserModel 269 const userToUpdate = res.locals.user as UserModel
259 const oldUserAuditView = new UserAuditView(userToUpdate.toFormattedJSON()) 270 const oldUserAuditView = new UserAuditView(userToUpdate.toFormattedJSON())
260 const roleChanged = body.role !== undefined && body.role !== userToUpdate.role 271 const roleChanged = body.role !== undefined && body.role !== userToUpdate.role
261 272
273 if (body.password !== undefined) userToUpdate.password = body.password
262 if (body.email !== undefined) userToUpdate.email = body.email 274 if (body.email !== undefined) userToUpdate.email = body.email
275 if (body.emailVerified !== undefined) userToUpdate.emailVerified = body.emailVerified
263 if (body.videoQuota !== undefined) userToUpdate.videoQuota = body.videoQuota 276 if (body.videoQuota !== undefined) userToUpdate.videoQuota = body.videoQuota
264 if (body.videoQuotaDaily !== undefined) userToUpdate.videoQuotaDaily = body.videoQuotaDaily 277 if (body.videoQuotaDaily !== undefined) userToUpdate.videoQuotaDaily = body.videoQuotaDaily
265 if (body.role !== undefined) userToUpdate.role = body.role 278 if (body.role !== undefined) userToUpdate.role = body.role
@@ -267,11 +280,11 @@ async function updateUser (req: express.Request, res: express.Response, next: ex
267 const user = await userToUpdate.save() 280 const user = await userToUpdate.save()
268 281
269 // Destroy user token to refresh rights 282 // Destroy user token to refresh rights
270 if (roleChanged) await deleteUserToken(userToUpdate.id) 283 if (roleChanged || body.password !== undefined) await deleteUserToken(userToUpdate.id)
271 284
272 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) 285 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)
273 286
274 // Don't need to send this update to followers, these attributes are not propagated 287 // Don't need to send this update to followers, these attributes are not federated
275 288
276 return res.sendStatus(204) 289 return res.sendStatus(204)
277} 290}
@@ -281,7 +294,7 @@ async function askResetUserPassword (req: express.Request, res: express.Response
281 294
282 const verificationString = await Redis.Instance.setResetPasswordVerificationString(user.id) 295 const verificationString = await Redis.Instance.setResetPasswordVerificationString(user.id)
283 const url = CONFIG.WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString 296 const url = CONFIG.WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString
284 await Emailer.Instance.addForgetPasswordEmailJob(user.email, url) 297 await Emailer.Instance.addPasswordResetEmailJob(user.email, url)
285 298
286 return res.status(204).end() 299 return res.status(204).end()
287} 300}
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index 591ec6b25..d5e154869 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -2,47 +2,34 @@ 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, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../../initializers' 5import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../../initializers'
6import { sendUpdateActor } from '../../../lib/activitypub/send' 6import { sendUpdateActor } from '../../../lib/activitypub/send'
7import { 7import {
8 asyncMiddleware, 8 asyncMiddleware,
9 asyncRetryTransactionMiddleware, 9 asyncRetryTransactionMiddleware,
10 authenticate, 10 authenticate,
11 commonVideosFiltersValidator,
12 paginationValidator, 11 paginationValidator,
13 setDefaultPagination, 12 setDefaultPagination,
14 setDefaultSort, 13 setDefaultSort,
15 userSubscriptionAddValidator,
16 userSubscriptionGetValidator,
17 usersUpdateMeValidator, 14 usersUpdateMeValidator,
18 usersVideoRatingValidator 15 usersVideoRatingValidator
19} from '../../../middlewares' 16} from '../../../middlewares'
20import { 17import { deleteMeValidator, videoImportsSortValidator, videosSortValidator } from '../../../middlewares/validators'
21 areSubscriptionsExistValidator,
22 deleteMeValidator,
23 userSubscriptionsSortValidator,
24 videoImportsSortValidator,
25 videosSortValidator
26} from '../../../middlewares/validators'
27import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 18import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
28import { UserModel } from '../../../models/account/user' 19import { UserModel } from '../../../models/account/user'
29import { VideoModel } from '../../../models/video/video' 20import { VideoModel } from '../../../models/video/video'
30import { VideoSortField } from '../../../../client/src/app/shared/video/sort-field.type' 21import { VideoSortField } from '../../../../client/src/app/shared/video/sort-field.type'
31import { buildNSFWFilter, createReqFiles } from '../../../helpers/express-utils' 22import { createReqFiles } from '../../../helpers/express-utils'
32import { UserVideoQuota } from '../../../../shared/models/users/user-video-quota.model' 23import { UserVideoQuota } from '../../../../shared/models/users/user-video-quota.model'
33import { updateAvatarValidator } from '../../../middlewares/validators/avatar' 24import { updateAvatarValidator } from '../../../middlewares/validators/avatar'
34import { updateActorAvatarFile } from '../../../lib/avatar' 25import { updateActorAvatarFile } from '../../../lib/avatar'
35import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' 26import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
36import { VideoImportModel } from '../../../models/video/video-import' 27import { VideoImportModel } from '../../../models/video/video-import'
37import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
38import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
39import { JobQueue } from '../../../lib/job-queue'
40import { logger } from '../../../helpers/logger'
41import { AccountModel } from '../../../models/account/account' 28import { AccountModel } from '../../../models/account/account'
42 29
43const auditLogger = auditLoggerFactory('users-me') 30const auditLogger = auditLoggerFactory('users-me')
44 31
45const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR }) 32const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
46 33
47const meRouter = express.Router() 34const meRouter = express.Router()
48 35
@@ -98,51 +85,6 @@ meRouter.post('/me/avatar/pick',
98 asyncRetryTransactionMiddleware(updateMyAvatar) 85 asyncRetryTransactionMiddleware(updateMyAvatar)
99) 86)
100 87
101// ##### Subscriptions part #####
102
103meRouter.get('/me/subscriptions/videos',
104 authenticate,
105 paginationValidator,
106 videosSortValidator,
107 setDefaultSort,
108 setDefaultPagination,
109 commonVideosFiltersValidator,
110 asyncMiddleware(getUserSubscriptionVideos)
111)
112
113meRouter.get('/me/subscriptions/exist',
114 authenticate,
115 areSubscriptionsExistValidator,
116 asyncMiddleware(areSubscriptionsExist)
117)
118
119meRouter.get('/me/subscriptions',
120 authenticate,
121 paginationValidator,
122 userSubscriptionsSortValidator,
123 setDefaultSort,
124 setDefaultPagination,
125 asyncMiddleware(getUserSubscriptions)
126)
127
128meRouter.post('/me/subscriptions',
129 authenticate,
130 userSubscriptionAddValidator,
131 asyncMiddleware(addUserSubscription)
132)
133
134meRouter.get('/me/subscriptions/:uri',
135 authenticate,
136 userSubscriptionGetValidator,
137 getUserSubscription
138)
139
140meRouter.delete('/me/subscriptions/:uri',
141 authenticate,
142 userSubscriptionGetValidator,
143 asyncRetryTransactionMiddleware(deleteUserSubscription)
144)
145
146// --------------------------------------------------------------------------- 88// ---------------------------------------------------------------------------
147 89
148export { 90export {
@@ -151,99 +93,6 @@ export {
151 93
152// --------------------------------------------------------------------------- 94// ---------------------------------------------------------------------------
153 95
154async function areSubscriptionsExist (req: express.Request, res: express.Response) {
155 const uris = req.query.uris as string[]
156 const user = res.locals.oauth.token.User as UserModel
157
158 const handles = uris.map(u => {
159 let [ name, host ] = u.split('@')
160 if (host === CONFIG.WEBSERVER.HOST) host = null
161
162 return { name, host, uri: u }
163 })
164
165 const results = await ActorFollowModel.listSubscribedIn(user.Account.Actor.id, handles)
166
167 const existObject: { [id: string ]: boolean } = {}
168 for (const handle of handles) {
169 const obj = results.find(r => {
170 const server = r.ActorFollowing.Server
171
172 return r.ActorFollowing.preferredUsername === handle.name &&
173 (
174 (!server && !handle.host) ||
175 (server.host === handle.host)
176 )
177 })
178
179 existObject[handle.uri] = obj !== undefined
180 }
181
182 return res.json(existObject)
183}
184
185async function addUserSubscription (req: express.Request, res: express.Response) {
186 const user = res.locals.oauth.token.User as UserModel
187 const [ name, host ] = req.body.uri.split('@')
188
189 const payload = {
190 name,
191 host,
192 followerActorId: user.Account.Actor.id
193 }
194
195 JobQueue.Instance.createJob({ type: 'activitypub-follow', payload })
196 .catch(err => logger.error('Cannot create follow job for subscription %s.', req.body.uri, err))
197
198 return res.status(204).end()
199}
200
201function getUserSubscription (req: express.Request, res: express.Response) {
202 const subscription: ActorFollowModel = res.locals.subscription
203
204 return res.json(subscription.ActorFollowing.VideoChannel.toFormattedJSON())
205}
206
207async function deleteUserSubscription (req: express.Request, res: express.Response) {
208 const subscription: ActorFollowModel = res.locals.subscription
209
210 await sequelizeTypescript.transaction(async t => {
211 return subscription.destroy({ transaction: t })
212 })
213
214 return res.type('json').status(204).end()
215}
216
217async function getUserSubscriptions (req: express.Request, res: express.Response) {
218 const user = res.locals.oauth.token.User as UserModel
219 const actorId = user.Account.Actor.id
220
221 const resultList = await ActorFollowModel.listSubscriptionsForApi(actorId, req.query.start, req.query.count, req.query.sort)
222
223 return res.json(getFormattedObjects(resultList.data, resultList.total))
224}
225
226async function getUserSubscriptionVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
227 const user = res.locals.oauth.token.User as UserModel
228 const resultList = await VideoModel.listForApi({
229 start: req.query.start,
230 count: req.query.count,
231 sort: req.query.sort,
232 includeLocalVideos: false,
233 categoryOneOf: req.query.categoryOneOf,
234 licenceOneOf: req.query.licenceOneOf,
235 languageOneOf: req.query.languageOneOf,
236 tagsOneOf: req.query.tagsOneOf,
237 tagsAllOf: req.query.tagsAllOf,
238 nsfw: buildNSFWFilter(res, req.query.nsfw),
239 filter: req.query.filter as VideoFilter,
240 withFiles: false,
241 actorId: user.Account.Actor.id
242 })
243
244 return res.json(getFormattedObjects(resultList.data, resultList.total))
245}
246
247async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 96async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
248 const user = res.locals.oauth.token.User as UserModel 97 const user = res.locals.oauth.token.User as UserModel
249 const resultList = await VideoModel.listUserVideosForApi( 98 const resultList = await VideoModel.listUserVideosForApi(
@@ -318,7 +167,7 @@ async function deleteMe (req: express.Request, res: express.Response) {
318 return res.sendStatus(204) 167 return res.sendStatus(204)
319} 168}
320 169
321async function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) { 170async function updateMe (req: express.Request, res: express.Response) {
322 const body: UserUpdateMe = req.body 171 const body: UserUpdateMe = req.body
323 172
324 const user: UserModel = res.locals.oauth.token.user 173 const user: UserModel = res.locals.oauth.token.user
@@ -327,7 +176,9 @@ async function updateMe (req: express.Request, res: express.Response, next: expr
327 if (body.password !== undefined) user.password = body.password 176 if (body.password !== undefined) user.password = body.password
328 if (body.email !== undefined) user.email = body.email 177 if (body.email !== undefined) user.email = body.email
329 if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy 178 if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy
179 if (body.webTorrentEnabled !== undefined) user.webTorrentEnabled = body.webTorrentEnabled
330 if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo 180 if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo
181 if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled
331 182
332 await sequelizeTypescript.transaction(async t => { 183 await sequelizeTypescript.transaction(async t => {
333 const userAccount = await AccountModel.load(user.Account.id) 184 const userAccount = await AccountModel.load(user.Account.id)
@@ -346,7 +197,7 @@ async function updateMe (req: express.Request, res: express.Response, next: expr
346 return res.sendStatus(204) 197 return res.sendStatus(204)
347} 198}
348 199
349async function updateMyAvatar (req: express.Request, res: express.Response, next: express.NextFunction) { 200async function updateMyAvatar (req: express.Request, res: express.Response) {
350 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] 201 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ]
351 const user: UserModel = res.locals.oauth.token.user 202 const user: UserModel = res.locals.oauth.token.user
352 const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) 203 const oldUserAuditView = new UserAuditView(user.toFormattedJSON())
diff --git a/server/controllers/api/users/my-blocklist.ts b/server/controllers/api/users/my-blocklist.ts
new file mode 100644
index 000000000..9575eab46
--- /dev/null
+++ b/server/controllers/api/users/my-blocklist.ts
@@ -0,0 +1,125 @@
1import * as express from 'express'
2import 'multer'
3import { getFormattedObjects } from '../../../helpers/utils'
4import {
5 asyncMiddleware,
6 asyncRetryTransactionMiddleware,
7 authenticate,
8 paginationValidator,
9 setDefaultPagination,
10 setDefaultSort,
11 unblockAccountByAccountValidator
12} from '../../../middlewares'
13import {
14 accountsBlocklistSortValidator,
15 blockAccountValidator,
16 blockServerValidator,
17 serversBlocklistSortValidator,
18 unblockServerByAccountValidator
19} from '../../../middlewares/validators'
20import { UserModel } from '../../../models/account/user'
21import { AccountModel } from '../../../models/account/account'
22import { AccountBlocklistModel } from '../../../models/account/account-blocklist'
23import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist'
24import { ServerBlocklistModel } from '../../../models/server/server-blocklist'
25import { ServerModel } from '../../../models/server/server'
26
27const myBlocklistRouter = express.Router()
28
29myBlocklistRouter.get('/me/blocklist/accounts',
30 authenticate,
31 paginationValidator,
32 accountsBlocklistSortValidator,
33 setDefaultSort,
34 setDefaultPagination,
35 asyncMiddleware(listBlockedAccounts)
36)
37
38myBlocklistRouter.post('/me/blocklist/accounts',
39 authenticate,
40 asyncMiddleware(blockAccountValidator),
41 asyncRetryTransactionMiddleware(blockAccount)
42)
43
44myBlocklistRouter.delete('/me/blocklist/accounts/:accountName',
45 authenticate,
46 asyncMiddleware(unblockAccountByAccountValidator),
47 asyncRetryTransactionMiddleware(unblockAccount)
48)
49
50myBlocklistRouter.get('/me/blocklist/servers',
51 authenticate,
52 paginationValidator,
53 serversBlocklistSortValidator,
54 setDefaultSort,
55 setDefaultPagination,
56 asyncMiddleware(listBlockedServers)
57)
58
59myBlocklistRouter.post('/me/blocklist/servers',
60 authenticate,
61 asyncMiddleware(blockServerValidator),
62 asyncRetryTransactionMiddleware(blockServer)
63)
64
65myBlocklistRouter.delete('/me/blocklist/servers/:host',
66 authenticate,
67 asyncMiddleware(unblockServerByAccountValidator),
68 asyncRetryTransactionMiddleware(unblockServer)
69)
70
71export {
72 myBlocklistRouter
73}
74
75// ---------------------------------------------------------------------------
76
77async function listBlockedAccounts (req: express.Request, res: express.Response) {
78 const user: UserModel = res.locals.oauth.token.User
79
80 const resultList = await AccountBlocklistModel.listForApi(user.Account.id, req.query.start, req.query.count, req.query.sort)
81
82 return res.json(getFormattedObjects(resultList.data, resultList.total))
83}
84
85async function blockAccount (req: express.Request, res: express.Response) {
86 const user: UserModel = res.locals.oauth.token.User
87 const accountToBlock: AccountModel = res.locals.account
88
89 await addAccountInBlocklist(user.Account.id, accountToBlock.id)
90
91 return res.status(204).end()
92}
93
94async function unblockAccount (req: express.Request, res: express.Response) {
95 const accountBlock: AccountBlocklistModel = res.locals.accountBlock
96
97 await removeAccountFromBlocklist(accountBlock)
98
99 return res.status(204).end()
100}
101
102async function listBlockedServers (req: express.Request, res: express.Response) {
103 const user: UserModel = res.locals.oauth.token.User
104
105 const resultList = await ServerBlocklistModel.listForApi(user.Account.id, req.query.start, req.query.count, req.query.sort)
106
107 return res.json(getFormattedObjects(resultList.data, resultList.total))
108}
109
110async function blockServer (req: express.Request, res: express.Response) {
111 const user: UserModel = res.locals.oauth.token.User
112 const serverToBlock: ServerModel = res.locals.server
113
114 await addServerInBlocklist(user.Account.id, serverToBlock.id)
115
116 return res.status(204).end()
117}
118
119async function unblockServer (req: express.Request, res: express.Response) {
120 const serverBlock: ServerBlocklistModel = res.locals.serverBlock
121
122 await removeServerFromBlocklist(serverBlock)
123
124 return res.status(204).end()
125}
diff --git a/server/controllers/api/users/my-history.ts b/server/controllers/api/users/my-history.ts
new file mode 100644
index 000000000..6cd782c47
--- /dev/null
+++ b/server/controllers/api/users/my-history.ts
@@ -0,0 +1,57 @@
1import * as express from 'express'
2import {
3 asyncMiddleware,
4 asyncRetryTransactionMiddleware,
5 authenticate,
6 paginationValidator,
7 setDefaultPagination,
8 userHistoryRemoveValidator
9} from '../../../middlewares'
10import { UserModel } from '../../../models/account/user'
11import { getFormattedObjects } from '../../../helpers/utils'
12import { UserVideoHistoryModel } from '../../../models/account/user-video-history'
13import { sequelizeTypescript } from '../../../initializers'
14
15const myVideosHistoryRouter = express.Router()
16
17myVideosHistoryRouter.get('/me/history/videos',
18 authenticate,
19 paginationValidator,
20 setDefaultPagination,
21 asyncMiddleware(listMyVideosHistory)
22)
23
24myVideosHistoryRouter.post('/me/history/videos/remove',
25 authenticate,
26 userHistoryRemoveValidator,
27 asyncRetryTransactionMiddleware(removeUserHistory)
28)
29
30// ---------------------------------------------------------------------------
31
32export {
33 myVideosHistoryRouter
34}
35
36// ---------------------------------------------------------------------------
37
38async function listMyVideosHistory (req: express.Request, res: express.Response) {
39 const user: UserModel = res.locals.oauth.token.User
40
41 const resultList = await UserVideoHistoryModel.listForApi(user, req.query.start, req.query.count)
42
43 return res.json(getFormattedObjects(resultList.data, resultList.total))
44}
45
46async function removeUserHistory (req: express.Request, res: express.Response) {
47 const user: UserModel = res.locals.oauth.token.User
48 const beforeDate = req.body.beforeDate || null
49
50 await sequelizeTypescript.transaction(t => {
51 return UserVideoHistoryModel.removeHistoryBefore(user, beforeDate, t)
52 })
53
54 // Do not send the delete to other instances, we delete OUR copy of this video abuse
55
56 return res.type('json').status(204).end()
57}
diff --git a/server/controllers/api/users/my-notifications.ts b/server/controllers/api/users/my-notifications.ts
new file mode 100644
index 000000000..76cf97587
--- /dev/null
+++ b/server/controllers/api/users/my-notifications.ts
@@ -0,0 +1,108 @@
1import * as express from 'express'
2import 'multer'
3import {
4 asyncMiddleware,
5 asyncRetryTransactionMiddleware,
6 authenticate,
7 paginationValidator,
8 setDefaultPagination,
9 setDefaultSort,
10 userNotificationsSortValidator
11} from '../../../middlewares'
12import { UserModel } from '../../../models/account/user'
13import { getFormattedObjects } from '../../../helpers/utils'
14import { UserNotificationModel } from '../../../models/account/user-notification'
15import { meRouter } from './me'
16import {
17 listUserNotificationsValidator,
18 markAsReadUserNotificationsValidator,
19 updateNotificationSettingsValidator
20} from '../../../middlewares/validators/user-notifications'
21import { UserNotificationSetting } from '../../../../shared/models/users'
22import { UserNotificationSettingModel } from '../../../models/account/user-notification-setting'
23
24const myNotificationsRouter = express.Router()
25
26meRouter.put('/me/notification-settings',
27 authenticate,
28 updateNotificationSettingsValidator,
29 asyncRetryTransactionMiddleware(updateNotificationSettings)
30)
31
32myNotificationsRouter.get('/me/notifications',
33 authenticate,
34 paginationValidator,
35 userNotificationsSortValidator,
36 setDefaultSort,
37 setDefaultPagination,
38 listUserNotificationsValidator,
39 asyncMiddleware(listUserNotifications)
40)
41
42myNotificationsRouter.post('/me/notifications/read',
43 authenticate,
44 markAsReadUserNotificationsValidator,
45 asyncMiddleware(markAsReadUserNotifications)
46)
47
48myNotificationsRouter.post('/me/notifications/read-all',
49 authenticate,
50 asyncMiddleware(markAsReadAllUserNotifications)
51)
52
53export {
54 myNotificationsRouter
55}
56
57// ---------------------------------------------------------------------------
58
59async function updateNotificationSettings (req: express.Request, res: express.Response) {
60 const user: UserModel = res.locals.oauth.token.User
61 const body = req.body
62
63 const query = {
64 where: {
65 userId: user.id
66 }
67 }
68
69 const values: UserNotificationSetting = {
70 newVideoFromSubscription: body.newVideoFromSubscription,
71 newCommentOnMyVideo: body.newCommentOnMyVideo,
72 videoAbuseAsModerator: body.videoAbuseAsModerator,
73 blacklistOnMyVideo: body.blacklistOnMyVideo,
74 myVideoPublished: body.myVideoPublished,
75 myVideoImportFinished: body.myVideoImportFinished,
76 newFollow: body.newFollow,
77 newUserRegistration: body.newUserRegistration,
78 commentMention: body.commentMention
79 }
80
81 await UserNotificationSettingModel.update(values, query)
82
83 return res.status(204).end()
84}
85
86async function listUserNotifications (req: express.Request, res: express.Response) {
87 const user: UserModel = res.locals.oauth.token.User
88
89 const resultList = await UserNotificationModel.listForApi(user.id, req.query.start, req.query.count, req.query.sort, req.query.unread)
90
91 return res.json(getFormattedObjects(resultList.data, resultList.total))
92}
93
94async function markAsReadUserNotifications (req: express.Request, res: express.Response) {
95 const user: UserModel = res.locals.oauth.token.User
96
97 await UserNotificationModel.markAsRead(user.id, req.body.ids)
98
99 return res.status(204).end()
100}
101
102async function markAsReadAllUserNotifications (req: express.Request, res: express.Response) {
103 const user: UserModel = res.locals.oauth.token.User
104
105 await UserNotificationModel.markAllAsRead(user.id)
106
107 return res.status(204).end()
108}
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts
new file mode 100644
index 000000000..accca6d52
--- /dev/null
+++ b/server/controllers/api/users/my-subscriptions.ts
@@ -0,0 +1,170 @@
1import * as express from 'express'
2import 'multer'
3import { getFormattedObjects } from '../../../helpers/utils'
4import { CONFIG, sequelizeTypescript } from '../../../initializers'
5import {
6 asyncMiddleware,
7 asyncRetryTransactionMiddleware,
8 authenticate,
9 commonVideosFiltersValidator,
10 paginationValidator,
11 setDefaultPagination,
12 setDefaultSort,
13 userSubscriptionAddValidator,
14 userSubscriptionGetValidator
15} from '../../../middlewares'
16import { areSubscriptionsExistValidator, userSubscriptionsSortValidator, videosSortValidator } from '../../../middlewares/validators'
17import { UserModel } from '../../../models/account/user'
18import { VideoModel } from '../../../models/video/video'
19import { buildNSFWFilter } from '../../../helpers/express-utils'
20import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
21import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
22import { JobQueue } from '../../../lib/job-queue'
23import { logger } from '../../../helpers/logger'
24
25const mySubscriptionsRouter = express.Router()
26
27mySubscriptionsRouter.get('/me/subscriptions/videos',
28 authenticate,
29 paginationValidator,
30 videosSortValidator,
31 setDefaultSort,
32 setDefaultPagination,
33 commonVideosFiltersValidator,
34 asyncMiddleware(getUserSubscriptionVideos)
35)
36
37mySubscriptionsRouter.get('/me/subscriptions/exist',
38 authenticate,
39 areSubscriptionsExistValidator,
40 asyncMiddleware(areSubscriptionsExist)
41)
42
43mySubscriptionsRouter.get('/me/subscriptions',
44 authenticate,
45 paginationValidator,
46 userSubscriptionsSortValidator,
47 setDefaultSort,
48 setDefaultPagination,
49 asyncMiddleware(getUserSubscriptions)
50)
51
52mySubscriptionsRouter.post('/me/subscriptions',
53 authenticate,
54 userSubscriptionAddValidator,
55 asyncMiddleware(addUserSubscription)
56)
57
58mySubscriptionsRouter.get('/me/subscriptions/:uri',
59 authenticate,
60 userSubscriptionGetValidator,
61 getUserSubscription
62)
63
64mySubscriptionsRouter.delete('/me/subscriptions/:uri',
65 authenticate,
66 userSubscriptionGetValidator,
67 asyncRetryTransactionMiddleware(deleteUserSubscription)
68)
69
70// ---------------------------------------------------------------------------
71
72export {
73 mySubscriptionsRouter
74}
75
76// ---------------------------------------------------------------------------
77
78async function areSubscriptionsExist (req: express.Request, res: express.Response) {
79 const uris = req.query.uris as string[]
80 const user = res.locals.oauth.token.User as UserModel
81
82 const handles = uris.map(u => {
83 let [ name, host ] = u.split('@')
84 if (host === CONFIG.WEBSERVER.HOST) host = null
85
86 return { name, host, uri: u }
87 })
88
89 const results = await ActorFollowModel.listSubscribedIn(user.Account.Actor.id, handles)
90
91 const existObject: { [id: string ]: boolean } = {}
92 for (const handle of handles) {
93 const obj = results.find(r => {
94 const server = r.ActorFollowing.Server
95
96 return r.ActorFollowing.preferredUsername === handle.name &&
97 (
98 (!server && !handle.host) ||
99 (server.host === handle.host)
100 )
101 })
102
103 existObject[handle.uri] = obj !== undefined
104 }
105
106 return res.json(existObject)
107}
108
109async function addUserSubscription (req: express.Request, res: express.Response) {
110 const user = res.locals.oauth.token.User as UserModel
111 const [ name, host ] = req.body.uri.split('@')
112
113 const payload = {
114 name,
115 host,
116 followerActorId: user.Account.Actor.id
117 }
118
119 JobQueue.Instance.createJob({ type: 'activitypub-follow', payload })
120 .catch(err => logger.error('Cannot create follow job for subscription %s.', req.body.uri, err))
121
122 return res.status(204).end()
123}
124
125function getUserSubscription (req: express.Request, res: express.Response) {
126 const subscription: ActorFollowModel = res.locals.subscription
127
128 return res.json(subscription.ActorFollowing.VideoChannel.toFormattedJSON())
129}
130
131async function deleteUserSubscription (req: express.Request, res: express.Response) {
132 const subscription: ActorFollowModel = res.locals.subscription
133
134 await sequelizeTypescript.transaction(async t => {
135 return subscription.destroy({ transaction: t })
136 })
137
138 return res.type('json').status(204).end()
139}
140
141async function getUserSubscriptions (req: express.Request, res: express.Response) {
142 const user = res.locals.oauth.token.User as UserModel
143 const actorId = user.Account.Actor.id
144
145 const resultList = await ActorFollowModel.listSubscriptionsForApi(actorId, req.query.start, req.query.count, req.query.sort)
146
147 return res.json(getFormattedObjects(resultList.data, resultList.total))
148}
149
150async function getUserSubscriptionVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
151 const user = res.locals.oauth.token.User as UserModel
152 const resultList = await VideoModel.listForApi({
153 start: req.query.start,
154 count: req.query.count,
155 sort: req.query.sort,
156 includeLocalVideos: false,
157 categoryOneOf: req.query.categoryOneOf,
158 licenceOneOf: req.query.licenceOneOf,
159 languageOneOf: req.query.languageOneOf,
160 tagsOneOf: req.query.tagsOneOf,
161 tagsAllOf: req.query.tagsAllOf,
162 nsfw: buildNSFWFilter(res, req.query.nsfw),
163 filter: req.query.filter as VideoFilter,
164 withFiles: false,
165 followerActorId: user.Account.Actor.id,
166 user
167 })
168
169 return res.json(getFormattedObjects(resultList.data, resultList.total))
170}
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index 1fa842d9c..db7602139 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -22,7 +22,7 @@ import { createVideoChannel } from '../../lib/video-channel'
22import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' 22import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
23import { setAsyncActorKeys } from '../../lib/activitypub' 23import { setAsyncActorKeys } from '../../lib/activitypub'
24import { AccountModel } from '../../models/account/account' 24import { AccountModel } from '../../models/account/account'
25import { CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../initializers' 25import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../initializers'
26import { logger } from '../../helpers/logger' 26import { logger } from '../../helpers/logger'
27import { VideoModel } from '../../models/video/video' 27import { VideoModel } from '../../models/video/video'
28import { updateAvatarValidator } from '../../middlewares/validators/avatar' 28import { updateAvatarValidator } from '../../middlewares/validators/avatar'
@@ -30,9 +30,10 @@ import { updateActorAvatarFile } from '../../lib/avatar'
30import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' 30import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
31import { resetSequelizeInstance } from '../../helpers/database-utils' 31import { resetSequelizeInstance } from '../../helpers/database-utils'
32import { UserModel } from '../../models/account/user' 32import { UserModel } from '../../models/account/user'
33import { JobQueue } from '../../lib/job-queue'
33 34
34const auditLogger = auditLoggerFactory('channels') 35const auditLogger = auditLoggerFactory('channels')
35const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR }) 36const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
36 37
37const videoChannelRouter = express.Router() 38const videoChannelRouter = express.Router()
38 39
@@ -197,15 +198,20 @@ async function removeVideoChannel (req: express.Request, res: express.Response)
197async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) { 198async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
198 const videoChannelWithVideos = await VideoChannelModel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id) 199 const videoChannelWithVideos = await VideoChannelModel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id)
199 200
201 if (videoChannelWithVideos.isOutdated()) {
202 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: videoChannelWithVideos.Actor.url } })
203 .catch(err => logger.error('Cannot create AP refresher job for actor %s.', videoChannelWithVideos.Actor.url, { err }))
204 }
205
200 return res.json(videoChannelWithVideos.toFormattedJSON()) 206 return res.json(videoChannelWithVideos.toFormattedJSON())
201} 207}
202 208
203async function listVideoChannelVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 209async function listVideoChannelVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
204 const videoChannelInstance: VideoChannelModel = res.locals.videoChannel 210 const videoChannelInstance: VideoChannelModel = res.locals.videoChannel
205 const actorId = isUserAbleToSearchRemoteURI(res) ? null : undefined 211 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
206 212
207 const resultList = await VideoModel.listForApi({ 213 const resultList = await VideoModel.listForApi({
208 actorId, 214 followerActorId,
209 start: req.query.start, 215 start: req.query.start,
210 count: req.query.count, 216 count: req.query.count,
211 sort: req.query.sort, 217 sort: req.query.sort,
@@ -215,9 +221,11 @@ async function listVideoChannelVideos (req: express.Request, res: express.Respon
215 languageOneOf: req.query.languageOneOf, 221 languageOneOf: req.query.languageOneOf,
216 tagsOneOf: req.query.tagsOneOf, 222 tagsOneOf: req.query.tagsOneOf,
217 tagsAllOf: req.query.tagsAllOf, 223 tagsAllOf: req.query.tagsAllOf,
224 filter: req.query.filter,
218 nsfw: buildNSFWFilter(res, req.query.nsfw), 225 nsfw: buildNSFWFilter(res, req.query.nsfw),
219 withFiles: false, 226 withFiles: false,
220 videoChannelId: videoChannelInstance.id 227 videoChannelId: videoChannelInstance.id,
228 user: res.locals.oauth ? res.locals.oauth.token.User : undefined
221 }) 229 })
222 230
223 return res.json(getFormattedObjects(resultList.data, resultList.total)) 231 return res.json(getFormattedObjects(resultList.data, resultList.total))
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts
index d0c81804b..32f9c4793 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,
@@ -22,6 +21,8 @@ import { VideoModel } from '../../../models/video/video'
22import { VideoAbuseModel } from '../../../models/video/video-abuse' 21import { VideoAbuseModel } from '../../../models/video/video-abuse'
23import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger' 22import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger'
24import { UserModel } from '../../../models/account/user' 23import { UserModel } from '../../../models/account/user'
24import { Notifier } from '../../../lib/notifier'
25import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag'
25 26
26const auditLogger = auditLoggerFactory('abuse') 27const auditLogger = auditLoggerFactory('abuse')
27const abuseVideoRouter = express.Router() 28const abuseVideoRouter = express.Router()
@@ -117,6 +118,8 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) {
117 await sendVideoAbuse(reporterAccount.Actor, videoAbuseInstance, videoInstance) 118 await sendVideoAbuse(reporterAccount.Actor, videoAbuseInstance, videoInstance)
118 } 119 }
119 120
121 Notifier.Instance.notifyOnNewVideoAbuse(videoAbuseInstance)
122
120 auditLogger.create(reporterAccount.Actor.getIdentifier(), new VideoAbuseAuditView(videoAbuseInstance.toFormattedJSON())) 123 auditLogger.create(reporterAccount.Actor.getIdentifier(), new VideoAbuseAuditView(videoAbuseInstance.toFormattedJSON()))
121 124
122 return videoAbuseInstance 125 return videoAbuseInstance
diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts
index 7f803c8e9..43b0516e7 100644
--- a/server/controllers/api/videos/blacklist.ts
+++ b/server/controllers/api/videos/blacklist.ts
@@ -16,6 +16,10 @@ import {
16} from '../../../middlewares' 16} from '../../../middlewares'
17import { VideoBlacklistModel } from '../../../models/video/video-blacklist' 17import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
18import { sequelizeTypescript } from '../../../initializers' 18import { sequelizeTypescript } from '../../../initializers'
19import { Notifier } from '../../../lib/notifier'
20import { VideoModel } from '../../../models/video/video'
21import { sendCreateVideo, sendDeleteVideo, sendUpdateVideo } from '../../../lib/activitypub/send'
22import { federateVideoIfNeeded } from '../../../lib/activitypub'
19 23
20const blacklistRouter = express.Router() 24const blacklistRouter = express.Router()
21 25
@@ -64,16 +68,26 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response)
64 68
65 const toCreate = { 69 const toCreate = {
66 videoId: videoInstance.id, 70 videoId: videoInstance.id,
71 unfederated: body.unfederate === true,
67 reason: body.reason 72 reason: body.reason
68 } 73 }
69 74
70 await VideoBlacklistModel.create(toCreate) 75 const blacklist = await VideoBlacklistModel.create(toCreate)
76 blacklist.Video = videoInstance
77
78 if (body.unfederate === true) {
79 await sendDeleteVideo(videoInstance, undefined)
80 }
81
82 Notifier.Instance.notifyOnVideoBlacklist(blacklist)
83
84 logger.info('Video %s blacklisted.', res.locals.video.uuid)
85
71 return res.type('json').status(204).end() 86 return res.type('json').status(204).end()
72} 87}
73 88
74async function updateVideoBlacklistController (req: express.Request, res: express.Response) { 89async function updateVideoBlacklistController (req: express.Request, res: express.Response) {
75 const videoBlacklist = res.locals.videoBlacklist as VideoBlacklistModel 90 const videoBlacklist = res.locals.videoBlacklist as VideoBlacklistModel
76 logger.info(videoBlacklist)
77 91
78 if (req.body.reason !== undefined) videoBlacklist.reason = req.body.reason 92 if (req.body.reason !== undefined) videoBlacklist.reason = req.body.reason
79 93
@@ -92,11 +106,20 @@ async function listBlacklist (req: express.Request, res: express.Response, next:
92 106
93async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) { 107async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) {
94 const videoBlacklist = res.locals.videoBlacklist as VideoBlacklistModel 108 const videoBlacklist = res.locals.videoBlacklist as VideoBlacklistModel
109 const video: VideoModel = res.locals.video
95 110
96 await sequelizeTypescript.transaction(t => { 111 await sequelizeTypescript.transaction(async t => {
97 return videoBlacklist.destroy({ transaction: t }) 112 const unfederated = videoBlacklist.unfederated
113 await videoBlacklist.destroy({ transaction: t })
114
115 // Re federate the video
116 if (unfederated === true) {
117 await federateVideoIfNeeded(video, true, t)
118 }
98 }) 119 })
99 120
121 Notifier.Instance.notifyOnVideoUnblacklist(video)
122
100 logger.info('Video %s removed from blacklist.', res.locals.video.uuid) 123 logger.info('Video %s removed from blacklist.', res.locals.video.uuid)
101 124
102 return res.type('json').status(204).end() 125 return res.type('json').status(204).end()
diff --git a/server/controllers/api/videos/captions.ts b/server/controllers/api/videos/captions.ts
index 3ba918189..9b3661368 100644
--- a/server/controllers/api/videos/captions.ts
+++ b/server/controllers/api/videos/captions.ts
@@ -2,7 +2,7 @@ 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, sequelizeTypescript, VIDEO_CAPTIONS_MIMETYPE_EXT } from '../../../initializers' 5import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../../initializers'
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' 8import { VideoModel } from '../../../models/video/video'
@@ -12,7 +12,7 @@ import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
12 12
13const reqVideoCaptionAdd = createReqFiles( 13const reqVideoCaptionAdd = createReqFiles(
14 [ 'captionfile' ], 14 [ 'captionfile' ],
15 VIDEO_CAPTIONS_MIMETYPE_EXT, 15 MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT,
16 { 16 {
17 captionfile: CONFIG.STORAGE.CAPTIONS_DIR 17 captionfile: CONFIG.STORAGE.CAPTIONS_DIR
18 } 18 }
diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts
index 4f2b4faee..70c1148ba 100644
--- a/server/controllers/api/videos/comment.ts
+++ b/server/controllers/api/videos/comment.ts
@@ -8,7 +8,7 @@ import { buildFormattedCommentTree, createVideoComment } from '../../../lib/vide
8import { 8import {
9 asyncMiddleware, 9 asyncMiddleware,
10 asyncRetryTransactionMiddleware, 10 asyncRetryTransactionMiddleware,
11 authenticate, 11 authenticate, optionalAuthenticate,
12 paginationValidator, 12 paginationValidator,
13 setDefaultPagination, 13 setDefaultPagination,
14 setDefaultSort 14 setDefaultSort
@@ -26,6 +26,7 @@ import { 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' 28import { UserModel } from '../../../models/account/user'
29import { Notifier } from '../../../lib/notifier'
29 30
30const auditLogger = auditLoggerFactory('comments') 31const auditLogger = auditLoggerFactory('comments')
31const videoCommentRouter = express.Router() 32const videoCommentRouter = express.Router()
@@ -36,10 +37,12 @@ videoCommentRouter.get('/:videoId/comment-threads',
36 setDefaultSort, 37 setDefaultSort,
37 setDefaultPagination, 38 setDefaultPagination,
38 asyncMiddleware(listVideoCommentThreadsValidator), 39 asyncMiddleware(listVideoCommentThreadsValidator),
40 optionalAuthenticate,
39 asyncMiddleware(listVideoThreads) 41 asyncMiddleware(listVideoThreads)
40) 42)
41videoCommentRouter.get('/:videoId/comment-threads/:threadId', 43videoCommentRouter.get('/:videoId/comment-threads/:threadId',
42 asyncMiddleware(listVideoThreadCommentsValidator), 44 asyncMiddleware(listVideoThreadCommentsValidator),
45 optionalAuthenticate,
43 asyncMiddleware(listVideoThreadComments) 46 asyncMiddleware(listVideoThreadComments)
44) 47)
45 48
@@ -69,10 +72,12 @@ export {
69 72
70async function listVideoThreads (req: express.Request, res: express.Response, next: express.NextFunction) { 73async function listVideoThreads (req: express.Request, res: express.Response, next: express.NextFunction) {
71 const video = res.locals.video as VideoModel 74 const video = res.locals.video as VideoModel
75 const user: UserModel = res.locals.oauth ? res.locals.oauth.token.User : undefined
76
72 let resultList: ResultList<VideoCommentModel> 77 let resultList: ResultList<VideoCommentModel>
73 78
74 if (video.commentsEnabled === true) { 79 if (video.commentsEnabled === true) {
75 resultList = await VideoCommentModel.listThreadsForApi(video.id, req.query.start, req.query.count, req.query.sort) 80 resultList = await VideoCommentModel.listThreadsForApi(video.id, req.query.start, req.query.count, req.query.sort, user)
76 } else { 81 } else {
77 resultList = { 82 resultList = {
78 total: 0, 83 total: 0,
@@ -85,10 +90,12 @@ async function listVideoThreads (req: express.Request, res: express.Response, ne
85 90
86async function listVideoThreadComments (req: express.Request, res: express.Response, next: express.NextFunction) { 91async function listVideoThreadComments (req: express.Request, res: express.Response, next: express.NextFunction) {
87 const video = res.locals.video as VideoModel 92 const video = res.locals.video as VideoModel
93 const user: UserModel = res.locals.oauth ? res.locals.oauth.token.User : undefined
94
88 let resultList: ResultList<VideoCommentModel> 95 let resultList: ResultList<VideoCommentModel>
89 96
90 if (video.commentsEnabled === true) { 97 if (video.commentsEnabled === true) {
91 resultList = await VideoCommentModel.listThreadCommentsForApi(video.id, res.locals.videoCommentThread.id) 98 resultList = await VideoCommentModel.listThreadCommentsForApi(video.id, res.locals.videoCommentThread.id, user)
92 } else { 99 } else {
93 resultList = { 100 resultList = {
94 total: 0, 101 total: 0,
@@ -113,6 +120,7 @@ async function addVideoCommentThread (req: express.Request, res: express.Respons
113 }, t) 120 }, t)
114 }) 121 })
115 122
123 Notifier.Instance.notifyOnNewComment(comment)
116 auditLogger.create(getAuditIdFromRes(res), new CommentAuditView(comment.toFormattedJSON())) 124 auditLogger.create(getAuditIdFromRes(res), new CommentAuditView(comment.toFormattedJSON()))
117 125
118 return res.json({ 126 return res.json({
@@ -134,6 +142,7 @@ async function addVideoCommentReply (req: express.Request, res: express.Response
134 }, t) 142 }, t)
135 }) 143 })
136 144
145 Notifier.Instance.notifyOnNewComment(comment)
137 auditLogger.create(getAuditIdFromRes(res), new CommentAuditView(comment.toFormattedJSON())) 146 auditLogger.create(getAuditIdFromRes(res), new CommentAuditView(comment.toFormattedJSON()))
138 147
139 return res.json({ comment: comment.toFormattedJSON() }).end() 148 return res.json({ comment: comment.toFormattedJSON() }).end()
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts
index 9e51e2000..7053d5253 100644
--- a/server/controllers/api/videos/import.ts
+++ b/server/controllers/api/videos/import.ts
@@ -3,14 +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 { 6import { CONFIG, MIMETYPES, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE } from '../../../initializers'
7 CONFIG,
8 IMAGE_MIMETYPE_EXT,
9 PREVIEWS_SIZE,
10 sequelizeTypescript,
11 THUMBNAILS_SIZE,
12 TORRENT_MIMETYPE_EXT
13} from '../../../initializers'
14import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl' 7import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl'
15import { createReqFiles } from '../../../helpers/express-utils' 8import { createReqFiles } from '../../../helpers/express-utils'
16import { logger } from '../../../helpers/logger' 9import { logger } from '../../../helpers/logger'
@@ -28,18 +21,18 @@ import { VideoChannelModel } from '../../../models/video/video-channel'
28import * as Bluebird from 'bluebird' 21import * as Bluebird from 'bluebird'
29import * as parseTorrent from 'parse-torrent' 22import * as parseTorrent from 'parse-torrent'
30import { getSecureTorrentName } from '../../../helpers/utils' 23import { getSecureTorrentName } from '../../../helpers/utils'
31import { readFile, rename } from 'fs-extra' 24import { readFile, move } from 'fs-extra'
32 25
33const auditLogger = auditLoggerFactory('video-imports') 26const auditLogger = auditLoggerFactory('video-imports')
34const videoImportsRouter = express.Router() 27const videoImportsRouter = express.Router()
35 28
36const reqVideoFileImport = createReqFiles( 29const reqVideoFileImport = createReqFiles(
37 [ 'thumbnailfile', 'previewfile', 'torrentfile' ], 30 [ 'thumbnailfile', 'previewfile', 'torrentfile' ],
38 Object.assign({}, TORRENT_MIMETYPE_EXT, IMAGE_MIMETYPE_EXT), 31 Object.assign({}, MIMETYPES.TORRENT.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT),
39 { 32 {
40 thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR, 33 thumbnailfile: CONFIG.STORAGE.TMP_DIR,
41 previewfile: CONFIG.STORAGE.PREVIEWS_DIR, 34 previewfile: CONFIG.STORAGE.TMP_DIR,
42 torrentfile: CONFIG.STORAGE.TORRENTS_DIR 35 torrentfile: CONFIG.STORAGE.TMP_DIR
43 } 36 }
44) 37)
45 38
@@ -78,7 +71,7 @@ async function addTorrentImport (req: express.Request, res: express.Response, to
78 71
79 // Rename the torrent to a secured name 72 // Rename the torrent to a secured name
80 const newTorrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, getSecureTorrentName(torrentName)) 73 const newTorrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, getSecureTorrentName(torrentName))
81 await rename(torrentfile.path, newTorrentPath) 74 await move(torrentfile.path, newTorrentPath)
82 torrentfile.path = newTorrentPath 75 torrentfile.path = newTorrentPath
83 76
84 const buf = await readFile(torrentfile.path) 77 const buf = await readFile(torrentfile.path)
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 7d55f06b6..76a318d13 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -8,14 +8,13 @@ import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../
8import { getFormattedObjects, getServerActor } from '../../../helpers/utils' 8import { getFormattedObjects, getServerActor } from '../../../helpers/utils'
9import { 9import {
10 CONFIG, 10 CONFIG,
11 IMAGE_MIMETYPE_EXT, 11 MIMETYPES,
12 PREVIEWS_SIZE, 12 PREVIEWS_SIZE,
13 sequelizeTypescript, 13 sequelizeTypescript,
14 THUMBNAILS_SIZE, 14 THUMBNAILS_SIZE,
15 VIDEO_CATEGORIES, 15 VIDEO_CATEGORIES,
16 VIDEO_LANGUAGES, 16 VIDEO_LANGUAGES,
17 VIDEO_LICENCES, 17 VIDEO_LICENCES,
18 VIDEO_MIMETYPE_EXT,
19 VIDEO_PRIVACIES 18 VIDEO_PRIVACIES
20} from '../../../initializers' 19} from '../../../initializers'
21import { 20import {
@@ -24,19 +23,20 @@ import {
24 fetchRemoteVideoDescription, 23 fetchRemoteVideoDescription,
25 getVideoActivityPubUrl 24 getVideoActivityPubUrl
26} from '../../../lib/activitypub' 25} from '../../../lib/activitypub'
27import { sendCreateView } from '../../../lib/activitypub/send'
28import { JobQueue } from '../../../lib/job-queue' 26import { JobQueue } from '../../../lib/job-queue'
29import { Redis } from '../../../lib/redis' 27import { Redis } from '../../../lib/redis'
30import { 28import {
31 asyncMiddleware, 29 asyncMiddleware,
32 asyncRetryTransactionMiddleware, 30 asyncRetryTransactionMiddleware,
33 authenticate, 31 authenticate,
32 checkVideoFollowConstraints,
34 commonVideosFiltersValidator, 33 commonVideosFiltersValidator,
35 optionalAuthenticate, 34 optionalAuthenticate,
36 paginationValidator, 35 paginationValidator,
37 setDefaultPagination, 36 setDefaultPagination,
38 setDefaultSort, 37 setDefaultSort,
39 videosAddValidator, 38 videosAddValidator,
39 videosCustomGetValidator,
40 videosGetValidator, 40 videosGetValidator,
41 videosRemoveValidator, 41 videosRemoveValidator,
42 videosSortValidator, 42 videosSortValidator,
@@ -56,27 +56,29 @@ import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-u
56import { videoCaptionsRouter } from './captions' 56import { videoCaptionsRouter } from './captions'
57import { videoImportsRouter } from './import' 57import { videoImportsRouter } from './import'
58import { resetSequelizeInstance } from '../../../helpers/database-utils' 58import { resetSequelizeInstance } from '../../../helpers/database-utils'
59import { rename } from 'fs-extra' 59import { move } from 'fs-extra'
60import { watchingRouter } from './watching' 60import { watchingRouter } from './watching'
61import { Notifier } from '../../../lib/notifier'
62import { sendView } from '../../../lib/activitypub/send/send-view'
61 63
62const auditLogger = auditLoggerFactory('videos') 64const auditLogger = auditLoggerFactory('videos')
63const videosRouter = express.Router() 65const videosRouter = express.Router()
64 66
65const reqVideoFileAdd = createReqFiles( 67const reqVideoFileAdd = createReqFiles(
66 [ 'videofile', 'thumbnailfile', 'previewfile' ], 68 [ 'videofile', 'thumbnailfile', 'previewfile' ],
67 Object.assign({}, VIDEO_MIMETYPE_EXT, IMAGE_MIMETYPE_EXT), 69 Object.assign({}, MIMETYPES.VIDEO.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT),
68 { 70 {
69 videofile: CONFIG.STORAGE.VIDEOS_DIR, 71 videofile: CONFIG.STORAGE.TMP_DIR,
70 thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR, 72 thumbnailfile: CONFIG.STORAGE.TMP_DIR,
71 previewfile: CONFIG.STORAGE.PREVIEWS_DIR 73 previewfile: CONFIG.STORAGE.TMP_DIR
72 } 74 }
73) 75)
74const reqVideoFileUpdate = createReqFiles( 76const reqVideoFileUpdate = createReqFiles(
75 [ 'thumbnailfile', 'previewfile' ], 77 [ 'thumbnailfile', 'previewfile' ],
76 IMAGE_MIMETYPE_EXT, 78 MIMETYPES.IMAGE.MIMETYPE_EXT,
77 { 79 {
78 thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR, 80 thumbnailfile: CONFIG.STORAGE.TMP_DIR,
79 previewfile: CONFIG.STORAGE.PREVIEWS_DIR 81 previewfile: CONFIG.STORAGE.TMP_DIR
80 } 82 }
81) 83)
82 84
@@ -122,8 +124,9 @@ videosRouter.get('/:id/description',
122) 124)
123videosRouter.get('/:id', 125videosRouter.get('/:id',
124 optionalAuthenticate, 126 optionalAuthenticate,
125 asyncMiddleware(videosGetValidator), 127 asyncMiddleware(videosCustomGetValidator('only-video-with-rights')),
126 getVideo 128 asyncMiddleware(checkVideoFollowConstraints),
129 asyncMiddleware(getVideo)
127) 130)
128videosRouter.post('/:id/views', 131videosRouter.post('/:id/views',
129 asyncMiddleware(videosGetValidator), 132 asyncMiddleware(videosGetValidator),
@@ -207,7 +210,7 @@ async function addVideo (req: express.Request, res: express.Response) {
207 // Move physical file 210 // Move physical file
208 const videoDir = CONFIG.STORAGE.VIDEOS_DIR 211 const videoDir = CONFIG.STORAGE.VIDEOS_DIR
209 const destination = join(videoDir, video.getVideoFilename(videoFile)) 212 const destination = join(videoDir, video.getVideoFilename(videoFile))
210 await rename(videoPhysicalFile.path, destination) 213 await move(videoPhysicalFile.path, destination)
211 // This is important in case if there is another attempt in the retry process 214 // This is important in case if there is another attempt in the retry process
212 videoPhysicalFile.filename = video.getVideoFilename(videoFile) 215 videoPhysicalFile.filename = video.getVideoFilename(videoFile)
213 videoPhysicalFile.path = destination 216 videoPhysicalFile.path = destination
@@ -270,6 +273,8 @@ async function addVideo (req: express.Request, res: express.Response) {
270 return videoCreated 273 return videoCreated
271 }) 274 })
272 275
276 Notifier.Instance.notifyOnNewVideo(videoCreated)
277
273 if (video.state === VideoState.TO_TRANSCODE) { 278 if (video.state === VideoState.TO_TRANSCODE) {
274 // Put uuid because we don't have id auto incremented for now 279 // Put uuid because we don't have id auto incremented for now
275 const dataInput = { 280 const dataInput = {
@@ -294,6 +299,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
294 const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON()) 299 const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON())
295 const videoInfoToUpdate: VideoUpdate = req.body 300 const videoInfoToUpdate: VideoUpdate = req.body
296 const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE 301 const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE
302 const wasUnlistedVideo = videoInstance.privacy === VideoPrivacy.UNLISTED
297 303
298 // Process thumbnail or create it from the video 304 // Process thumbnail or create it from the video
299 if (req.files && req.files['thumbnailfile']) { 305 if (req.files && req.files['thumbnailfile']) {
@@ -308,10 +314,8 @@ async function updateVideo (req: express.Request, res: express.Response) {
308 } 314 }
309 315
310 try { 316 try {
311 await sequelizeTypescript.transaction(async t => { 317 const videoInstanceUpdated = await sequelizeTypescript.transaction(async t => {
312 const sequelizeOptions = { 318 const sequelizeOptions = { transaction: t }
313 transaction: t
314 }
315 const oldVideoChannel = videoInstance.VideoChannel 319 const oldVideoChannel = videoInstance.VideoChannel
316 320
317 if (videoInfoToUpdate.name !== undefined) videoInstance.set('name', videoInfoToUpdate.name) 321 if (videoInfoToUpdate.name !== undefined) videoInstance.set('name', videoInfoToUpdate.name)
@@ -363,7 +367,11 @@ async function updateVideo (req: express.Request, res: express.Response) {
363 } 367 }
364 368
365 const isNewVideo = wasPrivateVideo && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE 369 const isNewVideo = wasPrivateVideo && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE
366 await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t) 370
371 // Don't send update if the video was unfederated
372 if (!videoInstanceUpdated.VideoBlacklist || videoInstanceUpdated.VideoBlacklist.unfederated === false) {
373 await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t)
374 }
367 375
368 auditLogger.update( 376 auditLogger.update(
369 getAuditIdFromRes(res), 377 getAuditIdFromRes(res),
@@ -371,7 +379,13 @@ async function updateVideo (req: express.Request, res: express.Response) {
371 oldVideoAuditView 379 oldVideoAuditView
372 ) 380 )
373 logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid) 381 logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid)
382
383 return videoInstanceUpdated
374 }) 384 })
385
386 if (wasUnlistedVideo || wasPrivateVideo) {
387 Notifier.Instance.notifyOnNewVideo(videoInstanceUpdated)
388 }
375 } catch (err) { 389 } catch (err) {
376 // Force fields we want to update 390 // Force fields we want to update
377 // If the transaction is retried, sequelize will think the object has not changed 391 // If the transaction is retried, sequelize will think the object has not changed
@@ -384,10 +398,17 @@ async function updateVideo (req: express.Request, res: express.Response) {
384 return res.type('json').status(204).end() 398 return res.type('json').status(204).end()
385} 399}
386 400
387function getVideo (req: express.Request, res: express.Response) { 401async function getVideo (req: express.Request, res: express.Response) {
388 const videoInstance = res.locals.video 402 // We need more attributes
403 const userId: number = res.locals.oauth ? res.locals.oauth.token.User.id : null
404 const video: VideoModel = await VideoModel.loadForGetAPI(res.locals.video.id, undefined, userId)
389 405
390 return res.json(videoInstance.toFormattedDetailsJSON()) 406 if (video.isOutdated()) {
407 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: video.url } })
408 .catch(err => logger.error('Cannot create AP refresher job for video %s.', video.url, { err }))
409 }
410
411 return res.json(video.toFormattedDetailsJSON())
391} 412}
392 413
393async function viewVideo (req: express.Request, res: express.Response) { 414async function viewVideo (req: express.Request, res: express.Response) {
@@ -406,8 +427,7 @@ async function viewVideo (req: express.Request, res: express.Response) {
406 ]) 427 ])
407 428
408 const serverActor = await getServerActor() 429 const serverActor = await getServerActor()
409 430 await sendView(serverActor, videoInstance, undefined)
410 await sendCreateView(serverActor, videoInstance, undefined)
411 431
412 return res.status(204).end() 432 return res.status(204).end()
413} 433}
@@ -425,7 +445,7 @@ async function getVideoDescription (req: express.Request, res: express.Response)
425 return res.json({ description }) 445 return res.json({ description })
426} 446}
427 447
428async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 448async function listVideos (req: express.Request, res: express.Response) {
429 const resultList = await VideoModel.listForApi({ 449 const resultList = await VideoModel.listForApi({
430 start: req.query.start, 450 start: req.query.start,
431 count: req.query.count, 451 count: req.query.count,
@@ -439,7 +459,7 @@ async function listVideos (req: express.Request, res: express.Response, next: ex
439 nsfw: buildNSFWFilter(res, req.query.nsfw), 459 nsfw: buildNSFWFilter(res, req.query.nsfw),
440 filter: req.query.filter as VideoFilter, 460 filter: req.query.filter as VideoFilter,
441 withFiles: false, 461 withFiles: false,
442 userId: res.locals.oauth ? res.locals.oauth.token.User.id : undefined 462 user: res.locals.oauth ? res.locals.oauth.token.User : undefined
443 }) 463 })
444 464
445 return res.json(getFormattedObjects(resultList.data, resultList.total)) 465 return res.json(getFormattedObjects(resultList.data, resultList.total))
diff --git a/server/controllers/api/videos/rate.ts b/server/controllers/api/videos/rate.ts
index dc322bb0c..53952a0a2 100644
--- a/server/controllers/api/videos/rate.ts
+++ b/server/controllers/api/videos/rate.ts
@@ -2,8 +2,8 @@ import * 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 { sequelizeTypescript, VIDEO_RATE_TYPES } from '../../../initializers'
5import { sendVideoRateChange } from '../../../lib/activitypub' 5import { getRateUrl, sendVideoRateChange } from '../../../lib/activitypub'
6import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoRateValidator } 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 { VideoModel } from '../../../models/video/video'
@@ -12,7 +12,7 @@ const rateVideoRouter = express.Router()
12 12
13rateVideoRouter.put('/:id/rate', 13rateVideoRouter.put('/:id/rate',
14 authenticate, 14 authenticate,
15 asyncMiddleware(videoRateValidator), 15 asyncMiddleware(videoUpdateRateValidator),
16 asyncRetryTransactionMiddleware(rateVideo) 16 asyncRetryTransactionMiddleware(rateVideo)
17) 17)
18 18
@@ -28,11 +28,12 @@ async 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: VideoModel = res.locals.video
31 const userAccount: AccountModel = res.locals.oauth.token.User.Account
31 32
32 await sequelizeTypescript.transaction(async t => { 33 await sequelizeTypescript.transaction(async t => {
33 const sequelizeOptions = { transaction: t } 34 const sequelizeOptions = { transaction: t }
34 35
35 const accountInstance = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) 36 const accountInstance = await AccountModel.load(userAccount.id, t)
36 const previousRate = await AccountVideoRateModel.load(accountInstance.id, videoInstance.id, t) 37 const previousRate = await AccountVideoRateModel.load(accountInstance.id, videoInstance.id, t)
37 38
38 let likesToIncrement = 0 39 let likesToIncrement = 0
@@ -44,20 +45,22 @@ async function rateVideo (req: express.Request, res: express.Response) {
44 // There was a previous rate, update it 45 // There was a previous rate, update it
45 if (previousRate) { 46 if (previousRate) {
46 // We will remove the previous rate, so we will need to update the video count attribute 47 // We will remove the previous rate, so we will need to update the video count attribute
47 if (previousRate.type === VIDEO_RATE_TYPES.LIKE) likesToIncrement-- 48 if (previousRate.type === 'like') likesToIncrement--
48 else if (previousRate.type === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement-- 49 else if (previousRate.type === 'dislike') dislikesToIncrement--
49 50
50 if (rateType === 'none') { // Destroy previous rate 51 if (rateType === 'none') { // Destroy previous rate
51 await previousRate.destroy(sequelizeOptions) 52 await previousRate.destroy(sequelizeOptions)
52 } else { // Update previous rate 53 } else { // Update previous rate
53 previousRate.type = rateType 54 previousRate.type = rateType
55 previousRate.url = getRateUrl(rateType, userAccount.Actor, videoInstance)
54 await previousRate.save(sequelizeOptions) 56 await previousRate.save(sequelizeOptions)
55 } 57 }
56 } else if (rateType !== 'none') { // There was not a previous rate, insert a new one if there is a rate 58 } else if (rateType !== 'none') { // There was not a previous rate, insert a new one if there is a rate
57 const query = { 59 const query = {
58 accountId: accountInstance.id, 60 accountId: accountInstance.id,
59 videoId: videoInstance.id, 61 videoId: videoInstance.id,
60 type: rateType 62 type: rateType,
63 url: getRateUrl(rateType, userAccount.Actor, videoInstance)
61 } 64 }
62 65
63 await AccountVideoRateModel.create(query, sequelizeOptions) 66 await AccountVideoRateModel.create(query, sequelizeOptions)