aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers/api
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers/api')
-rw-r--r--server/controllers/api/config.ts242
-rw-r--r--server/controllers/api/jobs.ts4
-rw-r--r--server/controllers/api/plugins.ts3
-rw-r--r--server/controllers/api/search.ts43
-rw-r--r--server/controllers/api/users/index.ts8
-rw-r--r--server/controllers/api/users/me.ts10
-rw-r--r--server/controllers/api/users/my-notifications.ts4
-rw-r--r--server/controllers/api/users/my-subscriptions.ts12
-rw-r--r--server/controllers/api/users/token.ts72
-rw-r--r--server/controllers/api/video-channel.ts66
-rw-r--r--server/controllers/api/videos/index.ts11
-rw-r--r--server/controllers/api/videos/ownership.ts2
12 files changed, 176 insertions, 301 deletions
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index fb108ca1c..2ddb73519 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -1,24 +1,17 @@
1import { Hooks } from '@server/lib/plugins/hooks'
2import * as express from 'express' 1import * as express from 'express'
3import { remove, writeJSON } from 'fs-extra' 2import { remove, writeJSON } from 'fs-extra'
4import { snakeCase } from 'lodash' 3import { snakeCase } from 'lodash'
5import validator from 'validator' 4import validator from 'validator'
6import { RegisteredExternalAuthConfig, RegisteredIdAndPassAuthConfig, ServerConfig, UserRight } from '../../../shared' 5import { getServerConfig } from '@server/lib/config'
6import { UserRight } from '../../../shared'
7import { About } from '../../../shared/models/server/about.model' 7import { About } from '../../../shared/models/server/about.model'
8import { CustomConfig } from '../../../shared/models/server/custom-config.model' 8import { CustomConfig } from '../../../shared/models/server/custom-config.model'
9import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger' 9import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger'
10import { objectConverter } from '../../helpers/core-utils' 10import { objectConverter } from '../../helpers/core-utils'
11import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' 11import { CONFIG, reloadConfig } from '../../initializers/config'
12import { getServerCommit } from '../../helpers/utils'
13import { getEnabledResolutions } from '../../lib/video-transcoding'
14import { CONFIG, isEmailEnabled, reloadConfig } from '../../initializers/config'
15import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '../../initializers/constants'
16import { ClientHtml } from '../../lib/client-html' 12import { ClientHtml } from '../../lib/client-html'
17import { PluginManager } from '../../lib/plugins/plugin-manager'
18import { getThemeOrDefault } from '../../lib/plugins/theme-utils'
19import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares' 13import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
20import { customConfigUpdateValidator } from '../../middlewares/validators/config' 14import { customConfigUpdateValidator } from '../../middlewares/validators/config'
21import { VideoTranscodingProfilesManager } from '@server/lib/video-transcoding-profiles'
22 15
23const configRouter = express.Router() 16const configRouter = express.Router()
24 17
@@ -46,174 +39,8 @@ configRouter.delete('/custom',
46 asyncMiddleware(deleteCustomConfig) 39 asyncMiddleware(deleteCustomConfig)
47) 40)
48 41
49let serverCommit: string
50
51async function getConfig (req: express.Request, res: express.Response) { 42async function getConfig (req: express.Request, res: express.Response) {
52 const { allowed } = await Hooks.wrapPromiseFun( 43 const json = await getServerConfig(req.ip)
53 isSignupAllowed,
54 {
55 ip: req.ip
56 },
57 'filter:api.user.signup.allowed.result'
58 )
59
60 const allowedForCurrentIP = isSignupAllowedForCurrentIP(req.ip)
61 const defaultTheme = getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME)
62
63 if (serverCommit === undefined) serverCommit = await getServerCommit()
64
65 const json: ServerConfig = {
66 instance: {
67 name: CONFIG.INSTANCE.NAME,
68 shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
69 isNSFW: CONFIG.INSTANCE.IS_NSFW,
70 defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
71 defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
72 customizations: {
73 javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT,
74 css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS
75 }
76 },
77 search: {
78 remoteUri: {
79 users: CONFIG.SEARCH.REMOTE_URI.USERS,
80 anonymous: CONFIG.SEARCH.REMOTE_URI.ANONYMOUS
81 },
82 searchIndex: {
83 enabled: CONFIG.SEARCH.SEARCH_INDEX.ENABLED,
84 url: CONFIG.SEARCH.SEARCH_INDEX.URL,
85 disableLocalSearch: CONFIG.SEARCH.SEARCH_INDEX.DISABLE_LOCAL_SEARCH,
86 isDefaultSearch: CONFIG.SEARCH.SEARCH_INDEX.IS_DEFAULT_SEARCH
87 }
88 },
89 plugin: {
90 registered: getRegisteredPlugins(),
91 registeredExternalAuths: getExternalAuthsPlugins(),
92 registeredIdAndPassAuths: getIdAndPassAuthPlugins()
93 },
94 theme: {
95 registered: getRegisteredThemes(),
96 default: defaultTheme
97 },
98 email: {
99 enabled: isEmailEnabled()
100 },
101 contactForm: {
102 enabled: CONFIG.CONTACT_FORM.ENABLED
103 },
104 serverVersion: PEERTUBE_VERSION,
105 serverCommit,
106 signup: {
107 allowed,
108 allowedForCurrentIP,
109 requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
110 },
111 transcoding: {
112 hls: {
113 enabled: CONFIG.TRANSCODING.HLS.ENABLED
114 },
115 webtorrent: {
116 enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED
117 },
118 enabledResolutions: getEnabledResolutions('vod'),
119 profile: CONFIG.TRANSCODING.PROFILE,
120 availableProfiles: VideoTranscodingProfilesManager.Instance.getAvailableProfiles('vod')
121 },
122 live: {
123 enabled: CONFIG.LIVE.ENABLED,
124
125 allowReplay: CONFIG.LIVE.ALLOW_REPLAY,
126 maxDuration: CONFIG.LIVE.MAX_DURATION,
127 maxInstanceLives: CONFIG.LIVE.MAX_INSTANCE_LIVES,
128 maxUserLives: CONFIG.LIVE.MAX_USER_LIVES,
129
130 transcoding: {
131 enabled: CONFIG.LIVE.TRANSCODING.ENABLED,
132 enabledResolutions: getEnabledResolutions('live'),
133 profile: CONFIG.LIVE.TRANSCODING.PROFILE,
134 availableProfiles: VideoTranscodingProfilesManager.Instance.getAvailableProfiles('live')
135 },
136
137 rtmp: {
138 port: CONFIG.LIVE.RTMP.PORT
139 }
140 },
141 import: {
142 videos: {
143 http: {
144 enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
145 },
146 torrent: {
147 enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
148 }
149 }
150 },
151 autoBlacklist: {
152 videos: {
153 ofUsers: {
154 enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
155 }
156 }
157 },
158 avatar: {
159 file: {
160 size: {
161 max: CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max
162 },
163 extensions: CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME
164 }
165 },
166 video: {
167 image: {
168 extensions: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME,
169 size: {
170 max: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max
171 }
172 },
173 file: {
174 extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME
175 }
176 },
177 videoCaption: {
178 file: {
179 size: {
180 max: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max
181 },
182 extensions: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME
183 }
184 },
185 user: {
186 videoQuota: CONFIG.USER.VIDEO_QUOTA,
187 videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
188 },
189 trending: {
190 videos: {
191 intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS,
192 algorithms: {
193 enabled: CONFIG.TRENDING.VIDEOS.ALGORITHMS.ENABLED,
194 default: CONFIG.TRENDING.VIDEOS.ALGORITHMS.DEFAULT
195 }
196 }
197 },
198 tracker: {
199 enabled: CONFIG.TRACKER.ENABLED
200 },
201
202 followings: {
203 instance: {
204 autoFollowIndex: {
205 indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL
206 }
207 }
208 },
209
210 broadcastMessage: {
211 enabled: CONFIG.BROADCAST_MESSAGE.ENABLED,
212 message: CONFIG.BROADCAST_MESSAGE.MESSAGE,
213 level: CONFIG.BROADCAST_MESSAGE.LEVEL,
214 dismissable: CONFIG.BROADCAST_MESSAGE.DISMISSABLE
215 }
216 }
217 44
218 return res.json(json) 45 return res.json(json)
219} 46}
@@ -284,69 +111,10 @@ async function updateCustomConfig (req: express.Request, res: express.Response)
284 return res.json(data) 111 return res.json(data)
285} 112}
286 113
287function getRegisteredThemes () {
288 return PluginManager.Instance.getRegisteredThemes()
289 .map(t => ({
290 name: t.name,
291 version: t.version,
292 description: t.description,
293 css: t.css,
294 clientScripts: t.clientScripts
295 }))
296}
297
298function getRegisteredPlugins () {
299 return PluginManager.Instance.getRegisteredPlugins()
300 .map(p => ({
301 name: p.name,
302 version: p.version,
303 description: p.description,
304 clientScripts: p.clientScripts
305 }))
306}
307
308function getIdAndPassAuthPlugins () {
309 const result: RegisteredIdAndPassAuthConfig[] = []
310
311 for (const p of PluginManager.Instance.getIdAndPassAuths()) {
312 for (const auth of p.idAndPassAuths) {
313 result.push({
314 npmName: p.npmName,
315 name: p.name,
316 version: p.version,
317 authName: auth.authName,
318 weight: auth.getWeight()
319 })
320 }
321 }
322
323 return result
324}
325
326function getExternalAuthsPlugins () {
327 const result: RegisteredExternalAuthConfig[] = []
328
329 for (const p of PluginManager.Instance.getExternalAuths()) {
330 for (const auth of p.externalAuths) {
331 result.push({
332 npmName: p.npmName,
333 name: p.name,
334 version: p.version,
335 authName: auth.authName,
336 authDisplayName: auth.authDisplayName()
337 })
338 }
339 }
340
341 return result
342}
343
344// --------------------------------------------------------------------------- 114// ---------------------------------------------------------------------------
345 115
346export { 116export {
347 configRouter, 117 configRouter
348 getRegisteredPlugins,
349 getRegisteredThemes
350} 118}
351 119
352// --------------------------------------------------------------------------- 120// ---------------------------------------------------------------------------
diff --git a/server/controllers/api/jobs.ts b/server/controllers/api/jobs.ts
index 861cc22b9..d7cee1605 100644
--- a/server/controllers/api/jobs.ts
+++ b/server/controllers/api/jobs.ts
@@ -9,10 +9,10 @@ import {
9 authenticate, 9 authenticate,
10 ensureUserHasRight, 10 ensureUserHasRight,
11 jobsSortValidator, 11 jobsSortValidator,
12 paginationValidatorBuilder,
12 setDefaultPagination, 13 setDefaultPagination,
13 setDefaultSort 14 setDefaultSort
14} from '../../middlewares' 15} from '../../middlewares'
15import { paginationValidator } from '../../middlewares/validators'
16import { listJobsValidator } from '../../middlewares/validators/jobs' 16import { listJobsValidator } from '../../middlewares/validators/jobs'
17 17
18const jobsRouter = express.Router() 18const jobsRouter = express.Router()
@@ -20,7 +20,7 @@ const jobsRouter = express.Router()
20jobsRouter.get('/:state?', 20jobsRouter.get('/:state?',
21 authenticate, 21 authenticate,
22 ensureUserHasRight(UserRight.MANAGE_JOBS), 22 ensureUserHasRight(UserRight.MANAGE_JOBS),
23 paginationValidator, 23 paginationValidatorBuilder([ 'jobs' ]),
24 jobsSortValidator, 24 jobsSortValidator,
25 setDefaultSort, 25 setDefaultSort,
26 setDefaultPagination, 26 setDefaultPagination,
diff --git a/server/controllers/api/plugins.ts b/server/controllers/api/plugins.ts
index 1c0b5edb1..a186de010 100644
--- a/server/controllers/api/plugins.ts
+++ b/server/controllers/api/plugins.ts
@@ -151,7 +151,7 @@ async function updatePlugin (req: express.Request, res: express.Response) {
151 const fromDisk = !!body.path 151 const fromDisk = !!body.path
152 const toUpdate = body.npmName || body.path 152 const toUpdate = body.npmName || body.path
153 try { 153 try {
154 const plugin = await PluginManager.Instance.update(toUpdate, undefined, fromDisk) 154 const plugin = await PluginManager.Instance.update(toUpdate, fromDisk)
155 155
156 return res.json(plugin.toFormattedJSON()) 156 return res.json(plugin.toFormattedJSON())
157 } catch (err) { 157 } catch (err) {
@@ -205,7 +205,6 @@ async function listAvailablePlugins (req: express.Request, res: express.Response
205 if (!resultList) { 205 if (!resultList) {
206 return res.status(HttpStatusCode.SERVICE_UNAVAILABLE_503) 206 return res.status(HttpStatusCode.SERVICE_UNAVAILABLE_503)
207 .json({ error: 'Plugin index unavailable. Please retry later' }) 207 .json({ error: 'Plugin index unavailable. Please retry later' })
208 .end()
209 } 208 }
210 209
211 return res.json(resultList) 210 return res.json(resultList)
diff --git a/server/controllers/api/search.ts b/server/controllers/api/search.ts
index 7e1b7b230..f0cdf3a89 100644
--- a/server/controllers/api/search.ts
+++ b/server/controllers/api/search.ts
@@ -1,8 +1,9 @@
1import * as express from 'express' 1import * as express from 'express'
2import { sanitizeUrl } from '@server/helpers/core-utils' 2import { sanitizeUrl } from '@server/helpers/core-utils'
3import { doRequest } from '@server/helpers/requests' 3import { doJSONRequest } from '@server/helpers/requests'
4import { CONFIG } from '@server/initializers/config' 4import { CONFIG } from '@server/initializers/config'
5import { getOrCreateVideoAndAccountAndChannel } from '@server/lib/activitypub/videos' 5import { getOrCreateVideoAndAccountAndChannel } from '@server/lib/activitypub/videos'
6import { Hooks } from '@server/lib/plugins/hooks'
6import { AccountBlocklistModel } from '@server/models/account/account-blocklist' 7import { AccountBlocklistModel } from '@server/models/account/account-blocklist'
7import { getServerActor } from '@server/models/application/application' 8import { getServerActor } from '@server/models/application/application'
8import { ServerBlocklistModel } from '@server/models/server/server-blocklist' 9import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
@@ -22,8 +23,8 @@ import {
22 paginationValidator, 23 paginationValidator,
23 setDefaultPagination, 24 setDefaultPagination,
24 setDefaultSearchSort, 25 setDefaultSearchSort,
25 videoChannelsSearchSortValidator,
26 videoChannelsListSearchValidator, 26 videoChannelsListSearchValidator,
27 videoChannelsSearchSortValidator,
27 videosSearchSortValidator, 28 videosSearchSortValidator,
28 videosSearchValidator 29 videosSearchValidator
29} from '../../middlewares' 30} from '../../middlewares'
@@ -87,16 +88,17 @@ function searchVideoChannels (req: express.Request, res: express.Response) {
87async function searchVideoChannelsIndex (query: VideoChannelsSearchQuery, res: express.Response) { 88async function searchVideoChannelsIndex (query: VideoChannelsSearchQuery, res: express.Response) {
88 const result = await buildMutedForSearchIndex(res) 89 const result = await buildMutedForSearchIndex(res)
89 90
90 const body = Object.assign(query, result) 91 const body = await Hooks.wrapObject(Object.assign(query, result), 'filter:api.search.video-channels.index.list.params')
91 92
92 const url = sanitizeUrl(CONFIG.SEARCH.SEARCH_INDEX.URL) + '/api/v1/search/video-channels' 93 const url = sanitizeUrl(CONFIG.SEARCH.SEARCH_INDEX.URL) + '/api/v1/search/video-channels'
93 94
94 try { 95 try {
95 logger.debug('Doing video channels search index request on %s.', url, { body }) 96 logger.debug('Doing video channels search index request on %s.', url, { body })
96 97
97 const searchIndexResult = await doRequest<ResultList<VideoChannel>>({ uri: url, body, json: true }) 98 const { body: searchIndexResult } = await doJSONRequest<ResultList<VideoChannel>>(url, { method: 'POST', json: body })
99 const jsonResult = await Hooks.wrapObject(searchIndexResult, 'filter:api.search.video-channels.index.list.result')
98 100
99 return res.json(searchIndexResult.body) 101 return res.json(jsonResult)
100 } catch (err) { 102 } catch (err) {
101 logger.warn('Cannot use search index to make video channels search.', { err }) 103 logger.warn('Cannot use search index to make video channels search.', { err })
102 104
@@ -107,14 +109,19 @@ async function searchVideoChannelsIndex (query: VideoChannelsSearchQuery, res: e
107async function searchVideoChannelsDB (query: VideoChannelsSearchQuery, res: express.Response) { 109async function searchVideoChannelsDB (query: VideoChannelsSearchQuery, res: express.Response) {
108 const serverActor = await getServerActor() 110 const serverActor = await getServerActor()
109 111
110 const options = { 112 const apiOptions = await Hooks.wrapObject({
111 actorId: serverActor.id, 113 actorId: serverActor.id,
112 search: query.search, 114 search: query.search,
113 start: query.start, 115 start: query.start,
114 count: query.count, 116 count: query.count,
115 sort: query.sort 117 sort: query.sort
116 } 118 }, 'filter:api.search.video-channels.local.list.params')
117 const resultList = await VideoChannelModel.searchForApi(options) 119
120 const resultList = await Hooks.wrapPromiseFun(
121 VideoChannelModel.searchForApi,
122 apiOptions,
123 'filter:api.search.video-channels.local.list.result'
124 )
118 125
119 return res.json(getFormattedObjects(resultList.data, resultList.total)) 126 return res.json(getFormattedObjects(resultList.data, resultList.total))
120} 127}
@@ -168,7 +175,7 @@ function searchVideos (req: express.Request, res: express.Response) {
168async function searchVideosIndex (query: VideosSearchQuery, res: express.Response) { 175async function searchVideosIndex (query: VideosSearchQuery, res: express.Response) {
169 const result = await buildMutedForSearchIndex(res) 176 const result = await buildMutedForSearchIndex(res)
170 177
171 const body: VideosSearchQuery = Object.assign(query, result) 178 let body: VideosSearchQuery = Object.assign(query, result)
172 179
173 // Use the default instance NSFW policy if not specified 180 // Use the default instance NSFW policy if not specified
174 if (!body.nsfw) { 181 if (!body.nsfw) {
@@ -181,14 +188,17 @@ async function searchVideosIndex (query: VideosSearchQuery, res: express.Respons
181 : 'both' 188 : 'both'
182 } 189 }
183 190
191 body = await Hooks.wrapObject(body, 'filter:api.search.videos.index.list.params')
192
184 const url = sanitizeUrl(CONFIG.SEARCH.SEARCH_INDEX.URL) + '/api/v1/search/videos' 193 const url = sanitizeUrl(CONFIG.SEARCH.SEARCH_INDEX.URL) + '/api/v1/search/videos'
185 194
186 try { 195 try {
187 logger.debug('Doing videos search index request on %s.', url, { body }) 196 logger.debug('Doing videos search index request on %s.', url, { body })
188 197
189 const searchIndexResult = await doRequest<ResultList<Video>>({ uri: url, body, json: true }) 198 const { body: searchIndexResult } = await doJSONRequest<ResultList<Video>>(url, { method: 'POST', json: body })
199 const jsonResult = await Hooks.wrapObject(searchIndexResult, 'filter:api.search.videos.index.list.result')
190 200
191 return res.json(searchIndexResult.body) 201 return res.json(jsonResult)
192 } catch (err) { 202 } catch (err) {
193 logger.warn('Cannot use search index to make video search.', { err }) 203 logger.warn('Cannot use search index to make video search.', { err })
194 204
@@ -197,13 +207,18 @@ async function searchVideosIndex (query: VideosSearchQuery, res: express.Respons
197} 207}
198 208
199async function searchVideosDB (query: VideosSearchQuery, res: express.Response) { 209async function searchVideosDB (query: VideosSearchQuery, res: express.Response) {
200 const options = Object.assign(query, { 210 const apiOptions = await Hooks.wrapObject(Object.assign(query, {
201 includeLocalVideos: true, 211 includeLocalVideos: true,
202 nsfw: buildNSFWFilter(res, query.nsfw), 212 nsfw: buildNSFWFilter(res, query.nsfw),
203 filter: query.filter, 213 filter: query.filter,
204 user: res.locals.oauth ? res.locals.oauth.token.User : undefined 214 user: res.locals.oauth ? res.locals.oauth.token.User : undefined
205 }) 215 }), 'filter:api.search.videos.local.list.params')
206 const resultList = await VideoModel.searchAndPopulateAccountAndServer(options) 216
217 const resultList = await Hooks.wrapPromiseFun(
218 VideoModel.searchAndPopulateAccountAndServer,
219 apiOptions,
220 'filter:api.search.videos.local.list.result'
221 )
207 222
208 return res.json(getFormattedObjects(resultList.data, resultList.total)) 223 return res.json(getFormattedObjects(resultList.data, resultList.total))
209} 224}
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index 3be1d55ae..e2b1ea7cd 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -2,8 +2,10 @@ import * as express from 'express'
2import * as RateLimit from 'express-rate-limit' 2import * as RateLimit from 'express-rate-limit'
3import { tokensRouter } from '@server/controllers/api/users/token' 3import { tokensRouter } from '@server/controllers/api/users/token'
4import { Hooks } from '@server/lib/plugins/hooks' 4import { Hooks } from '@server/lib/plugins/hooks'
5import { OAuthTokenModel } from '@server/models/oauth/oauth-token'
5import { MUser, MUserAccountDefault } from '@server/types/models' 6import { MUser, MUserAccountDefault } from '@server/types/models'
6import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared' 7import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared'
8import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
7import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' 9import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
8import { UserRegister } from '../../../../shared/models/users/user-register.model' 10import { UserRegister } from '../../../../shared/models/users/user-register.model'
9import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' 11import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
@@ -14,7 +16,6 @@ import { WEBSERVER } from '../../../initializers/constants'
14import { sequelizeTypescript } from '../../../initializers/database' 16import { sequelizeTypescript } from '../../../initializers/database'
15import { Emailer } from '../../../lib/emailer' 17import { Emailer } from '../../../lib/emailer'
16import { Notifier } from '../../../lib/notifier' 18import { Notifier } from '../../../lib/notifier'
17import { deleteUserToken } from '../../../lib/oauth-model'
18import { Redis } from '../../../lib/redis' 19import { Redis } from '../../../lib/redis'
19import { createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user' 20import { createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user'
20import { 21import {
@@ -52,7 +53,6 @@ import { myVideosHistoryRouter } from './my-history'
52import { myNotificationsRouter } from './my-notifications' 53import { myNotificationsRouter } from './my-notifications'
53import { mySubscriptionsRouter } from './my-subscriptions' 54import { mySubscriptionsRouter } from './my-subscriptions'
54import { myVideoPlaylistsRouter } from './my-video-playlists' 55import { myVideoPlaylistsRouter } from './my-video-playlists'
55import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
56 56
57const auditLogger = auditLoggerFactory('users') 57const auditLogger = auditLoggerFactory('users')
58 58
@@ -335,7 +335,7 @@ async function updateUser (req: express.Request, res: express.Response) {
335 const user = await userToUpdate.save() 335 const user = await userToUpdate.save()
336 336
337 // Destroy user token to refresh rights 337 // Destroy user token to refresh rights
338 if (roleChanged || body.password !== undefined) await deleteUserToken(userToUpdate.id) 338 if (roleChanged || body.password !== undefined) await OAuthTokenModel.deleteUserToken(userToUpdate.id)
339 339
340 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) 340 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)
341 341
@@ -395,7 +395,7 @@ async function changeUserBlock (res: express.Response, user: MUserAccountDefault
395 user.blockedReason = reason || null 395 user.blockedReason = reason || null
396 396
397 await sequelizeTypescript.transaction(async t => { 397 await sequelizeTypescript.transaction(async t => {
398 await deleteUserToken(user.id, t) 398 await OAuthTokenModel.deleteUserToken(user.id, t)
399 399
400 await user.save({ transaction: t }) 400 await user.save({ transaction: t })
401 }) 401 })
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index 5a3e9e51a..9f9d2d77f 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -2,7 +2,7 @@ import 'multer'
2import * as express from 'express' 2import * as express from 'express'
3import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '@server/helpers/audit-logger' 3import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '@server/helpers/audit-logger'
4import { Hooks } from '@server/lib/plugins/hooks' 4import { Hooks } from '@server/lib/plugins/hooks'
5import { UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../../shared' 5import { ActorImageType, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../../shared'
6import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 6import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
7import { UserVideoQuota } from '../../../../shared/models/users/user-video-quota.model' 7import { UserVideoQuota } from '../../../../shared/models/users/user-video-quota.model'
8import { createReqFiles } from '../../../helpers/express-utils' 8import { createReqFiles } from '../../../helpers/express-utils'
@@ -11,7 +11,7 @@ import { CONFIG } from '../../../initializers/config'
11import { MIMETYPES } from '../../../initializers/constants' 11import { MIMETYPES } from '../../../initializers/constants'
12import { sequelizeTypescript } from '../../../initializers/database' 12import { sequelizeTypescript } from '../../../initializers/database'
13import { sendUpdateActor } from '../../../lib/activitypub/send' 13import { sendUpdateActor } from '../../../lib/activitypub/send'
14import { deleteLocalActorAvatarFile, updateLocalActorAvatarFile } from '../../../lib/avatar' 14import { deleteLocalActorImageFile, updateLocalActorImageFile } from '../../../lib/actor-image'
15import { getOriginalVideoFileTotalDailyFromUser, getOriginalVideoFileTotalFromUser, sendVerifyUserEmail } from '../../../lib/user' 15import { getOriginalVideoFileTotalDailyFromUser, getOriginalVideoFileTotalFromUser, sendVerifyUserEmail } from '../../../lib/user'
16import { 16import {
17 asyncMiddleware, 17 asyncMiddleware,
@@ -25,7 +25,7 @@ import {
25 usersVideoRatingValidator 25 usersVideoRatingValidator
26} from '../../../middlewares' 26} from '../../../middlewares'
27import { deleteMeValidator, videoImportsSortValidator, videosSortValidator } from '../../../middlewares/validators' 27import { deleteMeValidator, videoImportsSortValidator, videosSortValidator } from '../../../middlewares/validators'
28import { updateAvatarValidator } from '../../../middlewares/validators/avatar' 28import { updateAvatarValidator } from '../../../middlewares/validators/actor-image'
29import { AccountModel } from '../../../models/account/account' 29import { AccountModel } from '../../../models/account/account'
30import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 30import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
31import { UserModel } from '../../../models/account/user' 31import { UserModel } from '../../../models/account/user'
@@ -238,7 +238,7 @@ async function updateMyAvatar (req: express.Request, res: express.Response) {
238 238
239 const userAccount = await AccountModel.load(user.Account.id) 239 const userAccount = await AccountModel.load(user.Account.id)
240 240
241 const avatar = await updateLocalActorAvatarFile(userAccount, avatarPhysicalFile) 241 const avatar = await updateLocalActorImageFile(userAccount, avatarPhysicalFile, ActorImageType.AVATAR)
242 242
243 return res.json({ avatar: avatar.toFormattedJSON() }) 243 return res.json({ avatar: avatar.toFormattedJSON() })
244} 244}
@@ -247,7 +247,7 @@ async function deleteMyAvatar (req: express.Request, res: express.Response) {
247 const user = res.locals.oauth.token.user 247 const user = res.locals.oauth.token.user
248 248
249 const userAccount = await AccountModel.load(user.Account.id) 249 const userAccount = await AccountModel.load(user.Account.id)
250 await deleteLocalActorAvatarFile(userAccount) 250 await deleteLocalActorImageFile(userAccount, ActorImageType.AVATAR)
251 251
252 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 252 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
253} 253}
diff --git a/server/controllers/api/users/my-notifications.ts b/server/controllers/api/users/my-notifications.ts
index 5f5e4c5e6..0a9101a46 100644
--- a/server/controllers/api/users/my-notifications.ts
+++ b/server/controllers/api/users/my-notifications.ts
@@ -80,7 +80,9 @@ async function updateNotificationSettings (req: express.Request, res: express.Re
80 newInstanceFollower: body.newInstanceFollower, 80 newInstanceFollower: body.newInstanceFollower,
81 autoInstanceFollowing: body.autoInstanceFollowing, 81 autoInstanceFollowing: body.autoInstanceFollowing,
82 abuseNewMessage: body.abuseNewMessage, 82 abuseNewMessage: body.abuseNewMessage,
83 abuseStateChange: body.abuseStateChange 83 abuseStateChange: body.abuseStateChange,
84 newPeerTubeVersion: body.newPeerTubeVersion,
85 newPluginVersion: body.newPluginVersion
84 } 86 }
85 87
86 await UserNotificationSettingModel.update(values, query) 88 await UserNotificationSettingModel.update(values, query)
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts
index ec77ddd7a..e8949ee59 100644
--- a/server/controllers/api/users/my-subscriptions.ts
+++ b/server/controllers/api/users/my-subscriptions.ts
@@ -1,5 +1,8 @@
1import 'multer' 1import 'multer'
2import * as express from 'express' 2import * as express from 'express'
3import { sendUndoFollow } from '@server/lib/activitypub/send'
4import { VideoChannelModel } from '@server/models/video/video-channel'
5import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
3import { VideoFilter } from '../../../../shared/models/videos/video-query.type' 6import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
4import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' 7import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
5import { getFormattedObjects } from '../../../helpers/utils' 8import { getFormattedObjects } from '../../../helpers/utils'
@@ -26,8 +29,6 @@ import {
26} from '../../../middlewares/validators' 29} from '../../../middlewares/validators'
27import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 30import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
28import { VideoModel } from '../../../models/video/video' 31import { VideoModel } from '../../../models/video/video'
29import { sendUndoFollow } from '@server/lib/activitypub/send'
30import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
31 32
32const mySubscriptionsRouter = express.Router() 33const mySubscriptionsRouter = express.Router()
33 34
@@ -66,7 +67,7 @@ mySubscriptionsRouter.post('/me/subscriptions',
66mySubscriptionsRouter.get('/me/subscriptions/:uri', 67mySubscriptionsRouter.get('/me/subscriptions/:uri',
67 authenticate, 68 authenticate,
68 userSubscriptionGetValidator, 69 userSubscriptionGetValidator,
69 getUserSubscription 70 asyncMiddleware(getUserSubscription)
70) 71)
71 72
72mySubscriptionsRouter.delete('/me/subscriptions/:uri', 73mySubscriptionsRouter.delete('/me/subscriptions/:uri',
@@ -130,10 +131,11 @@ function addUserSubscription (req: express.Request, res: express.Response) {
130 return res.status(HttpStatusCode.NO_CONTENT_204).end() 131 return res.status(HttpStatusCode.NO_CONTENT_204).end()
131} 132}
132 133
133function getUserSubscription (req: express.Request, res: express.Response) { 134async function getUserSubscription (req: express.Request, res: express.Response) {
134 const subscription = res.locals.subscription 135 const subscription = res.locals.subscription
136 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(subscription.ActorFollowing.VideoChannel.id)
135 137
136 return res.json(subscription.ActorFollowing.VideoChannel.toFormattedJSON()) 138 return res.json(videoChannel.toFormattedJSON())
137} 139}
138 140
139async function deleteUserSubscription (req: express.Request, res: express.Response) { 141async function deleteUserSubscription (req: express.Request, res: express.Response) {
diff --git a/server/controllers/api/users/token.ts b/server/controllers/api/users/token.ts
index 821429358..694bb0a92 100644
--- a/server/controllers/api/users/token.ts
+++ b/server/controllers/api/users/token.ts
@@ -1,11 +1,14 @@
1import { handleLogin, handleTokenRevocation } from '@server/lib/auth' 1import * as express from 'express'
2import * as RateLimit from 'express-rate-limit' 2import * as RateLimit from 'express-rate-limit'
3import { v4 as uuidv4 } from 'uuid'
4import { logger } from '@server/helpers/logger'
3import { CONFIG } from '@server/initializers/config' 5import { CONFIG } from '@server/initializers/config'
4import * as express from 'express' 6import { getAuthNameFromRefreshGrant, getBypassFromExternalAuth, getBypassFromPasswordGrant } from '@server/lib/auth/external-auth'
7import { handleOAuthToken } from '@server/lib/auth/oauth'
8import { BypassLogin, revokeToken } from '@server/lib/auth/oauth-model'
5import { Hooks } from '@server/lib/plugins/hooks' 9import { Hooks } from '@server/lib/plugins/hooks'
6import { asyncMiddleware, authenticate } from '@server/middlewares' 10import { asyncMiddleware, authenticate } from '@server/middlewares'
7import { ScopedToken } from '@shared/models/users/user-scoped-token' 11import { ScopedToken } from '@shared/models/users/user-scoped-token'
8import { v4 as uuidv4 } from 'uuid'
9 12
10const tokensRouter = express.Router() 13const tokensRouter = express.Router()
11 14
@@ -16,8 +19,7 @@ const loginRateLimiter = RateLimit({
16 19
17tokensRouter.post('/token', 20tokensRouter.post('/token',
18 loginRateLimiter, 21 loginRateLimiter,
19 handleLogin, 22 asyncMiddleware(handleToken)
20 tokenSuccess
21) 23)
22 24
23tokensRouter.post('/revoke-token', 25tokensRouter.post('/revoke-token',
@@ -42,10 +44,53 @@ export {
42} 44}
43// --------------------------------------------------------------------------- 45// ---------------------------------------------------------------------------
44 46
45function tokenSuccess (req: express.Request) { 47async function handleToken (req: express.Request, res: express.Response, next: express.NextFunction) {
46 const username = req.body.username 48 const grantType = req.body.grant_type
49
50 try {
51 const bypassLogin = await buildByPassLogin(req, grantType)
52
53 const refreshTokenAuthName = grantType === 'refresh_token'
54 ? await getAuthNameFromRefreshGrant(req.body.refresh_token)
55 : undefined
56
57 const options = {
58 refreshTokenAuthName,
59 bypassLogin
60 }
61
62 const token = await handleOAuthToken(req, options)
63
64 res.set('Cache-Control', 'no-store')
65 res.set('Pragma', 'no-cache')
66
67 Hooks.runAction('action:api.user.oauth2-got-token', { username: token.user.username, ip: req.ip })
68
69 return res.json({
70 token_type: 'Bearer',
47 71
48 Hooks.runAction('action:api.user.oauth2-got-token', { username, ip: req.ip }) 72 access_token: token.accessToken,
73 refresh_token: token.refreshToken,
74
75 expires_in: token.accessTokenExpiresIn,
76 refresh_token_expires_in: token.refreshTokenExpiresIn
77 })
78 } catch (err) {
79 logger.warn('Login error', { err })
80
81 return res.status(err.code || 400).json({
82 code: err.name,
83 error: err.message
84 })
85 }
86}
87
88async function handleTokenRevocation (req: express.Request, res: express.Response) {
89 const token = res.locals.oauth.token
90
91 const result = await revokeToken(token, { req, explicitLogout: true })
92
93 return res.json(result)
49} 94}
50 95
51function getScopedTokens (req: express.Request, res: express.Response) { 96function getScopedTokens (req: express.Request, res: express.Response) {
@@ -66,3 +111,14 @@ async function renewScopedTokens (req: express.Request, res: express.Response) {
66 feedToken: user.feedToken 111 feedToken: user.feedToken
67 } as ScopedToken) 112 } as ScopedToken)
68} 113}
114
115async function buildByPassLogin (req: express.Request, grantType: string): Promise<BypassLogin> {
116 if (grantType !== 'password') return undefined
117
118 if (req.body.externalAuthToken) {
119 // Consistency with the getBypassFromPasswordGrant promise
120 return getBypassFromExternalAuth(req.body.username, req.body.externalAuthToken)
121 }
122
123 return getBypassFromPasswordGrant(req.body.username, req.body.password)
124}
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index 03617dc8d..149d6cfb4 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -1,8 +1,8 @@
1import * as express from 'express' 1import * as express from 'express'
2import { Hooks } from '@server/lib/plugins/hooks' 2import { Hooks } from '@server/lib/plugins/hooks'
3import { getServerActor } from '@server/models/application/application' 3import { getServerActor } from '@server/models/application/application'
4import { MChannelAccountDefault } from '@server/types/models' 4import { MChannelBannerAccountDefault } from '@server/types/models'
5import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared' 5import { ActorImageType, VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
6import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' 6import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
7import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' 7import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
8import { resetSequelizeInstance } from '../../helpers/database-utils' 8import { resetSequelizeInstance } from '../../helpers/database-utils'
@@ -13,7 +13,7 @@ import { CONFIG } from '../../initializers/config'
13import { MIMETYPES } from '../../initializers/constants' 13import { MIMETYPES } from '../../initializers/constants'
14import { sequelizeTypescript } from '../../initializers/database' 14import { sequelizeTypescript } from '../../initializers/database'
15import { sendUpdateActor } from '../../lib/activitypub/send' 15import { sendUpdateActor } from '../../lib/activitypub/send'
16import { deleteLocalActorAvatarFile, updateLocalActorAvatarFile } from '../../lib/avatar' 16import { deleteLocalActorImageFile, updateLocalActorImageFile } from '../../lib/actor-image'
17import { JobQueue } from '../../lib/job-queue' 17import { JobQueue } from '../../lib/job-queue'
18import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel' 18import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel'
19import { 19import {
@@ -33,7 +33,7 @@ import {
33 videoPlaylistsSortValidator 33 videoPlaylistsSortValidator
34} from '../../middlewares' 34} from '../../middlewares'
35import { videoChannelsNameWithHostValidator, videoChannelsOwnSearchValidator, videosSortValidator } from '../../middlewares/validators' 35import { videoChannelsNameWithHostValidator, videoChannelsOwnSearchValidator, videosSortValidator } from '../../middlewares/validators'
36import { updateAvatarValidator } from '../../middlewares/validators/avatar' 36import { updateAvatarValidator, updateBannerValidator } from '../../middlewares/validators/actor-image'
37import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists' 37import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists'
38import { AccountModel } from '../../models/account/account' 38import { AccountModel } from '../../models/account/account'
39import { VideoModel } from '../../models/video/video' 39import { VideoModel } from '../../models/video/video'
@@ -42,6 +42,7 @@ import { VideoPlaylistModel } from '../../models/video/video-playlist'
42 42
43const auditLogger = auditLoggerFactory('channels') 43const auditLogger = auditLoggerFactory('channels')
44const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) 44const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
45const reqBannerFile = createReqFiles([ 'bannerfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { bannerfile: CONFIG.STORAGE.TMP_DIR })
45 46
46const videoChannelRouter = express.Router() 47const videoChannelRouter = express.Router()
47 48
@@ -69,6 +70,15 @@ videoChannelRouter.post('/:nameWithHost/avatar/pick',
69 asyncMiddleware(updateVideoChannelAvatar) 70 asyncMiddleware(updateVideoChannelAvatar)
70) 71)
71 72
73videoChannelRouter.post('/:nameWithHost/banner/pick',
74 authenticate,
75 reqBannerFile,
76 // Check the rights
77 asyncMiddleware(videoChannelsUpdateValidator),
78 updateBannerValidator,
79 asyncMiddleware(updateVideoChannelBanner)
80)
81
72videoChannelRouter.delete('/:nameWithHost/avatar', 82videoChannelRouter.delete('/:nameWithHost/avatar',
73 authenticate, 83 authenticate,
74 // Check the rights 84 // Check the rights
@@ -76,6 +86,13 @@ videoChannelRouter.delete('/:nameWithHost/avatar',
76 asyncMiddleware(deleteVideoChannelAvatar) 86 asyncMiddleware(deleteVideoChannelAvatar)
77) 87)
78 88
89videoChannelRouter.delete('/:nameWithHost/banner',
90 authenticate,
91 // Check the rights
92 asyncMiddleware(videoChannelsUpdateValidator),
93 asyncMiddleware(deleteVideoChannelBanner)
94)
95
79videoChannelRouter.put('/:nameWithHost', 96videoChannelRouter.put('/:nameWithHost',
80 authenticate, 97 authenticate,
81 asyncMiddleware(videoChannelsUpdateValidator), 98 asyncMiddleware(videoChannelsUpdateValidator),
@@ -134,26 +151,41 @@ async function listVideoChannels (req: express.Request, res: express.Response) {
134 return res.json(getFormattedObjects(resultList.data, resultList.total)) 151 return res.json(getFormattedObjects(resultList.data, resultList.total))
135} 152}
136 153
154async function updateVideoChannelBanner (req: express.Request, res: express.Response) {
155 const bannerPhysicalFile = req.files['bannerfile'][0]
156 const videoChannel = res.locals.videoChannel
157 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON())
158
159 const banner = await updateLocalActorImageFile(videoChannel, bannerPhysicalFile, ActorImageType.BANNER)
160
161 auditLogger.update(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannel.toFormattedJSON()), oldVideoChannelAuditKeys)
162
163 return res.json({ banner: banner.toFormattedJSON() })
164}
137async function updateVideoChannelAvatar (req: express.Request, res: express.Response) { 165async function updateVideoChannelAvatar (req: express.Request, res: express.Response) {
138 const avatarPhysicalFile = req.files['avatarfile'][0] 166 const avatarPhysicalFile = req.files['avatarfile'][0]
139 const videoChannel = res.locals.videoChannel 167 const videoChannel = res.locals.videoChannel
140 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON()) 168 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON())
141 169
142 const avatar = await updateLocalActorAvatarFile(videoChannel, avatarPhysicalFile) 170 const avatar = await updateLocalActorImageFile(videoChannel, avatarPhysicalFile, ActorImageType.AVATAR)
143 171
144 auditLogger.update(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannel.toFormattedJSON()), oldVideoChannelAuditKeys) 172 auditLogger.update(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannel.toFormattedJSON()), oldVideoChannelAuditKeys)
145 173
146 return res 174 return res.json({ avatar: avatar.toFormattedJSON() })
147 .json({
148 avatar: avatar.toFormattedJSON()
149 })
150 .end()
151} 175}
152 176
153async function deleteVideoChannelAvatar (req: express.Request, res: express.Response) { 177async function deleteVideoChannelAvatar (req: express.Request, res: express.Response) {
154 const videoChannel = res.locals.videoChannel 178 const videoChannel = res.locals.videoChannel
155 179
156 await deleteLocalActorAvatarFile(videoChannel) 180 await deleteLocalActorImageFile(videoChannel, ActorImageType.AVATAR)
181
182 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
183}
184
185async function deleteVideoChannelBanner (req: express.Request, res: express.Response) {
186 const videoChannel = res.locals.videoChannel
187
188 await deleteLocalActorImageFile(videoChannel, ActorImageType.BANNER)
157 189
158 return res.sendStatus(HttpStatusCode.NO_CONTENT_204) 190 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
159} 191}
@@ -177,7 +209,7 @@ async function addVideoChannel (req: express.Request, res: express.Response) {
177 videoChannel: { 209 videoChannel: {
178 id: videoChannelCreated.id 210 id: videoChannelCreated.id
179 } 211 }
180 }).end() 212 })
181} 213}
182 214
183async function updateVideoChannel (req: express.Request, res: express.Response) { 215async function updateVideoChannel (req: express.Request, res: express.Response) {
@@ -206,7 +238,7 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
206 } 238 }
207 } 239 }
208 240
209 const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) as MChannelAccountDefault 241 const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) as MChannelBannerAccountDefault
210 await sendUpdateActor(videoChannelInstanceUpdated, t) 242 await sendUpdateActor(videoChannelInstanceUpdated, t)
211 243
212 auditLogger.update( 244 auditLogger.update(
@@ -252,13 +284,13 @@ async function removeVideoChannel (req: express.Request, res: express.Response)
252} 284}
253 285
254async function getVideoChannel (req: express.Request, res: express.Response) { 286async function getVideoChannel (req: express.Request, res: express.Response) {
255 const videoChannelWithVideos = await VideoChannelModel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id) 287 const videoChannel = res.locals.videoChannel
256 288
257 if (videoChannelWithVideos.isOutdated()) { 289 if (videoChannel.isOutdated()) {
258 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: videoChannelWithVideos.Actor.url } }) 290 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: videoChannel.Actor.url } })
259 } 291 }
260 292
261 return res.json(videoChannelWithVideos.toFormattedJSON()) 293 return res.json(videoChannel.toFormattedJSON())
262} 294}
263 295
264async function listVideoChannelPlaylists (req: express.Request, res: express.Response) { 296async function listVideoChannelPlaylists (req: express.Request, res: express.Response) {
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 2447c1288..7fee278f2 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -17,7 +17,7 @@ import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../
17import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils' 17import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils'
18import { buildNSFWFilter, createReqFiles, getCountVideos } from '../../../helpers/express-utils' 18import { buildNSFWFilter, createReqFiles, getCountVideos } from '../../../helpers/express-utils'
19import { getMetadataFromFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils' 19import { getMetadataFromFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils'
20import { logger } from '../../../helpers/logger' 20import { logger, loggerTagsFactory } from '../../../helpers/logger'
21import { getFormattedObjects } from '../../../helpers/utils' 21import { getFormattedObjects } from '../../../helpers/utils'
22import { CONFIG } from '../../../initializers/config' 22import { CONFIG } from '../../../initializers/config'
23import { 23import {
@@ -67,6 +67,7 @@ import { ownershipVideoRouter } from './ownership'
67import { rateVideoRouter } from './rate' 67import { rateVideoRouter } from './rate'
68import { watchingRouter } from './watching' 68import { watchingRouter } from './watching'
69 69
70const lTags = loggerTagsFactory('api', 'video')
70const auditLogger = auditLoggerFactory('videos') 71const auditLogger = auditLoggerFactory('videos')
71const videosRouter = express.Router() 72const videosRouter = express.Router()
72 73
@@ -257,14 +258,14 @@ async function addVideo (req: express.Request, res: express.Response) {
257 }) 258 })
258 259
259 auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON())) 260 auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON()))
260 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid) 261 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid, lTags(videoCreated.uuid))
261 262
262 return { videoCreated } 263 return { videoCreated }
263 }) 264 })
264 265
265 // Create the torrent file in async way because it could be long 266 // Create the torrent file in async way because it could be long
266 createTorrentAndSetInfoHashAsync(video, videoFile) 267 createTorrentAndSetInfoHashAsync(video, videoFile)
267 .catch(err => logger.error('Cannot create torrent file for video %s', video.url, { err })) 268 .catch(err => logger.error('Cannot create torrent file for video %s', video.url, { err, ...lTags(video.uuid) }))
268 .then(() => VideoModel.loadAndPopulateAccountAndServerAndTags(video.id)) 269 .then(() => VideoModel.loadAndPopulateAccountAndServerAndTags(video.id))
269 .then(refreshedVideo => { 270 .then(refreshedVideo => {
270 if (!refreshedVideo) return 271 if (!refreshedVideo) return
@@ -276,7 +277,7 @@ async function addVideo (req: express.Request, res: express.Response) {
276 return sequelizeTypescript.transaction(t => federateVideoIfNeeded(refreshedVideo, true, t)) 277 return sequelizeTypescript.transaction(t => federateVideoIfNeeded(refreshedVideo, true, t))
277 }) 278 })
278 }) 279 })
279 .catch(err => logger.error('Cannot federate or notify video creation %s', video.url, { err })) 280 .catch(err => logger.error('Cannot federate or notify video creation %s', video.url, { err, ...lTags(video.uuid) }))
280 281
281 if (video.state === VideoState.TO_TRANSCODE) { 282 if (video.state === VideoState.TO_TRANSCODE) {
282 await addOptimizeOrMergeAudioJob(videoCreated, videoFile, res.locals.oauth.token.User) 283 await addOptimizeOrMergeAudioJob(videoCreated, videoFile, res.locals.oauth.token.User)
@@ -389,7 +390,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
389 new VideoAuditView(videoInstanceUpdated.toFormattedDetailsJSON()), 390 new VideoAuditView(videoInstanceUpdated.toFormattedDetailsJSON()),
390 oldVideoAuditView 391 oldVideoAuditView
391 ) 392 )
392 logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid) 393 logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid, lTags(videoInstance.uuid))
393 394
394 return videoInstanceUpdated 395 return videoInstanceUpdated
395 }) 396 })
diff --git a/server/controllers/api/videos/ownership.ts b/server/controllers/api/videos/ownership.ts
index 86adb6c69..a85d7c30b 100644
--- a/server/controllers/api/videos/ownership.ts
+++ b/server/controllers/api/videos/ownership.ts
@@ -107,7 +107,7 @@ async function acceptOwnership (req: express.Request, res: express.Response) {
107 // We need more attributes for federation 107 // We need more attributes for federation
108 const targetVideo = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoChangeOwnership.Video.id) 108 const targetVideo = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoChangeOwnership.Video.id)
109 109
110 const oldVideoChannel = await VideoChannelModel.loadByIdAndPopulateAccount(targetVideo.channelId) 110 const oldVideoChannel = await VideoChannelModel.loadAndPopulateAccount(targetVideo.channelId)
111 111
112 targetVideo.channelId = channel.id 112 targetVideo.channelId = channel.id
113 113